Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add music functionality #566

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9c7299c
feat: add SimplePolygonArea class
Ilwyd Dec 19, 2024
c279714
feat: add on_enter_simple_polygon_area functionality
Ilwyd Dec 19, 2024
14854f0
feat: add varp and varp bitNum to MusicTrack
Ilwyd Dec 19, 2024
63c1e50
feat: add track unlocking when entering Region or SimplePolygonArea
Ilwyd Dec 19, 2024
545c5fe
chore: add comment
Ilwyd Dec 19, 2024
fec334f
chore: remove player param from Player.unlockSong
Ilwyd Dec 19, 2024
e0c3774
feat: add music commands
Ilwyd Dec 19, 2024
d47ad09
fix: songs not unlocking in certain regions as expected
Ilwyd Dec 19, 2024
f65ffcf
feat: add option to not send message when unlocking a song
Ilwyd Dec 19, 2024
f999ab6
feat: adding missing songs that are present in cache to RegionMusicSe…
Ilwyd Dec 19, 2024
5478142
feat: unlock 64 default songs on login
Ilwyd Dec 19, 2024
4fa7d4e
feat: add ability to change songs
Ilwyd Dec 20, 2024
4ea96fc
feat: add command to unlock all songs
Ilwyd Dec 20, 2024
262a6f3
feat: set current song varbit on playing a song
Ilwyd Dec 20, 2024
02c0be6
feat: enable playing songs from the playlist
Ilwyd Dec 20, 2024
e0b1596
chore: adjust to values on music setEvents
Ilwyd Dec 20, 2024
9630986
fix: incorrect value for playlist setEvent
Ilwyd Dec 20, 2024
13cb122
feat: add playlist reordering
Ilwyd Dec 20, 2024
4d3bbaf
chore: remove unneeded player message
Ilwyd Dec 20, 2024
973feae
feat: adding and removing songs from playlist
Ilwyd Dec 20, 2024
9fef6ca
feat: add clear playlist button
Ilwyd Dec 21, 2024
27acdd1
feat: add ability to toggle playlist and shuffle
Ilwyd Dec 22, 2024
5dd6354
feat: add support for SoundSongEndMessage packet
Ilwyd Dec 22, 2024
32cd5e1
chore: removing unused println
Ilwyd Dec 22, 2024
d893a51
feat: add missing automatically unlocked songs
Ilwyd Dec 22, 2024
ab01327
fix: SimplePolygonAreas with only 2 vertices automatically become squ…
Ilwyd Dec 22, 2024
3eb2615
fix: error when song ends in an area or region with no associated songs
Ilwyd Dec 22, 2024
2d40fcd
chore: minor refactoring of on_login plugins for music
Ilwyd Dec 22, 2024
e7650a3
chore: commenting PlayerExt functions
Ilwyd Dec 22, 2024
9188393
chore: refactor getting the song index in Player.playSong
Ilwyd Dec 22, 2024
9c367e2
chore: refactor Player.unlockSong
Ilwyd Dec 22, 2024
ccb2f78
chore: refactor region_songs on_buttons
Ilwyd Dec 22, 2024
e1017d5
chore: refactor region_songs magic varbit numbers and deplicated code
Ilwyd Dec 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions data/packets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -621,11 +621,13 @@ in-packets:
length: 4
ignore: true

- message: gg.rsmod.game.message.impl.IgnoreMessage # No data
- message: gg.rsmod.game.message.impl.SoundSongEndMessage # No data
type: FIXED
opcode: 75
length: 4
ignore: true
structure:
- name: songId
type: INT

- message: gg.rsmod.game.message.impl.IgnoreMessage # No data
type: FIXED
Expand Down Expand Up @@ -1163,4 +1165,4 @@ in-packets:
- name: height
type: SHORT
- name: display_mode
type: BYTE
type: BYTE
125 changes: 125 additions & 0 deletions game/plugins/src/main/kotlin/gg/rsmod/plugins/api/ext/PlayerExt.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gg.rsmod.plugins.api.ext

import gg.rsmod.game.fs.def.EnumDef
import gg.rsmod.game.fs.def.ItemDef
import gg.rsmod.game.fs.def.VarbitDef
import gg.rsmod.game.message.impl.*
Expand All @@ -20,6 +21,7 @@ import gg.rsmod.plugins.api.cfg.Items
import gg.rsmod.plugins.api.cfg.Sfx
import gg.rsmod.plugins.content.combat.createProjectile
import gg.rsmod.plugins.content.combat.strategy.MagicCombatStrategy
import gg.rsmod.plugins.content.mechanics.music.RegionMusicService
import gg.rsmod.plugins.content.quests.QUEST_POINT_VARP
import gg.rsmod.plugins.content.quests.Quest
import gg.rsmod.plugins.content.skills.crafting.jewellery.JewelleryData
Expand Down Expand Up @@ -605,12 +607,135 @@ fun Player.playJingle(
write(MusicEffectMessage(id = id, volume = volume))
}

/**
* Sends a [MidiSongMessage] to the [Player] based on the provided [id]. The player's music tab
* interface is also updated using the provided [name]. The player's currently playing song varbit
* is also updated.
*
* @param id: The ID of the song to be played
* @param name: The name of the song to be played
*/
fun Player.playSong(
id: Int,
name: String = "",
) {
setComponentText(interfaceId = 187, component = 4, text = name)
write(MidiSongMessage(10, id, 255))

val index =
world.definitions
.get(EnumDef::class.java, 1351)
.getKeyForValue(id)
setVarbit(4388, index)
}

/**
* Unlocks a song for the player based on trackIndex and writes a message to the player's chat window.
*
* @param trackIndex: The index used to identify the song the cache
* @param sendMessage: Whether to send a message to the [Player] when this song is unlocked
*/
fun Player.unlockSong(
trackIndex: Int,
sendMessage: Boolean = true,
) {
val musicTrack =
world
.getService(
RegionMusicService::class.java,
)!!
.musicTrackList
.first { trackIndex == it.index }

val bitNum = musicTrack.bitNum
val oldValue = getVarp(musicTrack.varp)

// Return if the player has already unlocked this song
if (oldValue shr bitNum and 1 == 1) {
return
}

val newValue = (1 shl bitNum) + oldValue
setVarp(musicTrack.varp, newValue)

val trackName = world.definitions.get(EnumDef::class.java, 1345).getString(trackIndex)
if (sendMessage) {
message(
"<col=ff0000>You have unlocked a new music track: $trackName",
ChatMessageType
.GAME_MESSAGE,
)
}
}

/**
* Add a song to the [Player]s playlist based off of the interface slot that was interacte with.
* If the interface slot is odd that means that the "+" button was clicked, not right-click add song.
* Song indices are stored in varbits 7081 - 7092
*
* @param interfaceSlot: The slot number of the interface that was clicked
*/
fun Player.addSongToPlaylist(interfaceSlot: Int) {
var slot = interfaceSlot
if (slot % 2 != 0) slot -= 1

val trackIndex = slot / 2
val playlistVarbit = (7081..7092).first { getVarbit(it) == 32767 }
setVarbit(playlistVarbit, trackIndex)
}

/**
* Remove a song from the [Player]s play based on the interface slot in either the main track list.
* If [fromTrackList] is true, we need to figure out which varbit that song is in, otherwise we can just
* use [interfaceSlot] to determine the varbit we need to remove. All songs in the following varbits are
* moved back into the previous varbit.
*
* @param interfaceSlot: The slot number of the interface that was clicked
* @param fromTrackList: Whether the clicked slot was on the main track list or on the playlist interface
*/
fun Player.removeSongFromPlaylist(
interfaceSlot: Int,
fromTrackList: Boolean = false,
) {
var playlistSlot = interfaceSlot

if (fromTrackList) {
if (playlistSlot % 2 != 0) playlistSlot -= 1
playlistSlot /= 2
playlistSlot = (7081..7092).indexOfFirst { getVarbit(it) == playlistSlot }
} else {
if (playlistSlot > 11) playlistSlot -= 12
}
(playlistSlot..11).forEach {
if (it == 11) {
setVarbit(7081 + it, -1)
return@forEach
}
setVarbit(7081 + it, getVarbit(7081 + it + 1))
}
}

/**
* Sets all the [Player]s playlist varbits to -1 (Underflows to 32767)
*/
fun Player.clearPlaylist() {
(7081..7092).forEach {
setVarbit(it, -1)
}
}

/**
* Flips the [Player]s playlist varbit to 0 or 1
*/
fun Player.togglePlaylist() {
setVarbit(7078, getVarbit(7078) + 1) // value of 2 overflows back to 0
}

/**
* Flips the [Player]s playlist shuffle varbit to 0 or 1
*/
fun Player.togglePlaylistShuffle() {
setVarbit(7079, getVarbit(7079) + 1) // value of 2 overflows back to 0
}

fun Player.getVarp(id: Int): Int = varps.getState(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import gg.rsmod.plugins.content.inter.bank.openBank
import gg.rsmod.plugins.content.magic.TeleportType
import gg.rsmod.plugins.content.magic.teleport
import gg.rsmod.plugins.content.mechanics.multi.MultiService
import gg.rsmod.plugins.content.mechanics.music.RegionMusicService
import gg.rsmod.plugins.content.npcs.Constants
import gg.rsmod.plugins.content.skills.farming.core.FarmTicker
import gg.rsmod.plugins.content.skills.farming.data.SeedType
Expand Down Expand Up @@ -1465,6 +1466,32 @@ on_command("getvarc", Privilege.ADMIN_POWER) {
}
}

on_command("unlocksong", Privilege.ADMIN_POWER) {
val args = player.getCommandArgs()
tryWithUsage(
player,
args,
"Invalid format! Example of proper command <col=42C66C>::unlocksong 83</col>",
) { values ->
val trackId = values[0].toInt()
player.unlockSong(trackId)
}
}

on_command("resettracks", Privilege.ADMIN_POWER) {
world.getService(RegionMusicService::class.java)?.musicTrackVarps?.forEach {
if (it == -1) return@forEach
player.setVarp(it, 0)
}
}

on_command("unlockalltracks", Privilege.ADMIN_POWER) {
world.getService(RegionMusicService::class.java)?.musicTrackVarps?.forEach {
if (it == -1) return@forEach
player.setVarp(it, -1)
}
}

fun displayKillCounts(
player: Player,
killCounts: Map<String, Int>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gg.rsmod.plugins.content.mechanics.music
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import gg.rsmod.game.Server
import gg.rsmod.game.fs.def.EnumDef
import gg.rsmod.game.model.World
import gg.rsmod.game.service.Service
import gg.rsmod.util.ServerProperties
Expand All @@ -13,11 +14,48 @@ import java.io.FileNotFoundException

class RegionMusicService : Service {
val musicTrackList = ObjectArrayList<MusicTrack>()
val musicTrackVarps =
arrayOf(
20,
21,
22,
23,
24,
25,
298,
311,
346,
414,
464,
598,
662,
721,
906,
1009,
1104,
1136,
1180,
1202,
1381,
1394,
1434,
1596,
1618,
1619,
1620,
-1, // Adding -1 here because songs 864 - 895 are unused
1864,
1865,
2019,
2246,
)

data class MusicTrack(
val name: String,
val index: Int,
val areas: List<MusicTrackArea>,
val varp: Int,
val bitNum: Int,
)

data class MusicTrackArea(
Expand All @@ -40,6 +78,8 @@ class RegionMusicService : Service {
val rawMusic = ObjectMapper(YAMLFactory()).readValue(reader, Map::class.java)
rawMusic.forEach { (key, value) ->
val index = (value as Map<*, *>).getOrDefault("index", -1) as Int
val varp = musicTrackVarps[index.floorDiv(32)]
val bitNum = index - index.floorDiv(32) * 32
val areas =
when (val areasValue = value["areas"]) {
is List<*> -> {
Expand All @@ -64,10 +104,30 @@ class RegionMusicService : Service {
}
else -> emptyList()
}
val track = MusicTrack(name = key as String, index = index, areas = areas)
val track = MusicTrack(name = key as String, index = index, areas = areas, varp = varp, bitNum = bitNum)
musicTrackList.add(track)
}
}
// Adding tracks that aren't present in the music file but are in the cache
world.definitions
.get(
EnumDef::class.java,
1345,
).values
.filter { it.key !in musicTrackList.map { it.index } }
.forEach {
val varp = musicTrackVarps[it.key.floorDiv(32)]
val bitNum = it.key - it.key.floorDiv(32) * 32
val track =
MusicTrack(
name = it.value as String,
index = it.key,
areas = listOf(),
varp = varp,
bitNum = bitNum,
)
musicTrackList.add(track)
}
}

override fun postLoad(
Expand Down
Loading