From 034cc75a61246a6e9526a162e0bad47e1365a686 Mon Sep 17 00:00:00 2001 From: Dan Ginovker Date: Mon, 10 Apr 2023 07:13:54 +0000 Subject: [PATCH] Started implementing Castle Wars (not ready to enable yet) --- Server/data/configs/door_configs.json | 6 +- Server/data/configs/ground_spawns.json | 4 +- Server/data/configs/item_configs.json | 57 ++++-- Server/data/configs/npc_spawns.json | 4 + Server/data/configs/object_configs.json | 38 +++- Server/data/configs/shops.json | 9 + .../content/minigame/castlewars/CastleWars.kt | 81 ++++++++ .../castlewars/CastleWarsListeners.kt | 190 ++++++++++++++++++ .../minigame/castlewars/CastleWarsOverlay.kt | 22 ++ .../content/minigame/castlewars/Lanthus.kt | 131 ++++++++++++ .../castlewars/areas/CastleWarsArea.kt | 81 ++++++++ .../castlewars/areas/CastleWarsGameArea.kt | 101 ++++++++++ .../castlewars/areas/CastleWarsRespawnArea.kt | 40 ++++ .../castlewars/areas/CastleWarsWaitingArea.kt | 133 ++++++++++++ Server/src/main/core/ServerConstants.kt | 5 +- Server/src/main/core/api/ContentAPI.kt | 24 +-- Server/src/main/core/api/DialUtils.kt | 7 + Server/src/main/core/api/God.kt | 133 +++++++++++- .../core/cache/def/impl/ItemDefinition.java | 29 +++ .../main/core/game/container/Container.java | 44 ++++ .../game/dialogue/DialogueInterpreter.java | 1 + .../game/node/entity/impl/WalkingQueue.java | 1 + Server/src/main/core/game/shops/Shop.kt | 11 +- Server/src/main/core/game/shops/Shops.kt | 39 ++-- .../system/command/sets/TeleportCommandSet.kt | 2 + .../game/system/config/ItemConfigParser.kt | 10 +- .../game/system/config/ServerConfigParser.kt | 1 + .../src/main/core/game/world/GameSettings.kt | 3 + .../core/game/world/map/zone/ZoneBorders.java | 15 ++ Server/src/main/core/tools/TickUtils.kt | 15 +- Server/src/test/resources/test.conf | 1 + Server/worldprops/default.conf | 3 +- 32 files changed, 1166 insertions(+), 75 deletions(-) create mode 100644 Server/src/main/content/minigame/castlewars/CastleWars.kt create mode 100644 Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt create mode 100644 Server/src/main/content/minigame/castlewars/CastleWarsOverlay.kt create mode 100644 Server/src/main/content/minigame/castlewars/Lanthus.kt create mode 100644 Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt create mode 100644 Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt create mode 100644 Server/src/main/content/minigame/castlewars/areas/CastleWarsRespawnArea.kt create mode 100644 Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt diff --git a/Server/data/configs/door_configs.json b/Server/data/configs/door_configs.json index 21568f895..37011ac53 100644 --- a/Server/data/configs/door_configs.json +++ b/Server/data/configs/door_configs.json @@ -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" }, diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index 2e765ce2e..e3f393613 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -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", diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index c1bff50f6..eaa64f0bf 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -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", diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index 94107d9b3..95a7b32de 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -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}" diff --git a/Server/data/configs/object_configs.json b/Server/data/configs/object_configs.json index 354bfc6de..4ee1d9a18 100644 --- a/Server/data/configs/object_configs.json +++ b/Server/data/configs/object_configs.json @@ -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" } ] \ No newline at end of file diff --git a/Server/data/configs/shops.json b/Server/data/configs/shops.json index 94190d0e2..29c819583 100644 --- a/Server/data/configs/shops.json +++ b/Server/data/configs/shops.json @@ -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}" } ] \ No newline at end of file diff --git a/Server/src/main/content/minigame/castlewars/CastleWars.kt b/Server/src/main/content/minigame/castlewars/CastleWars.kt new file mode 100644 index 000000000..670ea453c --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/CastleWars.kt @@ -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 + +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt new file mode 100644 index 000000000..64d3bb4a0 --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt @@ -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 } + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsOverlay.kt b/Server/src/main/content/minigame/castlewars/CastleWarsOverlay.kt new file mode 100644 index 000000000..3cd3ab891 --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/CastleWarsOverlay.kt @@ -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 + } +} diff --git a/Server/src/main/content/minigame/castlewars/Lanthus.kt b/Server/src/main/content/minigame/castlewars/Lanthus.kt new file mode 100644 index 000000000..301e178ad --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/Lanthus.kt @@ -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? + + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt new file mode 100644 index 000000000..1932de737 --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt @@ -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 + } + } + +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt new file mode 100644 index 000000000..0999555d9 --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt @@ -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() + val zamorakPlayers = mutableSetOf() + + 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 { + 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() + } + } + +} diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsRespawnArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsRespawnArea.kt new file mode 100644 index 000000000..671564342 --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsRespawnArea.kt @@ -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() + val saradominPlayersInRespawnRoom = mutableSetOf() + } + + override fun defineAreaBorders(): Array { + 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() { + } + +} diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt new file mode 100644 index 000000000..cd7f3334a --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt @@ -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() + val waitingZamorakPlayers = mutableSetOf() + } + + override fun defineAreaBorders(): Array { + 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() + } + } + +} diff --git a/Server/src/main/core/ServerConstants.kt b/Server/src/main/core/ServerConstants.kt index affb329dc..5998ed181 100644 --- a/Server/src/main/core/ServerConstants.kt +++ b/Server/src/main/core/ServerConstants.kt @@ -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 diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 0bb6f8f29..3155234ea 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -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 } diff --git a/Server/src/main/core/api/DialUtils.kt b/Server/src/main/core/api/DialUtils.kt index 87e30b754..cc59185be 100644 --- a/Server/src/main/core/api/DialUtils.kt +++ b/Server/src/main/core/api/DialUtils.kt @@ -47,5 +47,12 @@ fun splitLines(message: String, perLineLimit: Int = 54) : Array { } pushLine() + + // If there's 5 lines, merge lines 4 and 5 into line 4. + if (lines.size > 4){ + lines[3] = lines[3] + "
" + lines[4] + lines = lines.sliceArray(0..3) + } + return lines } \ No newline at end of file diff --git a/Server/src/main/core/api/God.kt b/Server/src/main/core/api/God.kt index 72815bc97..58c880a97 100644 --- a/Server/src/main/core/api/God.kt +++ b/Server/src/main/core/api/God.kt @@ -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 -} \ No newline at end of file + 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, + ) +} diff --git a/Server/src/main/core/cache/def/impl/ItemDefinition.java b/Server/src/main/core/cache/def/impl/ItemDefinition.java index 2bba67cab..717511646 100644 --- a/Server/src/main/core/cache/def/impl/ItemDefinition.java +++ b/Server/src/main/core/cache/def/impl/ItemDefinition.java @@ -882,6 +882,35 @@ public class ItemDefinition extends Definition { 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. */ diff --git a/Server/src/main/core/game/container/Container.java b/Server/src/main/core/game/container/Container.java index 4265e0d76..bc7c099c6 100644 --- a/Server/src/main/core/game/container/Container.java +++ b/Server/src/main/core/game/container/Container.java @@ -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,
{@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,
{@code false} if some of the + * found items were null + */ + public boolean removeAll(int id) { + ArrayList 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. * diff --git a/Server/src/main/core/game/dialogue/DialogueInterpreter.java b/Server/src/main/core/game/dialogue/DialogueInterpreter.java index a78a4e93d..4974a39ad 100644 --- a/Server/src/main/core/game/dialogue/DialogueInterpreter.java +++ b/Server/src/main/core/game/dialogue/DialogueInterpreter.java @@ -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; diff --git a/Server/src/main/core/game/node/entity/impl/WalkingQueue.java b/Server/src/main/core/game/node/entity/impl/WalkingQueue.java index fdbd827da..048d3b632 100644 --- a/Server/src/main/core/game/node/entity/impl/WalkingQueue.java +++ b/Server/src/main/core/game/node/entity/impl/WalkingQueue.java @@ -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; } diff --git a/Server/src/main/core/game/shops/Shop.kt b/Server/src/main/core/game/shops/Shop.kt index f5eb7f710..ef755b058 100644 --- a/Server/src/main/core/game/shops/Shop.kt +++ b/Server/src/main/core/game/shops/Shop.kt @@ -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, 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, 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) } diff --git a/Server/src/main/core/game/shops/Shops.kt b/Server/src/main/core/game/shops/Shops.kt index 88d5190ed..6114aeaa6 100644 --- a/Server/src/main/core/game/shops/Shops.kt +++ b/Server/src/main/core/game/shops/Shops.kt @@ -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()}." } diff --git a/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt b/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt index b9468e139..2cbc404e2 100644 --- a/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt @@ -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){ diff --git a/Server/src/main/core/game/system/config/ItemConfigParser.kt b/Server/src/main/core/game/system/config/ItemConfigParser.kt index f3718d031..a6bbb7430 100644 --- a/Server/src/main/core/game/system/config/ItemConfigParser.kt +++ b/Server/src/main/core/game/system/config/ItemConfigParser.kt @@ -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", diff --git a/Server/src/main/core/game/system/config/ServerConfigParser.kt b/Server/src/main/core/game/system/config/ServerConfigParser.kt index d0cf93b3d..d72eaec4e 100644 --- a/Server/src/main/core/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/core/game/system/config/ServerConfigParser.kt @@ -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) ) diff --git a/Server/src/main/core/game/world/GameSettings.kt b/Server/src/main/core/game/world/GameSettings.kt index 35560b540..c75a55efd 100644 --- a/Server/src/main/core/game/world/GameSettings.kt +++ b/Server/src/main/core/game/world/GameSettings.kt @@ -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 ) diff --git a/Server/src/main/core/game/world/map/zone/ZoneBorders.java b/Server/src/main/core/game/world/map/zone/ZoneBorders.java index f371fae2d..b43fa5d5f 100644 --- a/Server/src/main/core/game/world/map/zone/ZoneBorders.java +++ b/Server/src/main/core/game/world/map/zone/ZoneBorders.java @@ -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. diff --git a/Server/src/main/core/tools/TickUtils.kt b/Server/src/main/core/tools/TickUtils.kt index 3d449d4ab..a9c0d0755 100644 --- a/Server/src/main/core/tools/TickUtils.kt +++ b/Server/src/main/core/tools/TickUtils.kt @@ -11,4 +11,17 @@ fun secondsToTicks(seconds: Int): Int { fun ticksToSeconds(ticks: Int): Int { val ticksMs = ticks * tick return ticksMs / 1000 -} \ No newline at end of file +} + +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 \ No newline at end of file diff --git a/Server/src/test/resources/test.conf b/Server/src/test/resources/test.conf index 0fa196892..d4d78b39e 100644 --- a/Server/src/test/resources/test.conf +++ b/Server/src/test/resources/test.conf @@ -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] diff --git a/Server/worldprops/default.conf b/Server/worldprops/default.conf index 523504064..7ae65e2d8 100644 --- a/Server/worldprops/default.conf +++ b/Server/worldprops/default.conf @@ -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" \ No newline at end of file +eco_data = "@data/eco"