Skip to content

Commit

Permalink
feat: social packets
Browse files Browse the repository at this point in the history
  • Loading branch information
Z-Kris committed Apr 10, 2024
1 parent 7ca4863 commit 60a02dd
Show file tree
Hide file tree
Showing 11 changed files with 673 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package net.rsprot.protocol.game.outgoing.codec.social

import io.netty.channel.ChannelHandlerContext
import net.rsprot.buffer.JagByteBuf
import net.rsprot.protocol.ServerProt
import net.rsprot.protocol.game.outgoing.prot.GameServerProt
import net.rsprot.protocol.game.outgoing.social.FriendListLoaded
import net.rsprot.protocol.message.codec.MessageEncoder
import net.rsprot.protocol.metadata.Consistent

@Consistent
public class FriendListLoadedEncoder : MessageEncoder<FriendListLoaded> {
override val prot: ServerProt = GameServerProt.FRIENDLIST_LOADED

override fun encode(
ctx: ChannelHandlerContext,
buffer: JagByteBuf,
message: FriendListLoaded,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package net.rsprot.protocol.game.outgoing.codec.social

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.prot.GameServerProt
import net.rsprot.protocol.game.outgoing.social.MessagePrivateEcho
import net.rsprot.protocol.message.codec.MessageEncoder
import net.rsprot.protocol.metadata.Consistent

@Consistent
public class MessagePrivateEchoEncoder : MessageEncoder<MessagePrivateEcho> {
override val prot: ServerProt = GameServerProt.MESSAGE_PRIVATE_ECHO

override fun encode(
ctx: ChannelHandlerContext,
buffer: JagByteBuf,
message: MessagePrivateEcho,
) {
val huffmanCodec =
ctx.channel().attr(ChannelAttributes.HUFFMAN_CODEC).get()
?: throw IllegalStateException("Huffman codec not initialized.")
buffer.pjstr(message.recipient)
huffmanCodec.encode(buffer, message.message)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package net.rsprot.protocol.game.outgoing.codec.social

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.prot.GameServerProt
import net.rsprot.protocol.game.outgoing.social.MessagePrivate
import net.rsprot.protocol.message.codec.MessageEncoder
import net.rsprot.protocol.metadata.Consistent

@Consistent
public class MessagePrivateEncoder : MessageEncoder<MessagePrivate> {
override val prot: ServerProt = GameServerProt.MESSAGE_PRIVATE

override fun encode(
ctx: ChannelHandlerContext,
buffer: JagByteBuf,
message: MessagePrivate,
) {
val huffmanCodec =
ctx.channel().attr(ChannelAttributes.HUFFMAN_CODEC).get()
?: throw IllegalStateException("Huffman codec not initialized.")
buffer.pjstr(message.sender)
buffer.p2(message.worldId)
buffer.p3(message.worldMessageCounter)
buffer.p1(message.chatCrownType)
huffmanCodec.encode(buffer, message.message)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package net.rsprot.protocol.game.outgoing.codec.social

import io.netty.channel.ChannelHandlerContext
import net.rsprot.buffer.JagByteBuf
import net.rsprot.protocol.ServerProt
import net.rsprot.protocol.game.outgoing.prot.GameServerProt
import net.rsprot.protocol.game.outgoing.social.UpdateFriendList
import net.rsprot.protocol.message.codec.MessageEncoder
import net.rsprot.protocol.metadata.Consistent

@Consistent
public class UpdateFriendListEncoder : MessageEncoder<UpdateFriendList> {
override val prot: ServerProt = GameServerProt.UPDATE_FRIENDLIST

override fun encode(
ctx: ChannelHandlerContext,
buffer: JagByteBuf,
message: UpdateFriendList,
) {
for (friend in message.friends) {
buffer.p1(if (friend.added) 1 else 0)
buffer.pjstr(friend.name)
buffer.pjstr(friend.previousName ?: "")
buffer.p2(friend.worldId)
buffer.p1(friend.rank)
buffer.p1(friend.properties)
if (friend is UpdateFriendList.OnlineFriend) {
buffer.pjstr(friend.worldName)
buffer.p1(friend.platform)
buffer.p4(friend.worldFlags)
}
buffer.pjstr(friend.notes)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package net.rsprot.protocol.game.outgoing.codec.social

import io.netty.channel.ChannelHandlerContext
import net.rsprot.buffer.JagByteBuf
import net.rsprot.protocol.ServerProt
import net.rsprot.protocol.game.outgoing.prot.GameServerProt
import net.rsprot.protocol.game.outgoing.social.UpdateIgnoreList
import net.rsprot.protocol.message.codec.MessageEncoder
import net.rsprot.protocol.metadata.Consistent

@Consistent
public class UpdateIgnoreListEncoder : MessageEncoder<UpdateIgnoreList> {
override val prot: ServerProt = GameServerProt.UPDATE_IGNORELIST

override fun encode(
ctx: ChannelHandlerContext,
buffer: JagByteBuf,
message: UpdateIgnoreList,
) {
for (ignore in message.ignores) {
when (ignore) {
is UpdateIgnoreList.AddedIgnoredEntry -> {
buffer.p1(if (ignore.added) 0x1 else 0)
buffer.pjstr(ignore.name)
buffer.pjstr(ignore.previousName ?: "")
buffer.pjstr(ignore.note)
}
is UpdateIgnoreList.RemovedIgnoredEntry -> {
buffer.p1(0x4)
buffer.pjstr(ignore.name)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.rsprot.protocol.game.outgoing.social

import net.rsprot.protocol.message.OutgoingMessage

/**
* Friend list loaded is used to mark the friend list
* as loaded if there are no friends to be sent.
* If there are friends to be sent, use the [UpdateFriendList]
* packet instead without this.
*/
public data object FriendListLoaded : OutgoingMessage
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package net.rsprot.protocol.game.outgoing.social

import net.rsprot.protocol.message.OutgoingMessage

/**
* Message private packets are used to send private messages between
* players across multiple worlds.
* This specific packet results in the `From name: message` being shown
* on the target's client.
* @property sender name of the player who is sending the message
* @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
* 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 forwarded to the recipient.
*/
public class MessagePrivate private constructor(
public val sender: String,
private val _worldId: UShort,
public val worldMessageCounter: Int,
private val _chatCrownType: UByte,
public val message: String,
) : OutgoingMessage {
public constructor(
sender: String,
worldId: Int,
worldMessageCounter: Int,
chatCrownType: Int,
message: String,
) : this(
sender,
worldId.toUShort(),
worldMessageCounter,
chatCrownType.toUByte(),
message,
)

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 MessagePrivate

if (sender != other.sender) 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 + _worldId.hashCode()
result = 31 * result + worldMessageCounter
result = 31 * result + _chatCrownType.hashCode()
result = 31 * result + message.hashCode()
return result
}

override fun toString(): String {
return "MessagePrivate(" +
"sender='$sender', " +
"worldId=$worldId, " +
"worldMessageCounter=$worldMessageCounter, " +
"chatCrownType=$chatCrownType, " +
"message='$message'" +
")"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.rsprot.protocol.game.outgoing.social

import net.rsprot.protocol.message.OutgoingMessage

/**
* Message private echo is used to show the messages
* the given player has sent out to others,
* in a "To name: message" format.
* @property recipient the name of the player who received
* the private message.
* @property message the message to be forwarded.
*/
public class MessagePrivateEcho(
public val recipient: String,
public val message: String,
) : OutgoingMessage {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as MessagePrivateEcho

if (recipient != other.recipient) return false
if (message != other.message) return false

return true
}

override fun hashCode(): Int {
var result = recipient.hashCode()
result = 31 * result + message.hashCode()
return result
}

override fun toString(): String {
return "MessagePrivateEcho(" +
"recipient='$recipient', " +
"message='$message'" +
")"
}
}
Loading

0 comments on commit 60a02dd

Please sign in to comment.