├── settings.gradle ├── lib └── OpenRS.jar ├── src └── main │ ├── kotlin │ └── io │ │ └── battlerune │ │ ├── game │ │ ├── event │ │ │ ├── Event.kt │ │ │ └── impl │ │ │ │ ├── RegionChangeEvent.kt │ │ │ │ ├── ButtonClickEvent.kt │ │ │ │ ├── DialogueContinueEvent.kt │ │ │ │ ├── MovementEvent.kt │ │ │ │ ├── ClientDimensionChangeEvent.kt │ │ │ │ ├── InterfaceClickEvent.kt │ │ │ │ ├── CommandEvent.kt │ │ │ │ └── ChatMessageEvent.kt │ │ ├── world │ │ │ ├── actor │ │ │ │ ├── Actor.kt │ │ │ │ └── pawn │ │ │ │ │ ├── player │ │ │ │ │ ├── cmd │ │ │ │ │ │ ├── Command.kt │ │ │ │ │ │ └── CommandParser.kt │ │ │ │ │ ├── Rights.kt │ │ │ │ │ ├── PlayerContext.kt │ │ │ │ │ ├── block │ │ │ │ │ │ ├── PlayerHitUpdateBlock.kt │ │ │ │ │ │ ├── PlayerFacePawnUpdateBlock.kt │ │ │ │ │ │ ├── PlayerContextMenuUpdateBlock.kt │ │ │ │ │ │ ├── PlayerFaceCoordinateUpdateBlock.kt │ │ │ │ │ │ ├── PlayerForceMovementUpdateBlock.kt │ │ │ │ │ │ ├── PlayerCacheMovementTypeUpdateBlock.kt │ │ │ │ │ │ ├── PlayerForceChatUpdateBlock.kt │ │ │ │ │ │ ├── PlayerTemporaryMovementUpdateBlock.kt │ │ │ │ │ │ ├── PlayerAnimationUpdateBlock.kt │ │ │ │ │ │ ├── PlayerGraphicUpdateBlock.kt │ │ │ │ │ │ ├── PlayerChatUpdateBlock.kt │ │ │ │ │ │ └── PlayerAppearanceUpdateBlock.kt │ │ │ │ │ ├── Appearance.kt │ │ │ │ │ ├── Viewport.kt │ │ │ │ │ └── Player.kt │ │ │ │ │ ├── Animation.kt │ │ │ │ │ ├── Graphic.kt │ │ │ │ │ ├── update │ │ │ │ │ ├── UpdateBlock.kt │ │ │ │ │ ├── BlockType.kt │ │ │ │ │ ├── PlayerCachedUpdateBlock.kt │ │ │ │ │ └── CachedUpdateBlock.kt │ │ │ │ │ ├── npc │ │ │ │ │ └── Npc.kt │ │ │ │ │ ├── PawnList.kt │ │ │ │ │ ├── Pawn.kt │ │ │ │ │ ├── ChatMessage.kt │ │ │ │ │ └── Movement.kt │ │ │ ├── scene │ │ │ │ ├── collision │ │ │ │ │ ├── TileMask.kt │ │ │ │ │ ├── DirectionMask.kt │ │ │ │ │ ├── CollisionMask.kt │ │ │ │ │ └── CollisionMatrix.kt │ │ │ │ └── MapObject.kt │ │ │ ├── Region.kt │ │ │ ├── RegionManager.kt │ │ │ ├── World.kt │ │ │ └── Position.kt │ │ ├── GameContext.kt │ │ ├── GameConstants.kt │ │ ├── widget │ │ │ └── DisplayType.kt │ │ ├── task │ │ │ └── StartupTask.kt │ │ ├── service │ │ │ ├── GameService.kt │ │ │ ├── StartupTaskService.kt │ │ │ └── StartupService.kt │ │ └── fs │ │ │ ├── decoder │ │ │ └── MapDecoder.kt │ │ │ └── Huffman.kt │ │ ├── net │ │ ├── ProtocolConstants.kt │ │ ├── crypt │ │ │ ├── ISAACCipherPair.kt │ │ │ └── ISAACCipher.kt │ │ ├── codec │ │ │ ├── game │ │ │ │ ├── AccessType.kt │ │ │ │ ├── ByteOrder.kt │ │ │ │ ├── ByteModification.kt │ │ │ │ ├── DownstreamPacketHandler.kt │ │ │ │ ├── UpstreamPacketHandler.kt │ │ │ │ └── RSByteBufReader.kt │ │ │ ├── update │ │ │ │ ├── FileRequest.kt │ │ │ │ ├── UpdateHandshakeMessage.kt │ │ │ │ ├── UpdateEncoder.kt │ │ │ │ └── UpdateDecoder.kt │ │ │ ├── login │ │ │ │ ├── LoginHandshakeMessage.kt │ │ │ │ ├── LoginRequest.kt │ │ │ │ ├── LoginResponseEncoder.kt │ │ │ │ ├── LoginRequestDecoder.kt │ │ │ │ ├── AuthorizationType.kt │ │ │ │ └── LoginDecoder.kt │ │ │ └── handshake │ │ │ │ ├── HandshakeMessage.kt │ │ │ │ ├── HandshakeDecoder.kt │ │ │ │ └── HandshakeEncoder.kt │ │ ├── packet │ │ │ ├── PacketType.kt │ │ │ ├── PacketEncoder.kt │ │ │ ├── PacketRepository.kt │ │ │ ├── Packet.kt │ │ │ ├── PacketDecoder.kt │ │ │ ├── out │ │ │ │ ├── LogoutPacketEncoder.kt │ │ │ │ ├── ResetVarpPacketEncoder.kt │ │ │ │ ├── NpcUpdatePacketEncoder.kt │ │ │ │ ├── SetWeightPacketEncoder.kt │ │ │ │ ├── DynamicRegionUpdatePacketEncoder.kt │ │ │ │ ├── SetEnergyPacketEncoder.kt │ │ │ │ ├── SystemUpdatePacketEncoder.kt │ │ │ │ ├── RemoveInterfacePacketEncoder.kt │ │ │ │ ├── RootInterfacePacketEncoder.kt │ │ │ │ ├── SetCameraPacketEncoder.kt │ │ │ │ ├── PlaySoundEffectPacketEncoder.kt │ │ │ │ ├── InterfaceTextPacketEncoder.kt │ │ │ │ ├── SetDestinationPacketEncoder.kt │ │ │ │ ├── PlaySongPacketEncoder.kt │ │ │ │ ├── SetRegionCoordintePacketEncoder.kt │ │ │ │ ├── DNSLookupPacketEncoder.kt │ │ │ │ ├── ServerMessagePacketEncoder.kt │ │ │ │ ├── SetSkillPacketEncoder.kt │ │ │ │ ├── InterfaceSetsPacketEncoder.kt │ │ │ │ ├── ShowGroundItemPacketEncoder.kt │ │ │ │ ├── ItemOnInterfacePacketEncoder.kt │ │ │ │ ├── InterfaceSettingPacketEncoder.kt │ │ │ │ ├── InterfacePacketEncoder.kt │ │ │ │ ├── VarpPacketEncoder.kt │ │ │ │ ├── CS2ScriptPacketEncoder.kt │ │ │ │ ├── StaticRegionUpdatePacketEncoder.kt │ │ │ │ └── PlayerUpdatePacketEncoder.kt │ │ │ └── in │ │ │ │ ├── RegionChangePacketDecoder.kt │ │ │ │ ├── ButtonClickPacketDecoder.kt │ │ │ │ ├── DialogueContinuePacketDecoder.kt │ │ │ │ ├── CommandPacketDecoder.kt │ │ │ │ ├── InterfaceClickPacketDecoder.kt │ │ │ │ ├── ClientDimensionChangePacketDecoder.kt │ │ │ │ ├── ClickToWalkPacketDecoder.kt │ │ │ │ └── ChatMessagePacketDecoder.kt │ │ ├── channel │ │ │ ├── ExceptionChannelHandler.kt │ │ │ ├── UpstreamFilteredChannelHandler.kt │ │ │ ├── UpstreamChannelHandler.kt │ │ │ └── PlayerChannel.kt │ │ ├── NetworkConstants.kt │ │ ├── ServerPipelineInitializer.kt │ │ ├── NetworkService.kt │ │ └── Client.kt │ │ ├── BattleRune.kt │ │ ├── util │ │ ├── extensions │ │ │ ├── StringExtensions.kt │ │ │ └── ByteBufExtensions.kt │ │ └── GsonParser.kt │ │ ├── content │ │ ├── MovementEventListener.kt │ │ ├── RegionChangeEventListener.kt │ │ ├── ChatMessageEventListener.kt │ │ ├── DialogueContinueEventListener.kt │ │ ├── InterfaceClickEventListener.kt │ │ ├── CommandEventListener.kt │ │ ├── ButtonClickEventListener.kt │ │ └── ClientDimensionChangeEventListener.kt │ │ └── io │ │ ├── FileSystemLoader.kt │ │ ├── HuffmanLoader.kt │ │ ├── PacketRepositoryLoader.kt │ │ └── RegionLoader.kt │ └── resources │ └── log4j2.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.toml ├── LICENSE ├── gradlew.bat ├── .gitignore └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'battlerune' 2 | 3 | -------------------------------------------------------------------------------- /lib/OpenRS.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scape-tools/kotlin-osrs/HEAD/lib/OpenRS.jar -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/Event.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event 2 | 3 | interface Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/Actor.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor 2 | 3 | open class Actor -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scape-tools/kotlin-osrs/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/ProtocolConstants.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net 2 | 3 | object ProtocolConstants { 4 | 5 | val CLIENT_VERSION = 155 6 | 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/crypt/ISAACCipherPair.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.crypt 2 | 3 | class ISAACCipherPair(val encoder: ISAACCipher, val decoder: ISAACCipher) -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/game/AccessType.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.game 2 | 3 | enum class AccessType { 4 | 5 | BIT, 6 | 7 | BYTE 8 | 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/update/FileRequest.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.update 2 | 3 | class FileRequest(val index : Int, val file : Int, val priority : Boolean) -------------------------------------------------------------------------------- /settings.toml: -------------------------------------------------------------------------------- 1 | [game] 2 | server_name = "BattleRune" 3 | 4 | [network] 5 | server_port = 43594 6 | packet_limit = 30 7 | login_limit = 50 8 | logout_limit = 50 9 | connection_timeout = 15 -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/cmd/Command.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.cmd 2 | 3 | // TODO implement eventually 4 | interface Command -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/PacketType.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet 2 | 3 | enum class PacketType { 4 | 5 | FIXED, 6 | VAR_BYTE, 7 | VAR_SHORT 8 | 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/game/ByteOrder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.game 2 | 3 | enum class ByteOrder { 4 | LE, 5 | 6 | BE, 7 | 8 | ME, 9 | 10 | IME 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/scene/collision/TileMask.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.scene.collision 2 | 3 | object TileMask { 4 | val BLOCKED_TILE = 0x1 5 | val BRIDGE_TILE = 0x2 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/Rights.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player 2 | 3 | enum class Rights(val code: Int) { 4 | PLAYER(0), 5 | MOD(1), 6 | ADMIN(2) 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/game/ByteModification.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.game 2 | 3 | enum class ByteModification { 4 | NONE, 5 | 6 | ADD, 7 | 8 | NEG, 9 | 10 | SUB 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/scene/MapObject.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.scene 2 | 3 | import io.battlerune.game.world.Position 4 | 5 | class MapObject(val id: Int, val position: Position, val type: Int, val rotation: Int) -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/BattleRune.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune 2 | 3 | import io.battlerune.game.service.StartupService 4 | 5 | fun main(args: Array) { 6 | 7 | val loader = StartupService() 8 | loader.start() 9 | 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/Animation.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn 2 | 3 | class Animation(val id: Int, val delay: Int) { 4 | 5 | companion object { 6 | val RESET = Animation(65535, 0) 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/impl/RegionChangeEvent.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event.impl 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | 6 | class RegionChangeEvent(val player: Player) : Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/Graphic.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn 2 | 3 | class Graphic(val id: Int, val height: Int = 92, val delay: Int = 0) { 4 | 5 | companion object { 6 | val RESET = Graphic(65535, 92) 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/login/LoginHandshakeMessage.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.login 2 | 3 | import io.battlerune.net.codec.handshake.HandshakeMessage 4 | 5 | class LoginHandshakeMessage(override val type: Int, override val response: Int) : HandshakeMessage -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/PacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | 5 | @FunctionalInterface 6 | interface PacketEncoder { 7 | 8 | fun encode(player: Player) : Packet 9 | 10 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 29 04:52:53 CDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-rc-2-all.zip 7 | -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/PacketRepository.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet 2 | 3 | import io.battlerune.game.event.Event 4 | 5 | object PacketRepository { 6 | 7 | val decoders = arrayOfNulls>(257) 8 | 9 | val sizes = IntArray(257) 10 | 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/update/UpdateHandshakeMessage.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.update 2 | 3 | import io.battlerune.net.codec.handshake.HandshakeMessage 4 | 5 | class UpdateHandshakeMessage(override val type: Int, override val response: Int, val version: Int) : HandshakeMessage -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/impl/ButtonClickEvent.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event.impl 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | 6 | class ButtonClickEvent(val player: Player, val interfaceId: Int, val buttonId: Int) : Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/scene/collision/DirectionMask.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.scene.collision 2 | 3 | object DirectionMask { 4 | 5 | val BLOCK_FLAG_NORTH = 0x1 6 | val BLOCK_FLAG_EAST = 0x2 7 | val BLOCK_FLAG_SOUTH = 0x4 8 | val BLOCK_FLAG_WEST = 0x8 9 | 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/impl/DialogueContinueEvent.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event.impl 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | 6 | data class DialogueContinueEvent(val player: Player, val widgetHash: Int, val slot: Int) : Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/impl/MovementEvent.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event.impl 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.Position 5 | import io.battlerune.game.world.actor.pawn.player.Player 6 | 7 | class MovementEvent(val player: Player, val destination: Position) : Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/Packet.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet 2 | 3 | import io.netty.buffer.ByteBuf 4 | 5 | class Packet(val opcode: Int, val packetType: PacketType, val payload: ByteBuf) { 6 | 7 | // TODO implement 8 | fun isPriority() : Boolean { 9 | return false 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/impl/ClientDimensionChangeEvent.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event.impl 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | 6 | class ClientDimensionChangeEvent(val player: Player, val width: Int, val height: Int, val resized: Boolean) : Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/impl/InterfaceClickEvent.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event.impl 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | 6 | class InterfaceClickEvent(val player: Player, val interfaceId: Int, val button: Int, val item: Int, val slot: Int) : Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/handshake/HandshakeMessage.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.handshake 2 | 3 | interface HandshakeMessage { 4 | 5 | val type: Int 6 | 7 | val response: Int 8 | 9 | companion object { 10 | val VERSION_CURRENT = 0 11 | val VERSION_EXPIRED = 6 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/PlayerContext.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player 2 | 3 | import io.battlerune.game.GameContext 4 | import io.battlerune.net.channel.PlayerChannel 5 | 6 | class PlayerContext(val channel: PlayerChannel, val username: String, val password: String, val gameContext: GameContext) -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/impl/CommandEvent.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event.impl 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.game.world.actor.pawn.player.cmd.CommandParser 6 | 7 | class CommandEvent(val player: Player, val parser : CommandParser) : Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/login/LoginRequest.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.login 2 | 3 | import io.battlerune.net.crypt.ISAACCipherPair 4 | import io.netty.channel.Channel 5 | 6 | class LoginRequest(val username: String, val password: String, val resizeable: Boolean, val lowMem: Boolean, val isaacPair: ISAACCipherPair, val channel: Channel) -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/event/impl/ChatMessageEvent.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.event.impl 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.actor.pawn.ChatMessage 5 | import io.battlerune.game.world.actor.pawn.player.Player 6 | 7 | class ChatMessageEvent(val player: Player, val msg: String, val color : Int, val effect : Int) : Event -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/update/UpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.update 2 | 3 | import io.battlerune.game.world.actor.pawn.Pawn 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | 6 | abstract class UpdateBlock(val mask: Int, val type: BlockType) { 7 | 8 | abstract fun encode(pawn: P, buffer: RSByteBufWriter) 9 | 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/util/extensions/StringExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.util.extensions 2 | 3 | fun String.ipToInt(): Int { 4 | val array = split(".") 5 | var ip = 0 6 | for (i in 0 until array.size) { 7 | val power = 3 - i 8 | 9 | ip += (array[i].toInt()) % 256 * Math.pow(256.toDouble(), power.toDouble()).toInt() 10 | 11 | } 12 | return ip 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/PacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet 2 | 3 | import io.battlerune.game.event.Event 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufReader 6 | 7 | @FunctionalInterface 8 | interface PacketDecoder { 9 | 10 | fun decode(player: Player, reader: RSByteBufReader) : E? 11 | 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/content/MovementEventListener.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.content 2 | 3 | import com.google.common.eventbus.Subscribe 4 | import io.battlerune.game.event.impl.MovementEvent 5 | 6 | class MovementEventListener { 7 | 8 | @Subscribe 9 | fun onEvent(event: MovementEvent) { 10 | println("clicked tile x=${event.destination.x} y=${event.destination.y}") 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/update/BlockType.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.update 2 | 3 | enum class BlockType { 4 | APPEARANCE, 5 | ANIMATION, 6 | FACE_PAWN, 7 | FORCED_CHAT, 8 | HIT, 9 | FACE_COORDINATE, 10 | CHAT, 11 | GFX, 12 | FORCE_MOVEMENT, 13 | CACHE_MOVEMENT_TYPE, 14 | TEMPORARY_MOVEMENT_TYPE, 15 | PLAYER_CONTEXT_MENU 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/content/RegionChangeEventListener.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.content 2 | 3 | import com.google.common.eventbus.Subscribe 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | 6 | class RegionChangeEventListener { 7 | 8 | @Subscribe 9 | fun onEvent(player: Player) { 10 | player.regionChanged = true 11 | 12 | println("a region changed") 13 | 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/npc/Npc.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.npc 2 | 3 | import io.battlerune.game.world.actor.pawn.Pawn 4 | 5 | class Npc : Pawn() { 6 | 7 | override fun preUpdate() { 8 | movement.processMovement() 9 | } 10 | 11 | override fun update() { 12 | 13 | } 14 | 15 | override fun postUpdate() { 16 | 17 | } 18 | 19 | override fun onMovement() { 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/GameContext.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game 2 | 3 | import io.battlerune.game.fs.Huffman 4 | import io.battlerune.game.world.RegionManager 5 | import io.battlerune.game.world.World 6 | import net.openrs.cache.Cache 7 | import java.nio.ByteBuffer 8 | 9 | class GameContext { 10 | 11 | val regionManager = RegionManager() 12 | lateinit var world: World 13 | lateinit var cache: Cache 14 | lateinit var checksumTable: ByteBuffer 15 | lateinit var huffman: Huffman 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/GameConstants.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game 2 | 3 | import com.moandjiezana.toml.Toml 4 | import java.io.File 5 | 6 | object GameConstants { 7 | 8 | val SERVER_NAME: String 9 | 10 | init { 11 | val parser = Toml().read(File("./settings.toml")).getTable("game") 12 | 13 | try { 14 | SERVER_NAME = parser.getString("server_name") 15 | } catch (ex: Exception) { 16 | throw ExceptionInInitializerError(ex) 17 | } 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/Region.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world 2 | 3 | import io.battlerune.game.world.scene.collision.CollisionMatrix 4 | import io.battlerune.game.world.scene.MapObject 5 | 6 | class Region(val regionID: Int) { 7 | 8 | val collisionMaps = arrayOf(CollisionMatrix(64, 64), CollisionMatrix(64, 64), CollisionMatrix(64, 64), CollisionMatrix(64, 64)) 9 | val floors = Array(4) { Array(64) { ByteArray(64) } } 10 | val objects = Array(4) { Array(64) { arrayOfNulls(64) } } 11 | 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/LogoutPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class LogoutPacketEncoder : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | return RSByteBufWriter.alloc().toPacket(28, PacketType.FIXED) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/in/RegionChangePacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.`in` 2 | 3 | import io.battlerune.game.event.impl.RegionChangeEvent 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufReader 6 | import io.battlerune.net.packet.PacketDecoder 7 | 8 | class RegionChangePacketDecoder: PacketDecoder { 9 | 10 | override fun decode(player: Player, reader: RSByteBufReader): RegionChangeEvent { 11 | return RegionChangeEvent(player) 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerHitUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | 8 | class PlayerHitUpdateBlock : UpdateBlock(0x20, BlockType.HIT) { 9 | 10 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 11 | // TODO 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/widget/DisplayType.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.widget 2 | 3 | enum class DisplayType(val root: Int) { 4 | 5 | FIXED(548), 6 | 7 | RESIZABLE(161), 8 | 9 | RESIZABLE_PANEL(164); 10 | 11 | companion object { 12 | 13 | fun lookup(id: Int) : DisplayType { 14 | if (id < 0 || id >= DisplayType.values().size) { 15 | throw IllegalStateException("id=$id must be >= 0 and < ${DisplayType.values().size}") 16 | } 17 | 18 | return DisplayType.values()[id] 19 | } 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerFacePawnUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | 8 | class PlayerFacePawnUpdateBlock : UpdateBlock(0x80, BlockType.FACE_PAWN) { 9 | 10 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 11 | // TODO 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/task/StartupTask.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.task 2 | 3 | import org.apache.logging.log4j.LogManager 4 | 5 | abstract class StartupTask(t: Class) : Runnable { 6 | 7 | val logger = LogManager.getLogger(t) 8 | 9 | override fun run() { 10 | try { 11 | if (load()) { 12 | onComplete() 13 | } 14 | } catch (ex: Exception) { 15 | logger.warn("Startup task failed. ", ex) 16 | } 17 | 18 | } 19 | 20 | abstract fun load() : Boolean 21 | 22 | abstract fun onComplete() 23 | 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/ResetVarpPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class ResetVarpPacketEncoder : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | return writer.toPacket(182, PacketType.FIXED) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerContextMenuUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | 8 | class PlayerContextMenuUpdateBlock : UpdateBlock(0x100, BlockType.PLAYER_CONTEXT_MENU) { 9 | 10 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 11 | // TODO 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerFaceCoordinateUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | 8 | class PlayerFaceCoordinateUpdateBlock : UpdateBlock(0x40, BlockType.FACE_COORDINATE) { 9 | 10 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 11 | // TODO 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerForceMovementUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | 8 | class PlayerForceMovementUpdateBlock : UpdateBlock(0x200, BlockType.FORCE_MOVEMENT) { 9 | 10 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 11 | // TODO implement 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerCacheMovementTypeUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | 8 | class PlayerCacheMovementTypeUpdateBlock : UpdateBlock(0x400, BlockType.CACHE_MOVEMENT_TYPE) { 9 | 10 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 11 | // TODO 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerForceChatUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | 8 | class PlayerForceChatUpdateBlock : UpdateBlock(0x1, BlockType.FORCED_CHAT) { 9 | 10 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 11 | buffer.writeString(pawn.forceChat) 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerTemporaryMovementUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | 8 | class PlayerTemporaryMovementUpdateBlock : UpdateBlock(0x1000, BlockType.TEMPORARY_MOVEMENT_TYPE) { 9 | 10 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 11 | // TODO 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/NpcUpdatePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class NpcUpdatePacketEncoder : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | // TODO implement 14 | return writer.toPacket(108, PacketType.VAR_SHORT) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/content/ChatMessageEventListener.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.content 2 | 3 | import com.google.common.eventbus.Subscribe 4 | import io.battlerune.game.event.impl.ChatMessageEvent 5 | import io.battlerune.game.world.actor.pawn.ChatMessage 6 | 7 | class ChatMessageEventListener { 8 | 9 | @Subscribe 10 | fun onEvent(event: ChatMessageEvent) { 11 | if (!ChatMessage.isValid(event.msg, event.color, event.effect)) { 12 | return 13 | } 14 | 15 | event.player.chat(event.msg, ChatMessage.ChatColor.values()[event.color], ChatMessage.ChatEffect.values()[event.effect]) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/SetWeightPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class SetWeightPacketEncoder(val amount: Int) : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | .writeShort(amount) 14 | return writer.toPacket(249, PacketType.FIXED) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/DynamicRegionUpdatePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class DynamicRegionUpdatePacketEncoder : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | // TODO implement 14 | return writer.toPacket(148, PacketType.VAR_SHORT) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/SetEnergyPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class SetEnergyPacketEncoder(val amount: Int) : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | writer.writeByte(amount) 14 | return writer.toPacket(226, PacketType.FIXED) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/SystemUpdatePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class SystemUpdatePacketEncoder(val seconds: Int) : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | writer.writeShort(seconds) 14 | return writer.toPacket(174, PacketType.FIXED) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/RemoveInterfacePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class RemoveInterfacePacketEncoder(val interfaceId: Int) : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc(4) 13 | writer.writeInt(interfaceId) 14 | return writer.toPacket(141, PacketType.FIXED) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/RootInterfacePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketType 7 | import io.battlerune.net.packet.PacketEncoder 8 | 9 | class RootInterfacePacketEncoder(private val interfaceId: Int): PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | writer.writeShort(interfaceId) 14 | return writer.toPacket(30, PacketType.FIXED) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/Appearance.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player 2 | 3 | class Appearance(val gender: Gender = Gender.MALE, val style: IntArray = intArrayOf(0, 10, 18, 26, 33, 36, 42), val colors: IntArray = intArrayOf(0, 0, 0, 0, 0)) { 4 | 5 | init { 6 | assert(style.size <= MAX_STYLES) 7 | assert(colors.size <= MAX_COLORS) 8 | } 9 | 10 | companion object { 11 | val DEFAULT = Appearance() 12 | 13 | enum class Gender(val code: Int) { 14 | MALE(0x0), 15 | FEMALE(0x1) 16 | } 17 | 18 | val MAX_STYLES = 7 19 | val MAX_COLORS = 5 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/SetCameraPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class SetCameraPacketEncoder(val value1: Int, val value2: Int) : PacketEncoder { 10 | override fun encode(player: Player): Packet { 11 | val writer = RSByteBufWriter.alloc(2) 12 | writer.writeByte(value1) 13 | .writeByte(value2) 14 | return writer.toPacket(159, PacketType.FIXED) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/channel/ExceptionChannelHandler.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.channel 2 | 3 | import io.battlerune.net.NetworkConstants 4 | import io.netty.channel.ChannelHandler 5 | import io.netty.channel.ChannelHandlerContext 6 | import io.netty.channel.ChannelInboundHandlerAdapter 7 | import org.apache.logging.log4j.LogManager 8 | 9 | @ChannelHandler.Sharable 10 | class ExceptionChannelHandler : ChannelInboundHandlerAdapter() { 11 | 12 | override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { 13 | if (!NetworkConstants.IGNORED_EXCEPTIONS.any { cause.message.equals(it) }) { 14 | cause.printStackTrace() 15 | } 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/in/ButtonClickPacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.`in` 2 | 3 | import io.battlerune.game.event.impl.ButtonClickEvent 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufReader 6 | import io.battlerune.net.packet.PacketDecoder 7 | 8 | class ButtonClickPacketDecoder : PacketDecoder { 9 | 10 | override fun decode(player: Player, reader: RSByteBufReader) : ButtonClickEvent { 11 | val value = reader.readInt() 12 | val interfaceId = value shr 16 13 | val buttonId = value and 0xFFFF 14 | return ButtonClickEvent(player, interfaceId, buttonId) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/io/FileSystemLoader.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.io 2 | 3 | import io.battlerune.game.GameContext 4 | import net.openrs.cache.Cache 5 | import net.openrs.cache.FileStore 6 | import org.apache.logging.log4j.LogManager 7 | 8 | class FileSystemLoader(val context: GameContext) : Runnable { 9 | 10 | companion object { 11 | val logger = LogManager.getLogger() 12 | } 13 | 14 | override fun run() { 15 | val cache = Cache(FileStore.open("./data/cache/")) 16 | val checksumTable = cache.createChecksumTable().encode() 17 | context.cache = cache 18 | context.checksumTable = checksumTable 19 | 20 | logger.info("Loaded: file system") 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/in/DialogueContinuePacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.`in` 2 | 3 | import io.battlerune.game.event.impl.DialogueContinueEvent 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufReader 6 | import io.battlerune.net.packet.PacketDecoder 7 | 8 | class DialogueContinuePacketDecoder : PacketDecoder { 9 | 10 | override fun decode(player: Player, reader: RSByteBufReader): DialogueContinueEvent? { 11 | val hash = reader.readInt() 12 | var slot = reader.readUShortLE() 13 | if (slot == 0xFFFF) 14 | slot = -1 15 | return DialogueContinueEvent(player, hash, slot) 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/PlaySoundEffectPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class PlaySoundEffectPacketEncoder(val id: Int, val type: Int, val delay: Int) : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | writer.writeShort(id) 14 | .writeByte(type) 15 | .writeShort(delay) 16 | return writer.toPacket(152, PacketType.FIXED) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerAnimationUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.ByteModification 7 | import io.battlerune.net.codec.game.RSByteBufWriter 8 | 9 | class PlayerAnimationUpdateBlock : UpdateBlock(0x8, BlockType.ANIMATION) { 10 | 11 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 12 | buffer.writeShort(pawn.animation.id, ByteModification.ADD) 13 | .writeByte(pawn.animation.delay) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/in/CommandPacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.`in` 2 | 3 | import io.battlerune.game.event.impl.CommandEvent 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.game.world.actor.pawn.player.cmd.CommandParser 6 | import io.battlerune.net.codec.game.RSByteBufReader 7 | import io.battlerune.net.packet.PacketDecoder 8 | 9 | class CommandPacketDecoder : PacketDecoder { 10 | 11 | override fun decode(player: Player, reader: RSByteBufReader): CommandEvent? { 12 | val cmd = reader.readString() 13 | 14 | if (cmd.isEmpty()) { 15 | return null 16 | } 17 | 18 | return CommandEvent(player, CommandParser(cmd)) 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/in/InterfaceClickPacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.`in` 2 | 3 | import io.battlerune.game.event.impl.InterfaceClickEvent 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufReader 6 | import io.battlerune.net.packet.PacketDecoder 7 | 8 | class InterfaceClickPacketDecoder : PacketDecoder { 9 | 10 | override fun decode(player: Player, reader: RSByteBufReader): InterfaceClickEvent { 11 | val packed = reader.readUInt().toInt() 12 | val item = reader.readUShort() 13 | val slot = reader.readUShort() 14 | return InterfaceClickEvent(player, packed shr 16, packed and 0xFFFF, item, slot) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/InterfaceTextPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketType 7 | import io.battlerune.net.packet.PacketEncoder 8 | 9 | class InterfaceTextPacketEncoder(private val root: Int, private val child: Int, private val message: String) : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | writer.writeInt((root shl 16) or child) 14 | .writeString(message) 15 | return writer.toPacket(42, PacketType.VAR_SHORT) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/io/HuffmanLoader.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.io 2 | 3 | import io.battlerune.game.GameContext 4 | import io.battlerune.game.fs.Huffman 5 | import io.battlerune.game.task.StartupTask 6 | 7 | class HuffmanLoader(val context: GameContext) : StartupTask(HuffmanLoader::class.java) { 8 | 9 | override fun load(): Boolean { 10 | val fileId = context.cache.getFileId(10, "huffman") 11 | val buffer = context.cache.read(10, fileId).data 12 | val size = buffer.remaining() 13 | val data = ByteArray(size) 14 | buffer.get(data) 15 | context.huffman = Huffman(data) 16 | return true 17 | } 18 | 19 | override fun onComplete() { 20 | logger.info("Loaded huffman") 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/SetDestinationPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.Position 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.Packet 7 | import io.battlerune.net.packet.PacketEncoder 8 | import io.battlerune.net.packet.PacketType 9 | 10 | class SetDestinationPacketEncoder(val position: Position) : PacketEncoder { 11 | 12 | override fun encode(player: Player): Packet { 13 | val writer = RSByteBufWriter.alloc(2) 14 | writer.writeByte(position.regionX) 15 | .writeByte(position.regionY) 16 | return writer.toPacket(88, PacketType.FIXED) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/channel/UpstreamFilteredChannelHandler.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.channel 2 | 3 | import io.netty.channel.ChannelHandler 4 | import io.netty.channel.ChannelHandlerContext 5 | import io.netty.channel.ChannelInboundHandlerAdapter 6 | 7 | @ChannelHandler.Sharable 8 | class UpstreamFilteredChannelHandler : ChannelInboundHandlerAdapter() { 9 | 10 | override fun channelRegistered(ctx: ChannelHandlerContext) { 11 | // TODO implement filtering for now just forward to next handler 12 | ctx.fireChannelRegistered() 13 | } 14 | 15 | override fun channelUnregistered(ctx: ChannelHandlerContext) { 16 | // TODO implement filtering for now just forward to next handler 17 | ctx.fireChannelUnregistered() 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/in/ClientDimensionChangePacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.`in` 2 | 3 | import io.battlerune.game.event.impl.ClientDimensionChangeEvent 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufReader 6 | import io.battlerune.net.packet.PacketDecoder 7 | 8 | class ClientDimensionChangePacketDecoder : PacketDecoder { 9 | 10 | override fun decode(player: Player, reader: RSByteBufReader): ClientDimensionChangeEvent { 11 | val resized = reader.readUByte() == 2 12 | val width = reader.readUShort() 13 | val height = reader.readUShort() 14 | return ClientDimensionChangeEvent(player, width, height, resized) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/PlaySongPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteModification 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.Packet 7 | import io.battlerune.net.packet.PacketEncoder 8 | import io.battlerune.net.packet.PacketType 9 | 10 | class PlaySongPacketEncoder(val songId: Int) : PacketEncoder { 11 | 12 | override fun encode(player: Player): Packet { 13 | val writer = RSByteBufWriter.alloc() 14 | writer.writeShort(songId, ByteModification.ADD) 15 | writer.write24IntLE(0) // dummy value 16 | return writer.toPacket(90, PacketType.FIXED) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/SetRegionCoordintePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.Position 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.Packet 7 | import io.battlerune.net.packet.PacketEncoder 8 | import io.battlerune.net.packet.PacketType 9 | 10 | class SetRegionCoordintePacketEncoder(val position: Position) : PacketEncoder { 11 | 12 | override fun encode(player: Player): Packet { 13 | val writer = RSByteBufWriter.alloc() 14 | writer.writeByte(position.regionX) 15 | .writeByte(position.regionY) 16 | return writer.toPacket(109, PacketType.FIXED) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/DNSLookupPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteOrder 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.Packet 7 | import io.battlerune.net.packet.PacketType 8 | import io.battlerune.net.packet.PacketEncoder 9 | import io.battlerune.util.extensions.ipToInt 10 | 11 | class DNSLookupPacketEncoder(val hostAddress: String) : PacketEncoder { 12 | 13 | override fun encode(player: Player): Packet { 14 | val writer = RSByteBufWriter.alloc() 15 | writer.writeInt(hostAddress.ipToInt(), ByteOrder.LE) 16 | return writer.toPacket(193, PacketType.FIXED) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/ServerMessagePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class ServerMessagePacketEncoder(val message: String) : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | writer.writeSmart(0) // must be more than 1 type 14 | .writeByte(0) // read another string if 1 15 | .writeString(message) 16 | return writer.toPacket(100, PacketType.VAR_BYTE) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/SetSkillPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteOrder 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.Packet 7 | import io.battlerune.net.packet.PacketEncoder 8 | import io.battlerune.net.packet.PacketType 9 | 10 | class SetSkillPacketEncoder(private val skill: Int, private val lvl: Int, private val xp: Int) : PacketEncoder { 11 | 12 | override fun encode(player: Player): Packet { 13 | val writer = RSByteBufWriter.alloc() 14 | .writeByte(lvl) 15 | .writeByte(skill) 16 | .writeInt(xp, ByteOrder.IME) 17 | return writer.toPacket(33, PacketType.FIXED) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/InterfaceSetsPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteOrder 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.Packet 7 | import io.battlerune.net.packet.PacketEncoder 8 | import io.battlerune.net.packet.PacketType 9 | 10 | class InterfaceSetsPacketEncoder(val fromRoot: Int, val fromChild: Int, val toRoot: Int, val toChild : Int) : PacketEncoder { 11 | 12 | override fun encode(player: Player): Packet { 13 | val writer = RSByteBufWriter.alloc() 14 | .writeInt((fromRoot shl 16) or fromChild) 15 | .writeInt((toRoot shl 16) or toChild, ByteOrder.ME) 16 | return writer.toPacket(16, PacketType.FIXED) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerGraphicUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.ByteModification 7 | import io.battlerune.net.codec.game.ByteOrder 8 | import io.battlerune.net.codec.game.RSByteBufWriter 9 | 10 | class PlayerGraphicUpdateBlock : UpdateBlock(0x800, BlockType.GFX) { 11 | 12 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 13 | buffer.writeShort(pawn.graphic.id, ByteModification.ADD, ByteOrder.LE) 14 | .writeInt((pawn.graphic.height shl 16) or pawn.graphic.delay, ByteOrder.ME) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/ShowGroundItemPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteModification 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.Packet 7 | import io.battlerune.net.packet.PacketEncoder 8 | import io.battlerune.net.packet.PacketType 9 | 10 | class ShowGroundItemPacketEncoder(val item: Int, val amount: Int) : PacketEncoder { 11 | 12 | override fun encode(player: Player): Packet { 13 | val writer = RSByteBufWriter.alloc() 14 | writer.writeShort(item, ByteModification.ADD) 15 | .writeShort(amount) 16 | .writeByte(0, ByteModification.ADD) // offset 17 | return writer.toPacket(187, PacketType.FIXED) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/content/DialogueContinueEventListener.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.content 2 | 3 | import com.google.common.eventbus.Subscribe 4 | import io.battlerune.game.event.impl.DialogueContinueEvent 5 | 6 | class DialogueContinueEventListener { 7 | 8 | @Subscribe 9 | fun onEvent(event: DialogueContinueEvent) { 10 | val widget = event.widgetHash shr 16 11 | val child = event.widgetHash and 0xFFFF 12 | 13 | println("Dialogue continue event: [widget=$widget, child=$child, slot=${event.slot}]") 14 | 15 | when (widget) { 16 | 193 -> { 17 | when (child) { 18 | 2 -> { 19 | val hash = (162 shl 16) or 546 20 | event.player.client.removeInterface(hash) 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/in/ClickToWalkPacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.`in` 2 | 3 | import io.battlerune.game.event.impl.MovementEvent 4 | import io.battlerune.game.world.Position 5 | import io.battlerune.game.world.actor.pawn.player.Player 6 | import io.battlerune.net.codec.game.ByteModification 7 | import io.battlerune.net.codec.game.RSByteBufReader 8 | import io.battlerune.net.packet.PacketDecoder 9 | 10 | class ClickToWalkPacketDecoder : PacketDecoder { 11 | 12 | override fun decode(player: Player, reader: RSByteBufReader): MovementEvent { 13 | reader.readByte() // constant 5 14 | reader.readByte(ByteModification.SUB) // type 15 | val x = reader.readShortLE(ByteModification.ADD) 16 | val y = reader.readShort() 17 | return MovementEvent(player, Position(x, y, player.position.z)) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/service/GameService.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.service 2 | 3 | import com.google.common.util.concurrent.AbstractScheduledService 4 | import io.battlerune.game.GameContext 5 | import java.util.concurrent.TimeUnit 6 | 7 | class GameService(val context: GameContext) : AbstractScheduledService() { 8 | 9 | companion object { 10 | val GAME_DELAY = 600.toLong() 11 | val TICK_RATE = 600.toLong() 12 | } 13 | 14 | override fun runOneIteration() { 15 | 16 | val world = context.world 17 | 18 | world.processLogins() 19 | 20 | world.updatePlayers() 21 | 22 | // run tasks 23 | 24 | // client sync 25 | 26 | world.processLogouts() 27 | 28 | } 29 | 30 | override fun scheduler(): Scheduler { 31 | return Scheduler.newFixedRateSchedule(GAME_DELAY, TICK_RATE, TimeUnit.MILLISECONDS) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/ItemOnInterfacePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteOrder 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.Packet 7 | import io.battlerune.net.packet.PacketEncoder 8 | import io.battlerune.net.packet.PacketType 9 | 10 | class ItemOnInterfacePacketEncoder(private val widgetId: Int, private val childId: Int, private val itemId: Int, private val itemZoom: Int) : PacketEncoder { 11 | 12 | override fun encode(player: Player): Packet { 13 | val writer = RSByteBufWriter.alloc() 14 | writer.writeInt((widgetId shl 16) or childId, ByteOrder.LE) 15 | .writeShort(itemId, ByteOrder.LE) 16 | .writeInt(itemZoom, ByteOrder.LE) 17 | return writer.toPacket(20, PacketType.FIXED) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/game/DownstreamPacketHandler.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.game 2 | 3 | import io.battlerune.net.crypt.ISAACCipher 4 | import io.battlerune.net.packet.Packet 5 | import io.battlerune.net.packet.PacketType 6 | import io.netty.buffer.ByteBuf 7 | import io.netty.channel.ChannelHandlerContext 8 | import io.netty.handler.codec.MessageToByteEncoder 9 | 10 | class DownstreamPacketHandler(val random: ISAACCipher) : MessageToByteEncoder() { 11 | 12 | override fun encode(ctx: ChannelHandlerContext, msg: Packet, out: ByteBuf) { 13 | val type = msg.packetType 14 | val payload = msg.payload 15 | 16 | out.writeByte(msg.opcode + random.nextInt()) 17 | 18 | if (type == PacketType.VAR_BYTE) { 19 | out.writeByte(payload.writerIndex()) 20 | } else if (type == PacketType.VAR_SHORT) { 21 | out.writeShort(payload.writerIndex()) 22 | } 23 | 24 | out.writeBytes(payload) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/in/ChatMessagePacketDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.`in` 2 | 3 | import io.battlerune.game.event.impl.ChatMessageEvent 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufReader 6 | import io.battlerune.net.packet.PacketDecoder 7 | 8 | class ChatMessagePacketDecoder : PacketDecoder { 9 | 10 | override fun decode(player: Player, reader: RSByteBufReader): ChatMessageEvent? { 11 | reader.readByte() // ? 12 | val color = reader.readByte() 13 | val effect = reader.readByte() 14 | val len = reader.readUSmart() 15 | val compressed = reader.readBytes(reader.size()) 16 | val decompressed = ByteArray(256) 17 | val huffman = player.context.huffman 18 | huffman.decompress(compressed, decompressed, len) 19 | val msg = String(decompressed, 0, len) 20 | return ChatMessageEvent(player, msg, color, effect) 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/RegionManager.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world 2 | 3 | import com.google.common.collect.ArrayListMultimap 4 | 5 | class RegionManager { 6 | 7 | private val regions = arrayOfNulls(32768) 8 | 9 | val keys: ArrayListMultimap = ArrayListMultimap.create() 10 | 11 | fun set(regionId: Int, region: Region) { 12 | if (regionId < 0 || regionId >= regions.size) { 13 | throw IllegalStateException("regionId $regionId must be >= 0 and < ${regions.size}") 14 | } 15 | 16 | regions[regionId] = region 17 | } 18 | 19 | fun lookup(regionId: Int) : Region { 20 | if (regionId < 0 || regionId >= regions.size) { 21 | throw IllegalStateException("regionId $regionId must be >= 0 and < ${regions.size}") 22 | } 23 | 24 | return regions[regionId] ?: throw IllegalStateException("region=$regionId should not be null") 25 | } 26 | 27 | fun count() : Int { 28 | return regions.size 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/InterfaceSettingPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteModification 5 | import io.battlerune.net.codec.game.ByteOrder 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | import io.battlerune.net.packet.Packet 8 | import io.battlerune.net.packet.PacketEncoder 9 | import io.battlerune.net.packet.PacketType 10 | 11 | class InterfaceSettingPacketEncoder(val root: Int, val component: Int, val fromSlot: Int, val toSlot: Int, val setting: Int) : PacketEncoder { 12 | 13 | override fun encode(player: Player): Packet { 14 | val writer = RSByteBufWriter.alloc() 15 | writer.writeShort(fromSlot, ByteModification.ADD) 16 | writer.writeInt(setting, ByteOrder.IME) 17 | writer.writeInt((root shl 16) or component) 18 | writer.writeShort(toSlot, ByteModification.ADD) 19 | return writer.toPacket(72, PacketType.FIXED) 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/update/PlayerCachedUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.update 2 | 3 | import com.google.common.collect.ImmutableSet 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.game.world.actor.pawn.player.block.* 6 | 7 | object PlayerCachedUpdateBlock { 8 | val CACHED_UPDATE_BLOCK : CachedUpdateBlock = CachedUpdateBlock(ImmutableSet.of>( 9 | PlayerFacePawnUpdateBlock(), 10 | PlayerGraphicUpdateBlock(), 11 | PlayerAppearanceUpdateBlock(), 12 | PlayerTemporaryMovementUpdateBlock(), 13 | PlayerCacheMovementTypeUpdateBlock(), 14 | PlayerContextMenuUpdateBlock(), 15 | PlayerForceMovementUpdateBlock(), 16 | PlayerHitUpdateBlock(), 17 | PlayerAnimationUpdateBlock(), 18 | PlayerChatUpdateBlock(), 19 | PlayerFaceCoordinateUpdateBlock(), 20 | PlayerForceChatUpdateBlock() 21 | )) 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/InterfacePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteModification 5 | import io.battlerune.net.codec.game.ByteOrder 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | import io.battlerune.net.packet.Packet 8 | import io.battlerune.net.packet.PacketType 9 | import io.battlerune.net.packet.PacketEncoder 10 | 11 | class InterfacePacketEncoder(private val rootInterfaceId: Int, private val childId: Int, private val interfaceId: Int, private val clickable: Boolean) : PacketEncoder { 12 | 13 | override fun encode(player: Player): Packet { 14 | val writer = RSByteBufWriter.alloc() 15 | writer.writeByte(if (clickable) 1 else 0, ByteModification.SUB) 16 | .writeShort(interfaceId, ByteOrder.LE) 17 | .writeInt((rootInterfaceId shl 16) or childId, ByteOrder.IME) 18 | return writer.toPacket(127, PacketType.FIXED) 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Nshusa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/VarpPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteModification 5 | import io.battlerune.net.codec.game.ByteOrder 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | import io.battlerune.net.packet.Packet 8 | import io.battlerune.net.packet.PacketEncoder 9 | import io.battlerune.net.packet.PacketType 10 | 11 | class VarpPacketEncoder(val id: Int, val state: Int) : PacketEncoder { 12 | 13 | override fun encode(player: Player): Packet { 14 | val writer = RSByteBufWriter.alloc() 15 | 16 | if (state <= Byte.MIN_VALUE || state >= Byte.MAX_VALUE) { // varp large 17 | writer.writeShort(id) 18 | .writeInt(state, ByteOrder.ME) 19 | return writer.toPacket(9, PacketType.FIXED) 20 | } else { // small varp 21 | writer.writeByte(state, ByteModification.SUB) 22 | .writeShort(id, ByteModification.ADD, ByteOrder.LE) 23 | return writer.toPacket(185, PacketType.FIXED) 24 | } 25 | 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/update/UpdateEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.update 2 | 3 | import io.battlerune.net.NetworkConstants 4 | import io.netty.buffer.ByteBuf 5 | import io.netty.channel.ChannelHandlerContext 6 | import io.netty.handler.codec.MessageToByteEncoder 7 | 8 | class UpdateEncoder: MessageToByteEncoder() { 9 | 10 | override fun encode(ctx: ChannelHandlerContext, msg: FileRequest, out: ByteBuf) { 11 | val index = msg.index 12 | val file = msg.file 13 | 14 | val response = ctx.alloc().buffer() 15 | response.writeByte(index) 16 | .writeShort(file) 17 | 18 | if (index == 255 && file == 255) { 19 | 20 | val playerChannel = ctx.channel().attr(NetworkConstants.SESSION_KEY).get() 21 | val checksums = playerChannel.context.checksumTable 22 | 23 | response.writeByte(0) 24 | .writeInt(checksums.limit()) 25 | .writeBytes(checksums) 26 | 27 | } else if (index == 255) { 28 | 29 | } else { 30 | 31 | } 32 | 33 | ctx.writeAndFlush(response) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerChatUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.ByteModification 7 | import io.battlerune.net.codec.game.ByteOrder 8 | import io.battlerune.net.codec.game.RSByteBufWriter 9 | 10 | class PlayerChatUpdateBlock : UpdateBlock(0x10, BlockType.CHAT) { 11 | 12 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 13 | val msg = pawn.chatMessage 14 | 15 | val compressed = ByteArray(256) 16 | val len = pawn.context.huffman.compress(msg.msg, compressed) 17 | buffer.writeShort((msg.color.code shl 8) or msg.effect.code, ByteOrder.LE) 18 | buffer.writeByte(pawn.rights.code) 19 | buffer.writeByte(0, ByteModification.ADD) 20 | buffer.writeByte(len + 1, ByteModification.NEG) 21 | buffer.writeBytesReverse(compressed, len) 22 | buffer.writeSmart(msg.msg.length) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/login/LoginResponseEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.login 2 | 3 | import io.battlerune.net.codec.game.UpstreamPacketHandler 4 | import io.battlerune.net.codec.game.DownstreamPacketHandler 5 | import io.netty.buffer.ByteBuf 6 | import io.netty.channel.ChannelHandlerContext 7 | import io.netty.handler.codec.MessageToByteEncoder 8 | 9 | class LoginResponseEncoder : MessageToByteEncoder() { 10 | 11 | override fun encode(ctx: ChannelHandlerContext, msg: LoginRequest, out: ByteBuf) { 12 | // successful 13 | out.writeByte(2) 14 | 15 | out.writeBoolean(false) // preference flag 1 16 | out.writeInt(0) // 5 17 | out.writeByte(2) // rights 6 18 | out.writeBoolean(true) // members 7 19 | out.writeShort(1) // index 9 20 | out.writeByte(1) // 10 21 | 22 | ctx.pipeline().replace(LoginDecoder::class.simpleName, UpstreamPacketHandler::class.simpleName, UpstreamPacketHandler(msg.isaacPair.decoder)) 23 | ctx.pipeline().replace(LoginResponseEncoder::class.simpleName, DownstreamPacketHandler::class.simpleName, DownstreamPacketHandler(msg.isaacPair.encoder)) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/login/LoginRequestDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.login 2 | 3 | import io.battlerune.net.ProtocolConstants 4 | import io.netty.buffer.ByteBuf 5 | import io.netty.channel.ChannelHandlerContext 6 | import io.netty.handler.codec.ByteToMessageDecoder 7 | 8 | class LoginRequestDecoder : ByteToMessageDecoder() { 9 | 10 | override fun decode(ctx: ChannelHandlerContext, inc: ByteBuf, out: MutableList) { 11 | 12 | if (!inc.isReadable || inc.readableBytes() < 8) { 13 | return 14 | } 15 | 16 | val loginType = inc.readUnsignedByte().toInt() 17 | 18 | val length = inc.readShort() 19 | 20 | if (length.toInt() != inc.readableBytes()) { 21 | return 22 | } 23 | 24 | val version = inc.readInt() 25 | 26 | if (version != ProtocolConstants.CLIENT_VERSION) { 27 | return 28 | } 29 | 30 | ctx.pipeline().replace(LoginRequestDecoder::class.simpleName, LoginDecoder::class.simpleName, LoginDecoder()) 31 | ctx.pipeline().addAfter(LoginDecoder::class.simpleName, LoginResponseEncoder::class.simpleName, LoginResponseEncoder()) 32 | 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/service/StartupTaskService.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.service 2 | 3 | import org.apache.logging.log4j.LogManager 4 | import java.util.concurrent.BlockingQueue 5 | import java.util.concurrent.Executors 6 | import java.util.concurrent.LinkedBlockingQueue 7 | import java.util.concurrent.TimeUnit 8 | 9 | class StartupTaskService { 10 | 11 | companion object { 12 | private val logger = LogManager.getLogger() 13 | 14 | private val service = Executors.newSingleThreadExecutor() 15 | 16 | private val queue: BlockingQueue = LinkedBlockingQueue() 17 | } 18 | 19 | fun start() { 20 | val tasks = queue.size 21 | while(queue.isNotEmpty()) { 22 | val task = queue.poll() ?: continue 23 | service.submit(task) 24 | } 25 | 26 | service.shutdown() 27 | 28 | logger.info("Loaded: $tasks startup tasks.") 29 | } 30 | 31 | fun awaitUntilFinished(timeout: Long = 5, unit: TimeUnit = TimeUnit.MINUTES) { 32 | service.awaitTermination(timeout, unit) 33 | } 34 | 35 | fun queue(task: Runnable) : StartupTaskService { 36 | queue.add(task) 37 | return this 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/update/CachedUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.update 2 | 3 | import com.google.common.collect.ImmutableSet 4 | import io.battlerune.game.world.actor.pawn.Pawn 5 | import io.battlerune.game.world.actor.pawn.player.Player 6 | import io.battlerune.game.world.actor.pawn.player.block.* 7 | import io.battlerune.net.codec.game.RSByteBufWriter 8 | 9 | open class CachedUpdateBlock(private val BLOCKS: ImmutableSet>) { 10 | 11 | fun encode(pawn: P, maskBuf: RSByteBufWriter) { 12 | var mask = 0 13 | 14 | BLOCKS.forEach { BLOCK -> 15 | if (pawn.updateFlags.contains(BLOCK.type)) { 16 | mask = mask or BLOCK.mask 17 | } 18 | } 19 | 20 | if (mask >= 0x100) { 21 | mask = mask or 0x4 22 | maskBuf.writeByte(mask and 0xFF) 23 | maskBuf.writeByte(mask shr 8) 24 | } else { 25 | maskBuf.writeByte(mask and 0xFF) 26 | } 27 | 28 | BLOCKS.forEach { block -> 29 | if (pawn.updateFlags.contains(block.type)) { 30 | block.encode(pawn, maskBuf) 31 | } 32 | } 33 | 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/cmd/CommandParser.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.cmd 2 | 3 | class CommandParser(val input: String) { 4 | 5 | val args: List = input.trim().split(" ") 6 | 7 | val cmd: String = args[0] 8 | 9 | var pointer = 0 10 | 11 | fun nextString() : String { 12 | if (pointer >= args.size) { 13 | throw ArrayIndexOutOfBoundsException("The next argument does not exist. [Size: ${args.size}, Attempted: $pointer]") 14 | } 15 | return args[++pointer] 16 | } 17 | 18 | fun nextLine() : String { 19 | val builder = StringBuilder() 20 | while(hasNext()) { 21 | val string = nextString() 22 | 23 | if (string.contains("\\n")) { 24 | break 25 | } 26 | 27 | builder.append(" ").append(string) 28 | } 29 | return builder.toString() 30 | } 31 | 32 | fun nextInt() : Int { 33 | return nextString().toInt() 34 | } 35 | 36 | fun nextLong() : Long { 37 | return nextString().toLong() 38 | } 39 | 40 | fun hasNext(length: Int = 1): Boolean { 41 | return pointer + length < args.size 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/login/AuthorizationType.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.login 2 | 3 | import io.netty.buffer.ByteBuf 4 | import sun.plugin.dom.exception.InvalidStateException 5 | 6 | enum class AuthorizationType { 7 | 8 | AUTHENTICATOR, 9 | 10 | NORMAL, 11 | 12 | TRUSTED_COMPUTER, 13 | 14 | TRUSTED_AUTHENTICATOR; 15 | 16 | companion object { 17 | 18 | fun lookup(id: Int) : AuthorizationType { 19 | if (id < 0 || id >= AuthorizationType.values().size) { 20 | throw InvalidStateException("id=$id must be >= 0 and < ${AuthorizationType.values().size}") 21 | } 22 | return AuthorizationType.values()[id] 23 | } 24 | 25 | } 26 | 27 | fun read(buf: ByteBuf) { 28 | when(this) { 29 | NORMAL -> { 30 | buf.readerIndex(buf.readerIndex() + 8) 31 | } 32 | 33 | TRUSTED_COMPUTER -> { 34 | buf.readInt() 35 | buf.readerIndex(buf.readerIndex() + 4) 36 | } 37 | 38 | AUTHENTICATOR, TRUSTED_AUTHENTICATOR -> { 39 | buf.readMedium() 40 | buf.readerIndex(buf.readerIndex() + 5) 41 | } 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/util/GsonParser.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.util 2 | 3 | import com.google.gson.JsonObject 4 | import com.google.gson.JsonParser 5 | import java.io.BufferedReader 6 | import java.io.File 7 | import java.io.FileReader 8 | 9 | abstract class GsonParser(val path: String) : Runnable { 10 | 11 | var count = 0 12 | 13 | abstract fun parse(data: JsonObject) 14 | 15 | abstract fun onComplete() 16 | 17 | override fun run() { 18 | try { 19 | BufferedReader(FileReader(File(path))).use { 20 | val parser = JsonParser() 21 | 22 | val jsonElement = parser.parse(it) 23 | 24 | if (jsonElement.isJsonArray) { 25 | for (e in jsonElement.asJsonArray) { 26 | val obj = e.asJsonObject 27 | 28 | parse(obj) 29 | 30 | count++ 31 | } 32 | } else if (jsonElement.isJsonObject) { 33 | parse(jsonElement.asJsonObject) 34 | count++ 35 | } 36 | 37 | onComplete() 38 | 39 | } 40 | } catch (ex: Throwable) { 41 | ex.printStackTrace() 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/service/StartupService.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.service 2 | 3 | import io.battlerune.game.GameContext 4 | import io.battlerune.io.RegionLoader 5 | import io.battlerune.io.FileSystemLoader 6 | import io.battlerune.io.HuffmanLoader 7 | import io.battlerune.io.PacketRepositoryLoader 8 | import io.battlerune.net.NetworkConstants 9 | import io.battlerune.net.NetworkService 10 | 11 | class StartupService { 12 | 13 | private val context = GameContext() 14 | 15 | private val startupService = StartupTaskService() 16 | private val gameService = GameService(context) 17 | private val networkService = NetworkService(context) 18 | 19 | private fun processStartupTasks() { 20 | startupService.queue(PacketRepositoryLoader()) 21 | .queue(FileSystemLoader(context)) 22 | .queue(RegionLoader(context)) 23 | .queue(HuffmanLoader(context)) 24 | } 25 | 26 | fun start() { 27 | processStartupTasks() 28 | startupService.start() 29 | startupService.awaitUntilFinished() 30 | gameService.startAsync() 31 | networkService.start(NetworkConstants.PORT) 32 | } 33 | 34 | // TODO implement eventually 35 | fun restart() { 36 | 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/handshake/HandshakeDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.handshake 2 | 3 | import io.battlerune.net.codec.update.UpdateHandshakeMessage 4 | import io.battlerune.net.codec.login.LoginHandshakeMessage 5 | import io.netty.buffer.ByteBuf 6 | import io.netty.channel.ChannelHandlerContext 7 | import io.netty.handler.codec.ByteToMessageDecoder 8 | 9 | class HandshakeDecoder : ByteToMessageDecoder() { 10 | 11 | companion object { 12 | val LOGIN_REQUEST_HANDSHAKE = 14 13 | val UPDATE_REQUEST_HANDSHAKE = 15 14 | } 15 | 16 | override fun decode(ctx: ChannelHandlerContext, incoming: ByteBuf, outgoing: MutableList) { 17 | 18 | if (!incoming.isReadable) { 19 | return 20 | } 21 | 22 | val requestType = incoming.readUnsignedByte().toInt() 23 | 24 | when(requestType) { 25 | LOGIN_REQUEST_HANDSHAKE -> { 26 | outgoing.add(LoginHandshakeMessage(requestType, HandshakeMessage.VERSION_CURRENT)) 27 | } 28 | 29 | UPDATE_REQUEST_HANDSHAKE -> { 30 | val revision = incoming.readInt() 31 | 32 | outgoing.add(UpdateHandshakeMessage(requestType, HandshakeMessage.VERSION_CURRENT, revision)) 33 | } 34 | } 35 | 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/io/PacketRepositoryLoader.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.io 2 | 3 | import com.google.gson.JsonObject 4 | import io.battlerune.game.event.Event 5 | import io.battlerune.net.packet.PacketRepository 6 | import io.battlerune.net.packet.PacketDecoder 7 | import io.battlerune.util.GsonParser 8 | import org.apache.logging.log4j.LogManager 9 | 10 | class PacketRepositoryLoader : GsonParser("./data/packet_repository.json") { 11 | 12 | companion object { 13 | val logger = LogManager.getLogger() 14 | } 15 | 16 | var decoders = 0 17 | 18 | override fun parse(data: JsonObject) { 19 | 20 | val opcode = data.get("opcode").asInt 21 | val size = data.get("size").asInt 22 | 23 | if (data.has("decoder")) { 24 | 25 | val decoder = Class.forName("io.battlerune.net.packet.in." + data.get("decoder").asString).newInstance() 26 | 27 | if (decoder is PacketDecoder) { 28 | PacketRepository.decoders[opcode] = decoder 29 | decoders++ 30 | } 31 | 32 | } 33 | 34 | PacketRepository.sizes[opcode] = size 35 | } 36 | 37 | override fun onComplete() { 38 | logger.info("Loaded: $count packet sizes.") 39 | logger.info("Loaded: $decoders packet decoders.") 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/update/UpdateDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.update 2 | 3 | import io.netty.buffer.ByteBuf 4 | import io.netty.channel.ChannelHandlerContext 5 | import io.netty.handler.codec.ByteToMessageDecoder 6 | 7 | class UpdateDecoder : ByteToMessageDecoder() { 8 | 9 | companion object { 10 | val NORMAL_FILE_REQUEST = 0 11 | val PRIORITY_FILE_REQUEST = 1 12 | val CLIENT_LOGGED_IN = 2 13 | val CLIENT_LOGGED_OUT = 3 14 | val NEW_ENCRYPTION_USED = 4 15 | } 16 | 17 | override fun decode(ctx: ChannelHandlerContext, incoming: ByteBuf, outgoing: MutableList) { 18 | 19 | if (incoming.readableBytes() >= 4) { 20 | val type = incoming.readUnsignedByte().toInt() 21 | 22 | when(type) { 23 | 24 | NORMAL_FILE_REQUEST, PRIORITY_FILE_REQUEST -> { 25 | 26 | val index = incoming.readUnsignedByte().toInt() 27 | val file = incoming.readUnsignedShort() 28 | val priority = type == 1 29 | 30 | outgoing.add(FileRequest(index, file, priority)) 31 | } 32 | 33 | CLIENT_LOGGED_IN, CLIENT_LOGGED_OUT, NEW_ENCRYPTION_USED -> { 34 | incoming.skipBytes(3) 35 | } 36 | 37 | } 38 | 39 | } 40 | 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/NetworkConstants.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net 2 | 3 | import com.moandjiezana.toml.Toml 4 | import io.netty.util.AttributeKey 5 | import io.battlerune.net.channel.PlayerChannel 6 | import java.io.File 7 | import com.google.common.collect.ImmutableList 8 | 9 | 10 | 11 | object NetworkConstants { 12 | 13 | val PORT: Int 14 | val PACKET_LIMIT: Int 15 | val LOGIN_LIMIT: Int 16 | val LOGOUT_LIMIT: Int 17 | val CONNECTION_TIMEOUT: Int 18 | 19 | init { 20 | val parser = Toml().read(File("./settings.toml")).getTable("network") 21 | 22 | try { 23 | PORT = parser.getLong("server_port").toInt() 24 | PACKET_LIMIT = parser.getLong("packet_limit").toInt() 25 | LOGIN_LIMIT = parser.getLong("login_limit").toInt() 26 | LOGOUT_LIMIT = parser.getLong("logout_limit").toInt() 27 | CONNECTION_TIMEOUT = parser.getLong("connection_timeout").toInt() 28 | } catch (ex: Exception) { 29 | throw ExceptionInInitializerError(ex) 30 | } 31 | } 32 | 33 | val SESSION_KEY: AttributeKey = AttributeKey.valueOf("session.key") 34 | 35 | val IGNORED_EXCEPTIONS = ImmutableList.of( 36 | "An existing connection was forcibly closed by the remote host", 37 | "An established connection was aborted by the software in your host machine") 38 | 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/CS2ScriptPacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | import io.battlerune.net.packet.Packet 6 | import io.battlerune.net.packet.PacketEncoder 7 | import io.battlerune.net.packet.PacketType 8 | 9 | class CS2ScriptPacketEncoder(val id: Int, val params: Array) : PacketEncoder { 10 | 11 | override fun encode(player: Player): Packet { 12 | val writer = RSByteBufWriter.alloc() 13 | 14 | var paramString = "" 15 | 16 | for (param in params.indices.reversed()) { 17 | if (params[param] is String) { 18 | paramString += "s" 19 | } else { 20 | paramString += "i" 21 | } 22 | } 23 | 24 | var parameter = 0 25 | for (index in params.size - 1 downTo 0) { 26 | if (paramString[index] == 's') { 27 | writer.writeString(params[parameter++] as String) 28 | } else { 29 | val param = params[parameter++] 30 | if (param is Int) { 31 | writer.writeInt(param.toString().toInt()) 32 | } else { 33 | writer.writeString(param as String) 34 | } 35 | } 36 | } 37 | 38 | return writer.toPacket(144, PacketType.VAR_SHORT) 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/scene/collision/CollisionMask.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.scene.collision 2 | 3 | object CollisionMask { 4 | val FLOOR_BLOCKSWALK = 0x200000 5 | val FLOORDECO_BLOCKSWALK = 0x40000 6 | val OBJ = 0x100 7 | val OBJ_BLOCKSFLY = 0x20000 8 | val OBJ_BLOCKSWALK_ALTERNATIVE = 0x40000000 9 | val WALLOBJ_NORTH = 0x2 10 | val WALLOBJ_EAST = 0x8 11 | val WALLOBJ_SOUTH = 0x20 12 | val WALLOBJ_WEST = 0x80 13 | val CORNEROBJ_NORTHWEST = 0x1 14 | val CORNEROBJ_NORTHEAST = 0x4 15 | val CORNEROBJ_SOUTHEAST = 0x10 16 | val CORNEROBJ_SOUTHWEST = 0x40 17 | val WALLOBJ_NORTH_BLOCKSFLY = 0x400 18 | val WALLOBJ_EAST_BLOCKSFLY = 0x1000 19 | val WALLOBJ_SOUTH_BLOCKSFLY = 0x4000 20 | val WALLOBJ_WEST_BLOCKSFLY = 0x10000 21 | val CORNEROBJ_NORTHWEST_BLOCKSFLY = 0x200 22 | val CORNEROBJ_NORTHEAST_BLOCKSFLY = 0x800 23 | val CORNEROBJ_SOUTHEAST_BLOCKSFLY = 0x2000 24 | val CORNEROBJ_SOUTHWEST_BLOCKSFLY = 0x8000 25 | val WALLOBJ_NORTH_BLOCKSWALK_ALTERNATIVE = 0x800000 26 | val WALLOBJ_EAST_BLOCKSWALK_ALTERNATIVE = 0x2000000 27 | val WALLOBJ_SOUTH_BLOCKSWALK_ALTERNATIVE = 0x8000000 28 | val WALLOBJ_WEST_BLOCKSWALK_ALTERNATIVE = 0x20000000 29 | val CORNEROBJ_NORTHWEST_BLOCKSWALK_ALTERNATIVE = 0x400000 30 | val CORNEROBJ_NORTHEAST_BLOCKSWALK_ALTERNATIVE = 0x1000000 31 | val CORNEROBJ_SOUTHEAST_BLOCKSWALK_ALTERNATIVE = 0x4000000 32 | val CORNEROBJ_SOUTHWEST_BLOCKSWALK_ALTERNATIVE = 0x10000000 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/handshake/HandshakeEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.handshake 2 | 3 | import io.battlerune.net.ProtocolConstants 4 | import io.battlerune.net.codec.update.UpdateDecoder 5 | import io.battlerune.net.codec.update.UpdateEncoder 6 | import io.battlerune.net.codec.update.UpdateHandshakeMessage 7 | import io.battlerune.net.codec.login.LoginRequestDecoder 8 | import io.netty.buffer.ByteBuf 9 | import io.netty.channel.ChannelHandlerContext 10 | import io.netty.handler.codec.MessageToByteEncoder 11 | 12 | class HandshakeEncoder : MessageToByteEncoder() { 13 | 14 | override fun encode(ctx: ChannelHandlerContext, msg: HandshakeMessage, out: ByteBuf) { 15 | // get past login stage 3 16 | out.writeByte(msg.response) 17 | 18 | if (msg is UpdateHandshakeMessage) { 19 | if (msg.version == ProtocolConstants.CLIENT_VERSION) { 20 | ctx.pipeline().replace(HandshakeDecoder::class.simpleName, UpdateDecoder::class.simpleName, UpdateDecoder()) 21 | ctx.pipeline().addAfter(UpdateDecoder::class.simpleName, UpdateEncoder::class.simpleName, UpdateEncoder()) 22 | } 23 | } else { 24 | 25 | // get past login stage 4 26 | out.writeLong((Math.random() * Long.MAX_VALUE).toLong()) 27 | ctx.pipeline().replace(HandshakeDecoder::class.simpleName, LoginRequestDecoder::class.simpleName, LoginRequestDecoder()) 28 | } 29 | 30 | ctx.pipeline().remove(this) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/PawnList.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn 2 | 3 | import java.util.* 4 | 5 | class PawnList(private val capacity: Int) { 6 | 7 | val list = mutableListOf() 8 | private val slots = Stack() 9 | 10 | init { 11 | for (i in 0 until capacity) { 12 | list.add(null) 13 | 14 | val slot = capacity - i 15 | 16 | slots.push(slot) 17 | } 18 | } 19 | 20 | fun add(t: T) { 21 | if (slots.isEmpty()) { 22 | return 23 | } 24 | 25 | val size = size() 26 | 27 | val slot = slots.pop() 28 | 29 | assert(slot >= 1 && slot < list.size) 30 | 31 | t.index = slot 32 | 33 | list[slot] = t 34 | 35 | assert(size == (size + 1) && (size + 1) < list.size) 36 | } 37 | 38 | fun remove(t: T) { 39 | assert(t.index >= 1 && t.index < list.size) 40 | 41 | val size = size() 42 | 43 | list[t.index] = null 44 | 45 | slots.push(t.index) 46 | 47 | assert(size == (size - 1) && (size - 1) >= 0) 48 | } 49 | 50 | fun get(index: Int) : T? { 51 | assert(index > 0 && index < list.size) 52 | 53 | return list[index] 54 | } 55 | 56 | fun contains(t: T) : Boolean { 57 | return t == list[t.index] 58 | } 59 | 60 | fun isEmpty() : Boolean { 61 | return slots.isEmpty() 62 | } 63 | 64 | fun size() : Int { 65 | return capacity - slots.size 66 | } 67 | 68 | fun capacity() : Int { 69 | return capacity 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/ServerPipelineInitializer.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net 2 | 3 | import io.battlerune.game.GameContext 4 | import io.battlerune.net.channel.ExceptionChannelHandler 5 | import io.battlerune.net.channel.UpstreamFilteredChannelHandler 6 | import io.battlerune.net.channel.PlayerChannel 7 | import io.battlerune.net.channel.UpstreamChannelHandler 8 | import io.battlerune.net.codec.handshake.HandshakeDecoder 9 | import io.battlerune.net.codec.handshake.HandshakeEncoder 10 | import io.netty.channel.ChannelHandler 11 | import io.netty.channel.ChannelInitializer 12 | import io.netty.channel.socket.SocketChannel 13 | import io.netty.handler.timeout.IdleStateHandler 14 | 15 | @ChannelHandler.Sharable 16 | class ServerPipelineInitializer(private val gameContext: GameContext) : ChannelInitializer() { 17 | 18 | companion object { 19 | val FILTER = UpstreamFilteredChannelHandler() 20 | val HANDLER = UpstreamChannelHandler() 21 | } 22 | 23 | override fun initChannel(ch: SocketChannel) { 24 | ch.attr(NetworkConstants.SESSION_KEY).setIfAbsent(PlayerChannel(ch, gameContext)) 25 | ch.pipeline() 26 | .addLast(UpstreamFilteredChannelHandler::class.simpleName, FILTER) 27 | .addLast(HandshakeDecoder::class.simpleName, HandshakeDecoder()) 28 | .addLast(HandshakeEncoder::class.simpleName, HandshakeEncoder()) 29 | .addLast(IdleStateHandler(NetworkConstants.CONNECTION_TIMEOUT, 0, 0)) 30 | .addLast(UpstreamChannelHandler::class.simpleName, HANDLER) 31 | .addLast(ExceptionChannelHandler::class.simpleName, ExceptionChannelHandler()) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/channel/UpstreamChannelHandler.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.channel 2 | 3 | import io.battlerune.net.NetworkConstants 4 | import io.battlerune.net.codec.update.FileRequest 5 | import io.battlerune.net.codec.handshake.HandshakeMessage 6 | import io.battlerune.net.codec.login.LoginRequest 7 | import io.battlerune.net.packet.Packet 8 | import io.netty.channel.ChannelHandler 9 | import io.netty.channel.ChannelHandlerContext 10 | import io.netty.channel.SimpleChannelInboundHandler 11 | 12 | @ChannelHandler.Sharable 13 | class UpstreamChannelHandler : SimpleChannelInboundHandler() { 14 | 15 | override fun channelRead0(ctx: ChannelHandlerContext, msg: Any) { 16 | if (msg is HandshakeMessage) { 17 | ctx.writeAndFlush(msg) 18 | } else if (msg is FileRequest) { 19 | ctx.writeAndFlush(msg) 20 | } else if (msg is LoginRequest) { 21 | val playerChannel = ctx.channel().attr(NetworkConstants.SESSION_KEY).get() ?: return 22 | playerChannel.handleLogin(msg) 23 | } else if (msg is Packet) { 24 | val playerChannel = ctx.channel().attr(NetworkConstants.SESSION_KEY).get() ?: return 25 | playerChannel.handleIncomingPacket(msg) 26 | } 27 | } 28 | 29 | override fun channelInactive(ctx: ChannelHandlerContext) { 30 | super.channelInactive(ctx) 31 | val playerChannel = ctx.channel().attr(NetworkConstants.SESSION_KEY).get() ?: throw IllegalStateException("session is null") 32 | val player = playerChannel.player 33 | 34 | if (!player.initialized) { 35 | return 36 | } 37 | 38 | player.onLogout() 39 | player.context.world.queueLogout(player) 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/util/extensions/ByteBufExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.util.extensions 2 | 3 | import io.netty.buffer.ByteBuf 4 | import io.netty.buffer.Unpooled 5 | 6 | fun ByteBuf.readString(): String { 7 | val sb = StringBuilder() 8 | var b: Byte 9 | while (isReadable) { 10 | b = readByte() 11 | 12 | if (b.toInt() == 0) { 13 | break 14 | } 15 | 16 | sb.append(b.toChar()) 17 | } 18 | return sb.toString() 19 | } 20 | 21 | fun ByteBuf.decryptXTEA(key: IntArray): ByteBuf { 22 | val bytes = ByteArray(readableBytes()) 23 | readBytes(bytes) 24 | val xteaBuffer = Unpooled.wrappedBuffer(bytes) 25 | xteaBuffer.decryptXTEA(0, bytes.size, key) 26 | return xteaBuffer 27 | } 28 | 29 | private fun ByteBuf.decryptXTEA(start: Int, end: Int, key: IntArray) { 30 | if (key.size != 4) { 31 | throw IllegalArgumentException() 32 | } 33 | val numQuads = (end - start) / 8 34 | for (i in 0 until numQuads) { 35 | var sum = -0x61c88647 * 32 36 | var v0 = getInt(start + i * 8) 37 | var v1 = getInt(start + i * 8 + 4) 38 | for (j in 0..31) { 39 | v1 -= (v0 shl 4 xor v0.ushr(5)) + v0 xor sum + key[sum.ushr(11) and 3] 40 | sum -= -0x61c88647 41 | v0 -= (v1 shl 4 xor v1.ushr(5)) + v1 xor sum + key[sum and 3] 42 | } 43 | setInt(start + i * 8, v0) 44 | setInt(start + i * 8 + 4, v1) 45 | } 46 | } 47 | 48 | fun ByteBuf.readJagString(): String { 49 | val sb = StringBuilder() 50 | var b: Byte 51 | readByte() 52 | while (isReadable) { 53 | 54 | b = readByte() 55 | 56 | if (b.toInt() == 0) { 57 | break 58 | } 59 | 60 | sb.append(b.toChar()) 61 | } 62 | return sb.toString() 63 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/Pawn.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn 2 | 3 | import io.battlerune.game.world.Position 4 | import io.battlerune.game.world.Region 5 | import io.battlerune.game.world.actor.Actor 6 | import io.battlerune.game.world.actor.pawn.update.BlockType 7 | import java.util.* 8 | 9 | abstract class Pawn : Actor() { 10 | 11 | lateinit var region: Region 12 | 13 | var position = Position(3222, 3222, 0, Position.RegionSize.DEFAULT) 14 | var lastPosition = Position(3222, 3222, 0, Position.RegionSize.DEFAULT) 15 | 16 | var index = -1 17 | 18 | val updateFlags = EnumSet.noneOf(BlockType::class.java) 19 | 20 | var forceChat = "" 21 | 22 | var regionChanged = false 23 | 24 | var graphic = Graphic.RESET 25 | var animation = Animation.RESET 26 | 27 | val movement = Movement(this) 28 | 29 | abstract fun preUpdate() 30 | abstract fun update() 31 | abstract fun postUpdate() 32 | abstract fun onMovement() 33 | 34 | fun playGfx(id: Int, height: Int = 92, delay: Int = 0) { 35 | graphic = Graphic(id, height, delay) 36 | updateFlags.add(BlockType.GFX) 37 | } 38 | 39 | fun resetGfx() { 40 | graphic = Graphic.RESET 41 | updateFlags.add(BlockType.GFX) 42 | } 43 | 44 | fun playAnim(id: Int, delay: Int = 0) { 45 | animation = Animation(id, delay) 46 | updateFlags.add(BlockType.ANIMATION) 47 | } 48 | 49 | fun resetAnim() { 50 | animation = Animation.RESET 51 | updateFlags.add(BlockType.ANIMATION) 52 | } 53 | 54 | fun forceChat(msg: String) { 55 | if (forceChat.isEmpty()) { 56 | return 57 | } 58 | 59 | forceChat = msg.trim() 60 | updateFlags.add(BlockType.FORCED_CHAT) 61 | } 62 | 63 | 64 | 65 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/io/RegionLoader.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.io 2 | 3 | import com.google.common.primitives.Ints 4 | import io.battlerune.game.GameContext 5 | import io.battlerune.game.fs.decoder.MapDecoder 6 | import io.battlerune.game.task.StartupTask 7 | import io.battlerune.game.world.Region 8 | import java.io.File 9 | import java.nio.file.Files 10 | 11 | class RegionLoader(val gameContext: GameContext) : StartupTask(RegionLoader::class.java) { 12 | 13 | var regions = 0 14 | 15 | override fun load() : Boolean { 16 | val dir = File("./data/xteas/") 17 | 18 | if (!dir.exists()) { 19 | return false 20 | } 21 | 22 | for (file in dir.listFiles()) { 23 | 24 | val keys = mutableListOf() 25 | 26 | val regionId = Integer.parseInt(file.name.substring(0, file.name.indexOf("."))) 27 | 28 | Files.lines(file.toPath()).forEach { keys.add(Integer.parseInt(it)) } 29 | 30 | val map = gameContext.cache.getFileId(5, "m" + (regionId shr 8) + "_" + (regionId and 0xFF)) 31 | val land = gameContext.cache.getFileId(5, "l" + (regionId shr 8) + "_" + (regionId and 0xFF)) 32 | 33 | if (map == -1 || land == -1) { 34 | continue 35 | } 36 | 37 | val region = Region(regionId) 38 | 39 | MapDecoder.decodeTerrain(region, gameContext.cache.read(5, map).data) 40 | MapDecoder.decodeStaticObjects(region, gameContext.cache.read(5, land, Ints.toArray(keys)).data) 41 | 42 | gameContext.regionManager.set(regionId, region) 43 | keys.forEach { gameContext.regionManager.keys.put(regionId, it) } 44 | 45 | regions++ 46 | 47 | } 48 | 49 | return true 50 | } 51 | 52 | override fun onComplete() { 53 | logger.info("Loaded $regions regions") 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/ChatMessage.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn 2 | 3 | /** 4 | * Represents an in-game chat message. 5 | * 6 | * @author nshusa 7 | */ 8 | class ChatMessage(val msg: String = "", val color: ChatColor = ChatColor.YELLOW, val effect: ChatEffect = ChatEffect.NONE) { 9 | 10 | companion object { 11 | val MAX_CHARACTERS = 256 12 | val MAX_COLORS = 12 13 | val MAX_EFFECTS = 6 14 | 15 | fun isValid(msg: String, color: ChatColor, effect: ChatEffect) : Boolean { 16 | return msg.isNotEmpty() && msg.length < MAX_CHARACTERS && color.code >= 0 && color.code < MAX_COLORS && effect.code >= 0 && effect.code < MAX_EFFECTS 17 | } 18 | 19 | fun isValid(msg: String, color: Int, effect: Int) : Boolean { 20 | return msg.isNotEmpty() && msg.length < MAX_CHARACTERS && color >= 0 && color < MAX_COLORS && effect >= 0 && effect < MAX_EFFECTS 21 | } 22 | } 23 | 24 | /** 25 | * Represents a color effect that can be used on a chat message. 26 | * 27 | * Order is really important, do not change the order. 28 | * 29 | * @author nshusa 30 | */ 31 | enum class ChatColor(val code: Int) { 32 | YELLOW(0), 33 | RED(1), 34 | GREEN(2), 35 | CYAN(3), 36 | PURPLE(4), 37 | WHITE(5), 38 | FLASH_1(6), 39 | FLASH_2(7), 40 | FLASH_3(8), 41 | GLOW_1(9), 42 | GLOW_2(10), 43 | GLOW_3(11) 44 | } 45 | 46 | /** 47 | * Represents a non-color effect that can be used on a chat message. 48 | * 49 | * Order is really important, do not change the order. 50 | * 51 | * @author nshusa 52 | */ 53 | enum class ChatEffect(val code: Int) { 54 | NONE(0), 55 | WAVE(1), 56 | WAVE_2(2), 57 | SHAKE(3), 58 | SCROLL(4), 59 | SLIDE(5) 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/StaticRegionUpdatePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.net.codec.game.ByteModification 5 | import io.battlerune.net.codec.game.ByteOrder 6 | import io.battlerune.net.codec.game.RSByteBufWriter 7 | import io.battlerune.net.packet.Packet 8 | import io.battlerune.net.packet.PacketEncoder 9 | import io.battlerune.net.packet.PacketType 10 | 11 | class StaticRegionUpdatePacketEncoder(val buffer: RSByteBufWriter) : PacketEncoder { 12 | 13 | override fun encode(player: Player): Packet { 14 | val writer = RSByteBufWriter.wrap(buffer.buffer) 15 | 16 | val chunkX = player.position.chunkX 17 | val chunkY = player.position.chunkY 18 | 19 | var forceSend = false 20 | 21 | if (((chunkX / 8) == 48 || (chunkX / 8) == 49) && (chunkY / 8) == 48) { 22 | forceSend = true 23 | } 24 | 25 | if (chunkX / 8 == 48 && chunkY / 8 == 149) { 26 | forceSend = true 27 | } 28 | 29 | var count = 0 30 | 31 | val xtea = RSByteBufWriter.alloc() 32 | for (xCalc in (chunkX - 6) / 8..(6 + chunkX) / 8) { 33 | for (yCalc in (chunkY - 6) / 8..(6 + chunkY) / 8) { 34 | val region = yCalc + (xCalc shl 8) 35 | if (!forceSend || yCalc != 49 && 149 != yCalc && 147 != yCalc && xCalc != 50 && (xCalc != 49 || yCalc != 47)) { 36 | val keys = player.context.regionManager.keys[region] 37 | 38 | keys.forEach { xtea.writeInt(it) } 39 | 40 | } 41 | 42 | count++ 43 | 44 | } 45 | } 46 | 47 | writer.writeShort(chunkX, ByteModification.ADD, ByteOrder.LE) 48 | writer.writeShort(chunkY, ByteModification.ADD) 49 | writer.writeShort(count) 50 | writer.writeBytes(xtea.buffer) 51 | return writer.toPacket(150, PacketType.VAR_SHORT) 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/NetworkService.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net 2 | 3 | import io.battlerune.game.GameConstants 4 | import io.battlerune.game.GameContext 5 | import io.battlerune.game.world.World 6 | import io.netty.bootstrap.ServerBootstrap 7 | import io.netty.channel.ChannelFutureListener 8 | import io.netty.channel.ChannelOption 9 | import io.netty.channel.nio.NioEventLoopGroup 10 | import io.netty.channel.socket.nio.NioServerSocketChannel 11 | import io.netty.handler.logging.LogLevel 12 | import io.netty.handler.logging.LoggingHandler 13 | import org.apache.logging.log4j.LogManager 14 | 15 | class NetworkService(val gameContext: GameContext) { 16 | 17 | companion object { 18 | val logger = LogManager.getLogger() 19 | } 20 | 21 | fun start(port: Int) { 22 | val bossGroup = NioEventLoopGroup(1) 23 | val loopGroup = NioEventLoopGroup() 24 | try { 25 | val sb = ServerBootstrap() 26 | 27 | sb.group(bossGroup, loopGroup) 28 | .channel(NioServerSocketChannel().javaClass) 29 | .handler(LoggingHandler(LogLevel.INFO)) 30 | .childHandler(ServerPipelineInitializer(gameContext)) 31 | .option(ChannelOption.SO_BACKLOG, 128) 32 | .option(ChannelOption.TCP_NODELAY, true) 33 | 34 | val f = sb.bind(port).syncUninterruptibly() 35 | 36 | val world = World(gameContext) 37 | gameContext.world = world 38 | 39 | logger.info("[World 1] ${GameConstants.SERVER_NAME} started on port: $port.") 40 | 41 | f.channel().closeFuture().sync() 42 | } catch (ex : Exception) { 43 | ex.printStackTrace() 44 | } finally { 45 | bossGroup.shutdownGracefully() 46 | loopGroup.shutdownGracefully() 47 | } 48 | 49 | } 50 | 51 | fun portAvailable(port: Int) : Boolean { 52 | val future = ServerBootstrap().bind(port) 53 | 54 | if (future.isSuccess) { 55 | future.addListener { ChannelFutureListener.CLOSE } 56 | } 57 | 58 | return future.isSuccess 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/content/InterfaceClickEventListener.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.content 2 | 3 | import com.google.common.eventbus.Subscribe 4 | import io.battlerune.game.event.impl.InterfaceClickEvent 5 | 6 | class InterfaceClickEventListener { 7 | 8 | @Subscribe 9 | fun onEvent(event: InterfaceClickEvent) { 10 | val player = event.player 11 | val button = event.button 12 | 13 | println("interface click event interface=${event.interfaceId} button=${event.button} item=${event.item} slot=${event.slot}") 14 | 15 | when(event.interfaceId) { 16 | 17 | // xp (used for testing atm) 18 | 160 -> { 19 | 20 | if (button == 1) { 21 | player.xpOverlay = !player.xpOverlay 22 | 23 | if (player.xpOverlay) { 24 | player.client.setInterface(548, 16, 122, true) 25 | .setVarp(1055, 131072) 26 | } else { 27 | player.client.setVarp(1055, 0) 28 | .removeInterface(35913744) 29 | } 30 | 31 | } else if (button == 29) { 32 | // minimap interface 33 | //player.client.sendCS2Script(1749, arrayOf(50973940)) 34 | player.client.setInterface(548, 21, 595, true) 35 | //.setInterfaceSettings(595, 17, 0, 4, 2) 36 | } 37 | 38 | } 39 | 40 | 162 -> { if (event.button == 26) player.client.setInterface(548, 20, 553, false)} 41 | 42 | 182 -> { if (event.button == 6) player.logout() } 43 | 44 | 261 -> { 45 | if (event.button == 21) { 46 | //player.client.sendCS2Script(917, arrayOf(-1, -1)) 47 | //player.client.setInterface(161, 13, 60, false) 48 | } 49 | } 50 | 51 | 595 -> { 52 | if (event.button == 34) { 53 | // click to remove minimap 54 | player.client.removeInterface(35913749) 55 | } 56 | } 57 | 58 | } 59 | 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/block/PlayerAppearanceUpdateBlock.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player.block 2 | 3 | import io.battlerune.game.world.actor.pawn.player.Player 4 | import io.battlerune.game.world.actor.pawn.update.UpdateBlock 5 | import io.battlerune.game.world.actor.pawn.update.BlockType 6 | import io.battlerune.net.codec.game.ByteModification 7 | import io.battlerune.net.codec.game.RSByteBufWriter 8 | 9 | class PlayerAppearanceUpdateBlock : UpdateBlock(0x2, BlockType.APPEARANCE) { 10 | 11 | override fun encode(pawn: Player, buffer: RSByteBufWriter) { 12 | val prop = RSByteBufWriter.alloc(64) 13 | prop.writeByte(pawn.appearance.gender.code) // gender 14 | prop.writeByte(if (pawn.skulled) 1 else -1) // skulled 15 | prop.writeByte(-1) // head icon 16 | 17 | // slots 18 | for (i in 0 until 4) { 19 | prop.writeByte(0) 20 | } 21 | 22 | prop.writeShort(0x100 + pawn.appearance.style[2]) // chest 23 | prop.writeByte(0) // shield 24 | prop.writeShort(0x100 + pawn.appearance.style[3]) // full body 25 | prop.writeShort(0x100 + pawn.appearance.style[5]) // legs 26 | prop.writeShort(0x100 + pawn.appearance.style[0]) // hat 27 | prop.writeShort(0x100 + pawn.appearance.style[4]) // hands 28 | prop.writeShort(0x100 + pawn.appearance.style[6]) // feet 29 | prop.writeShort(0x100 + pawn.appearance.style[1]) 30 | 31 | // colors 32 | for (i in 0 until 5) { 33 | prop.writeByte(0) 34 | } 35 | 36 | prop.writeShort(808) // stand anim 37 | .writeShort(823) // stand turn 38 | .writeShort(819) // walk anim 39 | .writeShort(820) // turn 180 40 | .writeShort(821) // turn 90 cw 41 | .writeShort(822) // turn 90 ccw 42 | .writeShort(824) // run anima 43 | .writeString(pawn.username) // username 44 | .writeByte(126) // combat level 45 | .writeShort(0) // skill level 46 | .writeByte(0) // hidden 47 | 48 | buffer.writeByte(prop.buffer.writerIndex() and 0xFF, ByteModification.ADD) 49 | buffer.writeBytes(prop.buffer, ByteModification.ADD) 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/Viewport.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player 2 | 3 | import io.battlerune.game.world.World 4 | import io.battlerune.net.codec.game.RSByteBufWriter 5 | 6 | /** 7 | * This class represents the view of the player. 8 | * 9 | * @author nshusa 10 | */ 11 | class Viewport(val player: Player) { 12 | 13 | companion object { 14 | /** 15 | * The amount of tiles away a player can see 16 | */ 17 | val VIEWING_DISTANCE = 14 18 | } 19 | 20 | /** 21 | * The players in this viewport 22 | */ 23 | val playersInViewport = arrayOfNulls(World.MAX_PLAYER_COUNT) 24 | 25 | /** 26 | * The number of players within this players viewing distance 27 | */ 28 | var playersInsideViewportCount = 0 29 | 30 | /** 31 | * The number of players outside of of this players viewing distance 32 | */ 33 | var playersOutsideViewportCount = 0 34 | 35 | /** 36 | * The indexes of players within this players viewing distance 37 | */ 38 | val playerIndexesInsideViewport = IntArray(World.MAX_PLAYER_COUNT) 39 | 40 | /** 41 | * The indexes of players outside of this players viewing distance 42 | */ 43 | val playerIndexesOutsideViewport = IntArray(World.MAX_PLAYER_COUNT) 44 | 45 | /** 46 | * An array that flags an index within this array where a player should be skipped on the next process 47 | */ 48 | val skipFlags = ByteArray(World.MAX_PLAYER_COUNT) 49 | 50 | var initialized = false 51 | 52 | /** 53 | * Initializes 54 | */ 55 | fun initGPI(buffer: RSByteBufWriter) { 56 | playersInsideViewportCount = 0 57 | playersOutsideViewportCount = 0 58 | 59 | playersInViewport[player.index] = player 60 | playerIndexesInsideViewport[playersInsideViewportCount++] = player.index 61 | 62 | buffer.switchToBitAccess() 63 | buffer.writeBits(30, player.position.toPositionPacked()) 64 | for (i in 1 until World.MAX_PLAYER_COUNT) { 65 | if (player.index != i) { 66 | buffer.writeBits(18, 0) 67 | playerIndexesOutsideViewport[playersOutsideViewportCount++] = i 68 | } 69 | } 70 | buffer.switchToByteAccess() 71 | initialized = true 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/scene/collision/CollisionMatrix.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.scene.collision 2 | 3 | class CollisionMatrix(val width: Int, val height: Int) { 4 | 5 | var masks = Array(width, {IntArray(height)}) 6 | 7 | fun addMask(localX: Int, localY: Int, mask: Int, add: Boolean) { 8 | if (localX < 0 || localX >= 64 || localY < 0 || localY >= 64) { 9 | return 10 | } 11 | 12 | masks[localX][localY] = (if (add) (masks[localX][localY] or mask) else (masks[localX][localY] and mask.inv())) 13 | } 14 | 15 | fun clipTile(localX: Int, localY: Int, add: Boolean) { 16 | addMask(localX, localY, 0x200000, add) 17 | } 18 | 19 | fun addFloor(localX: Int, localY: Int, add: Boolean) { 20 | if (localX < 0 || localX >= 64 || localY < 0 || localY >= 64) { 21 | return 22 | } 23 | addMask(localX, localY, 0x40000, add) 24 | } 25 | 26 | fun setMask(localX: Int, localY: Int, mask: Int) { 27 | masks[localX][localY] = mask 28 | } 29 | 30 | fun removeMask(localX: Int, localY: Int, mask: Int) { 31 | if (localX < 0 || localX >= 64 || localY < 0 || localY >= 64) { 32 | return 33 | } 34 | masks[localX][localY] and mask.inv() 35 | } 36 | 37 | fun removeFloor(localX: Int, localY: Int) { 38 | removeMask(localX, localY, 262144) 39 | } 40 | 41 | fun addObj(localX: Int, localY: Int, sizeX: Int, sizeY: Int, solid: Boolean, notAlternative: Boolean, add:Boolean) { 42 | var mask = 256 43 | 44 | if (solid) { 45 | mask = mask or 0x20000 46 | } 47 | 48 | if (notAlternative) { 49 | mask = mask or 0x40000000 50 | } 51 | 52 | for (tileX in localX until (localX + sizeX)) { 53 | for (tileY in localY until (localY + sizeY)) { 54 | addMask(tileX, tileY, mask, add) 55 | } 56 | } 57 | 58 | } 59 | 60 | fun removeObj(localX: Int, localY: Int, sizeX: Int, sizeY: Int, solid: Boolean, notAlternative: Boolean) { 61 | var mask = 256 62 | 63 | if (solid) { 64 | mask = mask or 131072 65 | } 66 | 67 | if (notAlternative) { 68 | mask = mask or 1073741824 69 | } 70 | 71 | for (tileX in localX until (localX + sizeX)) { 72 | for (tileY in localY until (localY + sizeY)) { 73 | removeMask(tileX, tileY, mask) 74 | } 75 | } 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/content/CommandEventListener.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.content 2 | 3 | import com.google.common.eventbus.Subscribe 4 | import io.battlerune.game.event.impl.CommandEvent 5 | import io.battlerune.game.world.Position 6 | 7 | class CommandEventListener { 8 | 9 | @Subscribe 10 | fun onEvent(event: CommandEvent) { 11 | val player = event.player 12 | val parser = event.parser 13 | when(parser.cmd) { 14 | 15 | "test" -> { 16 | player.playGfx(90, 92, 0) 17 | player.playAnim(711, 0) 18 | } 19 | 20 | "gi" -> { 21 | player.client.showGroundItem(4151, 1, Position(player.position.x + 1, player.position.y)) 22 | } 23 | 24 | "anim" -> { 25 | 26 | // e.g player.playAnim(866, 0) // dance emote 27 | 28 | if (parser.hasNext(2)) { 29 | val id = parser.nextInt() 30 | val delay = parser.nextInt() 31 | player.playAnim(id, delay) 32 | } else if (parser.hasNext()) { 33 | val id = parser.nextInt() 34 | player.playAnim(id) 35 | } 36 | } 37 | 38 | "gfx" -> { 39 | 40 | // example gfx player.playGfx(90, 92, 0) // wind strike 41 | 42 | if (parser.hasNext(3)) { 43 | val id = parser.nextInt() 44 | val height = parser.nextInt() 45 | val delay = parser.nextInt() 46 | player.playGfx(id, height, delay) 47 | } else if (parser.hasNext(2)) { 48 | val id = parser.nextInt() 49 | val height = parser.nextInt() 50 | player.playGfx(id, height) 51 | } else if (parser.hasNext()) { 52 | val id = parser.nextInt() 53 | player.playGfx(id) 54 | } 55 | } 56 | 57 | "fc" -> { // force chat 58 | 59 | if (parser.hasNext()) { 60 | player.forceChat(parser.nextLine()) 61 | } 62 | 63 | } 64 | 65 | "chat" -> { 66 | if (parser.hasNext()) { 67 | player.chat(parser.nextLine()) 68 | } else { 69 | player.chat("Testing!!!") 70 | } 71 | 72 | } 73 | 74 | } 75 | 76 | println("command event: ${event.parser.cmd}") 77 | 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/game/UpstreamPacketHandler.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.game 2 | 3 | import io.battlerune.net.crypt.ISAACCipher 4 | import io.battlerune.net.packet.Packet 5 | import io.battlerune.net.packet.PacketRepository 6 | import io.battlerune.net.packet.PacketType 7 | import io.netty.buffer.ByteBuf 8 | import io.netty.channel.ChannelHandlerContext 9 | import io.netty.handler.codec.ByteToMessageDecoder 10 | 11 | class UpstreamPacketHandler(private val random: ISAACCipher) : ByteToMessageDecoder() { 12 | companion object { 13 | enum class State { 14 | OPCODE, 15 | SIZE, 16 | PAYLOAD 17 | } 18 | } 19 | 20 | var state = State.OPCODE 21 | var opcode: Int = 0 22 | var size: Int = 0 23 | var packetType: PacketType = PacketType.FIXED 24 | 25 | override fun decode(ctx: ChannelHandlerContext, inc: ByteBuf, out: MutableList) { 26 | if (!inc.isReadable) { 27 | return 28 | } 29 | 30 | when(state) { 31 | State.OPCODE -> { readOpcode(inc) } 32 | State.SIZE -> { readSize(inc) } 33 | State.PAYLOAD -> { readPayload(inc, out) } 34 | } 35 | } 36 | 37 | private fun readOpcode(inc: ByteBuf) { 38 | if (!inc.isReadable) { 39 | return 40 | } 41 | 42 | // TODO ISAAC 43 | opcode = inc.readUnsignedByte().toInt() 44 | size = PacketRepository.sizes[opcode] 45 | 46 | when (size) { 47 | -2 -> packetType =PacketType.VAR_SHORT 48 | -1 -> packetType =PacketType.VAR_BYTE 49 | 0 -> return 50 | else -> packetType = PacketType.FIXED 51 | } 52 | 53 | state = (if (packetType == PacketType.FIXED) State.PAYLOAD else State.SIZE) 54 | } 55 | 56 | private fun readPayload(inc: ByteBuf, out: MutableList) { 57 | if (inc.readableBytes() < size) { 58 | return 59 | } 60 | 61 | if (size != 0) { 62 | out.add(Packet(opcode, packetType, inc.readBytes(size))) 63 | } 64 | state = State.OPCODE 65 | } 66 | 67 | private fun readSize(inc: ByteBuf) { 68 | if (packetType == PacketType.VAR_BYTE) { 69 | if (inc.isReadable) { 70 | size = inc.readUnsignedByte().toInt() 71 | } 72 | } else if (packetType == PacketType.VAR_SHORT) { 73 | if (inc.readableBytes() >= 2) { 74 | size = inc.readUnsignedShort() 75 | } 76 | } 77 | state = State.PAYLOAD 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/content/ButtonClickEventListener.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.content 2 | 3 | import com.google.common.eventbus.Subscribe 4 | import io.battlerune.game.event.impl.ButtonClickEvent 5 | 6 | class ButtonClickEventListener { 7 | 8 | @Subscribe 9 | fun onEvent(event: ButtonClickEvent) { 10 | val player = event.player 11 | 12 | println("interface: ${event.interfaceId} buttonId: ${event.buttonId}") 13 | 14 | when(event.interfaceId) { 15 | 16 | 182 -> { 17 | if (event.buttonId == 6) { 18 | player.logout() 19 | } 20 | } 21 | 22 | 378 -> { 23 | if (event.buttonId == 6) { 24 | player.inGame = true 25 | player.client 26 | .setRootInterface(player.displayType.root) 27 | .setInterfaceSets(165, 1, 548, 23) 28 | .setInterfaceSets(165, 2, 548, 13) 29 | .setInterfaceSets(165, 3, 548, 15) 30 | .setInterfaceSets(165, 4, 548, 16) 31 | .setInterfaceSets(165, 5, 548, 17) 32 | .setInterfaceSets(165, 6, 548, 20) 33 | .setInterfaceSets(165, 7, 548, 63) 34 | .setInterfaceSets(165, 8, 548, 65) 35 | .setInterfaceSets(165, 9, 548, 66) 36 | .setInterfaceSets(165, 10, 548, 67) 37 | .setInterfaceSets(165, 11, 548, 68) 38 | .setInterfaceSets(165, 12, 548, 69) 39 | .setInterfaceSets(165, 13, 548, 70) 40 | .setInterfaceSets(165, 14, 548, 71) 41 | .setInterfaceSets(165, 15, 548, 72) 42 | .setInterfaceSets(165, 16, 548, 73) 43 | .setInterfaceSets(165, 17, 548, 74) 44 | .setInterfaceSets(165, 18, 548, 75) 45 | .setInterfaceSets(165, 19, 548, 76) 46 | .setInterfaceSets(165, 20, 548, 77) 47 | .setInterfaceSets(165, 21, 548, 78) 48 | .setInterfaceSets(165, 22, 548, 14) 49 | .setInterfaceSets(165, 23, 548, 18) 50 | .setInterfaceSets(165, 24, 548, 10) 51 | .setInterfaceSets(165, 30, 548, 21) 52 | //.setVarp(1055, 132608) 53 | 54 | } 55 | } 56 | 57 | } 58 | 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/fs/decoder/MapDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.fs.decoder 2 | 3 | import io.battlerune.game.world.scene.MapObject 4 | import io.battlerune.game.world.Position 5 | import io.battlerune.game.world.Region 6 | import io.battlerune.game.world.scene.collision.TileMask 7 | import net.openrs.util.ByteBufferUtils 8 | import java.nio.ByteBuffer 9 | 10 | object MapDecoder { 11 | 12 | fun decodeTerrain(region: Region, mapBuf: ByteBuffer) { 13 | for (height in 0..3) { 14 | for (localX in 0..63) { 15 | for (localY in 0..63) { 16 | while (true) { 17 | val attributeId = mapBuf.get().toInt() and 0xFF 18 | 19 | if (attributeId == 0) { 20 | break 21 | } else if (attributeId == 1) { 22 | mapBuf.get() 23 | break 24 | } else if (attributeId <= 49) { 25 | mapBuf.get() 26 | } else if (attributeId <= 81) { 27 | region.floors[height][localX][localY] = (attributeId - 49).toByte() 28 | } 29 | } 30 | 31 | var realPlane = height 32 | 33 | val floor = region.floors[height][localX][localY].toInt() 34 | 35 | if (floor and TileMask.BLOCKED_TILE == 1) { 36 | 37 | if (floor and TileMask.BRIDGE_TILE == 2) { 38 | realPlane-- 39 | } 40 | 41 | if (realPlane >= 0) { 42 | region.collisionMaps[realPlane].addFloor(localX, localY, true) 43 | } 44 | 45 | } 46 | 47 | } 48 | } 49 | } 50 | 51 | } 52 | 53 | fun decodeStaticObjects(region: Region, objBuf: ByteBuffer) { 54 | var id = -1 55 | var idOffset = ByteBufferUtils.getUnsignedSmart(objBuf) 56 | 57 | while (idOffset != 0) { 58 | id += idOffset 59 | 60 | var packed = 0 61 | var positionOffset = ByteBufferUtils.getUnsignedSmart(objBuf) 62 | 63 | while (positionOffset != 0) { 64 | packed += positionOffset - 1 65 | 66 | val localY = packed and 0x3F 67 | val localX = packed shr 6 and 0x3F 68 | val height = packed shr 12 and 0x3 69 | 70 | val attributes = objBuf.get().toInt() and 0xFF 71 | val type = attributes shr 2 72 | val orientation = attributes and 0x3 73 | val position = Position((region.regionID shr 8 and 0xFF) * 64 + localX,(region.regionID and 0xFF) * 64 + localY, height) 74 | 75 | region.objects[height][localX][localY] = MapObject(id, position, type, orientation) 76 | 77 | positionOffset = ByteBufferUtils.getUnsignedSmart(objBuf) 78 | } 79 | 80 | idOffset = ByteBufferUtils.getUnsignedSmart(objBuf) 81 | } 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/World.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world 2 | 3 | import com.google.common.eventbus.EventBus 4 | import io.battlerune.content.* 5 | import io.battlerune.game.GameContext 6 | import io.battlerune.game.world.actor.pawn.Pawn 7 | import io.battlerune.game.world.actor.pawn.PawnList 8 | import io.battlerune.game.world.actor.pawn.player.Player 9 | import io.battlerune.net.NetworkConstants 10 | import org.apache.logging.log4j.LogManager 11 | import org.apache.logging.log4j.Logger 12 | import java.util.concurrent.ConcurrentLinkedQueue 13 | 14 | class World(val gameContext: GameContext) { 15 | 16 | companion object { 17 | val MAX_PLAYER_COUNT = 2048 18 | } 19 | 20 | val eventBus = EventBus() 21 | val logger: Logger = LogManager.getLogger() 22 | 23 | init { 24 | init() 25 | } 26 | 27 | val logins = ConcurrentLinkedQueue() 28 | val logouts = ConcurrentLinkedQueue() 29 | val players = PawnList(MAX_PLAYER_COUNT) 30 | 31 | fun queueLogin(player: Player) { 32 | logins.add(player) 33 | } 34 | 35 | fun queueLogout(player: Player) { 36 | logouts.add(player) 37 | } 38 | 39 | fun updatePlayers() { 40 | for (player in players.list) { 41 | player?.preUpdate() 42 | player?.update() 43 | player?.postUpdate() 44 | } 45 | } 46 | 47 | fun processLogins() { 48 | if (logins.isEmpty()) { 49 | return 50 | } 51 | 52 | for (i in 0 until NetworkConstants.LOGIN_LIMIT) { 53 | val player = logins.poll() ?: break 54 | register(player) 55 | } 56 | 57 | } 58 | 59 | fun processLogouts() { 60 | if (logouts.isEmpty()) { 61 | return 62 | } 63 | 64 | for (i in 0 until NetworkConstants.LOGOUT_LIMIT) { 65 | val player = logouts.poll() ?: break 66 | player.onLogout() 67 | unregister(player) 68 | } 69 | } 70 | 71 | private fun register(pawn: Pawn) { 72 | if (pawn is Player) { 73 | players.add(pawn) 74 | pawn.initialized = true 75 | pawn.teleported = true 76 | pawn.onLogin() 77 | } 78 | } 79 | 80 | private fun unregister(pawn: Pawn) { 81 | if (pawn is Player) { 82 | pawn.onLogout() 83 | players.remove(pawn) 84 | } 85 | } 86 | 87 | private fun init() { 88 | registerEvents() 89 | } 90 | 91 | private fun registerEvents() { 92 | eventBus.register(ButtonClickEventListener()) 93 | eventBus.register(ClientDimensionChangeEventListener()) 94 | eventBus.register(InterfaceClickEventListener()) 95 | eventBus.register(CommandEventListener()) 96 | eventBus.register(RegionChangeEventListener()) 97 | eventBus.register(MovementEventListener()) 98 | eventBus.register(ChatMessageEventListener()) 99 | eventBus.register(DialogueContinueEventListener()) 100 | logger.info("Registered event listeners") 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # BattleRune 2 | data/cache/ 3 | data/xteas/ 4 | 5 | ### Eclipse ### 6 | 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .settings/ 16 | .loadpath 17 | .recommenders 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # PyDev specific (Python IDE for Eclipse) 26 | *.pydevproject 27 | 28 | # CDT-specific (C/C++ Development Tooling) 29 | .cproject 30 | 31 | # Java annotation processor (APT) 32 | .factorypath 33 | 34 | # PDT-specific (PHP Development Tools) 35 | .buildpath 36 | 37 | # sbteclipse plugin 38 | .target 39 | 40 | # Tern plugin 41 | .tern-project 42 | 43 | # TeXlipse plugin 44 | .texlipse 45 | 46 | # STS (Spring Tool Suite) 47 | .springBeans 48 | 49 | # Code Recommenders 50 | .recommenders/ 51 | 52 | # Scala IDE specific (Scala & Java development for Eclipse) 53 | .cache-main 54 | .scala_dependencies 55 | .worksheet 56 | 57 | ### Eclipse Patch ### 58 | # Eclipse Core 59 | .project 60 | 61 | # JDT-specific (Eclipse Java Development Tools) 62 | .classpath 63 | 64 | ### Intellij ### 65 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 66 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 67 | 68 | # User-specific stuff: 69 | .idea/**/workspace.xml 70 | .idea/**/tasks.xml 71 | .idea/dictionaries 72 | 73 | # Sensitive or high-churn files: 74 | .idea/**/dataSources/ 75 | .idea/**/dataSources.ids 76 | .idea/**/dataSources.xml 77 | .idea/**/dataSources.local.xml 78 | .idea/**/sqlDataSources.xml 79 | .idea/**/dynamic.xml 80 | .idea/**/uiDesigner.xml 81 | 82 | # Gradle: 83 | .idea/**/gradle.xml 84 | .idea/**/libraries 85 | 86 | # CMake 87 | cmake-build-debug/ 88 | 89 | # Mongo Explorer plugin: 90 | .idea/**/mongoSettings.xml 91 | 92 | ## File-based project format: 93 | *.iws 94 | 95 | ## Plugin-specific files: 96 | 97 | # IntelliJ 98 | /out/ 99 | 100 | # mpeltonen/sbt-idea plugin 101 | .idea_modules/ 102 | 103 | # JIRA plugin 104 | atlassian-ide-plugin.xml 105 | 106 | # Cursive Clojure plugin 107 | .idea/replstate.xml 108 | 109 | # Crashlytics plugin (for Android Studio and IntelliJ) 110 | com_crashlytics_export_strings.xml 111 | crashlytics.properties 112 | crashlytics-build.properties 113 | fabric.properties 114 | 115 | ### Intellij Patch ### 116 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 117 | 118 | # *.iml 119 | # modules.xml 120 | # .idea/misc.xml 121 | # *.ipr 122 | 123 | # Sonarlint plugin 124 | .idea/sonarlint 125 | 126 | ### Java ### 127 | # Compiled class file 128 | *.class 129 | 130 | # Log file 131 | *.log 132 | 133 | # BlueJ files 134 | *.ctxt 135 | 136 | # Mobile Tools for Java (J2ME) 137 | .mtj.tmp/ 138 | 139 | # Package Files # 140 | *.jar 141 | *.war 142 | *.ear 143 | *.zip 144 | *.tar.gz 145 | *.rar 146 | 147 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 148 | hs_err_pid* 149 | 150 | ### Gradle ### 151 | .gradle 152 | **/build/ 153 | 154 | # Ignore Gradle GUI config 155 | gradle-app.setting 156 | 157 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 158 | !gradle-wrapper.jar 159 | 160 | # Cache of project 161 | .gradletasknamecache 162 | 163 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 164 | # gradle/wrapper/gradle-wrapper.properties 165 | 166 | # End of https://www.gitignore.io/api/java,gradle,eclipse,intellij 167 | \.idea/ -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/channel/PlayerChannel.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.channel 2 | 3 | import io.battlerune.game.GameContext 4 | import io.battlerune.net.packet.PacketRepository 5 | import io.battlerune.game.world.actor.pawn.player.Player 6 | import io.battlerune.net.NetworkConstants 7 | import io.battlerune.net.codec.game.RSByteBufReader 8 | import io.battlerune.net.codec.login.LoginRequest 9 | import io.battlerune.net.packet.Packet 10 | import io.battlerune.net.packet.PacketEncoder 11 | import io.netty.channel.Channel 12 | import io.netty.channel.socket.SocketChannel 13 | import org.apache.logging.log4j.LogManager 14 | import java.util.* 15 | import java.util.concurrent.ConcurrentLinkedQueue 16 | 17 | class PlayerChannel(val channel: Channel, val context: GameContext) { 18 | 19 | companion object { 20 | val logger = LogManager.getLogger() 21 | } 22 | 23 | val player = Player(this, context) 24 | 25 | val incomingPackets: Queue = ConcurrentLinkedQueue() 26 | val prioritizedPackets: Queue = ConcurrentLinkedQueue() 27 | 28 | val hostAddress: String = (channel as SocketChannel).remoteAddress().address.hostAddress 29 | 30 | fun handleLogin(loginRequest: LoginRequest) { 31 | val username = loginRequest.username 32 | val password = loginRequest.password 33 | 34 | if (!username.matches("^[a-z0-9_ ]{1,12}$".toRegex()) || password.isEmpty() || password.length > 20) { 35 | return 36 | } 37 | 38 | player.username = username 39 | player.password = password 40 | 41 | context.world.queueLogin(player) 42 | 43 | channel.writeAndFlush(loginRequest) 44 | } 45 | 46 | private fun handlePrioritizedPackets() { 47 | if (prioritizedPackets.isEmpty()) { 48 | return 49 | } 50 | 51 | val packet = prioritizedPackets.poll() ?: return 52 | 53 | val decoder = PacketRepository.decoders[packet.opcode] ?: return 54 | 55 | val event = decoder.decode(player, RSByteBufReader.wrap(packet.payload)) ?: return 56 | 57 | player.post(event) 58 | } 59 | 60 | fun handleQueuedPackets() { 61 | try { 62 | for (i in 0 until NetworkConstants.PACKET_LIMIT) { 63 | handlePrioritizedPackets() 64 | 65 | if (incomingPackets.isEmpty()) { 66 | break 67 | } 68 | 69 | val packet = incomingPackets.poll() ?: break 70 | 71 | val decoder = PacketRepository.decoders[packet.opcode] ?: continue 72 | 73 | val event = decoder.decode(player, RSByteBufReader.wrap(packet.payload)) ?: continue 74 | 75 | player.post(event) 76 | 77 | } 78 | } catch(ex: Throwable) { 79 | logger.error("An exception was caught while handling an incoming packet for player=${player.username}.", ex) 80 | } 81 | } 82 | 83 | fun handleIncomingPacket(packet: Packet) { 84 | if (incomingPackets.size > NetworkConstants.PACKET_LIMIT) { 85 | return 86 | } 87 | 88 | if (packet.isPriority()) { 89 | prioritizedPackets.add(packet) 90 | } else { 91 | incomingPackets.add(packet) 92 | } 93 | 94 | } 95 | 96 | fun handleDownstreamPacket(encoder: PacketEncoder, flushPacket: Boolean = false) { 97 | try { 98 | val packet = encoder.encode(player) 99 | 100 | if (flushPacket) { 101 | channel.writeAndFlush(packet) 102 | } else { 103 | channel.write(packet) 104 | } 105 | } catch (ex: Throwable) { 106 | logger.warn("An exception was caught writing a packet.", ex) 107 | } 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/Position.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world 2 | 3 | import org.lwjgl.opengl.Display.getHeight 4 | 5 | 6 | 7 | /** 8 | * Copyright (c) Kyle Fricilone 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | class Position constructor(val x: Int, val y: Int, val z: Int = 0, val mapSize: RegionSize = RegionSize.DEFAULT) { 30 | 31 | val xInRegion: Int 32 | get() = x and 0x3F 33 | 34 | val yInRegion: Int 35 | get() = y and 0x3F 36 | 37 | val localX: Int 38 | get() = x - 8 * (chunkX - (mapSize.size shr 4)) 39 | 40 | val localY: Int 41 | get() = y - 8 * (chunkY - (mapSize.size shr 4)) 42 | 43 | val chunkX: Int 44 | get() = x shr 3 45 | 46 | val chunkY: Int 47 | get() = y shr 3 48 | 49 | val regionX: Int 50 | get() = x shr 6 51 | 52 | val regionY: Int 53 | get() = y shr 6 54 | 55 | val regionID: Int 56 | get() = (regionX shl 8) + regionY 57 | 58 | enum class RegionSize(val size: Int) { 59 | DEFAULT(104), LARGE(120), XLARGE(136), XXLARGE(168) 60 | } 61 | 62 | constructor(localX: Int, localY: Int, height: Int, regionId: Int, mapSize: RegionSize) : this(localX + (regionId shr 8 and 0xFF shl 6), localX + (regionId and 0xff shl 6), height, mapSize) 63 | 64 | fun getLongestDelta(other: Position): Int { 65 | val deltaX = Math.abs(x - other.x) 66 | val deltaY = Math.abs(y - other.y) 67 | return Math.max(deltaX, deltaY) 68 | } 69 | 70 | fun getLocalX(pos: Position): Int { 71 | return x - 8 * (pos.chunkX - (mapSize.size shr 4)) 72 | } 73 | 74 | fun getLocalY(pos: Position): Int { 75 | return y - 8 * (pos.chunkY - (mapSize.size shr 4)) 76 | } 77 | 78 | fun toRegionPacked(): Int { 79 | return regionY + (regionX shl 8) + (z shl 16) 80 | } 81 | 82 | fun toPositionPacked(): Int { 83 | return y + (x shl 14) + (z shl 28) 84 | } 85 | 86 | fun toAbsolute(): Position { 87 | val xOff = x % 8 88 | val yOff = y % 8 89 | return Position(x - xOff, y - yOff, z) 90 | } 91 | 92 | fun withinDistance(other: Position, distance: Int) : Boolean { 93 | if (z != other.z) { 94 | return false 95 | } 96 | 97 | val deltaX = other.x - x 98 | val deltaY = other.y - y 99 | 100 | return Math.abs(deltaX) <= distance && Math.abs(deltaY) <= distance 101 | } 102 | 103 | fun transform(x: Int, y: Int, z: Int) { 104 | this.x + x 105 | this.y + y 106 | this.z + z 107 | } 108 | 109 | override fun toString(): String { 110 | return "Position=[X: $x, Y: $y, z: $z]" 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/content/ClientDimensionChangeEventListener.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.content 2 | 3 | import com.google.common.eventbus.Subscribe 4 | import io.battlerune.game.event.impl.ClientDimensionChangeEvent 5 | import io.battlerune.game.widget.DisplayType 6 | 7 | class ClientDimensionChangeEventListener { 8 | 9 | @Subscribe 10 | fun onEvent(event: ClientDimensionChangeEvent) { 11 | val player = event.player 12 | 13 | if (!player.inGame) { 14 | return 15 | } 16 | 17 | if (event.resized) { 18 | // if (player.displayType == DisplayType.FIXED) { 19 | // player.displayType = DisplayType.RESIZABLE 20 | // player.client.setRootInterface(player.displayType.root) 21 | // .setInterfaceSets(548, 23, 161, 29) 22 | // .setInterfaceSets(548, 20, 161, 13) 23 | // .setInterfaceSets(548, 13, 161, 3) 24 | // .setInterfaceSets(548, 15, 161, 6) 25 | // .setInterfaceSets(548, 63, 161, 66) 26 | // .setInterfaceSets(548, 65, 161, 68) 27 | // .setInterfaceSets(548, 66, 161, 69) 28 | // .setInterfaceSets(548, 67, 161, 70) 29 | // .setInterfaceSets(548, 68, 161, 71) 30 | // .setInterfaceSets(548, 69, 161, 72) 31 | // .setInterfaceSets(548, 70, 161, 73) 32 | // .setInterfaceSets(548, 71, 161, 74) 33 | // .setInterfaceSets(548, 72, 161, 75) 34 | // .setInterfaceSets(548, 73, 161, 76) 35 | // .setInterfaceSets(548, 74, 161, 77) 36 | // .setInterfaceSets(548, 75, 161, 78) 37 | // .setInterfaceSets(548, 76, 161, 79) 38 | // .setInterfaceSets(548, 77, 161, 80) 39 | // .setInterfaceSets(548, 78, 161, 81) 40 | // .setInterfaceSets(548, 14, 161, 4) 41 | // .setInterfaceSets(548, 18, 161, 9) 42 | // .setInterfaceSets(548, 10, 161, 28) 43 | // .setInterfaceSets(548, 16, 161, 7) 44 | // .setInterfaceSets(548, 17, 161, 8) 45 | // .setInterfaceSets(548, 21, 161, 14) 46 | // } 47 | } else { 48 | if (player.displayType != DisplayType.FIXED) { 49 | player.displayType = DisplayType.FIXED 50 | player.client.setRootInterface(player.displayType.root) 51 | .setInterfaceSets(161, 29, 548, 23) 52 | .setInterfaceSets(161, 13, 548, 20) 53 | .setInterfaceSets(161, 3, 548, 13) 54 | .setInterfaceSets(161, 6, 548, 15) 55 | .setInterfaceSets(161, 66, 548, 63) 56 | .setInterfaceSets(161, 68, 548, 65) 57 | .setInterfaceSets(161, 69, 548, 66) 58 | .setInterfaceSets(161, 70, 548, 67) 59 | .setInterfaceSets(161, 71, 548, 68) 60 | .setInterfaceSets(161, 72, 548, 69) 61 | .setInterfaceSets(161, 73, 548, 70) 62 | .setInterfaceSets(161, 74, 548, 71) 63 | .setInterfaceSets(161, 75, 548, 72) 64 | .setInterfaceSets(161, 76, 548, 73) 65 | .setInterfaceSets(161, 77, 548, 74) 66 | .setInterfaceSets(161, 78, 548, 75) 67 | .setInterfaceSets(161, 79, 548, 76) 68 | .setInterfaceSets(161, 80, 548, 77) 69 | .setInterfaceSets(161, 81, 548, 78) 70 | .setInterfaceSets(161, 4, 548, 14) 71 | .setInterfaceSets(161, 9, 548, 18) 72 | .setInterfaceSets(161, 28, 548, 10) 73 | .setInterfaceSets(161, 7, 548, 16) 74 | .setInterfaceSets(161, 8, 548, 17) 75 | .setInterfaceSets(161, 14, 548, 21) 76 | } 77 | } 78 | 79 | println("client dimension change event width ${event.width}, height ${event.height}, resized ${event.resized}") 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/login/LoginDecoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.login 2 | 3 | import io.battlerune.net.crypt.ISAACCipher 4 | import io.battlerune.net.crypt.ISAACCipherPair 5 | import io.battlerune.util.extensions.decryptXTEA 6 | import io.battlerune.util.extensions.readJagString 7 | import io.battlerune.util.extensions.readString 8 | import io.netty.buffer.ByteBuf 9 | import io.netty.buffer.Unpooled 10 | import io.netty.channel.ChannelHandlerContext 11 | import io.netty.handler.codec.ByteToMessageDecoder 12 | import java.math.BigInteger 13 | 14 | class LoginDecoder : ByteToMessageDecoder() { 15 | 16 | companion object { 17 | val RSA_MODULUS = BigInteger( 18 | "94904992129904410061849432720048295856082621425118273522925386720620318960919649616773860564226013741030211135158797393273808089000770687087538386210551037271884505217469135237269866084874090369313013016228010726263597258760029391951907049483204438424117908438852851618778702170822555894057960542749301583313") 19 | 20 | val RSA_EXPONENT = BigInteger( 21 | "72640252303588278644467876834506654511692882736878142674473705672822320822095174696379303197013981434572187481298130748148385818094460521624198552406940508805602215708418094058951352076283100448576575511642453669107583920561043364042814766866691981132717812444681081534760715694225059124574441435942822149161") 22 | 23 | } 24 | 25 | override fun decode(ctx: ChannelHandlerContext, inc: ByteBuf, out: MutableList) { 26 | 27 | if (!inc.isReadable) { 28 | return 29 | } 30 | 31 | val bytes = ByteArray(inc.readShort().toInt()) 32 | inc.readBytes(bytes) 33 | 34 | val rsaBuf = Unpooled.wrappedBuffer(BigInteger(bytes).modPow(RSA_EXPONENT, RSA_MODULUS).toByteArray()) 35 | 36 | val opcode = rsaBuf.readByte() 37 | 38 | if (opcode.toInt() != 1) { 39 | return 40 | } 41 | 42 | val authType = AuthorizationType.lookup(rsaBuf.readByte().toInt()) 43 | 44 | val clientKeys = IntArray(4) 45 | 46 | for (i in 0 until clientKeys.size) { 47 | clientKeys[i] = rsaBuf.readInt() 48 | } 49 | 50 | authType.read(rsaBuf) 51 | 52 | val password = rsaBuf.readString() 53 | 54 | val xteaBuf = inc.decryptXTEA(clientKeys) 55 | 56 | val username = xteaBuf.readString() 57 | 58 | val resizableAndMemory = xteaBuf.readByte() 59 | 60 | val resizable = (resizableAndMemory.toInt() shr 1) == 1 61 | 62 | val lowMem = (resizableAndMemory.toInt() and 1) == 1 63 | 64 | val width = xteaBuf.readShort() 65 | 66 | val height = xteaBuf.readShort() 67 | 68 | // some bytes for cache 69 | xteaBuf.skipBytes(24) 70 | 71 | val token = xteaBuf.readString() 72 | 73 | xteaBuf.readInt() 74 | 75 | // machine info 76 | xteaBuf.readByte() // machine info opcode 6 77 | xteaBuf.readByte() // os type 78 | xteaBuf.readByte() // 64 bit 79 | xteaBuf.readByte() // os version 80 | xteaBuf.readByte() // vendor 81 | xteaBuf.readByte() // major 82 | xteaBuf.readByte() // minor 83 | xteaBuf.readByte() // patch 84 | xteaBuf.readByte() // some flag 85 | xteaBuf.readShort() // max memory 86 | xteaBuf.readByte() 87 | xteaBuf.readMedium() 88 | xteaBuf.readShort() 89 | xteaBuf.readJagString() 90 | xteaBuf.readJagString() 91 | xteaBuf.readJagString() 92 | xteaBuf.readJagString() 93 | xteaBuf.readByte() 94 | xteaBuf.readShort() 95 | xteaBuf.readJagString() 96 | xteaBuf.readJagString() 97 | xteaBuf.readByte() 98 | xteaBuf.readByte() 99 | 100 | xteaBuf.readInt() 101 | xteaBuf.readInt() 102 | xteaBuf.readInt() 103 | xteaBuf.readInt() 104 | 105 | // end of machine info 106 | 107 | xteaBuf.readByte() 108 | xteaBuf.readInt() // crc opcode 0 109 | 110 | val crc = IntArray(17) 111 | 112 | for (i in 0 until crc.size) { 113 | crc[i] = xteaBuf.readInt() 114 | } 115 | 116 | val serverKeys = IntArray(4) 117 | 118 | for (i in 0 until serverKeys.size) { 119 | serverKeys[i] = clientKeys[i] + 50 120 | } 121 | 122 | out.add(LoginRequest(username, password, resizable, lowMem, ISAACCipherPair(ISAACCipher(serverKeys), ISAACCipher(clientKeys)), ctx.channel())) 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/Movement.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn 2 | 3 | import java.util.Deque 4 | import io.battlerune.game.world.Position 5 | import io.battlerune.game.world.actor.pawn.player.Player 6 | import java.util.LinkedList 7 | 8 | class Movement(val pawn: Pawn) { 9 | 10 | val steps: Deque = LinkedList() 11 | 12 | var walkingDirection = -1 13 | var runningDirection = -1 14 | 15 | var running = true 16 | 17 | var isRunningQueueEnabled = false 18 | 19 | val nextPoint: Step? 20 | get() { 21 | val availableFocusPoint = steps.poll() 22 | 23 | if (availableFocusPoint == null || availableFocusPoint.direction == -1) { 24 | return null 25 | } else { 26 | pawn.position.transform(DIRECTION_DELTA_X[availableFocusPoint.direction].toInt(), DIRECTION_DELTA_Y[availableFocusPoint.direction].toInt(), pawn.position.z) 27 | return availableFocusPoint 28 | } 29 | } 30 | 31 | val isMoving: Boolean 32 | get() = !steps.isEmpty() 33 | 34 | val isMovementDone: Boolean 35 | get() = steps.isEmpty() 36 | 37 | fun finish() { 38 | steps.removeFirst() 39 | } 40 | 41 | fun reset() { 42 | isRunningQueueEnabled = false 43 | steps.clear() 44 | steps.add(Step(pawn.position.x, pawn.position.y, -1)) 45 | } 46 | 47 | fun processMovement() { 48 | var walkingPoint: Step? = nextPoint 49 | var runningPoint: Step? = null 50 | 51 | if (running) { 52 | runningPoint = nextPoint 53 | } 54 | 55 | walkingDirection = (if (walkingPoint == null) -1 else walkingPoint.direction) 56 | 57 | runningDirection = (if (runningPoint == null) -1 else runningPoint.direction) 58 | 59 | val deltaX = pawn.position.x - pawn.lastPosition.regionX * 8 60 | val deltaY = pawn.position.y - pawn.lastPosition.regionY * 8 61 | 62 | if (pawn is Player) { 63 | if (deltaX < 16 || deltaX >= 88 || deltaY < 16 || deltaY > 88) { 64 | //entity.getPlayer().queuePacket(UpdateMapRegion()) 65 | } 66 | 67 | if (walkingPoint != null || runningPoint != null) { 68 | pawn.onMovement() 69 | } 70 | } 71 | } 72 | 73 | fun stop() { 74 | isRunningQueueEnabled = false 75 | steps.clear() 76 | steps.add(Step(pawn.position.x, pawn.position.y, -1)) 77 | } 78 | 79 | fun walk(location: Position) { 80 | reset() 81 | addToPath(location) 82 | finish() 83 | } 84 | 85 | fun addToPath(location: Position) { 86 | 87 | if (steps.isEmpty()) { 88 | reset() 89 | } 90 | 91 | val last = steps.peekLast() 92 | 93 | var deltaX = location.x - last.x 94 | 95 | var deltaY = location.y - last.y 96 | 97 | val max = Math.max(Math.abs(deltaX), Math.abs(deltaY)) 98 | 99 | for (i in 0 until max) { 100 | 101 | if (deltaX < 0) { 102 | deltaX++ 103 | } else if (deltaX > 0) { 104 | deltaX-- 105 | } 106 | 107 | if (deltaY < 0) { 108 | deltaY++ 109 | } else if (deltaY > 0) { 110 | deltaY-- 111 | } 112 | 113 | addStep(location.x - deltaX, location.y - deltaY) 114 | } 115 | } 116 | 117 | private fun addStep(x: Int, y: Int) { 118 | 119 | if (steps.size >= 50) { 120 | return 121 | } 122 | 123 | val lastPosition = steps.peekLast() 124 | 125 | val direction = parseDirection(x - lastPosition.x, y - lastPosition.y) 126 | 127 | if (direction > -1) { 128 | steps.add(Step(x, y, direction)) 129 | } 130 | } 131 | 132 | inner class Step(val x: Int, val y: Int, val direction: Int) 133 | 134 | companion object { 135 | 136 | private val DIRECTION_DELTA_X = byteArrayOf(-1, 0, 1, -1, 1, -1, 0, 1) 137 | private val DIRECTION_DELTA_Y = byteArrayOf(1, 1, 1, 0, 0, -1, -1, -1) 138 | 139 | fun parseDirection(deltaX: Int, deltaY: Int): Int { 140 | if (deltaX < 0) { 141 | if (deltaY < 0) { 142 | return 5 143 | } 144 | return if (deltaY > 0) { 145 | 0 146 | } else 3 147 | } 148 | if (deltaX > 0) { 149 | if (deltaY < 0) { 150 | return 7 151 | } 152 | return if (deltaY > 0) { 153 | 2 154 | } else 4 155 | } 156 | if (deltaY < 0) { 157 | return 6 158 | } 159 | return if (deltaY > 0) { 160 | 1 161 | } else -1 162 | } 163 | 164 | } 165 | 166 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/Client.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net 2 | 3 | import io.battlerune.game.world.Position 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.net.codec.game.RSByteBufWriter 6 | import io.battlerune.net.packet.out.* 7 | 8 | class Client(val player: Player) { 9 | 10 | fun itemChatbox(message: String, item: Int, zoom: Int = 1) { 11 | setInterface(162, 546, 193, false) 12 | setInterfaceText(193, 1, message) 13 | setInterfaceText(193, 2, "Click here to continue") 14 | player.write(ItemOnInterfacePacketEncoder(193, 0, item, zoom)) 15 | setInterfaceSettings(193, 2, -1, -1, 1) 16 | setInterfaceSettings(193, 3, -1, -1, 0) 17 | setInterfaceSettings(193, 4, -1, -1, 0) 18 | } 19 | 20 | fun setRootInterface(interfaceId: Int) : Client { 21 | player.write(RootInterfacePacketEncoder(interfaceId)) 22 | return this 23 | } 24 | 25 | fun setInterface(rootInterfaceId: Int, childId: Int, interfaceId: Int, clickable: Boolean) : Client { 26 | player.write(InterfacePacketEncoder(rootInterfaceId, childId, interfaceId, clickable)) 27 | return this 28 | } 29 | 30 | fun setInterfaceSets(fromRoot: Int, fromChild: Int, toRoot: Int, toChild: Int) : Client { 31 | player.write(InterfaceSetsPacketEncoder(fromRoot, fromChild, toRoot, toChild)) 32 | return this 33 | } 34 | 35 | fun setInterfaceText(root: Int, child: Int, message: String) : Client { 36 | player.write(InterfaceTextPacketEncoder(root, child, message)) 37 | return this 38 | } 39 | 40 | fun setRegionCoordinate(position: Position) : Client { 41 | player.write(SetRegionCoordintePacketEncoder(position)) 42 | return this 43 | } 44 | 45 | fun showGroundItem(id: Int, amount: Int, position: Position) : Client { 46 | setRegionCoordinate(position) 47 | player.write(ShowGroundItemPacketEncoder(id, amount)) 48 | return this 49 | } 50 | 51 | fun setInterfaceSettings(root: Int, component: Int, fromSlot: Int, toSlot: Int, setting: Int) : Client { 52 | player.write(InterfaceSettingPacketEncoder(root, component, fromSlot, toSlot, setting)) 53 | return this 54 | } 55 | 56 | fun setVarp(id: Int, state: Int) : Client { 57 | player.write(VarpPacketEncoder(id, state)) 58 | return this 59 | } 60 | 61 | fun setCamera(value1: Int, value2: Int) : Client { 62 | player.write(SetCameraPacketEncoder(value1, value2)) 63 | return this 64 | } 65 | 66 | fun setDestination(position: Position) : Client { 67 | player.write(SetDestinationPacketEncoder(position)) 68 | return this 69 | } 70 | 71 | fun sendCS2Script(id: Int, params: Array) : Client { 72 | player.write(CS2ScriptPacketEncoder(id, params)) 73 | return this 74 | } 75 | 76 | fun lookupDNS(hostAddress: String) : Client { 77 | player.write(DNSLookupPacketEncoder(hostAddress)) 78 | return this 79 | } 80 | 81 | fun sendRegionUpdate(buffer: RSByteBufWriter, flushPacket: Boolean) : Client { 82 | player.write(StaticRegionUpdatePacketEncoder(buffer), flushPacket) 83 | return this 84 | } 85 | 86 | fun removeInterface(interfaceId: Int) : Client { 87 | player.write(RemoveInterfacePacketEncoder(interfaceId)) 88 | return this 89 | } 90 | 91 | fun setWeight(amount: Int) : Client { 92 | player.write(SetWeightPacketEncoder(amount)) 93 | return this 94 | } 95 | 96 | fun playSong(songId: Int) : Client { 97 | player.write(PlaySongPacketEncoder(songId)) 98 | return this 99 | } 100 | 101 | fun playSound(id: Int, type: Int, delay: Int) : Client { 102 | player.write(PlaySoundEffectPacketEncoder(id, type, delay)) 103 | return this 104 | } 105 | 106 | fun sendMessage(message: String) : Client { 107 | player.write(ServerMessagePacketEncoder(message)) 108 | return this 109 | } 110 | 111 | fun setSkill(skill: Int, lvl: Int, xp: Int) : Client { 112 | player.write(SetSkillPacketEncoder(skill, lvl, xp)) 113 | return this 114 | } 115 | 116 | fun setEnergy(amount: Int) : Client { 117 | player.write(SetEnergyPacketEncoder(amount)) 118 | return this 119 | } 120 | 121 | fun setSystemUpdate(seconds: Int) : Client { 122 | player.write(SystemUpdatePacketEncoder((600 / seconds) * 10)) 123 | return this 124 | } 125 | 126 | fun resetVarps() : Client { 127 | player.write(ResetVarpPacketEncoder()) 128 | return this 129 | } 130 | 131 | fun npcUpdate() : Client { 132 | player.write(NpcUpdatePacketEncoder()) 133 | return this 134 | } 135 | 136 | fun updatePlayer() : Client { 137 | if (!player.viewport.initialized) { 138 | return this 139 | } 140 | player.write(PlayerUpdatePacketEncoder()) 141 | return this 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/world/actor/pawn/player/Player.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.world.actor.pawn.player 2 | 3 | import io.battlerune.game.GameContext 4 | import io.battlerune.game.event.Event 5 | import io.battlerune.game.widget.DisplayType 6 | import io.battlerune.game.world.actor.pawn.ChatMessage 7 | import io.battlerune.game.world.actor.pawn.Pawn 8 | import io.battlerune.game.world.actor.pawn.update.BlockType 9 | import io.battlerune.net.Client 10 | import io.battlerune.net.channel.PlayerChannel 11 | import io.battlerune.net.codec.game.RSByteBufWriter 12 | import io.battlerune.net.packet.PacketEncoder 13 | import io.battlerune.net.packet.out.LogoutPacketEncoder 14 | 15 | class Player(val channel: PlayerChannel, val context: GameContext) : Pawn() { 16 | 17 | var appearance = Appearance.DEFAULT 18 | 19 | val viewport = Viewport(this) 20 | 21 | var chatMessage = ChatMessage() 22 | 23 | var rights = Rights.PLAYER 24 | var skulled = false 25 | 26 | var initialized = false 27 | var xpOverlay = false 28 | 29 | var displayType = DisplayType.FIXED 30 | 31 | var inGame = false 32 | 33 | var teleported = false 34 | 35 | val client = Client(this) 36 | 37 | lateinit var username: String 38 | lateinit var password: String 39 | 40 | fun chat(msg: String, color: ChatMessage.ChatColor = ChatMessage.ChatColor.YELLOW, effect: ChatMessage.ChatEffect = ChatMessage.ChatEffect.NONE) { 41 | if (msg.isEmpty() || msg.length > ChatMessage.MAX_CHARACTERS) 42 | return 43 | 44 | chatMessage = ChatMessage(msg.trim(), color, effect) 45 | updateFlags.add(BlockType.CHAT) 46 | } 47 | 48 | fun skull() { 49 | skulled = !skulled 50 | updateFlags.add(BlockType.APPEARANCE) 51 | } 52 | 53 | fun post(event: Event) { 54 | context.world.eventBus.post(event) 55 | } 56 | 57 | fun onLogin() { 58 | updateFlags.add(BlockType.APPEARANCE) 59 | region = context.regionManager.lookup(position.regionID) 60 | 61 | val gpiBuffer = RSByteBufWriter.alloc() 62 | viewport.initGPI(gpiBuffer) 63 | client.sendRegionUpdate(gpiBuffer, true) 64 | .setCamera(0, 0) 65 | .lookupDNS("127.0.0.1") 66 | .setInterfaceText(378, 14, "Never tell anyone your password, even if they claim to work for Jagex!") 67 | .setInterfaceText(378, 15, "You have 0 unread messages in your message centre.") 68 | .setInterfaceText(378, 18, "You are not a member. Subscribe to access extra skills, areas and quests, and much
more besides.") 69 | .setInterfaceText(378, 20, "A membership subscription grants access to the members-only features of both versions of RuneScape.") 70 | .setInterfaceText(378, 21, "Keep your account secure.") 71 | .setInterfaceText(378, 13, "You last logged in earlier today.") 72 | .setInterfaceText(378, 16, "You do not have a Bank PIN. Please visit a bank if you would like one.") 73 | .setRootInterface(165) 74 | .setInterface(165, 1, 162, true) 75 | .setInterface(165, 23, 163, true) 76 | .setInterface(165, 24, 160, true) 77 | .setInterface(165, 29, 378, false) 78 | /** 79 | * Login themes 80 | * 81 | * 16 toxic bomb 82 | * 17 question marks 83 | * 18 jester 84 | * 19 vaults with red asterisks 85 | * 20 vault with green asterisks 86 | * 21 people trading 87 | * 22 vaults with no marks 88 | * 23 christmas themed 89 | * 50 blank scroll 90 | * 405 construction 91 | * 92 | */ 93 | 94 | val loginTheme = 50 95 | 96 | client.setInterface(165, 28, loginTheme, false) 97 | .setInterfaceText(50, 3, "Once you've had a graceful set repainted blue in Brimhaven,
you can get individual pieces repainted.
Next week, Halloween!") 98 | .sendCS2Script(233, arrayOf(3276804, 7085, 0, 0, 434, 1912, 0, 400, -1)) 99 | .sendCS2Script(233, arrayOf(3276805, 32817, 0, 100, 93, 179, 0, 800, 820)) 100 | .sendCS2Script(1080, arrayOf()) 101 | .setInterface(165, 9, 320, true) 102 | .setInterface(165, 10, 399, true) 103 | .setInterface(165, 11, 149, true) 104 | .setInterface(165, 12, 387, true) 105 | .setInterface(165, 13, 541, true) 106 | .setInterface(165, 14, 218, true) 107 | .setInterface(165, 16, 429, true) 108 | .setInterface(165, 17, 432, true) 109 | .setInterface(165, 18, 182, true) 110 | .setInterface(165, 19, 261, true) 111 | .setInterface(165, 20, 216, true) 112 | .setInterface(165, 21, 239, true) 113 | .setInterface(165, 15, 7, true) 114 | .setInterface(165, 8, 593, true) 115 | .setInterfaceText(593, 1, "Unarmed") 116 | .setInterfaceText(593, 2, "Combat Lvl: 126") 117 | .setInterfaceText(239, 5, "AUTO") 118 | .sendCS2Script(2014, arrayOf(0, 0, 0, 0, 0, 0)) 119 | .sendCS2Script(2015, arrayOf(0)) 120 | .setVarp(18, 1) 121 | .setVarp(20, 131072) 122 | .setVarp(21, 67141632) 123 | .setVarp(22, 33554432) 124 | .setVarp(23, 2097216) 125 | .setVarp(43, 1) 126 | .setVarp(101, 0) 127 | .setVarp(153, -1) 128 | .setVarp(166, 2) 129 | .setVarp(167, 0) 130 | .setVarp(168, 4) 131 | .setVarp(169, 4) 132 | .setVarp(170, 0) 133 | .setVarp(171, 0) 134 | .setVarp(173, 1) 135 | .setVarp(281, 1000) 136 | .setVarp(284, 60001) 137 | .setVarp(287, 0) 138 | .setVarp(300, 1000) 139 | .setVarp(406, 20) 140 | .setVarp(447, -1) 141 | .setVarp(449, 2097152) 142 | .setVarp(486, 1073741824) 143 | .setVarp(520, 1) 144 | .setVarp(553, -2147483648) 145 | .setVarp(788, 128) 146 | .setVarp(810, 33554432) 147 | .setVarp(849, -1) 148 | .setVarp(850, -1) 149 | .setVarp(851, -1) 150 | .setVarp(852, -1) 151 | .setVarp(853, -1) 152 | .setVarp(854, -1) 153 | .setVarp(855, -1) 154 | .setVarp(856, -1) 155 | .setVarp(872, 4) 156 | .setVarp(904, 253) 157 | .setVarp(913, 4194304) 158 | .setVarp(1010, 2048) 159 | .setVarp(1017, 8192) 160 | .setVarp(1050, 4096) 161 | .setVarp(1065, -1) 162 | .setVarp(1067, -1302855680) 163 | .setVarp(1074, 0) 164 | .setVarp(1075, -1) 165 | .setVarp(1107, 0) 166 | .setVarp(1151, -1) 167 | .setVarp(1224, 172395585) 168 | .setVarp(1225, 379887846) 169 | .setVarp(1226, 12) 170 | .setVarp(1306, 0) 171 | .setVarp(1427, -1) 172 | .sendMessage("Welcome to BattleRune #155!") 173 | .setEnergy(100) 174 | .playSong(1) 175 | 176 | for (i in 0..24) { 177 | client.setSkill(i, 99, 14_000_000) 178 | } 179 | 180 | } 181 | 182 | override fun preUpdate() { 183 | // handle packets 184 | channel.handleQueuedPackets() 185 | 186 | movement.processMovement() 187 | 188 | // TODO other processing before update occurs 189 | } 190 | 191 | override fun update() { 192 | client.updatePlayer() 193 | } 194 | 195 | override fun postUpdate() { 196 | updateFlags.clear() 197 | teleported = false 198 | regionChanged = false 199 | } 200 | 201 | override fun onMovement() { 202 | 203 | } 204 | 205 | fun onLogout() { 206 | 207 | } 208 | 209 | fun logout() { 210 | //TODO send logout packet 211 | 212 | context.world.queueLogout(this) 213 | 214 | write(LogoutPacketEncoder()) 215 | } 216 | 217 | fun write(encoder: PacketEncoder, flushPacket: Boolean = true) : Player { 218 | channel.handleDownstreamPacket(encoder, flushPacket) 219 | return this 220 | } 221 | 222 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/game/fs/Huffman.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.game.fs 2 | 3 | class Huffman(var sizes: ByteArray) { 4 | 5 | var masks: IntArray 6 | var keys: IntArray 7 | 8 | init { 9 | val i_2 = sizes.size 10 | masks = IntArray(i_2) 11 | val ints_3 = IntArray(33) 12 | keys = IntArray(8) 13 | var i_4 = 0 14 | 15 | for (i_5 in 0 until i_2) { 16 | val b_6 = sizes[i_5] 17 | if (b_6.toInt() != 0) { 18 | val i_7 = 1 shl 32 - b_6 19 | val i_8 = ints_3[b_6.toInt()] 20 | masks[i_5] = i_8 21 | val i_9: Int 22 | var i_10: Int 23 | var i_11: Int 24 | var i_12: Int 25 | if (i_8 and i_7 != 0) 26 | i_9 = ints_3[b_6 - 1] 27 | else { 28 | i_9 = i_8 or i_7 29 | 30 | i_10 = b_6 - 1 31 | while (i_10 >= 1) { 32 | i_11 = ints_3[i_10] 33 | if (i_11 != i_8) 34 | break 35 | 36 | i_12 = 1 shl 32 - i_10 37 | if (i_11 and i_12 != 0) { 38 | ints_3[i_10] = ints_3[i_10 - 1] 39 | break 40 | } 41 | 42 | ints_3[i_10] = i_11 or i_12 43 | --i_10 44 | } 45 | } 46 | 47 | ints_3[b_6.toInt()] = i_9 48 | 49 | i_10 = b_6 + 1 50 | while (i_10 <= 32) { 51 | if (ints_3[i_10] == i_8) 52 | ints_3[i_10] = i_9 53 | i_10++ 54 | } 55 | 56 | i_10 = 0 57 | 58 | i_11 = 0 59 | while (i_11 < b_6) { 60 | i_12 = Integer.MIN_VALUE.ushr(i_11) 61 | if (i_8 and i_12 != 0) { 62 | if (keys[i_10] == 0) 63 | keys[i_10] = i_4 64 | 65 | i_10 = keys[i_10] 66 | } else 67 | ++i_10 68 | 69 | if (i_10 >= keys.size) { 70 | val ints_13 = IntArray(keys.size * 2) 71 | 72 | for (i_14 in keys.indices) 73 | ints_13[i_14] = keys[i_14] 74 | 75 | keys = ints_13 76 | } 77 | 78 | i_12 = i_12 ushr 1 79 | i_11++ 80 | } 81 | 82 | keys[i_10] = i_5.inv() 83 | if (i_10 >= i_4) 84 | i_4 = i_10 + 1 85 | } 86 | } 87 | 88 | } 89 | 90 | fun compress(text: String, output: ByteArray): Int { 91 | var key = 0 92 | 93 | val input = text.toByteArray() 94 | 95 | var bitpos = 0 96 | for (pos in 0 until text.length) { 97 | val data = input[pos].toInt() and 255 98 | val size = sizes[data] 99 | val mask = masks[data] 100 | 101 | if (size.toInt() == 0) { 102 | throw RuntimeException("No codeword for data value " + data) 103 | } 104 | 105 | var remainder = bitpos and 7 106 | key = key and (-remainder shr 31) 107 | var offset = bitpos shr 3 108 | bitpos += size.toInt() 109 | val i_41_ = (-1 + (remainder - -size) shr 3) + offset 110 | remainder += 24 111 | key = key or mask.ushr(remainder) 112 | output[offset] = key.toByte() 113 | if (i_41_.inv() < offset.inv()) { 114 | remainder -= 8 115 | key = mask.ushr(remainder) 116 | output[++offset] = key.toByte() 117 | if (offset.inv() > i_41_.inv()) { 118 | remainder -= 8 119 | key = mask.ushr(remainder) 120 | output[++offset] = key.toByte() 121 | if (offset.inv() > i_41_.inv()) { 122 | remainder -= 8 123 | key = mask.ushr(remainder) 124 | output[++offset] = key.toByte() 125 | if (i_41_ > offset) { 126 | remainder -= 8 127 | key = mask shl -remainder 128 | output[++offset] = key.toByte() 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | return 7 + bitpos shr 3 136 | } 137 | 138 | fun decompress(compressed: ByteArray, decompressed: ByteArray, decompressedLength: Int): Int { 139 | var decompressedLen = decompressedLength 140 | val i_2 = 0 141 | var i_4 = 0 142 | if (decompressedLength == 0) 143 | return 0 144 | else { 145 | var i_7 = 0 146 | decompressedLen += i_4 147 | var i_8 = i_2 148 | 149 | while (true) { 150 | val b_9 = compressed[i_8] 151 | if (b_9 < 0) 152 | i_7 = keys[i_7] 153 | else 154 | ++i_7 155 | 156 | var i_10: Int 157 | i_10 = keys[i_7] 158 | if (i_10 < 0) { 159 | decompressed[i_4++] = i_10.inv().toByte() 160 | if (i_4 >= decompressedLength) 161 | break 162 | 163 | i_7 = 0 164 | } 165 | 166 | if (b_9.toInt() and 0x40 != 0) 167 | i_7 = keys[i_7] 168 | else 169 | ++i_7 170 | 171 | i_10 = keys[i_7] 172 | if (i_10 < 0) { 173 | decompressed[i_4++] = i_10.inv().toByte() 174 | if (i_4 >= decompressedLength) 175 | break 176 | 177 | i_7 = 0 178 | } 179 | 180 | if (b_9.toInt() and 0x20 != 0) 181 | i_7 = keys[i_7] 182 | else 183 | ++i_7 184 | 185 | i_10 = keys[i_7] 186 | if (i_10 < 0) { 187 | decompressed[i_4++] = i_10.inv().toByte() 188 | if (i_4 >= decompressedLength) 189 | break 190 | 191 | i_7 = 0 192 | } 193 | 194 | if (b_9.toInt() and 0x10 != 0) 195 | i_7 = keys[i_7] 196 | else 197 | ++i_7 198 | 199 | i_10 = keys[i_7] 200 | if (i_10 < 0) { 201 | decompressed[i_4++] = i_10.inv().toByte() 202 | if (i_4 >= decompressedLength) 203 | break 204 | 205 | i_7 = 0 206 | } 207 | 208 | if (b_9.toInt() and 0x8 != 0) 209 | i_7 = keys[i_7] 210 | else 211 | ++i_7 212 | 213 | i_10 = keys[i_7] 214 | if (i_10 < 0) { 215 | decompressed[i_4++] = i_10.inv().toByte() 216 | if (i_4 >= decompressedLength) 217 | break 218 | 219 | i_7 = 0 220 | } 221 | 222 | if (b_9.toInt() and 0x4 != 0) 223 | i_7 = keys[i_7] 224 | else 225 | ++i_7 226 | 227 | i_10 = keys[i_7] 228 | if (i_10 < 0) { 229 | decompressed[i_4++] = i_10.inv().toByte() 230 | if (i_4 >= decompressedLength) 231 | break 232 | 233 | i_7 = 0 234 | } 235 | 236 | if (b_9.toInt() and 0x2 != 0) 237 | i_7 = keys[i_7] 238 | else 239 | ++i_7 240 | 241 | i_10 = keys[i_7] 242 | if (i_10 < 0) { 243 | decompressed[i_4++] = i_10.inv().toByte() 244 | if (i_4 >= decompressedLength) 245 | break 246 | 247 | i_7 = 0 248 | } 249 | 250 | if (b_9.toInt() and 0x1 != 0) 251 | i_7 = keys[i_7] 252 | else 253 | ++i_7 254 | 255 | i_10 = keys[i_7] 256 | if (i_10 < 0) { 257 | decompressed[i_4++] = i_10.inv().toByte() 258 | if (i_4 >= decompressedLength) 259 | break 260 | 261 | i_7 = 0 262 | } 263 | 264 | ++i_8 265 | } 266 | 267 | return i_8 + 1 - i_2 268 | } 269 | } 270 | 271 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/packet/out/PlayerUpdatePacketEncoder.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.packet.out 2 | 3 | import io.battlerune.game.world.World 4 | import io.battlerune.game.world.actor.pawn.player.Player 5 | import io.battlerune.game.world.actor.pawn.player.Viewport 6 | import io.battlerune.game.world.actor.pawn.update.CachedUpdateBlock 7 | import io.battlerune.game.world.actor.pawn.update.PlayerCachedUpdateBlock 8 | import io.battlerune.net.codec.game.RSByteBufWriter 9 | import io.battlerune.net.packet.Packet 10 | import io.battlerune.net.packet.PacketEncoder 11 | import io.battlerune.net.packet.PacketType 12 | 13 | class PlayerUpdatePacketEncoder : PacketEncoder { 14 | lateinit var player: Player 15 | 16 | var playersAdded = 0 17 | var skip = 0 18 | 19 | override fun encode(player: Player): Packet { 20 | this.player = player 21 | 22 | val packetBuffer = RSByteBufWriter.alloc() 23 | val maskBuffer = RSByteBufWriter.alloc() 24 | processPlayersInViewport(packetBuffer, maskBuffer, true) 25 | processPlayersInViewport(packetBuffer, maskBuffer, false) 26 | processPlayersOutsideViewport(packetBuffer, maskBuffer, false) 27 | processPlayersOutsideViewport(packetBuffer, maskBuffer, true) 28 | 29 | player.viewport.playersInsideViewportCount = 0 30 | player.viewport.playersOutsideViewportCount = 0 31 | 32 | for (index in 1 until World.MAX_PLAYER_COUNT) { 33 | player.viewport.skipFlags[index] = (player.viewport.skipFlags[index].toInt() shr 1).toByte() 34 | 35 | val globalPlayer = player.context.world.players.get(index) 36 | 37 | if (globalPlayer != null) { 38 | player.viewport.playerIndexesInsideViewport[player.viewport.playersInsideViewportCount++] = index 39 | } else { 40 | player.viewport.playerIndexesOutsideViewport[player.viewport.playersOutsideViewportCount++] = index 41 | } 42 | } 43 | 44 | packetBuffer.writeBytes(maskBuffer.buffer) 45 | return packetBuffer.toPacket(83, PacketType.VAR_SHORT) 46 | } 47 | 48 | private fun processPlayersInViewport(buffer: RSByteBufWriter, maskBuffer: RSByteBufWriter, evenIndex: Boolean) { 49 | buffer.switchToBitAccess() 50 | skip = 0 51 | playersAdded = 0 52 | for (currentIndex in 0 until player.viewport.playersInsideViewportCount) { 53 | val localPlayerIndex = player.viewport.playerIndexesInsideViewport[currentIndex] 54 | 55 | // skip player indexes that are either even or odd. 56 | if (if(evenIndex) player.viewport.skipFlags[localPlayerIndex].toInt() and 0x1 != 0 else player.viewport.skipFlags[localPlayerIndex].toInt() and 0x1 == 0 ) { 57 | continue 58 | } 59 | 60 | if (skip > 0) { 61 | --skip 62 | player.viewport.skipFlags[localPlayerIndex] = (player.viewport.skipFlags[localPlayerIndex].toInt() or 0x2).toByte() 63 | continue 64 | } 65 | 66 | val localPlayer = player.viewport.playersInViewport[localPlayerIndex] 67 | 68 | val updateRequired = localPlayer != null && localPlayer.updateFlags.isNotEmpty() 69 | 70 | // 1. check to see if the local player needs and flag-based updates 71 | buffer.writeFlag(updateRequired) 72 | 73 | if (!updateRequired) { 74 | // there is no update for this player so we are going to skip them and see if we can skip more players 75 | skipPlayers(buffer, currentIndex, evenIndex) 76 | 77 | // flag this index as being skipped 78 | player.viewport.skipFlags[localPlayerIndex] = (player.viewport.skipFlags[localPlayerIndex].toInt() or 0x2).toByte() 79 | } else { 80 | // now that an update is required we need to know what type of update to perform 81 | updatePlayerInViewport(buffer, maskBuffer, localPlayer!!) 82 | } 83 | 84 | } 85 | buffer.switchToByteAccess() 86 | } 87 | 88 | private fun updatePlayerInViewport(buffer: RSByteBufWriter, maskBuffer: RSByteBufWriter, localPlayer: Player) { 89 | val flagUpdateRequired = localPlayer.updateFlags.isNotEmpty() 90 | buffer.writeFlag(flagUpdateRequired) 91 | 92 | if (flagUpdateRequired) { 93 | PlayerCachedUpdateBlock.CACHED_UPDATE_BLOCK.encode(localPlayer, maskBuffer) 94 | } 95 | 96 | // TODO support for walking type 1, running type 2 and teleporting type 3 97 | buffer.writeBits(2, 0) 98 | } 99 | 100 | private fun skipPlayers(buffer: RSByteBufWriter, currentIndex: Int, evenIndex: Boolean) { 101 | 102 | // first loop through the next indexes until a player can't be skipped or we reached the end of the list 103 | for (nextIndex in (currentIndex + 1) until player.viewport.playersInsideViewportCount) { 104 | val nextPlayerIndex = player.viewport.playerIndexesInsideViewport[nextIndex] 105 | 106 | // skip player indexes that are either even or odd. 107 | if (if(evenIndex) player.viewport.skipFlags[nextPlayerIndex].toInt() and 0x1 != 0 else player.viewport.skipFlags[nextPlayerIndex].toInt() and 0x1 == 0 ) { 108 | continue 109 | } 110 | 111 | // we are gonna grab the next player and determine if they should be skipped 112 | val nextPlayer = player.viewport.playersInViewport[nextPlayerIndex] 113 | 114 | val requiresFlagUpdates = nextPlayer != null && nextPlayer.updateFlags.isNotEmpty() 115 | 116 | // check the next player requires updates, if the player does then we can't skip them. 117 | if (requiresFlagUpdates) { 118 | break 119 | } 120 | 121 | skip++ 122 | } 123 | 124 | // after we determine how many players we can skip we need to tell the client 125 | writeSkip(buffer, skip) 126 | 127 | } 128 | 129 | private fun processPlayersOutsideViewport(buffer: RSByteBufWriter, updateBuffer: RSByteBufWriter, evenIndex: Boolean) { 130 | buffer.switchToBitAccess() 131 | skip = 0 132 | playersAdded = 0 133 | 134 | for (currentIndex in 0 until player.viewport.playersOutsideViewportCount) { 135 | val globalPlayerIndex = player.viewport.playerIndexesOutsideViewport[currentIndex] 136 | 137 | // skip player indexes that are either even or odd. 138 | if (if (evenIndex) player.viewport.skipFlags[globalPlayerIndex].toInt() and 0x1 != 0 else player.viewport.skipFlags[globalPlayerIndex].toInt() and 0x1 == 0) { 139 | continue 140 | } 141 | 142 | // get the global player 143 | val globalPlayer = player.context.world.players.get(globalPlayerIndex) 144 | 145 | /* 146 | Now we need to figure out what needs to be done, we should first decide if this player needs to be updated. 147 | 148 | A player should be updated if they are not skipped. 149 | 150 | A player should be skipped if... 151 | 152 | 1. They do not exist 153 | 2. They are not within viewing distance from us. 154 | 3. The player is the this player sending the packet 155 | */ 156 | 157 | val updateRequired = !(globalPlayer == null || !globalPlayer.position.withinDistance(player.position, Viewport.VIEWING_DISTANCE) || globalPlayer.index == player.index) 158 | 159 | buffer.writeFlag(updateRequired) 160 | 161 | if (!updateRequired) { // skip the player 162 | skipPlayers(buffer, currentIndex, evenIndex) 163 | player.viewport.skipFlags[globalPlayerIndex] = (player.viewport.skipFlags[globalPlayerIndex].toInt() or 0x2).toByte() 164 | continue 165 | } 166 | 167 | // perform some type of update on the player 168 | if (updatePlayerOutsideViewport(buffer, globalPlayer!!)) { 169 | // only if the player was updated should we process them next time around 170 | player.viewport.skipFlags[globalPlayerIndex] = (player.viewport.skipFlags[globalPlayerIndex].toInt() or 0x2).toByte() 171 | } 172 | 173 | } 174 | 175 | buffer.switchToByteAccess() 176 | } 177 | 178 | private fun updatePlayerOutsideViewport(buffer: RSByteBufWriter, globalPlayer: Player) : Boolean { 179 | 180 | return false 181 | } 182 | 183 | private fun writeSkip(buffer: RSByteBufWriter, skipCount: Int) { 184 | assert(skipCount >= 0) 185 | when { 186 | skipCount == 0 -> buffer.writeBits(2, 0) 187 | skipCount < 32 -> { 188 | buffer.writeBits(2, 1) 189 | buffer.writeBits(5, skipCount) 190 | } 191 | skipCount < 256 -> { 192 | buffer.writeBits(2, 2) 193 | buffer.writeBits(8, skipCount) 194 | } 195 | skipCount < 2048 -> { 196 | buffer.writeBits(2, 3) 197 | buffer.writeBits(11, skipCount) 198 | } 199 | } 200 | } 201 | 202 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/codec/game/RSByteBufReader.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.codec.game 2 | 3 | import io.battlerune.net.codec.game.ByteModification.* 4 | import io.netty.buffer.ByteBuf 5 | 6 | class RSByteBufReader private constructor(val buf: ByteBuf) { 7 | 8 | companion object { 9 | fun wrap(buf: ByteBuf) : RSByteBufReader { 10 | return RSByteBufReader(buf) 11 | } 12 | } 13 | 14 | fun readByte() : Int { 15 | return buf.readByte().toInt() 16 | } 17 | 18 | fun readUByte() : Int { 19 | return buf.readUnsignedByte().toInt() 20 | } 21 | 22 | fun readByte(mod: ByteModification = NONE) : Int { 23 | var value = buf.readByte().toInt() 24 | 25 | when (mod) { 26 | ADD -> { value += 128 } 27 | SUB -> { value = 128 - value } 28 | NEG -> { value = -value } 29 | NONE -> {} 30 | } 31 | return value 32 | } 33 | 34 | fun readUByte(mod: ByteModification = NONE) : Int { 35 | var value = buf.readByte().toInt() 36 | 37 | when (mod) { 38 | ADD -> { value += 128 } 39 | SUB -> { value = 128 - value } 40 | NEG -> { value = -value } 41 | NONE -> {} 42 | } 43 | return value and 0xFF 44 | } 45 | 46 | fun readBytes(amount: Int, mod: ByteModification = NONE) : ByteArray { 47 | val bytes = ByteArray(amount) 48 | for (i in 0 until amount) { 49 | bytes[i] = readByte(mod).toByte() 50 | } 51 | return bytes 52 | } 53 | 54 | fun readBytesReverse(amount: Int, mod: ByteModification = NONE): ByteArray { 55 | val data = ByteArray(amount) 56 | var dataPosition = 0 57 | for (index in buf.readerIndex() + amount - 1 downTo buf.readerIndex()) { 58 | var value = buf.getByte(index).toInt() 59 | when (mod) { 60 | ADD -> value -= 128 61 | NEG -> value = -value 62 | SUB -> value = 128 - value 63 | NONE -> {} 64 | } 65 | data[dataPosition++] = value.toByte() 66 | } 67 | return data 68 | } 69 | 70 | fun readShort(mod: ByteModification = NONE) : Int { 71 | var value = 0 72 | value = value or (readUByte() shl 8) 73 | value = value or readUByte(mod) 74 | return value 75 | } 76 | 77 | fun readUShort(mod: ByteModification = NONE) : Int { 78 | var value = 0 79 | value = value or (readUByte() shl 8) 80 | value = value or readUByte(mod) 81 | return value and 0xFFFF 82 | } 83 | 84 | fun readShortLE(mod: ByteModification = NONE) : Int { 85 | var value = 0 86 | value = value or readUByte(mod) 87 | value = value or (readUByte() shl 8) 88 | return value 89 | } 90 | 91 | fun readUShortLE(mod: ByteModification = NONE) : Int { 92 | var value = 0 93 | value = value or readUByte(mod) 94 | value = value or (readUByte() shl 8) 95 | return value and 0xFFFF 96 | } 97 | 98 | fun readUSmart(): Int { 99 | val peek = buf.getByte(buf.readerIndex()).toInt() and 0xFF 100 | return if (peek < 128) buf.readUnsignedByte().toInt() else buf.readUnsignedShort() - 32768 101 | } 102 | 103 | fun readSmart(): Int { 104 | val peek = buf.getUnsignedByte(buf.readerIndex()).toInt() 105 | return if (peek < 128) buf.readUnsignedByte() - 64 else buf.readUnsignedShort() - 49152 106 | } 107 | 108 | fun skipBytes(length: Int) { 109 | buf.skipBytes(length) 110 | } 111 | 112 | fun readInt(mod: ByteModification = NONE) : Int { 113 | var value = 0 114 | value = value or (readUByte() shl 24) 115 | value = value or (readUByte() shl 16) 116 | value = value or (readUByte() shl 8) 117 | value = value or readUByte(mod) 118 | return value 119 | } 120 | 121 | fun readUInt(mod: ByteModification = NONE) : Long { 122 | var value = 0 123 | value = value or (readUByte() shl 24) 124 | value = value or (readUByte() shl 16) 125 | value = value or (readUByte() shl 8) 126 | value = value or readUByte(mod) 127 | return value.toLong() and 0xFFFFFFFFL 128 | } 129 | 130 | fun readIntLE(mod: ByteModification = NONE) : Int { 131 | var value = 0 132 | value = value or readUByte(mod) 133 | value = value or (readUByte() shl 8) 134 | value = value or (readUByte() shl 16) 135 | value = value or (readUByte() shl 24) 136 | return value 137 | } 138 | 139 | fun readUIntLE(mod: ByteModification = NONE) : Long { 140 | var value = 0 141 | value = value or readUByte(mod) 142 | value = value or (readUByte() shl 8) 143 | value = value or (readUByte() shl 16) 144 | value = value or (readUByte() shl 24) 145 | return value.toLong() and 0xFFFFFFFFL 146 | } 147 | 148 | fun readIntMI(mod: ByteModification = NONE) : Int { 149 | var value = 0 150 | value = value or (readUByte() shl 8) 151 | value = value or readUByte(mod) 152 | value = value or (readUByte() shl 24) 153 | value = value or (readUByte() shl 16) 154 | return value 155 | } 156 | 157 | fun readUIntMI(mod: ByteModification = NONE) : Long { 158 | var value = 0 159 | value = value or (readUByte() shl 8) 160 | value = value or readUByte(mod) 161 | value = value or (readUByte() shl 24) 162 | value = value or (readUByte() shl 16) 163 | return value.toLong() and 0xFFFFFFFFL 164 | } 165 | 166 | fun readIntIM(mod: ByteModification = NONE) : Int { 167 | var value = 0 168 | value = value or (readUByte() shl 16) 169 | value = value or (readUByte() shl 24) 170 | value = value or readUByte(mod) 171 | value = value or (readUByte() shl 8) 172 | return value 173 | } 174 | 175 | fun readUIntIM(mod: ByteModification = NONE) : Long { 176 | var value = 0 177 | value = value or (readUByte() shl 16) 178 | value = value or (readUByte() shl 24) 179 | value = value or readUByte(mod) 180 | value = value or (readUByte() shl 8) 181 | return value.toLong() and 0xFFFFFFFFL 182 | } 183 | 184 | fun readInt24() : Int { 185 | var value = 0 186 | value = value or (readByte() shl 8) 187 | value = value or (readUByte() shl 16) 188 | value = value or readUByte() 189 | return value 190 | } 191 | 192 | fun readUInt24() : Int { 193 | var value = 0 194 | value = value or (readUByte() shl 8) 195 | value = value or (readUByte() shl 16) 196 | value = value or readUByte() 197 | return value 198 | } 199 | 200 | fun readLong(mod: ByteModification) : Long { 201 | var value : Long = 0 202 | value = value or (readUByte().toLong() and 56L) 203 | value = value or (readUByte().toLong() and 48L) 204 | value = value or (readUByte().toLong() and 40L) 205 | value = value or (readUByte().toLong() and 32L) 206 | value = value or (readUByte().toLong() and 24L) 207 | value = value or (readUByte().toLong() and 16L) 208 | value = value or (readUByte().toLong() and 8L) 209 | value = value or (readUByte(mod).toLong()) 210 | return value 211 | } 212 | 213 | fun readULong(mod: ByteModification) : Long { 214 | var value : Long = 0 215 | value = value or (readUByte().toLong() and 56L) 216 | value = value or (readUByte().toLong() and 48L) 217 | value = value or (readUByte().toLong() and 40L) 218 | value = value or (readUByte().toLong() and 32L) 219 | value = value or (readUByte().toLong() and 24L) 220 | value = value or (readUByte().toLong() and 16L) 221 | value = value or (readUByte().toLong() and 8L) 222 | value = value or readUByte(mod).toLong() 223 | return value and 0xFFFFFFFFL 224 | } 225 | 226 | fun readLongLE(mod: ByteModification) : Long { 227 | var value : Long = 0 228 | value = value or (readUByte().toLong() and 8L) 229 | value = value or (readUByte().toLong() and 16L) 230 | value = value or (readUByte().toLong() and 24L) 231 | value = value or (readUByte().toLong() and 32L) 232 | value = value or (readUByte().toLong() and 40L) 233 | value = value or (readUByte().toLong() and 48L) 234 | value = value or (readUByte().toLong() and 56L) 235 | value = value or readUByte(mod).toLong() 236 | return value 237 | } 238 | 239 | fun readULongLE(mod: ByteModification) : Long { 240 | var value : Long = 0 241 | value = value or (readUByte().toLong() and 8L) 242 | value = value or (readUByte().toLong() and 16L) 243 | value = value or (readUByte().toLong() and 24L) 244 | value = value or (readUByte().toLong() and 32L) 245 | value = value or (readUByte().toLong() and 40L) 246 | value = value or (readUByte().toLong() and 48L) 247 | value = value or (readUByte().toLong() and 56L) 248 | value = value or readUByte(mod).toLong() 249 | return value and 0xFFFFFFFFL 250 | } 251 | 252 | fun readString() : String { 253 | val sb = StringBuilder() 254 | var b: Byte 255 | while (buf.isReadable) { 256 | b = buf.readByte() 257 | 258 | if (b.toInt() == 0) { 259 | break 260 | } 261 | 262 | sb.append(b.toChar()) 263 | } 264 | return sb.toString() 265 | } 266 | 267 | fun setPosition(position: Int) { 268 | buf.readerIndex(position) 269 | } 270 | 271 | fun size() : Int { 272 | return buf.readableBytes() 273 | } 274 | 275 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/battlerune/net/crypt/ISAACCipher.kt: -------------------------------------------------------------------------------- 1 | package io.battlerune.net.crypt 2 | 3 | /** 4 | * 5 | * An implementation of the [ISAAC](http://www.burtleburtle.net/bob/rand/isaacafa.html) psuedorandom number 6 | * generator. 7 | * 8 | * 9 | *
 10 |  * ------------------------------------------------------------------------------
 11 |  * Rand.java: By Bob Jenkins.  My random number generator, ISAAC.
 12 |  * rand.init() -- initialize
 13 |  * rand.val()  -- get a random value
 14 |  * MODIFIED:
 15 |  * 960327: Creation (addition of randinit, really)
 16 |  * 970719: use context, not global variables, for internal state
 17 |  * 980224: Translate to Java
 18 |  * ------------------------------------------------------------------------------
 19 | 
* 20 | * 21 | * This class has been changed to be more conformant to Java and javadoc conventions. 22 | * 23 | * @author Bob Jenkins 24 | */ 25 | class ISAACCipher 26 | /** 27 | * Creates the random number generator with the specified seed. 28 | * 29 | * @param seed The seed. 30 | */ 31 | (seed: IntArray) { 32 | 33 | /** 34 | * The results given to the user. 35 | */ 36 | private val results = IntArray(SIZE) 37 | 38 | /** 39 | * The internal state. 40 | */ 41 | private val state = IntArray(SIZE) 42 | 43 | /** 44 | * The count through the results in the results array. 45 | */ 46 | private var count = SIZE 47 | 48 | /** 49 | * The accumulator. 50 | */ 51 | private var accumulator: Int = 0 52 | 53 | /** 54 | * The last result. 55 | */ 56 | private var last: Int = 0 57 | 58 | /** 59 | * The counter. 60 | */ 61 | private var counter: Int = 0 62 | 63 | init { 64 | val length = Math.min(seed.size, results.size) 65 | System.arraycopy(seed, 0, results, 0, length) 66 | init() 67 | } 68 | 69 | /** 70 | * Generates 256 results. 71 | */ 72 | private fun isaac() { 73 | var i: Int 74 | var j: Int 75 | var x: Int 76 | var y: Int 77 | 78 | last += ++counter 79 | i = 0 80 | j = SIZE / 2 81 | while (i < SIZE / 2) { 82 | x = state[i] 83 | accumulator = accumulator xor (accumulator shl 13) 84 | accumulator += state[j++] 85 | y = state[x and MASK shr 2] + accumulator + last 86 | state[i] = y 87 | last = state[y shr LOG_SIZE and MASK shr 2] + x 88 | results[i++] = last 89 | 90 | x = state[i] 91 | accumulator = accumulator xor accumulator.ushr(6) 92 | accumulator += state[j++] 93 | y = state[x and MASK shr 2] + accumulator + last 94 | state[i] = y 95 | last = state[y shr LOG_SIZE and MASK shr 2] + x 96 | results[i++] = last 97 | 98 | x = state[i] 99 | accumulator = accumulator xor (accumulator shl 2) 100 | accumulator += state[j++] 101 | y = state[x and MASK shr 2] + accumulator + last 102 | state[i] = y 103 | last = state[y shr LOG_SIZE and MASK shr 2] + x 104 | results[i++] = last 105 | 106 | x = state[i] 107 | accumulator = accumulator xor accumulator.ushr(16) 108 | accumulator += state[j++] 109 | y = state[x and MASK shr 2] + accumulator + last 110 | state[i] = y 111 | last = state[y shr LOG_SIZE and MASK shr 2] + x 112 | results[i++] = last 113 | } 114 | 115 | j = 0 116 | while (j < SIZE / 2) { 117 | x = state[i] 118 | accumulator = accumulator xor (accumulator shl 13) 119 | accumulator += state[j++] 120 | y = state[x and MASK shr 2] + accumulator + last 121 | state[i] = y 122 | last = state[y shr LOG_SIZE and MASK shr 2] + x 123 | results[i++] = last 124 | 125 | x = state[i] 126 | accumulator = accumulator xor accumulator.ushr(6) 127 | accumulator += state[j++] 128 | y = state[x and MASK shr 2] + accumulator + last 129 | state[i] = y 130 | last = state[y shr LOG_SIZE and MASK shr 2] + x 131 | results[i++] = last 132 | 133 | x = state[i] 134 | accumulator = accumulator xor (accumulator shl 2) 135 | accumulator += state[j++] 136 | y = state[x and MASK shr 2] + accumulator + last 137 | state[i] = y 138 | last = state[y shr LOG_SIZE and MASK shr 2] + x 139 | results[i++] = last 140 | 141 | x = state[i] 142 | accumulator = accumulator xor accumulator.ushr(16) 143 | accumulator += state[j++] 144 | y = state[x and MASK shr 2] + accumulator + last 145 | state[i] = y 146 | last = state[y shr LOG_SIZE and MASK shr 2] + x 147 | results[i++] = last 148 | } 149 | } 150 | 151 | /** 152 | * Initializes this random number generator. 153 | */ 154 | private fun init() { 155 | var i: Int 156 | var a: Int 157 | var b: Int 158 | var c: Int 159 | var d: Int 160 | var e: Int 161 | var f: Int 162 | var g: Int 163 | var h: Int 164 | h = GOLDEN_RATIO 165 | g = h 166 | f = g 167 | e = f 168 | d = e 169 | c = d 170 | b = c 171 | a = b 172 | 173 | i = 0 174 | while (i < 4) { 175 | a = a xor (b shl 11) 176 | d += a 177 | b += c 178 | b = b xor c.ushr(2) 179 | e += b 180 | c += d 181 | c = c xor (d shl 8) 182 | f += c 183 | d += e 184 | d = d xor e.ushr(16) 185 | g += d 186 | e += f 187 | e = e xor (f shl 10) 188 | h += e 189 | f += g 190 | f = f xor g.ushr(4) 191 | a += f 192 | g += h 193 | g = g xor (h shl 8) 194 | b += g 195 | h += a 196 | h = h xor a.ushr(9) 197 | c += h 198 | a += b 199 | ++i 200 | } 201 | 202 | i = 0 203 | while (i < SIZE) { /* fill in mem[] with messy stuff */ 204 | a += results[i] 205 | b += results[i + 1] 206 | c += results[i + 2] 207 | d += results[i + 3] 208 | e += results[i + 4] 209 | f += results[i + 5] 210 | g += results[i + 6] 211 | h += results[i + 7] 212 | 213 | a = a xor (b shl 11) 214 | d += a 215 | b += c 216 | b = b xor c.ushr(2) 217 | e += b 218 | c += d 219 | c = c xor (d shl 8) 220 | f += c 221 | d += e 222 | d = d xor e.ushr(16) 223 | g += d 224 | e += f 225 | e = e xor (f shl 10) 226 | h += e 227 | f += g 228 | f = f xor g.ushr(4) 229 | a += f 230 | g += h 231 | g = g xor (h shl 8) 232 | b += g 233 | h += a 234 | h = h xor a.ushr(9) 235 | c += h 236 | a += b 237 | state[i] = a 238 | state[i + 1] = b 239 | state[i + 2] = c 240 | state[i + 3] = d 241 | state[i + 4] = e 242 | state[i + 5] = f 243 | state[i + 6] = g 244 | state[i + 7] = h 245 | i += 8 246 | } 247 | 248 | i = 0 249 | while (i < SIZE) { 250 | a += state[i] 251 | b += state[i + 1] 252 | c += state[i + 2] 253 | d += state[i + 3] 254 | e += state[i + 4] 255 | f += state[i + 5] 256 | g += state[i + 6] 257 | h += state[i + 7] 258 | a = a xor (b shl 11) 259 | d += a 260 | b += c 261 | b = b xor c.ushr(2) 262 | e += b 263 | c += d 264 | c = c xor (d shl 8) 265 | f += c 266 | d += e 267 | d = d xor e.ushr(16) 268 | g += d 269 | e += f 270 | e = e xor (f shl 10) 271 | h += e 272 | f += g 273 | f = f xor g.ushr(4) 274 | a += f 275 | g += h 276 | g = g xor (h shl 8) 277 | b += g 278 | h += a 279 | h = h xor a.ushr(9) 280 | c += h 281 | a += b 282 | state[i] = a 283 | state[i + 1] = b 284 | state[i + 2] = c 285 | state[i + 3] = d 286 | state[i + 4] = e 287 | state[i + 5] = f 288 | state[i + 6] = g 289 | state[i + 7] = h 290 | i += 8 291 | } 292 | 293 | isaac() 294 | } 295 | 296 | /** 297 | * Gets the next random value. 298 | * 299 | * @return The next random value. 300 | */ 301 | fun nextInt(): Int { 302 | if (0 == count--) { 303 | isaac() 304 | count = SIZE - 1 305 | } 306 | return results[count] 307 | } 308 | 309 | companion object { 310 | 311 | /** 312 | * The golden ratio. 313 | */ 314 | private val GOLDEN_RATIO = -0x61c88647 315 | 316 | /** 317 | * The log of the size of the result and state arrays. 318 | */ 319 | private val LOG_SIZE = java.lang.Long.BYTES 320 | 321 | /** 322 | * The size of the result and states arrays. 323 | */ 324 | private val SIZE = 1 shl LOG_SIZE 325 | 326 | /** 327 | * A mask for pseudo-random lookup. 328 | */ 329 | private val MASK = SIZE - 1 shl 2 330 | } 331 | 332 | } --------------------------------------------------------------------------------