diff --git a/src/main/java/kinoko/database/AllianceAccessor.java b/src/main/java/kinoko/database/AllianceAccessor.java new file mode 100644 index 00000000..9050fef6 --- /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 9b4bf0e0..9972f512 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 2808fd09..91937419 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/database/GuildAccessor.java b/src/main/java/kinoko/database/GuildAccessor.java index dc326191..5557a82b 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/IdAccessor.java b/src/main/java/kinoko/database/IdAccessor.java index 399887c9..ab2ba23c 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/CassandraConnector.java b/src/main/java/kinoko/database/cassandra/CassandraConnector.java index 44ae4a0e..a9c2db73 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/database/cassandra/CassandraGuildAccessor.java b/src/main/java/kinoko/database/cassandra/CassandraGuildAccessor.java index f8fd07a0..c439b8cd 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/CassandraIdAccessor.java b/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java index 50b9249d..a5777576 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 f400149d..3f6231da 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"; @@ -40,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 e319afbf..1f8ca84a 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -6,8 +6,7 @@ import kinoko.packet.world.MessagePacket; import kinoko.packet.world.WvsContext; import kinoko.script.GuildHQ; -import kinoko.server.alliance.AllianceRequestType; -import kinoko.server.alliance.AllianceResultType; +import kinoko.server.alliance.*; import kinoko.server.guild.*; import kinoko.server.header.InHeader; import kinoko.server.packet.InPacket; @@ -220,14 +219,82 @@ 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 + switch (requestType) { + case Load -> { + // CWvsContext::OnAllianceResult + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.load(user.getAllianceId())); + } + case Withdraw -> { + // CTabGuildAlliance::OnWithdraw + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.withdraw()); + } + case Invite -> { + // CTabGuildAlliance::OnInvite + final String guildName = inPacket.decodeString(); + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.invite(guildName)); + } + 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); + } + } } @Handler(InHeader.AllianceResult) public static void handleAllianceResult(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceResultType resultType = AllianceResultType.getByValue(type); - // TODO + switch (resultType) { + case InviteAlliance_BlockedByOpt, InviteAlliance_AlreadyInvited, InviteAlliance_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/CentralPacket.java b/src/main/java/kinoko/packet/CentralPacket.java index 2c287faa..5aba0231 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/packet/world/AlliancePacket.java b/src/main/java/kinoko/packet/world/AlliancePacket.java new file mode 100644 index 00000000..fecf1adb --- /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.LoadAllianceDone); + 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.ChangeGrade_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/packet/world/GuildPacket.java b/src/main/java/kinoko/packet/world/GuildPacket.java index ae80fb2c..1f70b237 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 85d417f2..a28826fd 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -4,12 +4,16 @@ 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; import kinoko.world.user.User; import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; public final class GuildHQ extends ScriptHandler { public static final int GUILD_HEADQUARTERS = 200000301; @@ -39,7 +43,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)) { @@ -155,18 +159,53 @@ 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(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."); } - // TODO + + 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; + } } 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/Server.java b/src/main/java/kinoko/server/Server.java index 2054053a..8d1f6df3 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/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java new file mode 100644 index 00000000..d9a843a2 --- /dev/null +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -0,0 +1,216 @@ +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; + +import java.util.*; + +/** + * 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; + this.allianceLordId = allianceLordId; + this.gradeNames = new ArrayList<>(GameConstants.GUILD_GRADE_NAMES); + 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() { + return allianceId; + } + + 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); + } + + 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 void removeGuild(Guild guild) { + 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; + } + + public boolean addGuild(int guildId, Guild guild) { + if (!canAddGuild(guildId)) { + return false; + } + guilds.put(guildId, guild); + return true; + } + + public boolean removeGuild(int guildId) { + return guilds.remove((Integer) guildId) != null; + } + + public List getGuilds() { + return Collections.unmodifiableList(guilds.keySet().stream().toList()); + } + + public void registerInvite(int inviterId, int targetId) { + guildInvites.put(targetId, inviterId); + } + + public boolean unregisterInvite(int inviterId, int targetId) { + return guildInvites.remove(targetId) == inviterId; + } + + @Override + public void encode(OutPacket outPacket) { + // 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.keySet()) { + outPacket.encodeInt(guildId); + } + outPacket.encodeInt(memberMax); // nMaxMemberNum + outPacket.encodeString(notice); + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + 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 new file mode 100644 index 00000000..2cc9d356 --- /dev/null +++ b/src/main/java/kinoko/server/alliance/AllianceRequest.java @@ -0,0 +1,211 @@ +package kinoko.server.alliance; + +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 int guildId; + private String guildName; + + private int inviterId; + private int oldMasterId; + private int newMasterId; + private List gradeNames; + private int targetId; + private boolean gradeUp; + private String notice; + + public AllianceRequest(AllianceRequestType requestType) { + this.requestType = requestType; + } + + public AllianceRequestType getRequestType() { + return requestType; + } + + public int getAllianceId() { + return allianceId; + } + + public int getGuildId() { + return guildId; + } + + public String getGuildName() { + return guildName; + } + + public int getInviterId() { + return inviterId; + } + + public int getOldMasterId() { + return oldMasterId; + } + + public int getNewMasterId() { + return newMasterId; + } + + public int getTargetId() { + return targetId; + } + + public boolean isGradeUp() { + return gradeUp; + } + + public String getNotice() { + return notice; + } + + public List getGradeNames() { + return gradeNames; + } + + @Override + public void encode(OutPacket outPacket) { + outPacket.encodeByte(requestType.getValue()); + switch (requestType) { + case Load -> { + outPacket.encodeInt(allianceId); + } + case Withdraw -> { + } + case Invite -> { + outPacket.encodeString(guildName); + } + case Join -> { + outPacket.encodeInt(inviterId); + outPacket.encodeString(guildName); + } + case Kick -> { + outPacket.encodeInt(guildId); + } + case ChangeMaster -> { + outPacket.encodeInt(oldMasterId); + outPacket.encodeInt(newMasterId); + } + case SetGradeName -> { + for (int i = 0; i < GameConstants.UNION_GRADE_MAX; i++) { + outPacket.encodeString(gradeNames.get(i)); + } + } + case ChangeGrade -> { + outPacket.encodeInt(targetId); + outPacket.encodeByte(gradeUp); + } + case SetNotice -> { + outPacket.encodeString(notice); + } + } + } + + public static AllianceRequest decode(InPacket inPacket) { + final int type = inPacket.decodeByte(); + final AllianceRequest request = new AllianceRequest(AllianceRequestType.getByValue(type)); + switch (request.requestType) { + case Load -> { + request.allianceId = inPacket.decodeInt(); + } + case Withdraw -> { + } + case Invite -> { + request.guildName = inPacket.decodeString(); + } + case Join -> { + request.inviterId = inPacket.decodeInt(); + request.guildName = inPacket.decodeString(); + } + case Kick -> { + request.guildId = inPacket.decodeInt(); + } + case ChangeMaster -> { + request.oldMasterId = inPacket.decodeInt(); + request.newMasterId = inPacket.decodeInt(); + } + case SetGradeName -> { + request.gradeNames = new ArrayList<>(); + for (int i = 0; i < GameConstants.UNION_GRADE_MAX; i++) { + request.gradeNames.add(inPacket.decodeString()); + } + } + case ChangeGrade -> { + request.targetId = inPacket.decodeInt(); + request.gradeUp = inPacket.decodeBoolean(); + } + case SetNotice -> { + request.notice = 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 load(int allianceId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Load); + request.allianceId = allianceId; + return request; + } + + public static AllianceRequest withdraw() { + return new AllianceRequest(AllianceRequestType.Withdraw); + } + + public static AllianceRequest invite(String guildName) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Invite); + request.guildName = guildName; + return request; + } + + 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 kick(int guildId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Kick); + request.guildId = guildId; + return request; + } + + 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.Kick); + request.gradeNames = gradeNames; + return request; + } + + public static AllianceRequest changeGrade(int targetId, boolean gradeUp) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.ChangeGrade); + request.targetId = targetId; + request.gradeUp = gradeUp; + return request; + } + + public static AllianceRequest setNotice(String notice) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.SetNotice); + 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 593d9326..b61af579 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), 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 00000000..9a30b199 --- /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/command/Arguments.java b/src/main/java/kinoko/server/command/Arguments.java index 546df677..17769e7d 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 1f0d176b..8d51e68a 100644 --- a/src/main/java/kinoko/server/guild/Guild.java +++ b/src/main/java/kinoko/server/guild/Guild.java @@ -77,6 +77,14 @@ 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 Optional getGuildMaster() { + return guildMembers.values().stream().filter((member) -> member.getGuildRank() == GuildRank.MASTER).findFirst(); + } public List getBoardEntries() { return boardEntries; diff --git a/src/main/java/kinoko/server/guild/GuildStorage.java b/src/main/java/kinoko/server/guild/GuildStorage.java index 6ec8d53c..ef9da17a 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/header/CentralHeader.java b/src/main/java/kinoko/server/header/CentralHeader.java index 7e5b6fa2..13745c90 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 fade8137..228cef05 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -5,9 +5,11 @@ 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; +import kinoko.server.alliance.*; import kinoko.server.guild.*; import kinoko.server.header.CentralHeader; import kinoko.server.memo.Memo; @@ -29,6 +31,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 +79,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); @@ -666,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) -> { @@ -788,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 @@ -809,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)); }); @@ -822,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 @@ -886,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 @@ -915,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 @@ -946,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()) { @@ -965,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 @@ -1008,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()) { @@ -1032,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()) { @@ -1050,6 +1058,482 @@ 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; + } + 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 -> { + 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(); + 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; + } + 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))); + } + } + 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; + } + // 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 -> { + // 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 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(), guild)) { + 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 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 -> { + // 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 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()); + if (allianceResult.isEmpty()) { + 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; + } + 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(); + + // 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 changeGradePacket = AlliancePacket.changeGradeDone(targetMember); + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), changeGradePacket)); + }); + } + } + } + + 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 destroyPacket = AlliancePacket.destroyDone(alliance.getAllianceId()); + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), destroyPacket)); + }); + 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()); + } + } + } + } + private void handleBoardRequest(RemoteServerNode remoteServerNode, InPacket inPacket) { final int characterId = inPacket.decodeInt(); final GuildBoardRequest boardRequest = GuildBoardRequest.decode(inPacket); @@ -1063,7 +1547,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()) { @@ -1075,12 +1559,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 @@ -1092,18 +1576,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(), @@ -1114,62 +1598,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))); } } } @@ -1302,9 +1786,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 5a620ede..c27d1931 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; @@ -21,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; @@ -38,6 +41,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; @@ -184,12 +188,45 @@ public boolean removeGuild(Guild guild) { } public Optional getGuildById(int guildId) { - if (guildId == 0) { - return Optional.empty(); - } return guildStorage.getGuildById(guildId); } + + public Optional getGuildByName(String guildName) { + return guildStorage.getGuildByName(guildName); + } + + // 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); + + 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.empty(); + } + + 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/server/node/ChannelServerNode.java b/src/main/java/kinoko/server/node/ChannelServerNode.java index aa6b9340..0149e3dc 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 ed7529a1..75d69c40 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 ba897145..4c34df1a 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/GameConstants.java b/src/main/java/kinoko/world/GameConstants.java index 18617e51..6fd96582 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/CharacterData.java b/src/main/java/kinoko/world/user/CharacterData.java index ed9e8e25..0fe4d722 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; diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 3cb351d1..ca6db650 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, "", GuildRank.NONE); private final int guildId; private final String guildName; private final GuildRank guildRank; @@ -18,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; @@ -30,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() { @@ -71,7 +73,11 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } - + + public GuildRank getAllianceRank() { + return allianceRank; + } + @Override public void encode(OutPacket outPacket) { outPacket.encodeInt(guildId); @@ -97,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) { @@ -111,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() ); } }