From 769b858876ddb1a12e9e31ce1da85ffae1c9b1e5 Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 10 Apr 2024 16:47:56 +0300 Subject: [PATCH] feat: friend chat packets --- .idea/dictionaries/krist.xml | 3 +- .../kotlin/net/rsprot/compression/Base37.kt | 50 ++++++ .../friendchat/MessageFriendChannelEncoder.kt | 31 ++++ .../UpdateFriendChatChannelFullV1Encoder.kt | 31 ++++ .../UpdateFriendChatChannelFullV2Encoder.kt | 31 ++++ ...pdateFriendChatChannelSingleUserEncoder.kt | 28 ++++ .../friendchat/MessageFriendChannel.kt | 103 ++++++++++++ .../friendchat/UpdateFriendChatChannelFull.kt | 70 ++++++++ .../UpdateFriendChatChannelFullV1.kt | 75 +++++++++ .../UpdateFriendChatChannelFullV2.kt | 72 ++++++++ .../UpdateFriendChatChannelSingleUser.kt | 155 ++++++++++++++++++ .../game/outgoing/social/MessagePrivate.kt | 2 +- 12 files changed, 649 insertions(+), 2 deletions(-) create mode 100644 protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/MessageFriendChannelEncoder.kt create mode 100644 protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelFullV1Encoder.kt create mode 100644 protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelFullV2Encoder.kt create mode 100644 protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelSingleUserEncoder.kt create mode 100644 protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/MessageFriendChannel.kt create mode 100644 protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFull.kt create mode 100644 protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFullV1.kt create mode 100644 protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFullV2.kt create mode 100644 protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelSingleUser.kt diff --git a/.idea/dictionaries/krist.xml b/.idea/dictionaries/krist.xml index e191e5dd9..2d3115427 100644 --- a/.idea/dictionaries/krist.xml +++ b/.idea/dictionaries/krist.xml @@ -173,6 +173,7 @@ namedialog nascetur natoque + nbsp neque netus nibh @@ -334,4 +335,4 @@ zero'd - + \ No newline at end of file diff --git a/compression/src/main/kotlin/net/rsprot/compression/Base37.kt b/compression/src/main/kotlin/net/rsprot/compression/Base37.kt index 4eb446c7e..2dd2907ac 100644 --- a/compression/src/main/kotlin/net/rsprot/compression/Base37.kt +++ b/compression/src/main/kotlin/net/rsprot/compression/Base37.kt @@ -1,3 +1,5 @@ +@file:Suppress("DuplicatedCode") + package net.rsprot.compression /** @@ -6,6 +8,7 @@ package net.rsprot.compression public data object Base37 { private const val BASE_37: Long = 37 private const val MAXIMUM_POSSIBLE_12_CHARACTER_VALUE: Long = 6582952005840035280L + private const val NBSP: Int = 160 private val ALPHABET: CharArray = charArrayOf( '_', @@ -133,4 +136,51 @@ public data object Base37 { .reverse() .toString() } + + /** + * Decodes a base-37 encoded long into the respective string, + * replacing all underscores with spaces, as well as all first + * letters of each individual word to begin with an uppercase + * letter. + * If the input long is within the correct range, but isn't v + * @param encoded the base-37 encoded long value. + * @return the string that was encoded in base-37 encoding. + * @throws IllegalArgumentException if the encoded value exceeds + * the maximum 12-character long value, or if the value + * isn't in base-37 representation. + */ + public fun decodeWithCase(encoded: Long): String { + if (encoded == 0L) { + return "" + } + require(encoded in 0..MAXIMUM_POSSIBLE_12_CHARACTER_VALUE) { + "Invalid encoded value: $encoded" + } + require(encoded % BASE_37 != 0L) { + "Encoded value not in base-37: $encoded" + } + var length = 0 + var lengthCounter = encoded + while (lengthCounter != 0L) { + ++length + lengthCounter /= BASE_37 + } + val builder = StringBuilder(length) + var rem = encoded + while (rem != 0L) { + val var6 = rem + rem /= BASE_37 + var char = ALPHABET[(var6 - rem * BASE_37).toInt()] + if (char == '_') { + val lastIndex = builder.length - 1 + builder.setCharAt(lastIndex, builder[lastIndex].uppercaseChar()) + char = NBSP.toChar() + } + builder.append(char) + } + + builder.reverse() + builder.setCharAt(0, builder[0].uppercaseChar()) + return builder.toString() + } } diff --git a/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/MessageFriendChannelEncoder.kt b/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/MessageFriendChannelEncoder.kt new file mode 100644 index 000000000..1596c1973 --- /dev/null +++ b/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/MessageFriendChannelEncoder.kt @@ -0,0 +1,31 @@ +package net.rsprot.protocol.game.outgoing.codec.friendchat + +import io.netty.channel.ChannelHandlerContext +import net.rsprot.buffer.JagByteBuf +import net.rsprot.protocol.ServerProt +import net.rsprot.protocol.channel.ChannelAttributes +import net.rsprot.protocol.game.outgoing.friendchat.MessageFriendChannel +import net.rsprot.protocol.game.outgoing.prot.GameServerProt +import net.rsprot.protocol.message.codec.MessageEncoder +import net.rsprot.protocol.metadata.Consistent + +@Consistent +public class MessageFriendChannelEncoder : MessageEncoder { + override val prot: ServerProt = GameServerProt.MESSAGE_FRIENDCHANNEL + + override fun encode( + ctx: ChannelHandlerContext, + buffer: JagByteBuf, + message: MessageFriendChannel, + ) { + val huffmanCodec = + ctx.channel().attr(ChannelAttributes.HUFFMAN_CODEC).get() + ?: throw IllegalStateException("Huffman codec not initialized.") + buffer.pjstr(message.sender) + buffer.p8(message.channelNameBase37) + buffer.p2(message.worldId) + buffer.p3(message.worldMessageCounter) + buffer.p1(message.chatCrownType) + huffmanCodec.encode(buffer, message.message) + } +} diff --git a/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelFullV1Encoder.kt b/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelFullV1Encoder.kt new file mode 100644 index 000000000..affb5e20f --- /dev/null +++ b/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelFullV1Encoder.kt @@ -0,0 +1,31 @@ +package net.rsprot.protocol.game.outgoing.codec.friendchat + +import io.netty.channel.ChannelHandlerContext +import net.rsprot.buffer.JagByteBuf +import net.rsprot.protocol.ServerProt +import net.rsprot.protocol.game.outgoing.friendchat.UpdateFriendChatChannelFullV1 +import net.rsprot.protocol.game.outgoing.prot.GameServerProt +import net.rsprot.protocol.message.codec.MessageEncoder +import net.rsprot.protocol.metadata.Consistent + +@Consistent +public class UpdateFriendChatChannelFullV1Encoder : MessageEncoder { + override val prot: ServerProt = GameServerProt.UPDATE_FRIENDCHAT_CHANNEL_FULL_V1 + + override fun encode( + ctx: ChannelHandlerContext, + buffer: JagByteBuf, + message: UpdateFriendChatChannelFullV1, + ) { + buffer.pjstr(message.channelOwner) + buffer.p8(message.channelNameBase37) + buffer.p1(message.kickRank) + buffer.p1(message.entries.size) + for (entry in message.entries) { + buffer.pjstr(entry.name) + buffer.p2(entry.worldId) + buffer.p1(entry.rank) + buffer.pjstr(entry.worldName) + } + } +} diff --git a/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelFullV2Encoder.kt b/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelFullV2Encoder.kt new file mode 100644 index 000000000..b3e6b3096 --- /dev/null +++ b/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelFullV2Encoder.kt @@ -0,0 +1,31 @@ +package net.rsprot.protocol.game.outgoing.codec.friendchat + +import io.netty.channel.ChannelHandlerContext +import net.rsprot.buffer.JagByteBuf +import net.rsprot.protocol.ServerProt +import net.rsprot.protocol.game.outgoing.friendchat.UpdateFriendChatChannelFullV2 +import net.rsprot.protocol.game.outgoing.prot.GameServerProt +import net.rsprot.protocol.message.codec.MessageEncoder +import net.rsprot.protocol.metadata.Consistent + +@Consistent +public class UpdateFriendChatChannelFullV2Encoder : MessageEncoder { + override val prot: ServerProt = GameServerProt.UPDATE_FRIENDCHAT_CHANNEL_FULL_V2 + + override fun encode( + ctx: ChannelHandlerContext, + buffer: JagByteBuf, + message: UpdateFriendChatChannelFullV2, + ) { + buffer.pjstr(message.channelOwner) + buffer.p8(message.channelNameBase37) + buffer.p1(message.kickRank) + buffer.pSmart1or2(message.entries.size) + for (entry in message.entries) { + buffer.pjstr(entry.name) + buffer.p2(entry.worldId) + buffer.p1(entry.rank) + buffer.pjstr(entry.worldName) + } + } +} diff --git a/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelSingleUserEncoder.kt b/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelSingleUserEncoder.kt new file mode 100644 index 000000000..2fe68ac58 --- /dev/null +++ b/protocol/osrs-221-desktop/src/main/kotlin/net/rsprot/protocol/game/outgoing/codec/friendchat/UpdateFriendChatChannelSingleUserEncoder.kt @@ -0,0 +1,28 @@ +package net.rsprot.protocol.game.outgoing.codec.friendchat + +import io.netty.channel.ChannelHandlerContext +import net.rsprot.buffer.JagByteBuf +import net.rsprot.protocol.ServerProt +import net.rsprot.protocol.game.outgoing.friendchat.UpdateFriendChatChannelSingleUser +import net.rsprot.protocol.game.outgoing.prot.GameServerProt +import net.rsprot.protocol.message.codec.MessageEncoder +import net.rsprot.protocol.metadata.Consistent + +@Consistent +public class UpdateFriendChatChannelSingleUserEncoder : MessageEncoder { + override val prot: ServerProt = GameServerProt.UPDATE_FRIENDCHAT_CHANNEL_SINGLEUSER + + override fun encode( + ctx: ChannelHandlerContext, + buffer: JagByteBuf, + message: UpdateFriendChatChannelSingleUser, + ) { + val user = message.user + buffer.pjstr(user.name) + buffer.p2(user.worldId) + buffer.p1(user.rank) + if (user is UpdateFriendChatChannelSingleUser.AddedFriendChatUser) { + buffer.pjstr(user.worldName) + } + } +} diff --git a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/MessageFriendChannel.kt b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/MessageFriendChannel.kt new file mode 100644 index 000000000..6f79b118b --- /dev/null +++ b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/MessageFriendChannel.kt @@ -0,0 +1,103 @@ +package net.rsprot.protocol.game.outgoing.friendchat + +import net.rsprot.compression.Base37 +import net.rsprot.protocol.message.OutgoingMessage + +/** + * Message friendchannel is used to transmit messages within a friend + * chat channel. + * @property sender the name of the player who is sending the message + * @property channelName the name of the friend chat channel + * @property worldMessageCounter the world-local message counter. + * Each world must have its own message counter which is used to create + * a unique id for each message. This message counter must be + * incrementing with each message that is sent out. + * If two messages share the same unique id (which is a combination of + * the [worldId] and the [worldMessageCounter] properties), + * the client will not render the second message if it already has one + * received in the last 100 messages. + * It is additionally worth noting that servers with low population + * should probably not start the counter at the same value with each + * game boot, as the probability of multiple messages coinciding + * is relatively high in that scenario, given the low quantity of + * messages sent out to begin with. + * Additionally, only the first 24 bits of the counter are utilized, + * meaning a value from 0 to 16,777,215 (inclusive). + * A good starting point for message counting would be to take the + * hour of the year and multiply it by 50,000 when the server boots + * up. This means the roll-over happens roughly after every two weeks. + * Fine-tuning may be used to make it more granular, but the overall + * idea remains the same. + * @property chatCrownType the id of the crown to render next to the + * name of the sender. + * @property message the message to be sent in the friend chat + * channel. + */ +public class MessageFriendChannel private constructor( + public val sender: String, + public val channelNameBase37: Long, + private val _worldId: UShort, + public val worldMessageCounter: Int, + private val _chatCrownType: UByte, + public val message: String, +) : OutgoingMessage { + public constructor( + sender: String, + channelName: String, + worldId: Int, + worldMessageCounter: Int, + chatCrownType: Int, + message: String, + ) : this( + sender, + Base37.encode(channelName), + worldId.toUShort(), + worldMessageCounter, + chatCrownType.toUByte(), + message, + ) + + public val channelName: String + get() = Base37.decodeWithCase(channelNameBase37) + public val worldId: Int + get() = _worldId.toInt() + public val chatCrownType: Int + get() = _chatCrownType.toInt() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MessageFriendChannel + + if (sender != other.sender) return false + if (channelNameBase37 != other.channelNameBase37) return false + if (_worldId != other._worldId) return false + if (worldMessageCounter != other.worldMessageCounter) return false + if (_chatCrownType != other._chatCrownType) return false + if (message != other.message) return false + + return true + } + + override fun hashCode(): Int { + var result = sender.hashCode() + result = 31 * result + channelNameBase37.hashCode() + result = 31 * result + _worldId.hashCode() + result = 31 * result + worldMessageCounter + result = 31 * result + _chatCrownType.hashCode() + result = 31 * result + message.hashCode() + return result + } + + override fun toString(): String { + return "MessageFriendChannel(" + + "sender='$sender', " + + "channelName='$channelName', " + + "worldId=$worldId, " + + "worldMessageCounter=$worldMessageCounter, " + + "chatCrownType=$chatCrownType, " + + "message='$message'" + + ")" + } +} diff --git a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFull.kt b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFull.kt new file mode 100644 index 000000000..3efd7427d --- /dev/null +++ b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFull.kt @@ -0,0 +1,70 @@ +package net.rsprot.protocol.game.outgoing.friendchat + +public sealed class UpdateFriendChatChannelFull { + public abstract val channelOwner: String + public abstract val channelName: String + public abstract val kickRank: Int + public abstract val entries: List + + /** + * A class to contain all the properties of a player in a friend chat. + * @property name the name of the player that is in the friend chat + * @property worldId the id of the world in which the given user is + * @property rank the rank of the given used in this friend chat + * @property worldName world name, unused in OldSchool RuneScape. + */ + public class FriendChatEntry private constructor( + public val name: String, + private val _worldId: UShort, + private val _rank: Byte, + public val worldName: String, + ) { + public constructor( + name: String, + worldId: Int, + rank: Int, + string: String, + ) : this( + name, + worldId.toUShort(), + rank.toByte(), + string, + ) + + public val worldId: Int + get() = _worldId.toInt() + public val rank: Int + get() = _rank.toInt() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FriendChatEntry + + if (name != other.name) return false + if (_worldId != other._worldId) return false + if (_rank != other._rank) return false + if (worldName != other.worldName) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + _worldId.hashCode() + result = 31 * result + _rank.hashCode() + result = 31 * result + worldName.hashCode() + return result + } + + override fun toString(): String { + return "FriendChatEntry(" + + "name='$name', " + + "worldId=$worldId, " + + "rank=$rank, " + + "worldName='$worldName'" + + ")" + } + } +} diff --git a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFullV1.kt b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFullV1.kt new file mode 100644 index 000000000..4ae4404d6 --- /dev/null +++ b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFullV1.kt @@ -0,0 +1,75 @@ +package net.rsprot.protocol.game.outgoing.friendchat + +import net.rsprot.compression.Base37 +import net.rsprot.protocol.message.OutgoingMessage + +/** + * Update friendchat channel full V1 is used to send full channel updates + * where the list of entries has a size of 255 or less, as that is + * the maximum theoretical limitation. + * @property channelOwner the name of the player who owns this channel + * @property channelName the name of the friend chat channel. + * This name must be compatible with base-37 encoding, meaning + * it cannot have special symbols, and it must be 12 characters of less. + * @property kickRank the minimum rank id to kick another player from + * the friend chat. + * @property entries the list of friend chat entries to be added. + */ +public class UpdateFriendChatChannelFullV1 private constructor( + override val channelOwner: String, + public val channelNameBase37: Long, + private val _kickRank: Byte, + override val entries: List, +) : UpdateFriendChatChannelFull(), OutgoingMessage { + public constructor( + channelOwner: String, + channelName: String, + kickRank: Int, + entries: List, + ) : this( + channelOwner, + Base37.encode(channelName), + kickRank.toByte(), + entries, + ) { + require(entries.size <= 255) { + "Cannot send more than 255 entries in a channel full V1 update." + } + } + + override val channelName: String + get() = Base37.decode(channelNameBase37) + override val kickRank: Int + get() = _kickRank.toInt() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UpdateFriendChatChannelFullV1 + + if (channelOwner != other.channelOwner) return false + if (channelNameBase37 != other.channelNameBase37) return false + if (_kickRank != other._kickRank) return false + if (entries != other.entries) return false + + return true + } + + override fun hashCode(): Int { + var result = channelOwner.hashCode() + result = 31 * result + channelNameBase37.hashCode() + result = 31 * result + _kickRank.hashCode() + result = 31 * result + entries.hashCode() + return result + } + + override fun toString(): String { + return "UpdateFriendChatChannelFullV1(" + + "channelOwner='$channelOwner', " + + "channelName='$channelName', " + + "kickRank=$kickRank, " + + "entries=$entries" + + ")" + } +} diff --git a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFullV2.kt b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFullV2.kt new file mode 100644 index 000000000..c26e0c70e --- /dev/null +++ b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelFullV2.kt @@ -0,0 +1,72 @@ +package net.rsprot.protocol.game.outgoing.friendchat + +import net.rsprot.compression.Base37 +import net.rsprot.protocol.message.OutgoingMessage + +/** + * Update friendchat channel full V2 is used to send full channel updates + * where the list of entries has a size of more than 255. + * It can also support sizes below that, but for sizes in range of 128..255, + * it is more efficient by 1 byte to use V1 of this packet. + * @property channelOwner the name of the player who owns this channel + * @property channelName the name of the friend chat channel. + * This name must be compatible with base-37 encoding, meaning + * it cannot have special symbols, and it must be 12 characters of less. + * @property kickRank the minimum rank id to kick another player from + * the friend chat. + * @property entries the list of friend chat entries to be added. + */ +public class UpdateFriendChatChannelFullV2 private constructor( + override val channelOwner: String, + public val channelNameBase37: Long, + private val _kickRank: Byte, + override val entries: List, +) : UpdateFriendChatChannelFull(), OutgoingMessage { + public constructor( + channelOwner: String, + channelName: String, + kickRank: Int, + entries: List, + ) : this( + channelOwner, + Base37.encode(channelName), + kickRank.toByte(), + entries, + ) + + override val channelName: String + get() = Base37.decode(channelNameBase37) + override val kickRank: Int + get() = _kickRank.toInt() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UpdateFriendChatChannelFullV2 + + if (channelOwner != other.channelOwner) return false + if (channelNameBase37 != other.channelNameBase37) return false + if (_kickRank != other._kickRank) return false + if (entries != other.entries) return false + + return true + } + + override fun hashCode(): Int { + var result = channelOwner.hashCode() + result = 31 * result + channelNameBase37.hashCode() + result = 31 * result + _kickRank.hashCode() + result = 31 * result + entries.hashCode() + return result + } + + override fun toString(): String { + return "UpdateFriendChatChannelFullV2(" + + "channelOwner='$channelOwner', " + + "channelName='$channelName', " + + "kickRank=$kickRank, " + + "entries=$entries" + + ")" + } +} diff --git a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelSingleUser.kt b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelSingleUser.kt new file mode 100644 index 000000000..ad9c344cc --- /dev/null +++ b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/friendchat/UpdateFriendChatChannelSingleUser.kt @@ -0,0 +1,155 @@ +package net.rsprot.protocol.game.outgoing.friendchat + +import net.rsprot.protocol.message.OutgoingMessage + +/** + * Update friendchat singleuser is used to perform a change + * to a friend chat for a single user, whether that be + * adding the user to the friend chat, or removing them. + * @property user the user entry being removed or added. + * Use [AddedFriendChatUser] and [RemovedFriendChatUser] + * respectively to perform different updates. + */ +public class UpdateFriendChatChannelSingleUser private constructor( + public val user: FriendChatUser, +) : OutgoingMessage { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UpdateFriendChatChannelSingleUser + + return user == other.user + } + + override fun hashCode(): Int { + return user.hashCode() + } + + override fun toString(): String { + return "UpdateFriendChatChannelSingleUser(user=$user)" + } + + public sealed interface FriendChatUser { + public val name: String + public val worldId: Int + public val rank: Int + } + + /** + * Added friendchat user indicates a single player + * that is being added to the given friend chat channel. + * @property name the name of the player being added to the friend chat + * @property worldId the id of the world in which that player resides + * @property rank the rank of that player in the friend chat + * @property worldName world name, unused in OldSchool RuneScape. + */ + public class AddedFriendChatUser private constructor( + override val name: String, + private val _worldId: UShort, + private val _rank: Byte, + public val worldName: String, + ) : FriendChatUser { + public constructor( + name: String, + worldId: Int, + rank: Int, + string: String, + ) : this( + name, + worldId.toUShort(), + rank.toByte(), + string, + ) { + require(rank != -128) { + "Rank cannot be -128 as that is used to indicate a removed entry." + } + } + + override val worldId: Int + get() = _worldId.toInt() + override val rank: Int + get() = _rank.toInt() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AddedFriendChatUser + + if (name != other.name) return false + if (_worldId != other._worldId) return false + if (_rank != other._rank) return false + if (worldName != other.worldName) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + _worldId.hashCode() + result = 31 * result + _rank + result = 31 * result + worldName.hashCode() + return result + } + + override fun toString(): String { + return "AddedFriendChatUser(" + + "name='$name', " + + "worldId=$worldId, " + + "rank=$rank, " + + "worldName='$worldName'" + + ")" + } + } + + /** + * Removed friendchat user indicates that a player + * is leaving a friend chat channel. + * @property name the name of the player leaving this friend chat channel + * @property worldId the id of the world in which the player resided. + * Note that the world id must match up or the user will not be removed. + */ + public class RemovedFriendChatUser private constructor( + override val name: String, + private val _worldId: UShort, + ) : FriendChatUser { + public constructor( + name: String, + worldId: Int, + ) : this( + name, + worldId.toUShort(), + ) + + override val worldId: Int + get() = _worldId.toInt() + override val rank: Int + get() = -128 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RemovedFriendChatUser + + if (name != other.name) return false + if (_worldId != other._worldId) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + _worldId.hashCode() + return result + } + + override fun toString(): String { + return "RemovedFriendChatUser(" + + "name='$name', " + + "worldId=$worldId" + + ")" + } + } +} diff --git a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/social/MessagePrivate.kt b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/social/MessagePrivate.kt index 196274d29..47078bd4d 100644 --- a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/social/MessagePrivate.kt +++ b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/social/MessagePrivate.kt @@ -11,7 +11,7 @@ import net.rsprot.protocol.message.OutgoingMessage * @property worldId the id of the world from which the message is sent * @property worldMessageCounter the world-local message counter. * Each world must have its own message counter which is used to create - * a unique id for each private message. This message counter must be + * a unique id for each message. This message counter must be * incrementing with each message that is sent out. * If two messages share the same unique id (which is a combination of * the [worldId] and the [worldMessageCounter] properties),