Started implementing Castle Wars (not ready to enable yet)

This commit is contained in:
Dan Ginovker
2023-04-10 07:13:54 +00:00
committed by Ryan
parent dd3c957f8b
commit 034cc75a61
32 changed files with 1166 additions and 75 deletions
+3 -3
View File
@@ -979,19 +979,19 @@
},
{
"id": "4423",
"replaceId": "4429",
"replaceId": "4425",
"fence": "false",
"metal": "false"
},
{
"id": "4424",
"replaceId": "11382",
"replaceId": "4426",
"fence": "false",
"metal": "false"
},
{
"id": "4427",
"replaceId": "11380",
"replaceId": "4429",
"fence": "false",
"metal": "false"
},
+2 -2
View File
@@ -169,7 +169,7 @@
},
{
"item_id": "590",
"loc_data": "{1,3112,3369,2,10485900}-{1,3209,3734,0,13107300}"
"loc_data": "{1,2368,3135,0,10485900}-{1,2431,3072,0,10485900}-{1,3112,3369,2,10485900}-{1,3209,3734,0,13107300}"
},
{
"item_id": "687",
@@ -385,7 +385,7 @@
},
{
"item_id": "1925",
"loc_data": "{1,3225,3294,0,6553680}-{1,3121,3359,0,6553660}-{1,3216,9625,0,6553680}-{1,3026,3289,0,6553680}-{1,2958,3510,0,6553680}-{1,3221,3497,1,6553680}-{1,2958,3510,0,6553680}-{1,3222,3491,1,6553680}-{1,3307,3195,0,5898270}-{1,1941,4956,0,5898270}"
"loc_data": "{1,2428,3079,0,6553680}-{1,2428,3080,0,6553680}-{1,2371,3128,0,6553680}-{1,2371,3127,0,6553680}-{1,3225,3294,0,6553680}-{1,3121,3359,0,6553660}-{1,3216,9625,0,6553680}-{1,3026,3289,0,6553680}-{1,2958,3510,0,6553680}-{1,3221,3497,1,6553680}-{1,2958,3510,0,6553680}-{1,3222,3491,1,6553680}-{1,3307,3195,0,5898270}-{1,1941,4956,0,5898270}"
},
{
"item_id": "1929",
+38 -19
View File
@@ -36897,7 +36897,8 @@
"archery_ticket_price": "0",
"id": "4068",
"stand_turn_anim": "823",
"bonuses": "13,18,-2,0,0,0,3,2,0,0,0,16,0,0,0"
"bonuses": "13,18,-2,0,0,0,3,2,0,0,0,16,0,0,0",
"castle_wars_ticket_price": "5"
},
{
"remove_sleeves": "true",
@@ -36907,7 +36908,8 @@
"archery_ticket_price": "0",
"id": "4069",
"bonuses": "0,0,0,-30,-10,32,31,24,-6,31,5,0,0,0,0",
"equipment_slot": "4"
"equipment_slot": "4",
"castle_wars_ticket_price": "8"
},
{
"requirements": "{1,5}",
@@ -36916,7 +36918,8 @@
"archery_ticket_price": "0",
"id": "4070",
"bonuses": "0,0,0,-21,-7,17,16,15,-4,16,2,0,0,0,0",
"equipment_slot": "7"
"equipment_slot": "7",
"castle_wars_ticket_price": "6"
},
{
"remove_head": "true",
@@ -36929,7 +36932,8 @@
"archery_ticket_price": "0",
"id": "4071",
"bonuses": "0,0,0,-3,-1,7,8,6,-1,7,3,0,0,0,0",
"equipment_slot": "0"
"equipment_slot": "0",
"castle_wars_ticket_price": "4"
},
{
"requirements": "{1,5}",
@@ -36940,7 +36944,8 @@
"archery_ticket_price": "0",
"id": "4072",
"bonuses": "0,0,0,-8,-2,13,15,14,-1,14,5,0,0,0,0",
"equipment_slot": "5"
"equipment_slot": "5",
"castle_wars_ticket_price": "6"
},
{
"examine": "Not so useful for lighting a fire.",
@@ -41189,7 +41194,8 @@
"archery_ticket_price": "0",
"id": "4503",
"stand_turn_anim": "823",
"bonuses": "15,20,-2,0,0,0,3,2,0,0,0,22,0,0,0"
"bonuses": "15,20,-2,0,0,0,3,2,0,0,0,22,0,0,0",
"castle_wars_ticket_price": "50"
},
{
"remove_sleeves": "true",
@@ -41199,7 +41205,8 @@
"archery_ticket_price": "0",
"id": "4504",
"bonuses": "0,0,0,-30,-10,46,44,38,-6,44,20,0,0,0,0",
"equipment_slot": "4"
"equipment_slot": "4",
"castle_wars_ticket_price": "80"
},
{
"requirements": "{1,20}",
@@ -41208,7 +41215,8 @@
"archery_ticket_price": "0",
"id": "4505",
"bonuses": "0,0,0,-21,-7,24,22,20,-4,22,5,0,0,0,0",
"equipment_slot": "7"
"equipment_slot": "7",
"castle_wars_ticket_price": "60"
},
{
"remove_head": "true",
@@ -41221,7 +41229,8 @@
"archery_ticket_price": "0",
"id": "4506",
"bonuses": "0,0,0,-3,-1,10,11,9,-1,10,5,0,0,0,0",
"equipment_slot": "0"
"equipment_slot": "0",
"castle_wars_ticket_price": "40"
},
{
"requirements": "{1,20}",
@@ -41232,7 +41241,8 @@
"archery_ticket_price": "0",
"id": "4507",
"bonuses": "0,0,0,-8,-2,18,22,20,-1,20,20,0,0,0,0",
"equipment_slot": "5"
"equipment_slot": "5",
"castle_wars_ticket_price": "60"
},
{
"requirements": "{0,30}",
@@ -41255,7 +41265,8 @@
"archery_ticket_price": "0",
"id": "4508",
"stand_turn_anim": "823",
"bonuses": "20,29,-2,0,0,0,3,2,0,0,0,31,0,0,0"
"bonuses": "20,29,-2,0,0,0,3,2,0,0,0,31,0,0,0",
"castle_wars_ticket_price": "500"
},
{
"remove_sleeves": "true",
@@ -41265,7 +41276,8 @@
"archery_ticket_price": "0",
"id": "4509",
"bonuses": "0,0,0,-30,-10,65,63,55,-6,63,30,0,0,0,0",
"equipment_slot": "4"
"equipment_slot": "4",
"castle_wars_ticket_price": "800"
},
{
"requirements": "{1,30}",
@@ -41274,7 +41286,8 @@
"archery_ticket_price": "0",
"id": "4510",
"bonuses": "0,0,0,-21,-7,33,31,29,-4,31,10,0,0,0,0",
"equipment_slot": "7"
"equipment_slot": "7",
"castle_wars_ticket_price": "600"
},
{
"remove_head": "true",
@@ -41287,7 +41300,8 @@
"archery_ticket_price": "0",
"id": "4511",
"bonuses": "0,0,0,-3,-1,14,15,13,-1,14,6,0,0,0,0",
"equipment_slot": "0"
"equipment_slot": "0",
"castle_wars_ticket_price": "400"
},
{
"requirements": "{1,30}",
@@ -41298,7 +41312,8 @@
"archery_ticket_price": "0",
"id": "4512",
"bonuses": "0,0,0,-8,-2,27,31,29,-1,29,30,0,0,0,0",
"equipment_slot": "5"
"equipment_slot": "5",
"castle_wars_ticket_price": "600"
},
{
"remove_head": "true",
@@ -41307,7 +41322,8 @@
"archery_ticket_price": "0",
"id": "4513",
"bonuses": "0,0,0,0,0,0,1,2,0,1,0,0,0,0,0",
"equipment_slot": "0"
"equipment_slot": "0",
"castle_wars_ticket_price": "10"
},
{
"durability": null,
@@ -41315,7 +41331,8 @@
"archery_ticket_price": "0",
"id": "4514",
"bonuses": "0,0,0,0,0,0,1,1,0,2,0,0,0,0,0",
"equipment_slot": "1"
"equipment_slot": "1",
"castle_wars_ticket_price": "10"
},
{
"remove_head": "true",
@@ -41324,7 +41341,8 @@
"archery_ticket_price": "0",
"id": "4515",
"bonuses": "0,0,0,0,0,0,1,2,0,1,0,0,0,0,0",
"equipment_slot": "0"
"equipment_slot": "0",
"castle_wars_ticket_price": "10"
},
{
"durability": null,
@@ -41332,7 +41350,8 @@
"archery_ticket_price": "0",
"id": "4516",
"bonuses": "0,0,0,0,0,0,1,1,0,2,0,0,0,0,0",
"equipment_slot": "1"
"equipment_slot": "1",
"castle_wars_ticket_price": "10"
},
{
"ge_buy_limit": "1000",
+4
View File
@@ -3471,6 +3471,10 @@
"npc_id": "1520",
"loc_data": "{3434,3486,0,0,0}"
},
{
"npc_id": "1526",
"loc_data": "{2443,3089,0,0,0}"
},
{
"npc_id": "1553",
"loc_data": "{2828,10065,1,0,0}"
+33 -5
View File
@@ -145,7 +145,7 @@
},
{
"examine": "Used for storage.",
"ids": "63,64"
"ids": "63,64,36351,36352"
},
{
"examine": "Surprisingly sturdy looking.",
@@ -2601,7 +2601,7 @@
},
{
"examine": "I can climb this.",
"ids": "1729"
"ids": "1729,36347,36363,36644"
},
{
"examine": "Dare I go up?",
@@ -3641,7 +3641,7 @@
},
{
"examine": "I can climb down this.",
"ids": "2408"
"ids": "2408,36693,36694"
},
{
"examine": "A commonly found tree.",
@@ -6777,7 +6777,7 @@
},
{
"examine": "A solid stone staircase.",
"ids": "4415,4416,4417,4418,4419,4420"
"ids": "4415,4416,4417,4418,4419,4420,36480,36481,36484,36495,36521,36523,36532,36540"
},
{
"examine": "A spiky barricade.",
@@ -7609,7 +7609,7 @@
},
{
"examine": "I can climb down this.",
"ids": "4911,4912"
"ids": "4911,4912,36349,36365"
},
{
"examine": "It looks cramped and dark.",
@@ -20602,5 +20602,33 @@
{
"examine": "Keeps the puppies out of trouble.",
"ids": "28341"
},
{
"examine": "There are some toolboxes on this table.",
"ids": "36573,36580"
},
{
"examine": "There's some rock I can use with the catapult here.",
"ids": "36574,36581"
},
{
"examine": "There are some barricades here.",
"ids": "36575,36582"
},
{
"examine": "There is some rope here.",
"ids": "36576,36583"
},
{
"examine": "There are some explosive potions here.",
"ids": "36577,36584"
},
{
"examine": "There are some pickaxes on this table.",
"ids": "36578,36585"
},
{
"examine": "There are some bandages on this table.",
"ids": "36579,36586"
}
]
+9
View File
@@ -2177,5 +2177,14 @@
"id": "254",
"title": "Gabooty's Tai Bwo Wannai Drinky Store",
"stock": "{2084,0,100}-{2092,0,100}-{2048,0,100}-{2054,0,100}-{2064,0,100}-{2074,0,100}"
},
{
"npcs": "1526",
"high_alch": "0",
"currency": "4067",
"general_store": "false",
"id": "255",
"title": "Castle Wars Ticket Exchange",
"stock": "{4068,1,100}-{4069,1,100}-{4070,1,100}-{4071,1,100}-{4072,1,100}-{4503,1,100}-{4504,1,100}-{4505,1,100}-{4506,1,100}-{4507,1,100}-{4508,1,100}-{4509,1,100}-{4510,1,100}-{4511,1,100}-{4512,1,100}-{4513,1,100}-{4514,1,100}-{4515,1,100}-{4516,1,100}"
}
]
@@ -0,0 +1,81 @@
package rs09.game.content.activity.castlewars
import core.game.world.map.zone.ZoneBorders
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import org.rs09.consts.Scenery
object CastleWars {
// Scenery IDs
const val joinSaradominTeamPortal: Int = Scenery.SARADOMIN_PORTAL_4387
const val joinZamorakTeamPortal: Int = Scenery.ZAMORAK_PORTAL_4388
const val saradominLeaveLobbyPortal: Int = Scenery.PORTAL_4389
const val zamorakLeaveLobbyPortal: Int = Scenery.PORTAL_4390
const val joinGuthixTeamPortal: Int = Scenery.GUTHIX_PORTAL_4408
const val cwCastleClimbingRope: Int = Scenery.CLIMBING_ROPE_36312
const val castleWaterTap: Int = Scenery.TAP_4482
const val cwSteppingStones: Int = Scenery.STEPPING_STONE_4411
// Map of the item table scenery id to the item id they give
val cwTableItemRewardMap = mapOf(
Scenery.TABLE_36573 to Items.TOOLKIT_4051, // Saradomin Toolkit Table
Scenery.TABLE_36580 to Items.TOOLKIT_4051, // Zamorak Toolkit Table
Scenery.TABLE_36574 to Items.ROCK_4043, // Saradomin Rock Table
Scenery.TABLE_36581 to Items.ROCK_4043, // Zamorak Rock Table
Scenery.TABLE_36575 to Items.BARRICADE_4053, // Saradomin Barricade Table
Scenery.TABLE_36582 to Items.BARRICADE_4053, // Zamorak Barricade Table
Scenery.TABLE_36576 to Items.CLIMBING_ROPE_4047, // Saradomin Climbing Rope Table
Scenery.TABLE_36583 to Items.CLIMBING_ROPE_4047, // Zamorak Climbing Rope Table
Scenery.TABLE_36577 to Items.EXPLOSIVE_POTION_4045, // Saradomin Explosive Potion Table
Scenery.TABLE_36584 to Items.EXPLOSIVE_POTION_4045, // Zamorak Explosive Potion Table
Scenery.TABLE_36578 to Items.BRONZE_PICKAXE_1265, // Saradomin Pickaxe Table
Scenery.TABLE_36585 to Items.BRONZE_PICKAXE_1265, // Zamorak Pickaxe Table
Scenery.TABLE_36579 to Items.BANDAGES_4049, // Saradomin Bandages Table
Scenery.TABLE_36586 to Items.BANDAGES_4049 // Zamorak Bandages Table
)
val cwCastleBattlementsMap = mapOf(
Scenery.BATTLEMENTS_4446 to Scenery.BATTLEMENTS_36313,
Scenery.BATTLEMENTS_4447 to Scenery.BATTLEMENTS_36314
)
// Item IDs
const val saradominTeamHoodedCloak: Int = Items.HOODED_CLOAK_4041
const val zamorakTeamHoodedCloak: Int = Items.HOODED_CLOAK_4042
const val saradominFlag: Int = Items.SARADOMIN_BANNER_4037 // Might be 4038
const val zamorakFlag: Int = Items.ZAMORAK_BANNER_4039 // Might be 4040
const val cwPickaxe: Int = Items.BRONZE_PICKAXE_1265
const val cwRock: Int = Items.ROCK_4043
const val cwExplosivePotion: Int = Items.EXPLOSIVE_POTION_4045
const val cwClimbingRope: Int = Items.CLIMBING_ROPE_4047
const val cwBandages: Int = Items.BANDAGES_4049
const val cwToolkit: Int = Items.TOOLKIT_4051
const val cwBarricade: Int = Items.BARRICADE_4053
const val cwManualBook: Int = Items.CASTLEWARS_MANUAL_4055
const val cwTicketRewardCurrency: Int = Items.CASTLE_WARS_TICKET_4067
// NPC IDs
const val sheep = NPCs.SHEEP_1529
const val imp = NPCs.IMP_1531
const val rabbit = NPCs.RABBIT_1530
const val unknownCwarsBarricade1 = NPCs.BARRICADE_1532
const val unknownCwarsBarricadeOnFire = NPCs.BARRICADE_1533
const val unknownCwarsBarricade2 = NPCs.BARRICADE_1534
const val unknownCwarsBarricade3 = NPCs.BARRICADE_1535
// Locations
val lobbyBankArea: ZoneBorders = ZoneBorders(2440, 3092, 2444, 3086, 0)
// Strings
const val saradominName = "Saradomin"
const val zamorakName = "Zamorak"
const val guthixName = "Guthix"
const val portalAttribute = "castlewars_portal"
const val invFullMessage = "Your inventory is too full to hold any more "
// Ints
const val gameTimeMinutes = 20
const val gameCooldownMinutes = 5
const val ropeAliveTicks = -1 // TODO: Find amount of time until wall reverts to non rope state
}
@@ -0,0 +1,190 @@
package rs09.game.content.activity.castlewars
import core.api.*
import content.global.skill.agility.AgilityHandler
import content.global.skill.summoning.familiar.BurdenBeast
import core.cache.def.impl.ItemDefinition
import core.game.container.Container
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.TeleportManager
import core.game.node.entity.player.link.audio.Audio
import core.game.node.item.Item
import core.game.node.scenery.Scenery
import core.game.node.scenery.SceneryBuilder
import core.game.world.GameWorld
import core.game.world.map.Location
import core.game.world.update.flag.context.Animation
import core.tools.Log
import org.rs09.consts.Items
import rs09.game.content.activity.castlewars.areas.CastleWarsWaitingArea
import java.util.*
@Suppress("unused")
class CastleWarsListeners : InteractionListener {
override fun defineListeners() {
// Scenery Interactions - PORTALS
on(CastleWars.joinSaradominTeamPortal, IntType.SCENERY, "Enter") { player, _ ->
if (GameWorld.settings?.enable_castle_wars != true) return@on false
if (joinError(player)) return@on true
// MapArea handles joining properties
player.properties.teleportLocation = CastleWarsWaitingArea.saradominWaitingRoom.randomWalkableLoc
player.setAttribute(CastleWars.portalAttribute, CastleWars.saradominName)
return@on true
}
on(CastleWars.joinZamorakTeamPortal, IntType.SCENERY, "Enter") { player, _ ->
if (GameWorld.settings?.enable_castle_wars != true) return@on false
if (joinError(player)) return@on true
// MapArea handles joining properties
player.properties.teleportLocation = CastleWarsWaitingArea.zamorakWaitingRoom.randomWalkableLoc
player.setAttribute(CastleWars.portalAttribute, CastleWars.zamorakName)
return@on true
}
on(CastleWars.saradominLeaveLobbyPortal, IntType.SCENERY, "Exit") { player, _ ->
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
// MapArea handles leaving properties
return@on true
}
on(CastleWars.zamorakLeaveLobbyPortal, IntType.SCENERY, "Exit") { player, _ ->
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
// MapArea handles leaving properties
return@on true
}
on(CastleWars.joinGuthixTeamPortal, IntType.SCENERY, "Enter") { player, _ ->
if (GameWorld.settings?.enable_castle_wars != true) return@on false
if (joinError(player)) return@on true
// Join the team with fewer players, if they're equal, join randomly
if (CastleWarsWaitingArea.waitingSaradominPlayers.size < CastleWarsWaitingArea.waitingZamorakPlayers.size) {
player.properties.teleportLocation = CastleWarsWaitingArea.saradominWaitingRoom.randomWalkableLoc
} else if (CastleWarsWaitingArea.waitingSaradominPlayers.size > CastleWarsWaitingArea.waitingZamorakPlayers.size) {
player.properties.teleportLocation = CastleWarsWaitingArea.zamorakWaitingRoom.randomWalkableLoc
} else {
if (Math.random() < 0.5) {
player.properties.teleportLocation = CastleWarsWaitingArea.saradominWaitingRoom.randomWalkableLoc
} else {
player.properties.teleportLocation = CastleWarsWaitingArea.zamorakWaitingRoom.randomWalkableLoc
}
}
player.setAttribute(CastleWars.portalAttribute, CastleWars.guthixName)
return@on true
}
// Scenery Interactions - Item Tables
on(CastleWars.cwTableItemRewardMap.keys.toIntArray(), SCENERY, "take-from") { player, node ->
// Retrieve the item id from the map (null safe)
val rewardItem: Int = CastleWars.cwTableItemRewardMap.getValue(node.id)
// If item is added to inventory, play pickup sound
if (addItem(player, rewardItem)) {
playAudio(player, Audio(2582))
// Warn player inventory full using custom dialogue box
} else {
val formattedItemName = (ItemDefinition.forId(rewardItem).name.lowercase() + "s.") // Get the formatted item name string
.replace("pes.", "pe.") // Replacement for ropes. -> rope.
.replace("bronze ", "") // Replacement for bronze pickaxes. -> pickaxes.
// Toolkit gets a more grammatically correct sentence
if (rewardItem == Items.TOOLKIT_4051) {
sendDialogue(player, "Your inventory is too full to hold a toolkit.")
} else {
sendDialogue(player, (CastleWars.invFullMessage + formattedItemName))
}
}
return@on true
}
// Item/Scenery Interaction - Outside Battlement (Wall) Rope Climb Setup
onUseWith(SCENERY, CastleWars.cwClimbingRope, *CastleWars.cwCastleBattlementsMap.keys.toIntArray()) { player, rope, wall ->
// Remove rope item from inventory
removeItem(player, rope)
// First, replace the scenery with the rope on top of the wall
replaceScenery(wall.asScenery(), CastleWars.cwCastleBattlementsMap.getValue(wall.id), CastleWars.ropeAliveTicks)
// Second, create a new scenery with the rope falling down
val toAdd = Scenery(CastleWars.cwCastleClimbingRope, wall.location, 4, wall.direction.toInteger())
SceneryBuilder.add(toAdd, CastleWars.ropeAliveTicks)
toAdd.isActive = true
return@onUseWith true
}
// Scenery Interactions - Outside Battlement (Wall) Player Climbs Rope
on(CastleWars.cwCastleClimbingRope, SCENERY, "climb") { player, rope ->
// Get the direction of the scenery
val dir = rope.asScenery().direction.opposite
// Move player on top of the wall from that specific rope location
teleport(player, Location(dir.stepY + player.location.x, -dir.stepX + player.location.y), TeleportManager.TeleportType.INSTANT)
return@on true
}
// Scenery Interactions - Water Tap - No pulse
onUseWith(SCENERY, Items.BUCKET_1925, CastleWars.castleWaterTap) { player, used, _ ->
// Lock player
lock(player, 1)
// Bucket fill animation
animate(player, 832)
// Bucket fill sound
playAudio(player, Audio(2609, 1))
// Replace empty -> full water bucket
replaceSlot(player, used.asItem().slot, Item(Items.BUCKET_OF_WATER_1929))
return@onUseWith true
}
// Scenery Interactions - Stepping stones
on(CastleWars.cwSteppingStones, SCENERY, "jump-to") { player, stone ->
// Lock the player
lock(player, 3)
// Make the player "Jump" to the next stepping stone
AgilityHandler.forceWalk(player, -1, player.location, stone.location, Animation(741), 10, 0.0, null, 1)
// Delay sound to line up with "jump" movement
runTask(player, 1) {
// Play jumping sound
playAudio(player, Audio(2461))
}
return@on true
}
}
private fun hasNonCombatItems(container: Container): Boolean {
for (item in container.toArray()) {
if (item?.id != null && (item.id == Items.COINS_995 || item.definition?.noteId == item.id))
return true
}
return false
}
private fun capeOrHelmetError(player: Player): String? {
val wornCape = getItemFromEquipment(player, EquipmentSlot.CAPE)?.id ?: -1
val wornHelmet = getItemFromEquipment(player, EquipmentSlot.HAT)?.id ?: -1
if (wornCape != -1 || wornHelmet != -1) return "You can't wear hats, capes, or helms in the arena."
return null
}
private fun nonCombatItemsCheck(player: Player): String? {
// Coins & Noted items, possibly others?
if (hasNonCombatItems(player.inventory)) return "You can't take non-combat items into the arena."
return null
}
private fun familiarCheck(player: Player): String? {
// https://forum.tip.it/topic/250066-castle-wars-familiar-msg/
// Your familiar can't hold any non-combat items
// Check the player's familiar container for Coins or Noted items
val familiar: BurdenBeast = player.familiarManager.familiar as? BurdenBeast ?: return null
if (hasNonCombatItems(familiar.container)) return "Your familiar can't take non-combat items into the arena."
return null
}
private fun joinError(player: Player): Boolean {
val errorMessage = capeOrHelmetError(player) ?: nonCombatItemsCheck(player) ?: familiarCheck(player) ?: return false
player.sendMessage(errorMessage).also { return true }
}
}
@@ -0,0 +1,22 @@
import core.api.setVarbit
import core.game.node.entity.player.Player
/**
* Handles updating the Castle Wars overlay interface
* @author dginovker
*/
object CastleWarsOverlay {
@JvmStatic
fun sendLobbyUpdate(player: Player, bothTeamsHavePlayers: Boolean, gameStartMinutes: Int) {
player.varpManager.get(380).setVarbit(0, if (bothTeamsHavePlayers) gameStartMinutes else 0).send(player)
}
@JvmStatic
fun sendGameUpdate(player: Player) {
// Todo - Figure out underground mine/etc
setVarbit(player, 143, 0); // Flag status - safe = 0, taken = 1, dropped = 2
setVarbit(player, 145, 5); // Saradomin's score
setVarbit(player, 153, 0); // Flag status - safe = 0, taken = 1, dropped = 2
setVarbit(player, 155, 7); // Zamorak's score
}
}
@@ -0,0 +1,131 @@
package rs09.game.content.activity.castlewars
import core.api.TickListener
import core.api.openDialogue
import core.api.sendMessage
import core.game.dialogue.DialogueFile
import core.game.dialogue.FacialExpression
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.npc.NPC
import core.game.node.item.Item
import core.tools.END_DIALOGUE
import org.rs09.consts.Items
import org.rs09.consts.NPCs
class Lanthus: InteractionListener, TickListener {
override fun defineListeners() {
on(NPCs.LANTHUS_1526, IntType.NPC, "talk-to") { player, npcNode ->
val lanthusNpc = npcNode.asNpc()
openDialogue(player, object : DialogueFile(){
override fun handle(componentID: Int, buttonID: Int) {
when(stage) {
0 -> npcl(FacialExpression.FRIENDLY, "Good day, how may I help you?").also { stage = 2 }
2 -> options(
"What is this place?",
"What do you have for trade?",
"Do you have a manual? I'd like to learn how to play!"
).also { stage++ }
3 -> when (buttonID) {
1 -> player("What is this place?").also { stage = 4 }
2 -> lanthusNpc.openShop(player).also { stage = END_DIALOGUE }
3 -> player("Do you have a manual? I'd like to learn how to play!").also { stage = 50 }
}
4 -> npcl(
FacialExpression.FRIENDLY,
"This is the great Castle Wars arena! Here you can fight for the glory of Saradomin or Zamorak."
).also { stage++ }
5 -> options(
"Really, how do I do that?",
"Are there any rules?",
"What can I win?"
).also { stage++ }
6 -> when (buttonID) {
1 -> player("Really, how do I do that?").also { stage = 7 }
2 -> player("Are there any rules?").also { stage = 80 }
3 -> player("What can I win?").also { stage = 90 }
}
7 -> npcl(
FacialExpression.FRIENDLY,
"Easy, you just step through one of the three portals. To join Zamorak, pass through the red portal. To join Saradomin, pass through the blue portal. If you don't mind then pass through the green portal.",
).also { stage ++ }
8 -> options(
"Are there any rules?",
"What can I win?",
"What do you have for trade?",
"Do you have a manual? I'd like to learn how to play!"
).also { stage++ }
9 -> when (buttonID) {
1 -> player("Are there any rules?").also { stage = 80 }
2 -> player("What can I win?").also { stage = 90 }
3 -> lanthusNpc.openShop(player).also { stage = END_DIALOGUE }
4 -> player("Do you have a manual? I'd like to learn how to play!").also { stage = 50 }
}
80 -> npcl(
FacialExpression.FRIENDLY, "Of course, there are always rules. Firstly you can't wear a cape as you enter the portal, you'll be given your team colours to wear while in the arena. You're also prohibited from taking non-combat related items in")
.also { stage ++ }
81 -> npcl(
FacialExpression.FRIENDLY, " with you. So you should only have equipment, potions, and runes on you. Secondly, attacking your own team or your team's defences isn't allowed. You don't want to be angering"
).also { stage ++ }
82 -> npcl(
FacialExpression.FRIENDLY, "your patron god, do you? Other than that, just have fun and enjoy it!"
).also { stage ++ }
50 -> npcl(
FacialExpression.FRIENDLY,
"Sure, here you go.").also { stage = END_DIALOGUE }.also { player.inventory.add(Item(Items.CASTLEWARS_MANUAL_4055)) }
83 -> player("Great! Oh, how do I win the game?").also { stage ++ }
84 -> npcl(
FacialExpression.FRIENDLY,
"The aim is to get into your opponents' castle and take their team standard. Then bring that back and capture it on your team's standard."
).also { stage ++ }
85 -> options(
"What can I win?",
"What do you have to trade?",
"Do you have a manual? I'd like to learn how to play!"
).also { stage ++ }
86 -> when (buttonID) {
1 -> player("What can I win?").also { stage = 90 }
2 -> lanthusNpc.openShop(player).also { stage = END_DIALOGUE }
3 -> player("Do you have a manual? I'd like to learn how to play!").also { stage = 50 }
}
90 -> npcl(
FacialExpression.FRIENDLY,
"Players on the winning team will receive 2 Castle Wars Tickets which you can trade back to me for other items. In the event of a draw every player will get 1 ticket."
).also { stage ++ }
91 -> options(
"Are there any rules?",
"What do you have to trade?",
"Do you have a manual? I'd like to learn how to play!"
).also { stage ++ }
92 -> when (buttonID) {
1 -> player("Are there any rules?").also { stage = 80 }
2 -> lanthusNpc.openShop(player).also { stage = END_DIALOGUE }
3 -> player("Do you have a manual? I'd like to learn how to play!").also { stage = 50 }
}
// Default
else -> sendMessage(player, "Error - unknown stage $stage").also { stage = END_DIALOGUE }
}
}
}, lanthusNpc)
return@on true
}
on(NPCs.LANTHUS_1526, IntType.NPC, "trade-with") { player, npcNode ->
npcNode.asNpc().openShop(player)
return@on true
}
}
override fun tick() {
"The next game will start in 4 minutes"
"The next game will start in 1 minute"
// He also talks to Postie Pete sometimes?
}
}
@@ -0,0 +1,81 @@
package rs09.game.content.activity.castlewars.areas
import content.global.skill.summoning.familiar.BurdenBeast
import core.api.LogoutListener
import core.api.MapArea
import core.api.log
import core.api.sendMessage
import core.game.interaction.InteractionListener
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.entity.state.EntityState
import core.tools.Log
import rs09.game.content.activity.castlewars.CastleWars
abstract class CastleWarsArea : MapArea, LogoutListener, InteractionListener {
override fun areaLeave(entity: Entity, logout: Boolean) {
super.areaLeave(entity, logout)
exitArea(entity as? Player ?: return)
}
override fun logout(player: Player) {
if (!defineAreaBorders().any { it.insideBorder(player.location) }) {
return // We're not in this area
}
// Move the player to the lobby bank area
// Set location directly here, because we want it to take effect immediately
player.location = CastleWars.lobbyBankArea.randomWalkableLoc
exitArea(player)
}
open fun exitArea(player: Player) {
// Remove any transformation
player.appearance.transformNPC(-1)
// Give the player their tabs back
player.interfaceManager.restoreTabs()
// Let the player run
player.walkingQueue.isRunDisabled = false
// If we're not entering another Castle Wars area, remove the Castle Wars items
if ((CastleWarsGameArea.areaBorders + CastleWarsWaitingArea.areaBorders).none { it.insideBorder(player.location) }) {
exitCastleWars(player)
}
}
private fun exitCastleWars(player: Player) {
// Close the overlay interface
player.interfaceManager.closeOverlay()
// Remove teleblock
player.stateManager.remove(EntityState.TELEBLOCK)
// Remove any Castle Wars items
// Todo Remove any tinderboxes or other castle wars items - See Jan 2018 update: https://oldschool.runescape.wiki/w/Castle_Wars
val cwarsItems = intArrayOf(CastleWars.saradominTeamHoodedCloak,
CastleWars.zamorakTeamHoodedCloak,
CastleWars.saradominFlag,
CastleWars.zamorakFlag)
player.equipment.removeAll(cwarsItems)
player.inventory.removeAll(cwarsItems)
(player.familiarManager.familiar as? BurdenBeast)?.container?.removeAll(cwarsItems)
}
override fun defineListeners() {
onUnequip(intArrayOf(CastleWars.saradominTeamHoodedCloak, CastleWars.zamorakTeamHoodedCloak)) { player, _ ->
defineAreaBorders().forEach { border ->
if (border.insideBorder(player)) {
sendMessage(player, "You can't remove your team's colours")
// TODO: Equipping a cape or helmet causes issues
return@onUnequip false
}
}
return@onUnequip true
}
}
}
@@ -0,0 +1,101 @@
package rs09.game.content.activity.castlewars.areas
import core.api.TickListener
import core.api.log
import core.game.component.Component
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.entity.state.EntityState
import core.game.node.item.Item
import core.game.world.map.Location
import core.game.world.map.zone.ZoneBorders
import core.tools.Log
import core.tools.ticksPerMinute
import org.rs09.consts.Components
import rs09.game.content.activity.castlewars.CastleWars
/**
* Handles the Castle Wars game map
*/
class CastleWarsGameArea : CastleWarsArea(), TickListener {
companion object {
private val saradominStandardFloor: ZoneBorders = ZoneBorders(Location.create(2426, 3073, 3), Location.create(2430, 3077, 3))
private val zamorakStandardFloor: ZoneBorders = ZoneBorders(Location.create(2373, 3134, 3), Location.create(2369, 3130, 3))
private val saradominUpperFloor: ZoneBorders = ZoneBorders(Location.create(2431, 3080, 2), Location.create(2423, 3072, 2))
private val zamorakUpperFloor: ZoneBorders = ZoneBorders(Location.create(2368, 3127, 2), Location.create(2376, 3135, 2))
private val saradominFloor: ZoneBorders = ZoneBorders(Location.create(2420, 3072, 1), Location.create(2431, 3083, 1))
private val zamorakFloor: ZoneBorders = ZoneBorders(Location.create(2379, 3135, 1), Location.create(2368, 3124, 1))
private val battleField: ZoneBorders = ZoneBorders(Location.create(2368, 3135, 0), Location.create(2431, 3072, 0))
private val saradominTunnels: ZoneBorders = ZoneBorders(Location.create(2430, 9481, 0), Location.create(2400, 9504, 0))
private val zamorakTunnels: ZoneBorders = ZoneBorders(Location.create(2401, 9503, 0), Location.create(2366, 9529, 0))
val areaBorders = arrayOf(saradominStandardFloor, zamorakStandardFloor, saradominUpperFloor, zamorakUpperFloor, saradominFloor, zamorakFloor, battleField, saradominTunnels, zamorakTunnels)
val saradominPlayers = mutableSetOf<Player>()
val zamorakPlayers = mutableSetOf<Player>()
var ticksLeftInGame = 0
fun startGame() {
saradominPlayers.addAll(CastleWarsWaitingArea.waitingSaradominPlayers)
zamorakPlayers.addAll(CastleWarsWaitingArea.waitingZamorakPlayers)
CastleWarsWaitingArea.waitingSaradominPlayers.clear()
CastleWarsWaitingArea.waitingZamorakPlayers.clear()
ticksLeftInGame = CastleWars.gameTimeMinutes * ticksPerMinute
// Put all the players in their respawn area
saradominPlayers.forEach { player ->
player.properties.teleportLocation = CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc
}
zamorakPlayers.forEach { player ->
player.properties.teleportLocation = CastleWarsRespawnArea.zamorakRespawnRoom.randomWalkableLoc
}
}
}
private fun endGame() {
saradominPlayers.forEach { player ->
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
zamorakPlayers.forEach { player ->
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
saradominPlayers.clear()
zamorakPlayers.clear()
}
override fun defineAreaBorders(): Array<ZoneBorders> {
return areaBorders
}
override fun areaEnter(entity: Entity) {
val player = entity as? Player ?: return
super.areaEnter(player)
// Block player teleport (for the remaining duration of the game)
player.stateManager.set(EntityState.TELEBLOCK, (CastleWars.gameTimeMinutes)*60*2)
if (saradominPlayers.contains(player)) {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_SARADOMIN_58))
player.equipment.replace(Item(CastleWars.saradominTeamHoodedCloak), 1)
} else if (zamorakPlayers.contains(player)) {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_ZAMORAK_59))
player.equipment.replace(Item(CastleWars.zamorakTeamHoodedCloak), 1)
}
}
override fun exitArea(player: Player) {
super.exitArea(player)
// Remove player from the players set (whichever one that is)
saradominPlayers.remove(player)
zamorakPlayers.remove(player)
}
override fun tick() {
ticksLeftInGame--
if (ticksLeftInGame == 0) {
endGame()
}
}
}
@@ -0,0 +1,40 @@
package rs09.game.content.activity.castlewars.areas
import core.api.TickListener
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.world.map.Location
import core.game.world.map.zone.ZoneBorders
import rs09.game.content.activity.castlewars.CastleWars
/**
* Handles the Castle Wars respawn rooms
*/
class CastleWarsRespawnArea : CastleWarsArea(), TickListener {
companion object {
val zamorakRespawnRoom: ZoneBorders = ZoneBorders(Location.create(2376, 3127, 1), Location.create(2368, 3135, 1))
val saradominRespawnRoom: ZoneBorders = ZoneBorders(Location.create(2423, 3080, 1), Location.create(2431, 3072, 1))
val zamorakPlayersInRespawnRoom = mutableSetOf<Player>()
val saradominPlayersInRespawnRoom = mutableSetOf<Player>()
}
override fun defineAreaBorders(): Array<ZoneBorders> {
return arrayOf(zamorakRespawnRoom, saradominRespawnRoom)
}
override fun areaEnter(entity: Entity) {
val player = entity as? Player ?: return
if (saradominRespawnRoom.insideBorder(player.location)) {
player.equipment.replace(Item(CastleWars.saradominTeamHoodedCloak), 1)
} else if (zamorakRespawnRoom.insideBorder(player.location)) {
player.equipment.replace(Item(CastleWars.zamorakTeamHoodedCloak), 1)
}
}
override fun tick() {
}
}
@@ -0,0 +1,133 @@
package rs09.game.content.activity.castlewars.areas
import CastleWarsOverlay
import core.api.God
import core.api.TickListener
import core.api.hasGodItem
import core.api.sendDialogue
import core.game.component.Component
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.entity.state.EntityState
import core.game.node.item.Item
import core.game.world.map.zone.ZoneBorders
import core.tools.ticksPerMinute
import org.rs09.consts.Components
import rs09.game.content.activity.castlewars.CastleWars
/**
* Handles the Castle Wars "waiting room"
*/
class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
companion object {
val zamorakWaitingRoom: ZoneBorders = ZoneBorders(2432, 9510, 2407, 9534, 0)
val saradominWaitingRoom: ZoneBorders = ZoneBorders(2394, 9497, 2369, 9481, 0)
val areaBorders = arrayOf(zamorakWaitingRoom, saradominWaitingRoom)
val waitingSaradominPlayers = mutableSetOf<Player>()
val waitingZamorakPlayers = mutableSetOf<Player>()
}
override fun defineAreaBorders(): Array<ZoneBorders> {
return areaBorders
}
override fun areaEnter(entity: Entity) {
val player = entity as? Player ?: return
super.areaEnter(player)
// Block player teleport (for at least the max wait time)
player.stateManager.set(EntityState.TELEBLOCK, (CastleWars.gameCooldownMinutes + CastleWars.gameTimeMinutes)*60*2)
// Set team attribute and equip the hooded cloak on the entity based on which waiting room they're in
if (zamorakWaitingRoom.insideBorder(player.location)) {
player.equipment.replace(Item(CastleWars.zamorakTeamHoodedCloak), 1)
waitingZamorakPlayers.add(player)
} else if (saradominWaitingRoom.insideBorder(player.location)) {
player.equipment.replace(Item(CastleWars.saradominTeamHoodedCloak), 1)
waitingSaradominPlayers.add(player)
}
var transformed = false
if (player.attributes[CastleWars.portalAttribute] == CastleWars.guthixName
&& (hasGodItem(player, God.ZAMORAK) || hasGodItem(player, God.SARADOMIN))) {
// https://www.youtube.com/watch?v=Vg3Om-jZNIY
// Turn the player into a sheep
player.appearance.transformNPC(CastleWars.sheep)
// Send the player a chat dialog
sendDialogue(player, "I pity your faith in my brothers. I shall bless you with some time in the most holy of forms; maybe its wisdom will rub off on you and you'll see the error of your ways.")
transformed = true
}
if (player.attributes[CastleWars.portalAttribute] == CastleWars.zamorakName
&& hasGodItem(player, God.SARADOMIN) || hasGodItem(player, God.GUTHIX))
{
// https://youtu.be/hmF-CU9OEY4?t=63
// Turn the player into an imp!
player.appearance.transformNPC(CastleWars.imp)
// Send the player a chat dialog
sendDialogue(player, "You're wearing objects of my ignorant brothers and you come to me? Such treachery must be rewarded! Enjoy some time in the most mischievous of forms.")
transformed = true
}
if (player.attributes[CastleWars.portalAttribute] == CastleWars.saradominName
&& hasGodItem(player, God.ZAMORAK) || hasGodItem(player, God.GUTHIX))
{
// https://www.youtube.com/watch?v=Vg3Om-jZNIY
// Turn the player into a rabbit!
player.appearance.transformNPC(CastleWars.rabbit)
// Send the player a chat dialog
sendDialogue(player, "You wear objects of my foolish brothers? Perhaps some time spent as the lowliest of forms will help you appreciate the gifts that I can bestow upon my followers.")
transformed = true
}
if (transformed) {
// Remove the tabs
player.interfaceManager.removeTabs(0, 1, 2, 3, 4, 5, 6, 11, 12)
// Disable player run
player.walkingQueue.isRunDisabled = true
}
// Remove the join portal attribute
player.removeAttribute(CastleWars.portalAttribute)
// Open the white text overlay
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_57))
}
override fun exitArea(player: Player) {
super.exitArea(player)
// Remove player from waiting room set (whichever one that is)
waitingSaradominPlayers.remove(player)
waitingZamorakPlayers.remove(player)
}
override fun tick() {
var nextStart = Int.MAX_VALUE
// The game will start when there's enough players and CastleWarsGameArea.ticksLeftInGame = -5 minutes
if (CastleWarsGameArea.ticksLeftInGame >= 0) {
// A game is going on, display 5 minutes + remaining game time
nextStart = CastleWarsGameArea.ticksLeftInGame + CastleWars.gameCooldownMinutes * ticksPerMinute
}
else if (waitingSaradominPlayers.isEmpty() || waitingZamorakPlayers.isEmpty()) {
// A game is not going and there aren't enough players - Keep the game over time to the current value
CastleWarsGameArea.ticksLeftInGame = -1
}
else {
// There are enough players - Set nextStart to gameCooldownMinutes + ticksLeftInGame
nextStart = CastleWars.gameCooldownMinutes * ticksPerMinute + CastleWarsGameArea.ticksLeftInGame
}
for (player in waitingSaradominPlayers + waitingZamorakPlayers) {
CastleWarsOverlay.sendLobbyUpdate(
player,
waitingSaradominPlayers.isNotEmpty() && waitingZamorakPlayers.isNotEmpty() || CastleWarsGameArea.ticksLeftInGame >= 0,
(nextStart - 1) / ticksPerMinute + 1 // Displays max 5 minutes, min 1 minute, seems authentic
)
}
if (nextStart <= 0) {
CastleWarsGameArea.startGame()
}
}
}
+3 -2
View File
@@ -5,6 +5,7 @@ import core.game.world.map.Location
import core.tools.LogLevel
import core.tools.mysql.Database
import core.tools.secondsToTicks
import rs09.game.content.activity.castlewars.CastleWars
import java.math.BigInteger
/**
@@ -224,9 +225,9 @@ class ServerConstants {
arrayOf(Location.create(2328, 3677, 0), "piscatoris"),
arrayOf(Location.create(2660, 3158, 0), "fishing trawler", "trawler"),
arrayOf(Location.create(2800, 3667, 0), "mountain camp"),
arrayOf(Location.create(2575, 3250, 0), "clocktower")
arrayOf(Location.create(2575, 3250, 0), "clocktower"),
arrayOf(CastleWars.lobbyBankArea.randomLoc, "cwars", "castle wars", "castle war", "castlewars", "castlewar", "castle")
)
//arrayOf(Location.create(0, 0, 0), ""),
@JvmField
var DATABASE: Database? = null
+12 -12
View File
@@ -408,33 +408,33 @@ fun rewardXP(player: Player, skill: Int, amount: Double) {
/**
* Replace an object with the given revert timer
* @param toReplace the GameObject instance we are replacing
* @param with the ID of the GameObject we wish to replace toReplace with
* @param for_ticks the number of ticks the object should be replaced for. Use -1 for permanent.
* @param toReplace the Scenery instance we are replacing
* @param with the ID of the Scenery we wish to replace toReplace with
* @param forTicks the number of ticks the object should be replaced for. Use -1 for permanent.
* @param loc the location to move the new object to if necessary. Defaults to null.
*/
fun replaceScenery(toReplace: Scenery, with: Int, for_ticks: Int, loc: Location? = null) {
fun replaceScenery(toReplace: Scenery, with: Int, forTicks: Int, loc: Location? = null) {
val newLoc = when (loc) {
null -> toReplace.location
else -> loc
}
if (for_ticks == -1) {
if (forTicks == -1) {
SceneryBuilder.replace(toReplace, toReplace.transform(with, toReplace.rotation, newLoc))
} else {
SceneryBuilder.replace(toReplace, toReplace.transform(with, toReplace.rotation, newLoc), for_ticks)
SceneryBuilder.replace(toReplace, toReplace.transform(with, toReplace.rotation, newLoc), forTicks)
}
toReplace.isActive = false
}
/**
* Replace an object with the given revert timer with the given rotation
* @param toReplace the GameObject instance we are replacing
* @param with the ID of the GameObject we wish to replace toReplace with
* @param for_ticks the number of ticks the object should be replaced for. Use -1 for permanent.
* @param toReplace the Scenery instance we are replacing
* @param with the ID of the Scenery we wish to replace toReplace with
* @param forTicks the number of ticks the object should be replaced for. Use -1 for permanent.
* @Param rotation the Direction of the rotation it should use. Direction.NORTH, Direction.SOUTH, etc
* @param loc the location to move the new object to if necessary. Defaults to null.
*/
fun replaceScenery(toReplace: Scenery, with: Int, for_ticks: Int, rotation: Direction, loc: Location? = null) {
fun replaceScenery(toReplace: Scenery, with: Int, forTicks: Int, rotation: Direction, loc: Location? = null) {
val newLoc = when (loc) {
null -> toReplace.location
else -> loc
@@ -449,10 +449,10 @@ fun replaceScenery(toReplace: Scenery, with: Int, for_ticks: Int, rotation: Dire
Direction.SOUTH_WEST -> 5
Direction.WEST -> 3
}
if (for_ticks == -1) {
if (forTicks == -1) {
SceneryBuilder.replace(toReplace, toReplace.transform(with, rot, newLoc))
} else {
SceneryBuilder.replace(toReplace, toReplace.transform(with, rot, newLoc), for_ticks)
SceneryBuilder.replace(toReplace, toReplace.transform(with, rot, newLoc), forTicks)
}
toReplace.isActive = false
}
+7
View File
@@ -47,5 +47,12 @@ fun splitLines(message: String, perLineLimit: Int = 54) : Array<String> {
}
pushLine()
// If there's 5 lines, merge lines 4 and 5 into line 4.
if (lines.size > 4){
lines[3] = lines[3] + "<br>" + lines[4]
lines = lines.sliceArray(0..3)
}
return lines
}
+126 -7
View File
@@ -1,10 +1,129 @@
package core.api
import org.rs09.consts.Items
enum class God(vararg val validItems: Int) {
ARMADYL(87, 11694, 11718, 11720, 11722, 12670, 12671, 14671),
BANDOS(11061, 11696, 11724, 11726, 11728),
SARADOMIN(1718, 2412, 2415, 2661, 2663, 2665, 2667, 3479, 3675, 3489, 3840, 4682, 6762, 8055, 10384, 10386, 10388, 10390, 10440, 10446, 10452, 10458, 10464, 10470, 11181, 11698, 11730,542,544),
ZAMORAK(11716, 11700, 1724, 2414, 2417, 2653, 2655, 2657, 2659, 3478, 3674, 3841, 3842, 3852, 4683, 6764, 8056, 10368, 10370, 10372, 10374, 10444, 10450, 10456, 10460, 10468, 10474, 10776, 10786, 10790, 14662),
GUTHIX,
ZAROS
}
ARMADYL(
Items.ARMADYL_CHESTPLATE_11720,
Items.ARMADYL_GODSWORD_11694,
Items.ARMADYL_HELMET_11718,
Items.ARMADYL_HELMET_CHARGED_12671,
Items.ARMADYL_HELMET_E_12670,
Items.ARMADYL_PENDANT_87,
Items.ARMADYL_PLATESKIRT_11722,
),
BANDOS(
Items.ANCIENT_MACE_11061,
Items.BANDOS_BOOTS_11728,
Items.BANDOS_CHESTPLATE_11724,
Items.BANDOS_GODSWORD_11696,
Items.BANDOS_TASSETS_11726,
),
SARADOMIN(
Items.ANCIENT_SYMBOL_11181,
Items.GILDED_KITESHIELD_3489,
Items.HOLY_BOOK_3840,
Items.HOLY_SYMBOL_1718,
Items.HOLY_SYMBOL_4682,
Items.MONKS_ROBE_542,
Items.MONKS_ROBE_544,
Items.RUNE_HERALDIC_HELM_8488,
Items.SARADOMIN_BRACERS_10384,
Items.SARADOMIN_CAPE_2412,
Items.SARADOMIN_CHAPS_10388,
Items.SARADOMIN_CLOAK_10446,
Items.SARADOMIN_COIF_10390,
Items.SARADOMIN_CROZIER_10440,
Items.SARADOMIN_DHIDE_10386,
Items.SARADOMIN_FULL_HELM_2665,
Items.SARADOMIN_GODSWORD_11698,
Items.SARADOMIN_KITESHIELD_2667,
Items.SARADOMIN_MITRE_10452,
Items.SARADOMIN_MJOLNIR_6762,
Items.SARADOMIN_PLATEBODY_2661,
Items.SARADOMIN_PLATELEGS_2663,
Items.SARADOMIN_PLATESKIRT_3479,
Items.SARADOMIN_PLATESKIRT_3675,
Items.SARADOMIN_ROBE_LEGS_10464,
Items.SARADOMIN_ROBE_TOP_10458,
Items.SARADOMIN_STAFF_2415,
Items.SARADOMIN_STOLE_10470,
Items.SARADOMIN_SWORD_11730,
Items.SARADOMIN_SYMBOL_8055,
Items.STEEL_HERALDIC_HELM_8706,
),
ZAMORAK(
Items.DAGONHAI_HAT_14499,
Items.DAGONHAI_ROBE_TOP_14497,
Items.DAGONHAI_ROBE_BOTTOM_14501,
Items.DAMAGED_BOOK_3841,
Items.RUNE_HERALDIC_HELM_8494,
Items.STEEL_HERALDIC_HELM_8712,
Items.UNHOLY_BOOK_3842,
Items.UNHOLY_SYMBOL_1724,
Items.UNHOLY_SYMBOL_3852,
Items.UNHOLY_SYMBOL_4683,
Items.ZAMORAK_BRACERS_10368,
Items.ZAMORAK_CAPE_2414,
Items.ZAMORAK_CHAPS_10372,
Items.ZAMORAK_CLOAK_10450,
Items.ZAMORAK_COIF_10374,
Items.ZAMORAK_CROZIER_10444,
Items.ZAMORAK_DHIDE_10370,
Items.ZAMORAK_DHIDE_10790,
Items.ZAMORAK_FULL_HELM_2657,
Items.ZAMORAK_GODSWORD_11700,
Items.ZAMORAKIAN_SPEAR_11716,
Items.ZAMORAK_KITESHIELD_2659,
Items.ZAMORAK_MITRE_10456,
Items.ZAMORAK_MJOLNIR_6764,
Items.ZAMORAK_PLATEBODY_10776,
Items.ZAMORAK_PLATEBODY_2653,
Items.ZAMORAK_PLATELEGS_2655,
Items.ZAMORAK_PLATESKIRT_3478,
Items.ZAMORAK_PLATESKIRT_3674,
Items.ZAMORAK_ROBE_LEGS_10468,
Items.ZAMORAK_ROBE_TOP_10460,
Items.ZAMORAK_ROBE_TOP_10786,
Items.ZAMORAK_STAFF_2417,
Items.ZAMORAK_STOLE_10474,
Items.ZAMORAK_SYMBOL_8056,
),
GUTHIX(
Items.STEEL_HERALDIC_HELM_8692,
Items.RUNE_HERALDIC_HELM_8474,
Items.GUTHIX_BRACERS_10376,
Items.GUTHIX_CAPE_10720,
Items.GUTHIX_CHAPS_10380,
Items.GUTHIX_CLOAK_10448,
Items.GUTHIX_COIF_10382,
Items.GUTHIX_CROZIER_10442,
Items.GUTHIX_DRAGONHIDE_10378,
Items.GUTHIX_DRAGONHIDE_10794,
Items.GUTHIX_FULL_HELM_13833,
Items.GUTHIX_FULL_HELM_2673,
Items.GUTHIX_KITESHIELD_13834,
Items.GUTHIX_KITESHIELD_2675,
Items.GUTHIX_MITRE_10454,
Items.GUTHIX_MJOLNIR_6760,
Items.GUTHIX_PLATEBODY_10780,
Items.GUTHIX_PLATEBODY_2669,
Items.GUTHIX_PLATEBODY_13830,
Items.GUTHIX_PLATELEGS_2671,
Items.GUTHIX_PLATELEGS_13831,
Items.GUTHIX_PLATESKIRT_13832,
Items.GUTHIX_PLATESKIRT_3676,
Items.GUTHIX_ROBE_LEGS_10466,
Items.GUTHIX_ROBE_TOP_10462,
Items.GUTHIX_ROBE_TOP_10788,
Items.GUTHIX_STAFF_2416,
Items.GUTHIX_STOLE_10472,
Items.GUTHIX_SYMBOL_8057
),
ZAROS(
Items.ANCIENT_STAFF_13406,
Items.ANCIENT_STAFF_4675,
Items.ANCIENT_MACE_11061,
Items.ANCIENT_BOOK_7633,
)
}
+29
View File
@@ -882,6 +882,35 @@ public class ItemDefinition extends Definition<Item> {
return value;
}
/**
* Gets whether the Item has a value in a custom currency
* @param currency the configuration string of the currency in the item definition
* @return {@code True} if so.
*/
public boolean hasShopCurrencyValue(String currency) {
return getHandlers().getOrDefault(currency, "0") != "0";
}
/**
* Gets whether shops value the Item (even if the value is 0) in a currency
* @param currency the ID of the currency
* @return {@code True} if so.
*/
public boolean hasShopCurrencyValue(int currency) {
switch (currency) {
case Items.COINS_995:
return isTradeable();
case Items.TOKKUL_6529:
return hasShopCurrencyValue(ItemConfigParser.TOKKUL_PRICE);
case Items.ARCHERY_TICKET_1464:
return hasShopCurrencyValue(ItemConfigParser.ARCHERY_TICKET_PRICE);
case Items.CASTLE_WARS_TICKET_4067:
return hasShopCurrencyValue(ItemConfigParser.CASTLE_WARS_TICKET_PRICE);
default:
return false;
}
}
/**
* @return The value.
*/
@@ -372,6 +372,50 @@ public class Container {
return true;
}
/**
* Removes all items from the container that have the passed in IDs
*
* @param ids Array of IDs to remove
* @return {@code True} if all the items got removed, <br> {@code false} if some of the
* found items were null
*/
public boolean removeAll(int[] ids) {
boolean removedAll = true;
for (int id : ids) {
if (!removeAll(id)) {
removedAll = false;
}
}
update();
return removedAll;
}
/**
* Removes all items from the container that have the passed in ID
*
* @param id Item ID to remove all instances of
* @return {@code True} if all the items got removed, <br> {@code false} if some of the
* found items were null
*/
public boolean removeAll(int id) {
ArrayList<Item> matchingIdItems = new ArrayList<>();
for (Item item : this.items) {
// If the item is not null and the item ID matches the ID we're looking for
if (item != null && item.getId() == id) {
// Add the item to the list
matchingIdItems.add(item);
}
}
boolean res = true;
// Remove all the items from the container
for (Item item : matchingIdItems) {
if (!remove(item, false)) {
res = false;
}
}
return res;
}
/**
* Replaces the item on the given slot with the argued item.
*
@@ -463,6 +463,7 @@ public final class DialogueInterpreter {
*/
public Component sendDialogues(int npcId, int expression, String... messages) {
if (messages.length < 1 || messages.length > 4) {
System.err.println("Invalid amount of messages: " + messages.length);
return null;
}
boolean npc = npcId > -1;
@@ -341,6 +341,7 @@ public final class WalkingQueue {
* {@code false} if not.
*/
public boolean isRunningBoth() {
if (isRunDisabled()) return false;
if (entity instanceof Player && ((Player) entity).getSettings().isRunToggled()) {
return true;
}
+7 -4
View File
@@ -17,6 +17,7 @@ import org.rs09.consts.Components
import org.rs09.consts.Items
import core.ServerConstants
import core.game.shops.Shops.Companion.logShop
import core.game.system.config.ItemConfigParser
import core.game.world.GameWorld
import java.lang.Integer.max
import java.lang.Integer.min
@@ -159,8 +160,9 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
val item = cont[slot]
val price = when(currency)
{
Items.TOKKUL_6529 -> item.definition.getConfiguration("tokkul_price", 1)
Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration("archery_ticket_price", 1)
Items.TOKKUL_6529 -> item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1)
Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, 1)
Items.CASTLE_WARS_TICKET_4067 -> item.definition.getConfiguration(ItemConfigParser.CASTLE_WARS_TICKET_PRICE, 1)
else -> getGPCost(Item(item.id, 1), if (isMainStock) stock[item.slot].amount else playerStock[slot].amount, if (isMainStock) item.amount else playerStock[slot].amount)
}
@@ -199,8 +201,9 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
val price = when(currency)
{
Items.TOKKUL_6529 -> (item.definition.getConfiguration("tokkul_price", 1) / 10.0).toInt() // selling items authentically return 10x less tokkul (floored/truncated) than the item's shop price
Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration("archery_ticket_price", 1)
Items.TOKKUL_6529 -> (item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1) / 10.0).toInt() // selling items authentically return 10x less tokkul (floored/truncated) than the item's shop price
Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, 1)
Items.CASTLE_WARS_TICKET_4067 -> item.definition.getConfiguration(ItemConfigParser.CASTLE_WARS_TICKET_PRICE, 1)
else -> getGPSell(Item(shopItemId, 1), stockAmt, currentAmt)
}
+22 -17
View File
@@ -52,20 +52,25 @@ class Shops : StartupListener, TickListener, InteractionListener, InterfaceListe
return items
}
stock.split('-').map {
val tokens = it.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray()
var amount = tokens[1].trim()
if(amount == "inf")
amount = "-1"
val item = tokens[0].toInt()
if(idsInStock[item] != null) {
log(this::class.java, Log.WARN, "[SHOPS] MALFORMED STOCK IN SHOP ID $id FOR ITEM $item")
items.forEach { if(it.itemId == item) {
it.amount += amount.toInt()
return@map
}}
} else {
items.add(ShopItem(item, amount.toInt(), tokens.getOrNull(2)?.toIntOrNull() ?: 100))
idsInStock[item] = true
try {
val tokens = it.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray()
var amount = tokens[1].trim()
if(amount == "inf")
amount = "-1"
val item = tokens[0].toInt()
if(idsInStock[item] != null) {
log(this::class.java, Log.WARN, "[SHOPS] MALFORMED STOCK IN SHOP ID $id FOR ITEM $item")
items.forEach { if(it.itemId == item) {
it.amount += amount.toInt()
return@map
}}
} else {
items.add(ShopItem(item, amount.toInt(), tokens.getOrNull(2)?.toIntOrNull() ?: 100))
idsInStock[item] = true
}
} catch (e: Exception) {
log(this::class.java, Log.WARN, "[SHOPS] MALFORMED STOCK IN SHOP ID $id FOR ITEM $it")
throw e
}
}
return items
@@ -215,9 +220,9 @@ class Shops : StartupListener, TickListener, InteractionListener, InterfaceListe
val valueMsg = when {
(price.amount == -1)
|| !def.isTradeable
|| def.id in intArrayOf(Items.COINS_995, Items.TOKKUL_6529, Items.ARCHERY_TICKET_1464)
|| def.hasDestroyAction() -> "This shop will not buy that item."
|| !def.hasShopCurrencyValue(price.id)
|| def.id in intArrayOf(Items.COINS_995, Items.TOKKUL_6529, Items.ARCHERY_TICKET_1464, Items.CASTLE_WARS_TICKET_4067)
-> "This shop will not buy that item."
else -> "${player.inventory[slot].name}: This shop will buy this item for ${price.amount} ${price.name.lowercase()}."
}
@@ -7,8 +7,10 @@ import core.plugin.Initializable
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import core.ServerConstants
import core.api.log
import core.game.system.command.Privilege
import core.game.world.repository.Repository
import core.tools.Log
@Initializable
class TeleportCommandSet : CommandSet(Privilege.ADMIN){
@@ -217,6 +217,11 @@ class ItemConfigParser {
*/
const val ARCHERY_TICKET_PRICE = "archery_ticket_price"
/**
* The Castle Wars Ticket price of items (in Lanthas' shop).
*/
const val CASTLE_WARS_TICKET_PRICE = "castle_wars_ticket_price"
}
val parser = JSONParser()
@@ -270,8 +275,9 @@ class ItemConfigParser {
//ints
"equip_audio",
"point_price",
"tokkul_price",
"archery_ticket_price",
TOKKUL_PRICE,
ARCHERY_TICKET_PRICE,
CASTLE_WARS_TICKET_PRICE,
"ge_buy_limit",
"weapon_interface",
"attack_speed",
@@ -92,6 +92,7 @@ object ServerConfigParser {
jad_practice_enabled = data.getBoolean("world.jad_practice_enabled"),
ge_announcement_limit = data.getLong("world.ge_announcement_limit", 500L).toInt(),
smartpathfinder_bfs = data.getBoolean("world.smartpathfinder_bfs", false),
enable_castle_wars = data.getBoolean("world.enable_castle_wars", false),
message_model = data.getString("world.motw_identifier").toInt(),
message_string = data.getString("world.motw_text").replace("@name", ServerConstants.SERVER_NAME)
)
@@ -84,6 +84,7 @@ class GameSettings
var jad_practice_enabled: Boolean,
var ge_announcement_limit: Int,
var smartpathfinder_bfs: Boolean,
var enable_castle_wars: Boolean,
/**"Lobby" interface
* The message of the week models to display
@@ -140,6 +141,7 @@ class GameSettings
val jad_practice_enabled = if(data.containsKey("jad_practice_enabled")) data["jad_practice_enabled"] as Boolean else true
val ge_announcement_limit = data["ge_announcement_limit"].toString().toInt()
val smartpathfinder_bfs = if(data.containsKey("smartpathfinder_bfs")) data["smartpathfinder_bfs"] as Boolean else false
val enable_castle_wars = if(data.containsKey("enable_castle_wars")) data["enable_castle_wars"] as Boolean else false
val allow_token_purchase = data["allow_token_purchase"] as Boolean
val message_of_the_week_identifier = data["message_of_the_week_identifier"].toString().toInt()
val message_of_the_week_text = data["message_of_the_week_text"].toString()
@@ -171,6 +173,7 @@ class GameSettings
jad_practice_enabled,
ge_announcement_limit,
smartpathfinder_bfs,
enable_castle_wars,
message_of_the_week_identifier,
message_of_the_week_text
)
@@ -2,6 +2,7 @@ package core.game.world.map.zone;
import core.game.node.Node;
import core.game.world.map.Location;
import core.game.world.map.RegionManager;
import core.tools.RandomFunction;
import java.util.ArrayList;
@@ -97,6 +98,10 @@ public final class ZoneBorders {
this.zeroPlaneCheck = zeroPlaneCheck;
}
public ZoneBorders(Location l1, Location l2) {
this(l1.getX(), l1.getY(), l2.getX(), l2.getY(), l1.getZ());
}
/**
* Creates zone borders for the given region id.
* @param regionId The region id.
@@ -234,6 +239,16 @@ public final class ZoneBorders {
return new Location(x, y, plane);
}
public Location getRandomWalkableLoc() {
Location loc = getRandomLoc();
int tries = 0; // prevent bad code from DOSing server
while (!RegionManager.isTeleportPermitted(loc) && tries < 20) {
loc = getRandomLoc();
tries += 1;
}
return loc;
}
/**
* Adds an exception.
* @param exception The exception to add.
+14 -1
View File
@@ -11,4 +11,17 @@ fun secondsToTicks(seconds: Int): Int {
fun ticksToSeconds(ticks: Int): Int {
val ticksMs = ticks * tick
return ticksMs / 1000
}
}
fun minutesToTicks(minutes: Int): Int {
val minutesMs = minutes * 60 * 1000
return minutesMs / tick
}
fun ticksToMinutes(ticks: Int): Int {
val ticksMs = ticks * tick
return ticksMs / 1000 / 60
}
const val ticksPerSecond = second / tick
const val ticksPerMinute = 60 * ticksPerSecond
+1
View File
@@ -49,6 +49,7 @@ max_adv_bots = 100
enable_doubling_money_scammers = true
wild_pvp_enabled = false
jad_practice_enabled = false
enable_castle_wars = false
personalized_shops = false
[paths]
+2 -1
View File
@@ -71,6 +71,7 @@ wild_pvp_enabled = false
jad_practice_enabled = true
#minimum HA value for announcements of bots selling on ge
ge_announcement_limit = 500
enable_castle_wars = false
personalized_shops = true
bots_influence_ge_price = true
#verbose cutscene logging (for cutscenes in the new system)
@@ -111,4 +112,4 @@ object_parser_path = "@data/ObjectParser.xml"
#path logs are written to
logs_path = "@data/logs"
bot_data = "@data/botdata"
eco_data = "@data/eco"
eco_data = "@data/eco"