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

feat: lost city #504

Merged
merged 4 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 4 additions & 4 deletions data/cfg/doors/double-doors.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@
},
{
"opened": {
"left": 1517,
"right": 1520
"left": 12046,
"right": 12048
},
"closed": {
"left": 1516,
"right": 1519
"left": 12047,
"right": 12045
}
}
]
107 changes: 106 additions & 1 deletion game/plugins/src/main/kotlin/gg/rsmod/plugins/api/ext/PlayerExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1099,4 +1099,109 @@ fun Player.refreshBonuses() {
bonusName = StringBuilder(bonusName).append("%").toString()
setComponentText(667, 31 + i, bonusName)//31 to 48 is bonuses
}
}
}

private val entranaPermittedItems: List<Int> = listOf(
Items.PENANCE_GLOVES, Items.PENANCE_GLOVES_10554, Items.ICE_GLOVES, Items.GLOVES_OF_SILENCE,
Items.SPINACH_ROLL, Items.BREAD, Items.CHOCOLATE_SLICE, Items.BAGUETTE,
Items.KEBAB, Items.STEW, Items.SPICY_STEW, Items.CABBAGE, Items.ONION, Items.SPICY_SAUCE, Items.CHILLI_CON_CARNE,
Items.SCRAMBLED_EGG, Items.EVIL_TURNIP, Items._23_EVIL_TURNIP, Items._13_EVIL_TURNIP, Items.TUNA_AND_CORN,
Items.POT_OF_CREAM, Items.DWELLBERRIES, Items.PEACH, Items.ROLL, Items.CHOCOLATE_BAR,
Items.ANCHOVIES, Items.STUFFED_SNAKE, Items.UGTHANKI_MEAT, Items.SHRIMPS, Items.SARDINE, Items.HERRING, Items.MACKEREL,
Items.COD, Items.TROUT, Items.CAVE_EEL, Items.CAVE_EEL_O, Items.PIKE, Items.SALMON, Items.TUNA, Items.FURY_SHARK, Items.LAVA_EEL,
Items.LOBSTER, Items.BASS, Items.SHARK, Items.SHARK_6969, Items.SEA_TURTLE, Items.MANTA_RAY, Items.ROCKTAIL, Items.CRAB_MEAT,
Items.FROG_MASK, Items.FROG_MASK_10721, Items.GNOME_SCARF, Items.GNOME_SCARF_22215, Items.GNOME_SCARF_22216,
Items.GNOME_SCARF_22217, Items.GNOME_SCARF_22218,
)

fun Player.hasEntranaRestrictedEquipment(): Boolean {
val allItemsList = (inventory.sequence() + equipment.sequence())
.filterNot { item ->
exceptionList(item) || excludedNames(this, item) ||
item.getDef(world.definitions).equipSlot < 0
}
.toList()

return allItemsList.any()
}

private fun exceptionList(item: Item): Boolean {
return item.id in entranaPermittedItems
}

private fun excludedNames(player: Player, item: Item): Boolean {
val name = item.getName(player.world.definitions)
if (name.startsWith("Ring of ")) {
return true
}
if (name.startsWith("Amulet of ")) {
return true
}
if (name.startsWith("Ancient ceremonial")) {
return true
}
if (name.contains(" ring")) {
return true
}
if (name.contains(" amulet")) {
return true
}
if (name.contains(" necklace")) {
return true
}
if (name.contains(" pendant")) {
return true
}
if (name.contains(" bracelet")) {
return true
}
if (name.endsWith(" cloak")) {
return true
}
if (name.endsWith(" dragon mask")) {
return true
}
if (name.endsWith(" bolts")) {
return true
}
if (name.endsWith(" bolts (e)")) {
return true
}
if (name.endsWith(" afro")) {
return true
}
if (name.lowercase().contains("arrow")) {
return true
}
if (name.endsWith(" (p)")) {
return true
}
if (name.endsWith(" (p+)")) {
return true
}
if (name.endsWith(" (p++)")) {
return true
}
if (name.endsWith(" partyhat")) {
return true
}
if (name.endsWith(" of lightness")) {
return true
}
if (name.contains("cavalier")) {
return true
}
if (name.contains("book")) {
return true
}
if (name.contains(" cape")) {
return true
}
if (name.contains("beret")) {
return true
}
if (name.startsWith("Ghostly")) {
return true
}
return false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package gg.rsmod.plugins.content.areas.entrana

import gg.rsmod.game.model.attr.HAS_SPAWNED_TREE_SPIRIT
import gg.rsmod.plugins.content.quests.advanceToNextStage
import gg.rsmod.plugins.content.quests.getCurrentStage
import gg.rsmod.plugins.content.quests.impl.LostCity
import gg.rsmod.plugins.content.skills.woodcutting.AxeType
import gg.rsmod.game.model.Tile
import gg.rsmod.game.model.queue.QueueTask
import gg.rsmod.plugins.content.magic.TeleportType
import gg.rsmod.plugins.content.magic.teleport
import gg.rsmod.plugins.content.quests.finishedQuest

val ladder = Objs.LADDER_2408
val dungeonTile = Tile(2822, 9774, 0)

suspend fun monkDialogue(it: QueueTask) {
it.chatNpc(
"Be careful going in there! You are unarmed, and there",
"is much evilness lurking down there! The evilness seems",
"to block off our contact with our gods,",
npc = Npcs.CAVE_MONK)
it.chatNpc(
"so our prayers seem to have less effect down there. Oh,",
"also, you wont be able to come back this way - This",
"ladder only goes one way",
npc = Npcs.CAVE_MONK)
it.chatNpc(
"The only exit from the caves below is a portal which is",
"guarded by greater demons!",
npc = Npcs.CAVE_MONK)
when(it.options(
"I don't think I'm strong enough to enter then.",
"Well, that is a risk I will have to take.")) {
1 -> {
it.chatPlayer("I don't think I'm strong enough to enter then.")
}
2 -> {
it.chatPlayer("Well, that is a risk I will have to take.")
dungeonEntrance(it.player)
}
}
}

fun dungeonEntrance(player: Player) {
player.moveTo(dungeonTile)
}

on_obj_option(ladder, "climb-down") {
val currentStage = player.getCurrentStage(LostCity)
if (currentStage == LostCity.ENTRANA_DUNGEON) {
player.queue{
monkDialogue(this)
}
}
if (currentStage > LostCity.ENTRANA_DUNGEON) {
dungeonEntrance(player)
}
else {
player.message("Nothing interesting happens.")
}
}

on_item_on_item(Items.KNIFE, Items.DRAMEN_BRANCH) {
player.queue {
player.lock()
player.animate(1248)
wait(8)
player.inventory.remove(Items.DRAMEN_BRANCH, amount = 1)
player.inventory.add(Items.DRAMEN_STAFF)
messageBox("You carve the branch into a staff.")
player.unlock()
}
}

on_obj_option(Objs.DRAMEN_TREE, "chop down") {
when (player.getCurrentStage(LostCity)) {
LostCity.ENTRANA_DUNGEON -> {
world.spawn(Npc(Npcs.TREE_SPIRIT, Tile(2860, 9737, 0), world = world))
player.attr.set(HAS_SPAWNED_TREE_SPIRIT, value = 1)
}
LostCity.CUT_DRAMEN_TREE, LostCity.CREATE_DRAMEN_BRANCH, LostCity.QUEST_COMPLETE -> {
player.queue {
if (player.skills.getMaxLevel(Skills.WOODCUTTING) >= 36) {
if (player.inventory.hasFreeSpace()) {
chopDramenTree(this)
} else {
messageBox("Your inventory is too full to hold any more logs.")
}
} else {
messageBox("You need a Woodcutting level of 36 to chop this tree.")
}
}
}
else -> {
player.queue {
messageBox("The tree seems to have an ominous aura. <br> You do not feel like chopping it down.")
}
}
}
}

suspend fun chopDramenTree(it: QueueTask) {
val player = it.player
val axe = AxeType.values.reversed().firstOrNull {
player.skills.getMaxLevel(Skills.WOODCUTTING) >= it.level && (player.equipment.contains(it.item) || player.inventory.contains(it.item))
} ?: return
val axeAnimation = axe.animation
player.lock()
player.animate(axeAnimation, idleOnly = true)
player.filterableMessage("You swing your hatchet at the dramen tree.")
player.animate(axeAnimation, idleOnly = true)
it.wait(5)
if (player.getCurrentStage(LostCity) == LostCity.CUT_DRAMEN_TREE) {
player.advanceToNextStage(LostCity)
}
player.inventory.add(Items.DRAMEN_BRANCH, amount = 1, assureFullInsertion = true)
player.animate(-1)
player.unlock()
}

on_obj_option(Objs.MAGIC_DOOR, "open") {
val obj = player.getInteractingGameObj()
player.lockingQueue(TaskPriority.STRONG) {
handleDoor(player, obj)
wait(2)
player.message("You feel the world around you dissolve...")
player.playSound(Sfx.FT_FAIRY_TELEPORT)
player.teleport(Tile(3237, 3773, 0), type = TeleportType.FAIRY)
}
}

suspend fun handleDoor(player: Player, obj: GameObject) {
val openDoor = DynamicObject(id = 20988, type = 0, rot = 1, tile = Tile(x = 2874, z = 9750))
val door = DynamicObject(id = 2407, type = 0, rot = 0, tile = Tile(x = 2874, z = 9750))
player.lockingQueue {
val x = 2874
val z = 9750
world.remove(obj)
world.spawn(openDoor)
player.playSound(Sfx.DOOR_OPEN)
player.walkTo(tile = Tile(x = x, z = z), detectCollision = false)
wait(3)
world.remove(openDoor)
player.playSound(Sfx.DOOR_CLOSE)
world.spawn(door)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package gg.rsmod.plugins.content.areas.lumbridge

import gg.rsmod.plugins.content.quests.getCurrentStage
import gg.rsmod.plugins.content.quests.impl.LostCity

val lostCity = LostCity

on_npc_option(npc = Npcs.ARCHER_649, option = "talk-to") {
player.queue {
when (player.getCurrentStage(lostCity)) {
LostCity.NOT_STARTED -> beforeLostCity(this)
LostCity.FINDING_SHAMUS -> findingShamus(this)
LostCity.FOUND_SHAMUS -> afterLostCity(this)
LostCity.ENTRANA_DUNGEON, LostCity.CUT_DRAMEN_TREE, LostCity.CREATE_DRAMEN_BRANCH -> afterLostCity(this)
LostCity.QUEST_COMPLETE -> afterLostCity(this)
}
}
}

suspend fun beforeLostCity(it: QueueTask) {
it.chatPlayer("Why are you guys standing around here?")
it.chatNpc("(ahem)... 'Guys'?")
it.chatPlayer("Um... yeah, sorry about that.",
"Why are you all standing around out here?")
it.chatNpc("Well, that's really none of your business.")

}

suspend fun findingShamus(it: QueueTask) {
it.chatPlayer(
"So I hear there's a leprechaun around here who can show",
"me the way to Zanaris?")
it.chatNpc(
"... W-what? How did you...?",
"Wait a minute! How did you know about that?")
it.chatNpc(
"No. You're wrong. Now go away.")
}

suspend fun afterLostCity(it: QueueTask) {
it.chatPlayer(
"So you didn't find the entrance to Zanaris yet, huh?")
it.chatNpc(
"Don't tell me a novice like YOU has found it!")
it.chatPlayer(
"Yep. Found it REALLY easily too.")
it.chatNpc(
"... I Cannot believe that someone like you could find",
"the portal when experienced adventurers such as",
"ourselves could not.")
it.chatPlayer("Believe what you want. Enjoy your little camp fire.")
}
Loading