Merge branch 2009scape:master into master

This commit is contained in:
Tooze 2026-07-26 17:33:06 +00:00
commit aff4b7bfae
113 changed files with 2116 additions and 1334 deletions

View file

@ -29,6 +29,7 @@
* [Prerequisites](#prerequisites)
* [Project Setup](#project-setup)
* [Running the project](#running-the-project)
* [Browser WebSocket Transport](#browser-websocket-transport)
* [License](#license)
* [Contact](#contact)
@ -125,6 +126,40 @@ Start the game server with the included run script. Use `./run -h` for more info
Start the game server with `run-server.bat`
#### Browser WebSocket Transport
Enable the listener in `Server/worldprops/default.conf`:
```properties
websocket_enabled = true
websocket_port = 0
```
When `websocket_port = 0`, the listener uses `53594 + world_id`. For world `1`, that is `53595`.
Both plain WebSocket (`ws://`) and secure WebSocket (`wss://`) are supported. Use `ws://` for local HTTP testing. PWA requires HTTPS/WSS.
Enable WSS with a Java keystore:
```properties
websocket_tls_enabled = true
websocket_tls_keystore_path = "certs/dev-wss.p12"
websocket_tls_keystore_password = "<keystore-password>"
```
The keystore password is optional. Leave `websocket_tls_keystore_password` blank when using a PKCS12 file exported with an empty password:
```bash
openssl pkcs12 -export \
-in fullchain.pem \
-inkey privkey.pem \
-out certs/dev-wss.p12 \
-name websocket \
-passout pass:
```
For local WSS development, create a certificate for your hostname or LAN IP that the browser will use, then point the server at a PKCS12 keystore. For production, use a normal certificate from certbot. Plain WS does not require a certificate.
#### Docker
Make sure [Docker Engine](https://docs.docker.com/engine/install/) & [Docker Compose](https://docs.docker.com/compose/install/) plugin are installed first:

View file

@ -8353,6 +8353,20 @@
"description": "",
"main": []
},
{
"default": [
{
"minAmount": "1",
"weight": "100.0",
"id": "526",
"maxAmount": "1"
}
],
"charm": [],
"ids": "1329",
"description": "",
"main": []
},
{
"default": [
{
@ -19684,7 +19698,14 @@
]
},
{
"default": [],
"default": [
{
"minAmount": "1",
"weight": "100.0",
"id": "526",
"maxAmount": "1"
}
],
"charm": [],
"ids": "1307",
"description": "",
@ -29810,10 +29831,10 @@
"maxAmount": "1"
},
{
"minAmount": "1",
"minAmount": "2",
"weight": "100.0",
"id": "12161",
"maxAmount": "1"
"maxAmount": "2"
},
{
"minAmount": "1",
@ -41175,7 +41196,7 @@
{
"minAmount": "1",
"weight": "5.0",
"id": "1",
"id": "12070",
"maxAmount": "1"
},
{

View file

@ -415,6 +415,10 @@
"item_id": "1755",
"loc_data": "{1,2935,3286,0,90}-"
},
{
"item_id": "1773",
"loc_data": "{1,2563,3261,0,100}-"
},
{
"item_id": "1785",
"loc_data": "{1,2822,3355,0,150}-"

View file

@ -539,6 +539,12 @@
"walkable": "true",
"tabIndex": "-1"
},
{
"id": "375",
"interfaceType": "3",
"walkable": "false",
"tabIndex": "-1"
},
{
"id": "377",
"interfaceType": "8",

View file

@ -35942,6 +35942,7 @@
"id": "4158",
"name": "Leaf-bladed spear",
"examine": "A spear with a leaf-shaped point.",
"alchemizable": "true",
"archery_ticket_price": "0",
"attack_anims": "2080,2081,2082,2080",
"attack_speed": "5",
@ -77818,6 +77819,16 @@
"durability": null,
"weight": "1"
},
{
"id": "9721",
"name": "Scroll",
"examine": "A scroll with some writing on it."
},
{
"id": "9722",
"name": "Key",
"examine": "Quite a small key."
},
{
"id": "9723",
"name": "Pipe",
@ -99252,7 +99263,7 @@
{
"id": "12184",
"name": "Gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "How cute!",
"archery_ticket_price": "0",
"durability": null
},
@ -100617,42 +100628,42 @@
{
"id": "12486",
"name": "Baby raccoon",
"examine": "(Baby) A stripy little baby raccoon.(Adult) He can run with us.",
"examine": "A stripy little baby raccoon.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12487",
"name": "Raccoon",
"examine": "(Baby) A stripy little baby raccoon.(Adult) He can run with us.",
"examine": "He can run with us.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12488",
"name": "Baby gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "It's so adorably tiny!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12489",
"name": "Gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "How cute!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12490",
"name": "Baby squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "A tiny nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12491",
"name": "Squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "An experienced nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
@ -100689,10 +100700,15 @@
{
"id": "12496",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12497",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12498",
"name": "Vulture chick",
@ -102382,66 +102398,111 @@
{
"id": "12682",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12683",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12684",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12685",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12686",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12687",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12688",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12689",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12690",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12691",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12692",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12693",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12694",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12695",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12696",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12697",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12698",
"name": "Baby monkey",
"examine": "(Baby) It's a tiny, cheeky monkey. (Adult) Hide the bananas!",
"examine": "It's a tiny, cheeky monkey.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12699",
"name": "Monkey",
"examine": "Hide the bananas!"
},
{
"id": "12700",
"name": "Terrier puppy",
@ -102707,84 +102768,84 @@
{
"id": "12734",
"name": "Baby raccoon",
"examine": "(Baby) A stripy little baby raccoon.(Adult) He can run with us.",
"examine": "A stripy little baby raccoon.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12735",
"name": "Raccoon",
"examine": "(Baby) A stripy little baby raccoon.(Adult) He can run with us.",
"examine": "He can run with us.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12736",
"name": "Baby raccoon",
"examine": "(Baby) A stripy little baby raccoon.(Adult) He can run with us.",
"examine": "A stripy little baby raccoon.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12737",
"name": "Raccoon",
"examine": "(Baby) A stripy little baby raccoon.(Adult) He can run with us.",
"examine": "He can run with us.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12738",
"name": "Baby gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "It's so adorably tiny!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12739",
"name": "Baby gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "It's so adorably tiny!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12740",
"name": "Baby gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "It's so adorably tiny!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12741",
"name": "Baby gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "It's so adorably tiny!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12742",
"name": "Gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "How cute!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12743",
"name": "Gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "How cute!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12744",
"name": "Gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "How cute!",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12745",
"name": "Gecko",
"examine": "(Baby) It's so adorably tiny! (Adult) How cute!",
"examine": "How cute!",
"archery_ticket_price": "0",
"durability": null
},
@ -102855,56 +102916,56 @@
{
"id": "12754",
"name": "Baby squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "A tiny nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12755",
"name": "Squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "An experienced nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12756",
"name": "Baby squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "A tiny nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12757",
"name": "Squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "An experienced nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12758",
"name": "Baby squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "A tiny nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12759",
"name": "Squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "An experienced nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12760",
"name": "Baby squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "A tiny nut-thief.",
"archery_ticket_price": "0",
"durability": null
},
{
"id": "12761",
"name": "Squirrel",
"examine": "Adult: An experienced nut-thief.Baby: A tiny nut-thief.",
"examine": "An experienced nut-thief.",
"archery_ticket_price": "0",
"durability": null
},

View file

@ -25592,7 +25592,7 @@
"death_animation": "6003",
"defence_animation": "6008",
"defence_level": "5",
"lifepoints": "7",
"lifepoints": "10",
"magic_animation": "0",
"melee_animation": "6007",
"range_animation": "0",
@ -25610,7 +25610,7 @@
"death_animation": "6003",
"defence_animation": "6008",
"defence_level": "5",
"lifepoints": "7",
"lifepoints": "10",
"magic_animation": "0",
"melee_animation": "6007",
"range_animation": "0",
@ -25628,7 +25628,7 @@
"death_animation": "6003",
"defence_animation": "6008",
"defence_level": "5",
"lifepoints": "7",
"lifepoints": "10",
"magic_animation": "0",
"melee_animation": "6007",
"range_animation": "0",
@ -25646,7 +25646,7 @@
"death_animation": "6003",
"defence_animation": "6008",
"defence_level": "5",
"lifepoints": "7",
"lifepoints": "10",
"magic_animation": "0",
"melee_animation": "6007",
"range_animation": "0",
@ -25697,61 +25697,61 @@
"id": "2075",
"name": "Cave goblin miner",
"examine": "He's working hard!",
"attack_level": "1",
"attack_level": "5",
"combat_audio": "469,472,471",
"death_animation": "6003",
"defence_animation": "6008",
"defence_level": "1",
"defence_level": "5",
"lifepoints": "10",
"melee_animation": "6007",
"range_level": "1",
"safespot": null,
"strength_level": "1"
"strength_level": "5"
},
{
"id": "2076",
"name": "Cave goblin miner",
"examine": "He's working hard!",
"attack_level": "1",
"attack_level": "5",
"combat_audio": "469,472,471",
"death_animation": "6003",
"defence_animation": "6008",
"defence_level": "1",
"defence_level": "5",
"lifepoints": "10",
"melee_animation": "6007",
"range_level": "1",
"safespot": null,
"strength_level": "1"
"strength_level": "5"
},
{
"id": "2077",
"name": "Cave goblin miner",
"examine": "He's working hard!",
"attack_level": "1",
"attack_level": "5",
"combat_audio": "469,472,471",
"death_animation": "6003",
"defence_animation": "6008",
"defence_level": "1",
"defence_level": "5",
"lifepoints": "10",
"melee_animation": "6007",
"range_level": "1",
"safespot": null,
"strength_level": "1"
"strength_level": "5"
},
{
"id": "2078",
"name": "Cave goblin miner",
"examine": "He's working hard!",
"attack_level": "1",
"attack_level": "5",
"combat_audio": "469,472,471",
"death_animation": "6003",
"defence_animation": "6008",
"defence_level": "1",
"defence_level": "5",
"lifepoints": "10",
"melee_animation": "6007",
"range_level": "1",
"safespot": null,
"strength_level": "1"
"strength_level": "5"
},
{
"id": "2079",
@ -38057,11 +38057,13 @@
},
{
"id": "3284",
"name": "Squirrel"
"name": "Squirrel",
"examine": "Bushy tail!"
},
{
"id": "3285",
"name": "Squirrel"
"name": "Squirrel",
"examine": "Bushy tail!"
},
{
"id": "3286",
@ -38081,7 +38083,8 @@
},
{
"id": "3287",
"name": "Raccoon"
"name": "Raccoon",
"examine": "A raccoon."
},
{
"id": "3288",
@ -77156,20 +77159,23 @@
},
{
"id": "6913",
"name": "Baby raccoon"
"name": "Baby raccoon",
"examine": "A stripy little baby raccoon."
},
{
"id": "6914",
"name": "Raccoon"
"name": "Raccoon",
"examine": "He can run with us."
},
{
"id": "6915",
"name": "Baby gecko"
"name": "Baby gecko",
"examine": "It's so adorably tiny!"
},
{
"id": "6916",
"name": "Gecko",
"examine": ""
"examine": "How cute!"
},
{
"id": "6917",
@ -77194,11 +77200,13 @@
},
{
"id": "6919",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "6920",
"name": "Squirrel"
"name": "Squirrel",
"examine": "An experienced nut-thief."
},
{
"id": "6921",
@ -77299,6 +77307,7 @@
{
"id": "6942",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -77310,6 +77319,7 @@
{
"id": "6943",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -77461,7 +77471,8 @@
},
{
"id": "6970",
"name": "Pikkupstix"
"name": "Pikkupstix",
"examine": "He's a very important druid."
},
{
"id": "6971",
@ -77805,6 +77816,16 @@
},
{
"id": "6988",
"name": "Pikkupstix",
"examine": "He's a very important druid."
},
{
"id": "6989",
"name": "Pikkupstix",
"examine": "He's a very important druid."
},
{
"id": "6990",
"name": "Giant wolpertinger"
},
{
@ -79666,6 +79687,7 @@
{
"id": "7210",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79677,6 +79699,7 @@
{
"id": "7211",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79688,6 +79711,7 @@
{
"id": "7212",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79699,6 +79723,7 @@
{
"id": "7213",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79710,6 +79735,7 @@
{
"id": "7214",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79721,6 +79747,7 @@
{
"id": "7215",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79732,6 +79759,7 @@
{
"id": "7216",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79743,6 +79771,7 @@
{
"id": "7217",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79754,6 +79783,7 @@
{
"id": "7218",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79765,6 +79795,7 @@
{
"id": "7219",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79776,6 +79807,7 @@
{
"id": "7220",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79787,6 +79819,7 @@
{
"id": "7221",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79798,6 +79831,7 @@
{
"id": "7222",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79809,6 +79843,7 @@
{
"id": "7223",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79820,6 +79855,7 @@
{
"id": "7224",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79831,6 +79867,7 @@
{
"id": "7225",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79842,6 +79879,7 @@
{
"id": "7226",
"name": "Baby monkey",
"examine": "It's a tiny, cheeky monkey.",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -79853,6 +79891,7 @@
{
"id": "7227",
"name": "Monkey",
"examine": "Hide the bananas!",
"attack_level": "1",
"combat_audio": "629,631,630",
"defence_level": "1",
@ -80161,19 +80200,23 @@
},
{
"id": "7271",
"name": "Baby raccoon"
"name": "Baby raccoon",
"examine": "A stripy little baby raccoon."
},
{
"id": "7272",
"name": "Raccoon"
"name": "Raccoon",
"examine": "He can run with us."
},
{
"id": "7273",
"name": "Baby raccoon"
"name": "Baby raccoon",
"examine": "A stripy little baby raccoon."
},
{
"id": "7274",
"name": "Raccoon"
"name": "Raccoon",
"examine": "He can run with us."
},
{
"id": "7275",
@ -80193,40 +80236,48 @@
},
{
"id": "7276",
"name": "Baby raccoon"
"name": "Baby raccoon",
"examine": "A stripy little baby raccoon."
},
{
"id": "7277",
"name": "Baby gecko"
"name": "Baby gecko",
"examine": "It's so adorably tiny!"
},
{
"id": "7278",
"name": "Baby gecko"
"name": "Baby gecko",
"examine": "It's so adorably tiny!"
},
{
"id": "7279",
"name": "Baby gecko"
"name": "Baby gecko",
"examine": "It's so adorably tiny!"
},
{
"id": "7280",
"name": "Baby gecko"
"name": "Baby gecko",
"examine": "It's so adorably tiny!"
},
{
"id": "7281",
"name": "Gecko",
"examine": ""
"examine": "How cute!"
},
{
"id": "7282",
"name": "Gecko"
"name": "Gecko",
"examine": "How cute!"
},
{
"id": "7283",
"name": "Gecko"
"name": "Gecko",
"examine": "How cute!"
},
{
"id": "7284",
"name": "Gecko"
"name": "Gecko",
"examine": "How cute!"
},
{
"id": "7285",
@ -80252,7 +80303,7 @@
{
"id": "7287",
"name": "Baby gecko",
"examine": ""
"examine": "It's so adorably tiny!"
},
{
"id": "7288",
@ -80261,7 +80312,8 @@
},
{
"id": "7289",
"name": "Gecko"
"name": "Gecko",
"examine": "How cute!"
},
{
"id": "7290",
@ -80270,7 +80322,8 @@
},
{
"id": "7291",
"name": "Gecko"
"name": "Gecko",
"examine": "How cute!"
},
{
"id": "7292",
@ -80311,51 +80364,63 @@
},
{
"id": "7301",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "7302",
"name": "Squirrel"
"name": "Squirrel",
"examine": "An experienced nut-thief."
},
{
"id": "7303",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "7304",
"name": "Squirrel"
"name": "Squirrel",
"examine": "An experienced nut-thief."
},
{
"id": "7305",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "7306",
"name": "Squirrel"
"name": "Squirrel",
"examine": "An experienced nut-thief."
},
{
"id": "7307",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "7308",
"name": "Squirrel"
"name": "Squirrel",
"examine": "An experienced nut-thief."
},
{
"id": "7309",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "7310",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "7311",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "7312",
"name": "Baby squirrel"
"name": "Baby squirrel",
"examine": "A tiny nut-thief."
},
{
"id": "7313",

View file

@ -91,6 +91,11 @@
<version>2.9.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.7</version>
</dependency>
</dependencies>
<build>

View file

@ -1,172 +0,0 @@
package content.data;
import core.game.node.entity.player.Player;
/**
* The BossKillcounter keeps track of the amount of bosses the player has slain.
* addtoKillcount(player, npcId) should be added in the finalizeDeath() method of the combat handler for the boss.
* @author Splinter
*/
public enum BossKillCounter {
/* ORDINAL BOUND */
KING_BLACK_DRAGON(new int[] { 50 }, "King Black Dragon", 14649),
BORK(new int[] { 7133, 7134 }, "Bork", -1),
DAGANNOTH_SUPREME(new int[] { 2881 }, "Dagannoth Supreme", 14639),
DAGANNOTH_PRIME(new int[] { 2882 }, "Dagannoth Prime", 14640),
DAGANNOTH_REX(new int[] { 2883 }, "Dagannoth Rex", 14641),
CHAOS_ELEMENTAL(new int[] { 3200 }, "Chaos Elemental", 14638),
GIANT_MOLE(new int[] { 3340 }, "Giant Mole", 14642),
SARADOMIN(new int[] { 6247 }, "Commander Zilyana", 14647),
ZAMORAK(new int[] { 6203 }, "K'ril Tsutsaroth", 14648),
BANDOS(new int[] { 6260 }, "General Graardor", 14646),
ARMADYL(new int[] { 6222 }, "Kree'arra", 14645),
JAD(new int[] { 2745 }, "Tz-Tok Jad", 14828),
KALPHITE_QUEEN(new int[] { 1160 }, "Kalphite Queen", 14650),
CORPOREAL_BEAST(new int[] { 8133 }, "Corporeal Beast", 14653),
TORMENTED_DEMONS(new int[] {
8349, 8350, 8351, 8352, 8353, 8354,
8355, 8356, 8357, 8358, 8359, 8360,
8361, 8362, 8363, 8364, 8365, 8366,
}, "Tormented demon", -1),
;
/**
* The npcs that can increase the killcounter
*/
private final int[] npc;
/**
* The name of the NPC, to be displayed as a sendMessage
*/
private final String name;
/**
* The item ID of the pet relating to the boss.
*/
private final int petId;
/**
* Constructs a new {@code BossKillCounter} {@code Object}.
* @param npc the npc.
* @param name the npc's string name
*/
BossKillCounter(final int[] npc, final String name, final int petId) {
this.npc = npc;
this.name = name;
this.petId = petId;
}
/**
* Gets the npc.
* @return The npc.
*/
public int[] getNpc() {
return npc;
}
/**
* Gets the NPC's name
* @return their name
*/
public String getName() {
return name;
}
/**
* Gets the petId
* @return The petId
*/
public int getPetId() {
return petId;
}
/**
* Gets the type for the npc.
* @param npc the npc.
* @return the BossKillcounter
*/
public static BossKillCounter forNPC(final int npc) {
for (BossKillCounter kc : BossKillCounter.values()) {
for (int i : kc.getNpc()) {
if (npc == i) {
return kc;
}
}
}
return null;
}
/**
* Adds to the player's killcount for that particular boss.
* @param killer The player who killed the npc
* @param npcid the ID of the npc that just died
*/
public static void addtoKillcount(Player killer, int npcid) {
if (killer == null) {
return;
}
BossKillCounter boss = BossKillCounter.forNPC(npcid);
if (boss == null) {
return;
}
killer.getSavedData().getGlobalData().getBossCounters()[boss.ordinal()]++;
killer.getPacketDispatch().sendMessage("Your " + boss.getName() + " killcount is now: <col=ff0000>" + killer.getSavedData().getGlobalData().getBossCounters()[boss.ordinal()] + "</col>.");
// addBossPet(killer, npcid, boss);
}
// /**
// * Gives the player the pet if they killed a certain boss.
// * The chance by default is 1/5000. This rate lowers to 1/2200 if the <GlobalEvents> for Boss Pets is active.
// * Note: Not all bosses have pet versions of themselves.
// */
// private static void addBossPet(Player killer, int npcid, BossKillCounter boss){
// if(boss.getPetId() == -1){ //The boss does not have a pet version.
// return;
// }
// int number = 5000;
// if (npcid == 2745) {
// number = 200;
// if (killer.getSlayer().getTask() == Tasks.JAD) {
// number = 100;
// }
// } else if (npcid == 3200) {
// number = 300;
// }
// int rand = number;
// if(rand == 10){
// for (int i = 0; i < killer.getFamiliarManager().getInsuredPets().size(); i++) {
// if (killer.getFamiliarManager().getInsuredPets().get(i).getBabyItemId() == boss.getPetId()) {
// return;
// }
// }
// if(killer.getFamiliarManager().hasFamiliar() && killer.getInventory().freeSlots() < 1){
// return;
// }
// if(!killer.getFamiliarManager().hasFamiliar()){
// killer.getFamiliarManager().summon(new Item(boss.getPetId()), true);
// killer.sendNotificationMessage("You have a funny feeling like you're being followed.");
// } else if (killer.getInventory().freeSlots() > 0){
// killer.getInventory().add(new Item(boss.getPetId(), 1));
// killer.sendNotificationMessage("You feel something weird sneaking into your backpack.");
// }
// Repository.sendNews(killer.getUsername()+" now commands a miniature "+(boss.equals(CORPOREAL_BEAST) ? "Dark core" : boss.getName())+"!");
// }
// }
/**
* Increments the player's Barrows chest counter.
* @param player the player
*/
public static void addtoBarrowsCount(Player player) {
if (player == null) {
return;
}
player.getSavedData().getGlobalData().setBarrowsLoots(player.getSavedData().getGlobalData().getBarrowsLoots() + 1);
player.getPacketDispatch().sendMessage("Your Barrows chest count is: <col=ff0000>" + player.getSavedData().getGlobalData().getBarrowsLoots() + "</col>.");
}
}

View file

@ -0,0 +1,72 @@
package content.data
import core.api.sendMessage
import core.game.node.entity.player.Player
import org.rs09.consts.NPCs
/**
* Enumerates the bosses that have their kill counts tracked in a player's statistics (::stats).
* addToBossKillCount(player, npcId) should be added in the finalizeDeath() method of the combat handler for the boss.
* ORDINAL BOUND
* @author Bishop
*/
enum class BossKillCounter(val bossId: IntArray, val bossName: String) {
KING_BLACK_DRAGON(intArrayOf(NPCs.KING_BLACK_DRAGON_50), "King Black Dragon"),
BORK(intArrayOf(NPCs.BORK_7133, NPCs.BORK_7134), "Bork"),
DAGANNOTH_SUPREME(intArrayOf(NPCs.DAGANNOTH_SUPREME_2881), "Dagannoth Supreme"),
DAGANNOTH_PRIME(intArrayOf(NPCs.DAGANNOTH_PRIME_2882), "Dagannoth Prime"),
DAGANNOTH_REX(intArrayOf(NPCs.DAGANNOTH_REX_2883), "Dagannoth Rex"),
CHAOS_ELEMENTAL(intArrayOf(NPCs.CHAOS_ELEMENTAL_3200), "Chaos Elemental"),
GIANT_MOLE(intArrayOf(NPCs.GIANT_MOLE_3340), "Giant Mole"),
SARADOMIN(intArrayOf(NPCs.COMMANDER_ZILYANA_6247), "Commander Zilyana"),
ZAMORAK(intArrayOf(NPCs.KRIL_TSUTSAROTH_6203), "K'ril Tsutsaroth"),
BANDOS(intArrayOf(NPCs.GENERAL_GRAARDOR_6260), "General Graardor"),
ARMADYL(intArrayOf(NPCs.KREEARRA_6222), "Kree'arra"),
JAD(intArrayOf(NPCs.TZTOK_JAD_2745), "Tz-Tok Jad"),
KALPHITE_QUEEN(intArrayOf(NPCs.KALPHITE_QUEEN_1160), "Kalphite Queen"),
CORPOREAL_BEAST(intArrayOf(NPCs.CORPOREAL_BEAST_8133), "Corporeal Beast"),
TORMENTED_DEMONS(
intArrayOf(
NPCs.TORMENTED_DEMON_8349, NPCs.TORMENTED_DEMON_8350, NPCs.TORMENTED_DEMON_8351,
NPCs.TORMENTED_DEMON_8352, NPCs.TORMENTED_DEMON_8353, NPCs.TORMENTED_DEMON_8354,
NPCs.TORMENTED_DEMON_8355, NPCs.TORMENTED_DEMON_8356, NPCs.TORMENTED_DEMON_8357,
NPCs.TORMENTED_DEMON_8358, NPCs.TORMENTED_DEMON_8359, NPCs.TORMENTED_DEMON_8360,
NPCs.TORMENTED_DEMON_8361, NPCs.TORMENTED_DEMON_8362, NPCs.TORMENTED_DEMON_8363,
NPCs.TORMENTED_DEMON_8364, NPCs.TORMENTED_DEMON_8365, NPCs.TORMENTED_DEMON_8366),
"Tormented demon"
),
PENANCE_QUEEN(intArrayOf(NPCs.PENANCE_QUEEN_5247), "Penance Queen");
companion object {
private fun forBossId(npc: Int): BossKillCounter? {
for (kc in values()) {
for (i in kc.bossId) {
if (npc == i) {
return kc
}
}
}
return null
}
@JvmStatic
fun addToBossKillCount(killer: Player?, bossId: Int) {
if (killer == null) {
return
}
val boss: BossKillCounter = forBossId(bossId) ?: return
killer.getSavedData().globalData.bossCounters[boss.ordinal]++
sendMessage(killer, "Your ${boss.bossName} killcount is now: <col=ff0000>${killer.getSavedData().globalData.bossCounters[boss.ordinal]}</col>.")
}
fun addToBarrowsChestCount(player: Player?) {
if (player == null) {
return
}
player.getSavedData().globalData.barrowsLoots++
sendMessage(player, "Your Barrows chest count is: <col=ff0000>${player.getSavedData().globalData.barrowsLoots}</col>.")
}
}
}

View file

@ -193,7 +193,7 @@ enum class EnchantedJewellery(
),
RING_OF_LIFE(arrayOf<String>(),
arrayOf(
Location.create(ServerConstants.HOME_LOCATION)
Location.create(ServerConstants.HOME_LOCATION!!)
),
true,
Items.RING_OF_LIFE_2570
@ -246,7 +246,13 @@ enum class EnchantedJewellery(
when (stage) {
0 -> {
lock(player, 4)
visualize(player, ANIMATION, GRAPHICS)
// This is an intentional teleport animation & graphics difference as these two jewellery
// animations are authentic to 2009 while others are currently unknown.
if (this == RING_OF_DUELING || this == GAMES_NECKLACE) {
visualize(player, JEWELLERY_ANIMATION, JEWELLERY_GRAPHICS)
} else {
visualize(player, ANIMATION, GRAPHICS)
}
playGlobalAudio(player.location, Sounds.TELEPORT_ALL_200)
player.impactHandler.disabledTicks = 4
closeInterface(player)
@ -255,6 +261,9 @@ enum class EnchantedJewellery(
1 -> {
teleport(player, location)
resetAnimator(player)
if (this == RING_OF_DUELING || this == GAMES_NECKLACE) {
player.graphics(JEWELLERY_GRAPHICS)
}
unlock(player)
player.dispatch(TeleportEvent(TeleportManager.TeleportType.NORMAL, TeleportMethod.JEWELRY, item, location))
@ -370,6 +379,8 @@ enum class EnchantedJewellery(
companion object {
private val ANIMATION = Animation(714)
private val GRAPHICS = Graphics(308, 100, 50)
private val JEWELLERY_ANIMATION = Animation(9603)
private val JEWELLERY_GRAPHICS = Graphics(1684)
val idMap = HashMap<Int, EnchantedJewellery>()
init {

View file

@ -353,7 +353,7 @@ public enum Consumables {
FISHING(new Potion(new int[] {2438, 151, 153, 155}, new SkillEffect(Skills.FISHING, 3, 0))),
PRAYER(new Potion(new int[] {2434, 139, 141, 143}, new PrayerEffect(7, 0.25))),
SUPER_RESTO(new Potion(new int[] {3024, 3026, 3028, 3030}, new RestoreEffect(8, 0.25, true))),
ZAMMY_BREW(new Potion(new int[] {2450, 189, 191, 193}, new MultiEffect(new DamageEffect(10, true), new SkillEffect(Skills.ATTACK, 0, 0.25), new SkillEffect(Skills.STRENGTH, 0, 0.15), new SkillEffect(Skills.DEFENCE, 0, -0.1)))),
ZAMMY_BREW(new Potion(new int[] {2450, 189, 191, 193}, new MultiEffect(new DamageEffect(10, true, 2), new SkillEffect(Skills.ATTACK, 2, 0.20), new SkillEffect(Skills.STRENGTH, 2, 0.12), new SkillEffect(Skills.DEFENCE, -2, -0.10), new PrayerEffect(0.0, 0.10, true)))),
ANTIFIRE(new Potion(new int[] {2452, 2454, 2456, 2458}, new AddTimerEffect("dragonfire:immunity", 600, true))),
GUTH_REST(new Potion(new int[] {4417, 4419, 4421, 4423}, new MultiEffect(new RemoveTimerEffect("poison"), new EnergyEffect(5), new HealingEffect(5)))),
MAGIC_ESS(new Potion(new int[] {9021, 9022, 9023, 9024}, new SkillEffect(Skills.MAGIC,3,0))),
@ -363,7 +363,7 @@ public enum Consumables {
/** Barbarian Mixes */
PRAYERMIX(new BarbarianMix(new int[] {11465, 11467}, new MultiEffect(new PrayerEffect(7, 0.25), new HealingEffect(6)))),
ZAMMY_MIX(new BarbarianMix(new int[] {11521, 11523}, new MultiEffect(new DamageEffect(10, true), new SkillEffect(Skills.ATTACK, 0, 0.15), new SkillEffect(Skills.STRENGTH, 0, 0.25), new SkillEffect(Skills.DEFENCE, 0, -0.1)))),
ZAMMY_MIX(new BarbarianMix(new int[]{11521, 11523}, new MultiEffect(new ZamorakMixEffect(), new SkillEffect(Skills.ATTACK, 2, 0.20), new SkillEffect(Skills.STRENGTH, 2, 0.12), new SkillEffect(Skills.DEFENCE, -2, -0.10)))),
ATT_MIX(new BarbarianMix(new int[] {11429, 11431}, new MultiEffect(new SkillEffect(Skills.ATTACK, 3, 0.1), new HealingEffect(3)))),
ANTIP_MIX(new BarbarianMix(new int[] {11433, 11435}, new MultiEffect(new AddTimerEffect("poison:immunity", secondsToTicks(90)), new HealingEffect(3)))),
RELIC_MIX(new BarbarianMix(new int[] {11437, 11439}, new MultiEffect(new CureDiseaseEffect(), new HealingEffect(3)))),

View file

@ -7,15 +7,21 @@ import core.game.node.entity.player.Player;
public class DamageEffect extends ConsumableEffect {
final double amt;
final boolean isPercent;
final int baseAmount;
public DamageEffect(double amt,boolean isPercent){
public DamageEffect(double amt, boolean isPercent) {
this(amt, isPercent, 0);
}
public DamageEffect(double amt, boolean isPercent, int baseAmount) {
this.amt = amt;
this.isPercent = isPercent;
this.baseAmount = baseAmount;
}
@Override
public void activate(Player p) {
p.getImpactHandler().manualHit(p,-getHealthEffectValue(p), ImpactHandler.HitsplatType.NORMAL);
p.getImpactHandler().manualHit(p, -getHealthEffectValue(p), ImpactHandler.HitsplatType.NORMAL);
}
@Override
@ -23,8 +29,8 @@ public class DamageEffect extends ConsumableEffect {
double amount = amt;
if (isPercent) {
amount /= 100;
return (int) -(amount * player.getSkills().getLifepoints());
return (int) -(amount * player.getSkills().getLifepoints() + baseAmount);
}
return (int) -amt;
return (int) -(amt + baseAmount);
}
}

View file

@ -5,16 +5,18 @@ import core.game.consumable.ConsumableEffect
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import org.rs09.consts.Items
import kotlin.math.ceil
import kotlin.math.floor
class PrayerEffect(var base: Double, var bonus: Double) : ConsumableEffect() {
class PrayerEffect @JvmOverloads constructor(var base: Double, var bonus: Double, private val roundUp: Boolean = false) : ConsumableEffect() {
override fun activate(player: Player?) {
if(player == null) return
val level = getStatLevel(player, Skills.PRAYER)
var b = bonus
if(inInventory(player, Items.HOLY_WRENCH_6714))
b += 0.02 // Leaving this in for futureproofing. Current update is for properly rounding down.
val amount = floor(base + (level * b))
val rawAmount = base + (level * b)
val amount = if (roundUp) ceil(rawAmount) else floor(rawAmount)
modPrayerPoints(player, amount)
}
}

View file

@ -0,0 +1,25 @@
package content.data.consumables.effects;
import core.game.consumable.ConsumableEffect;
import core.game.node.entity.combat.ImpactHandler;
import core.game.node.entity.player.Player;
/**
* The Zamorak mix shows the regular Zamorak brew hitsplat (10% of pre-drink lifepoints + 2, rounded down)
* but also heals 6 lifepoints. The heal must be applied before the hit so the net change can never kill
* the player, while the hitsplat still reflects the pre-heal lifepoints.
*/
public class ZamorakMixEffect extends ConsumableEffect {
@Override
public void activate(Player p) {
int damage = (int) (p.getSkills().getLifepoints() * 0.10 + 2);
p.getSkills().heal(6);
p.getImpactHandler().manualHit(p, damage, ImpactHandler.HitsplatType.NORMAL);
}
@Override
public int getHealthEffectValue(Player player) {
return 6 - (int) (player.getSkills().getLifepoints() * 0.10 + 2);
}
}

View file

@ -39,6 +39,7 @@ abstract class RandomEventNPC(id: Int) : NPC(id) {
var finalized = false
var timerPaused = false
var ticksLeft = secondsToTicks(180)
private val combatBracket = intArrayOf(10, 20, 40, 60, 90)
open fun create(player: Player, loot: WeightBasedTable? = null, type: String = ""): RandomEventNPC {
val event = this::class.createInstance()
@ -144,7 +145,7 @@ abstract class RandomEventNPC(id: Int) : NPC(id) {
}
fun idForCombatLevel(ids: List<Int>, player: Player): Int {
val index = min(ids.size, ceil(player.properties.currentCombatLevel / 20.0).toInt()) - 1
val index = combatBracket.indexOfFirst { player.properties.currentCombatLevel <= it }.takeIf { it != -1 }?: ids.lastIndex
return ids[index]
}

View file

@ -23,7 +23,7 @@ class QuizMasterNPC(var type: String = "", override var loot: WeightBasedTable?
super.init()
sendChat("Hey ${player.username}! It's your lucky day!")
face(player)
kidnapPlayer(this, player, Location(1952, 4764, 0)) { player, _ ->
kidnapPlayer(this, player, Location(1952, 4764, 1)) { player, _ ->
setAttribute(player, QuizMasterDialogueFile.QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0)
sendMessage(player, "Answer four questions correctly in a row to be teleported back where you came from.")
sendMessage(player, "You will need to relog in if you lose the quiz dialog.") // Inauthentic, but there to notify the player in case.

View file

@ -5,6 +5,7 @@ import core.game.bots.SkillingBotAssembler
import core.game.bots.Script
import core.game.interaction.IntType
import core.game.interaction.InteractionListeners
import core.game.node.entity.skill.Skills
import java.util.*
class ManThiever : Script() {
@ -22,6 +23,6 @@ class ManThiever : Script() {
}
init {
equipment.addAll(Arrays.asList(Item(1103), Item(1139), Item(1265)))
skills[Skills.THIEVING] = 20
}
}

View file

@ -1,37 +0,0 @@
package content.global.handlers.iface;
import static core.api.ContentAPIKt.*;
import core.game.component.Component;
import core.game.component.ComponentDefinition;
import core.game.component.ComponentPlugin;
import content.global.travel.glider.GliderPulse;
import content.global.travel.glider.Gliders;
import core.game.node.entity.player.Player;
import core.plugin.Initializable;
import core.plugin.Plugin;
/**
* Represents the glider interface component.
* @author Emperor
* @author 'Vexia
* @version 1.0
*/
@Initializable
public final class GliderInterface extends ComponentPlugin {
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
ComponentDefinition.put(138, this);
return this;
}
@Override
public boolean handle(final Player player, Component component, int opcode, int button, int slot, int itemId) {
final Gliders glider = Gliders.forId(button);
if (glider == null) {
return true;
}
submitWorldPulse(new GliderPulse(1, player, glider));
return true;
}
}

View file

@ -0,0 +1,17 @@
package content.global.handlers.iface
import core.api.submitWorldPulse
import content.global.travel.glider.GliderPulse
import content.global.travel.glider.Gliders
import core.game.interaction.InterfaceListener
import org.rs09.consts.Components
class GliderInterface : InterfaceListener {
override fun defineInterfaceListeners() {
on(Components.GLIDERMAP_138) { player, _, _, button, _, _ ->
val glider = Gliders.forId(button) ?: return@on true
submitWorldPulse(GliderPulse(1, player, glider))
return@on true
}
}
}

View file

@ -1,39 +0,0 @@
package content.global.handlers.iface;
import core.game.component.Component;
import core.game.component.ComponentDefinition;
import core.game.component.ComponentPlugin;
import core.game.node.entity.player.Player;
import core.plugin.Initializable;
import core.plugin.Plugin;
/**
* Handles the world map interface.
* @author Emperor
*
*/
@Initializable
public final class WorldMapInterface extends ComponentPlugin {
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
ComponentDefinition.forId(755).setPlugin(this);
return this;
}
//Thanks snicker!
@Override
public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) {
switch (button) {
case 3:
player.getInterfaceManager().openWindowsPane(new Component(player.getInterfaceManager().isResizable() ? 746 : 548), 2);
player.getPacketDispatch().sendRunScript(1187, "ii", 0, 0);
player.updateSceneGraph(true);
return true;
default:
//log(this.getClass(), Log.ERR, "World map: buttonid: " + button + ", opcode: " + opcode + ", slot: " + slot);
return true;
}
}
}

View file

@ -0,0 +1,23 @@
package content.global.handlers.iface
import core.game.component.Component
import core.game.interaction.InterfaceListener
import org.rs09.consts.Components
class WorldMapInterface : InterfaceListener {
override fun defineInterfaceListeners() {
on(Components.WORLDMAP_755) { player, _, _, button, _, _ ->
if (button == 3) {
val paneId = if (player.interfaceManager.isResizable) {
Components.TOPLEVEL_FULLSCREEN_746
} else {
Components.TOPLEVEL_548
}
player.interfaceManager.openWindowsPane(Component(paneId), 2)
player.packetDispatch.sendRunScript(1187, "ii", 0, 0)
player.updateSceneGraph(true)
}
return@on true
}
}
}

View file

@ -2,7 +2,6 @@ package content.global.handlers.item
import content.global.skill.slayer.SlayerUtils
import core.api.*
import content.global.skill.slayer.Tasks
import org.rs09.consts.Items
import core.game.dialogue.DialogueFile
import core.game.dialogue.IfTopic
@ -39,12 +38,7 @@ class EnchantedGemDialogue() : DialogueFile() {
if(!hasSlayerTask(player!!)) {
npcl(core.game.dialogue.FacialExpression.HALF_THINKING, "You need something new to hunt. Come and see me when you can and I'll give you a new task.").also { stage = 1 }
} else {
if(getSlayerTask(player!!) == Tasks.JAD) {
npcl(core.game.dialogue.FacialExpression.FRIENDLY, "You're currently assigned to kill TzTok-Jad!")
} else {
npcl(core.game.dialogue.FacialExpression.FRIENDLY, "You're currently assigned to kill ${SlayerUtils.pluralise(
getSlayerTaskName(player!!))}; only ${getSlayerTaskKillsRemaining(player!!)} more to go.")
}
npcl(core.game.dialogue.FacialExpression.FRIENDLY, "You're currently assigned to kill ${SlayerUtils.pluralise(getSlayerTaskName(player!!))}; only ${getSlayerTaskKillsRemaining(player!!)} more to go.")
setVarp(player!!, 2502, getSlayerTaskFlags(player!!) shr 4)
stage = 1
}

View file

@ -0,0 +1,77 @@
package content.global.handlers.item
import core.api.*
import core.game.interaction.InteractionListener
import core.game.interaction.InterfaceListener
import core.game.node.entity.player.Player
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import org.rs09.consts.Sounds
/**
* Handles morphing with the Ring of Stone and Easter Ring.
*/
class MorphItemListener : InteractionListener, InterfaceListener {
// the morphing items
private val morphItems = intArrayOf(Items.RING_OF_STONE_6583, Items.EASTER_RING_7927)
// the NPCs to morph into
private val easterEggs = intArrayOf(
NPCs.EGG_3689,
NPCs.EGG_3690,
NPCs.EGG_3691,
NPCs.EGG_3692,
NPCs.EGG_3693,
NPCs.EGG_3694
)
private val stone = intArrayOf(NPCs.ROCKS_2626)
// the "unmorph" interface
private val morphIface = 375
// equipping the morph items
override fun defineListeners() {
onEquip(morphItems) { player, item ->
morph(player, item.id)
return@onEquip false
}
}
// the interface that appears when you morph
override fun defineInterfaceListeners() {
// hitting the 'unmorph' button
on(morphIface){ player, _, _, buttonID, _, _ ->
when(buttonID) {
3 -> closeAllInterfaces(player)
}
return@on true
}
// after the interface closes
onClose(morphIface) { player, _ ->
unmorph(player)
return@onClose true
}
}
// morphs the player
private fun morph(player: Player, item: Int) {
val morphId = when (item) {
Items.RING_OF_STONE_6583 -> stone.random()
else -> easterEggs.random()
}
playAudio(player, Sounds.EASTER06_HUMAN_INTO_EGG_1520)
stopWalk(player)
closeAllInterfaces(player)
player.appearance.transformNPC(morphId)
openInterface(player, morphIface)
}
// unmorphs the player
private fun unmorph(player: Player) {
player.appearance.transformNPC(-1)
}
}

View file

@ -1,113 +0,0 @@
package content.global.handlers.item;
import core.cache.def.impl.ItemDefinition;
import core.game.component.CloseEvent;
import core.game.component.Component;
import core.game.component.ComponentDefinition;
import core.game.component.ComponentPlugin;
import core.game.node.entity.player.Player;
import core.game.node.item.Item;
import core.game.world.GameWorld;
import core.plugin.Initializable;
import core.plugin.Plugin;
import core.plugin.ClassScanner;
import core.tools.RandomFunction;
import org.rs09.consts.Sounds;
import static core.api.ContentAPIKt.playAudio;
/**
* Handles a morph item.
* @author Vexia
*/
@Initializable
public class MorphItemPlugin implements Plugin<Object> {
/**
* The easter egg ids.
*/
protected static final int[] EASTER_EGG_IDS = new int[] { 3689, 3690, 3691, 3692, 3693, 3694 };
/**
* The morph component.
*/
private static final Component COMPONENT = new Component(375).setCloseEvent(new CloseEvent() {
@Override
public boolean close(Player player, Component c) {
unmorph(player);
return true;
}
});
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
ItemDefinition.forId(7927).getHandlers().put("equipment", this);
ItemDefinition.forId(6583).getHandlers().put("equipment", this);
ClassScanner.definePlugin(new MorphInterfacePlugin());
return this;
}
@Override
public Object fireEvent(String identifier, Object... args) {
final Player player = (Player) args[0];
final Item item = (Item) args[1];
switch (identifier) {
case "equip":
morph(player, item);
return false;
}
return true;
}
/**
* Morphs the player.
* @param player the player.
* @param item the item.
*/
private void morph(Player player, Item item) {
int morphId = item.getId() == 6583 ? 2626 : EASTER_EGG_IDS[RandomFunction.random(EASTER_EGG_IDS.length)];
playAudio(player, 1520);
player.getInterfaceManager().close();
player.getAppearance().transformNPC(morphId);
player.getInterfaceManager().removeTabs(0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);
player.getLocks().lockMovement(GameWorld.getTicks() + 900000000);
player.getLocks().lockInteractions(GameWorld.getTicks() + 90000000);
player.getLocks().lockTeleport(GameWorld.getTicks() + 900000000);
player.getInterfaceManager().openSingleTab(COMPONENT);
player.getAppearance().sync();
player.getWalkingQueue().reset();
}
/**
* Unmorphs the player.
* @param player the player.
*/
private static void unmorph(Player player) {
player.getAppearance().transformNPC(-1);
player.unlock();
player.getInterfaceManager().restoreTabs();
}
/**
* Handles the morph interface plugin.
* @author Vexia
*/
public class MorphInterfacePlugin extends ComponentPlugin {
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
ComponentDefinition.forId(375).setPlugin(this);
return this;
}
@Override
public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) {
player.getInterfaceManager().closeSingleTab();
return true;
}
}
}

View file

@ -129,9 +129,9 @@ class SheepBehavior : NPCBehavior(*sheepIds), InteractionListener {
val sheepDirection = Direction.getDirection(sheepLocation, playerLocation) // Get direction sheep is facing, from the player's location
val sheepOppositeDirection = sheepDirection.getOpposite() // Switch to opposite direction
val xWalkLocation = sheepLocation.getX() + (sheepOppositeDirection.getStepX() * 3) // Gets x location, if set, 3 steps away from player
val yWalkLocation = sheepLocation.getY() + (sheepOppositeDirection.getStepY() * 3) // Gets y location, if set, 3 steps away from player
val sheepWalkToLocation = Location(xWalkLocation, yWalkLocation, sheepLocation.getZ()); // New location for pathfinding
val xWalkLocation = sheepLocation.x + (sheepOppositeDirection.getStepX() * 3) // Gets x location, if set, 3 steps away from player
val yWalkLocation = sheepLocation.y + (sheepOppositeDirection.getStepY() * 3) // Gets y location, if set, 3 steps away from player
val sheepWalkToLocation = Location(xWalkLocation, yWalkLocation, sheepLocation.z); // New location for pathfinding
sendMessage(player, messagePlayer)

View file

@ -139,6 +139,13 @@ public final class AgilityHandler {
* @return The force movement instance, if force movement is used.
*/
public static ForceMovement forceWalk(final Player player, final int courseIndex, Location start, Location end, Animation animation, int speed, final double experience, final String message) {
return forceWalk(player, courseIndex, start, end, animation, speed, experience, message, null);
}
/**
* Walks across an obstacle while using an explicit force-movement facing direction.
*/
public static ForceMovement forceWalk(final Player player, final int courseIndex, Location start, Location end, Animation animation, int speed, final double experience, final String message, Direction direction) {
player.logoutListeners.put("forcewalk", p -> {
p.setLocation(player.getLocation().transform(0,0,0));
return Unit.INSTANCE;
@ -158,6 +165,9 @@ public final class AgilityHandler {
player.logoutListeners.remove("forcewalk");
}
};
if (direction != null) {
movement.setDirection(direction);
}
movement.start();
GameWorld.getPulser().submit(movement);
return movement;

View file

@ -5,6 +5,9 @@ import core.game.node.entity.Entity;
import core.game.node.entity.player.Player;
import core.game.system.task.MovementHook;
import core.game.system.task.Pulse;
import core.net.packet.PacketRepository;
import core.net.packet.context.PlayerContext;
import core.net.packet.out.ClearMinimapFlag;
import kotlin.Unit;
import core.game.world.GameWorld;
import core.game.world.map.Direction;
@ -24,6 +27,10 @@ public final class BladeTrap implements MovementHook {
final Direction dir = e.getDirection();
final Player player = (Player) e;
final Location start = l.transform(-dir.getStepX(), -dir.getStepY(), 0);
player.getPulseManager().clear();
player.getWalkingQueue().reset();
PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(player));
e.lock(5);
if(e.isPlayer())
{

View file

@ -119,6 +119,8 @@ public final class BrimhavenCourse extends OptionHandler {
final Direction dir = direction;
final Direction faceDirection = Direction.get(object.getDirection().toInteger() - 1 & 3);
final Location start = player.getLocation();
player.setDirection(faceDirection);
player.faceLocation(start.transform(faceDirection));
player.getAppearance().setAnimations(Animation.create(1118 + m));
player.getAppearance().sync();
AgilityHandler.climb(player, -1, new Animation(1117 + m), start.transform(dir), 0.0, null);
@ -143,12 +145,12 @@ public final class BrimhavenCourse extends OptionHandler {
} else if (count == 6) {
player.getAppearance().setAnimations();
player.getAppearance().sync();
AgilityHandler.forceWalk(player, -1, last, last.transform(dir), Animation.create(1120 + m), 5, getExp(player, 22.0), null).setDirection(faceDirection);
AgilityHandler.forceWalk(player, -1, last, last.transform(dir), Animation.create(1120 + m), 5, getExp(player, 22.0), null, faceDirection);
player.logoutListeners.remove("brimcourse");
return true;
}
player.logoutListeners.remove("brimcourse");
AgilityHandler.forceWalk(player, -1, last, last = last.transform(dir), Animation.create(1118 + m), 5, 0.0, null).setDirection(faceDirection);
AgilityHandler.forceWalk(player, -1, last, last = last.transform(dir), Animation.create(1118 + m), 5, 0.0, null, faceDirection);
return false;
}
});
@ -272,7 +274,7 @@ public final class BrimhavenCourse extends OptionHandler {
public boolean pulse() {
if (!finish) {
setDelay(2);
AgilityHandler.fail(player, 1, player.getLocation().transform(0, 0, -3), Animation.create(189), getHitAmount(player), "You stepped on a broken piece of plank!");
AgilityHandler.fail(player, 1, player.getLocation().transform(0, 0, -3), Animation.create(768), getHitAmount(player), "You stepped on a broken piece of plank!");
finish = true;
return false;
}
@ -282,7 +284,7 @@ public final class BrimhavenCourse extends OptionHandler {
});
return;
}
AgilityHandler.walk(player, -1, start, end, ForceMovement.WALK_ANIMATION, getExp(player, 6.0), null);
AgilityHandler.walk(player, -1, start, end, Animation.create(1426), getExp(player, 6.0), null);
}
/**
@ -389,6 +391,7 @@ public final class BrimhavenCourse extends OptionHandler {
}
break;
case 3551:
case 3583:
return n.getLocation();
}
return null;

View file

@ -7,6 +7,8 @@ import core.game.node.entity.impl.Projectile;
import core.game.node.entity.player.Player;
import core.game.system.task.MovementHook;
import core.game.system.task.Pulse;
import core.net.packet.context.PlayerContext;
import core.net.packet.out.ClearMinimapFlag;
import kotlin.Unit;
import core.game.world.GameWorld;
import core.game.world.map.Direction;
@ -29,6 +31,9 @@ public final class DartTrap implements MovementHook {
final Direction dir = e.getDirection();
final Player player = (Player) e;
final Location start = l.transform(-dir.getStepX(), -dir.getStepY(), 0);
player.getPulseManager().clear();
player.getWalkingQueue().reset();
PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(player));
e.lock(6);
if(e.isPlayer())
{

View file

@ -6,6 +6,9 @@ import core.game.node.entity.Entity;
import core.game.node.entity.player.Player;
import core.game.system.task.MovementHook;
import core.game.system.task.Pulse;
import core.net.packet.PacketRepository;
import core.net.packet.context.PlayerContext;
import core.net.packet.out.ClearMinimapFlag;
import kotlin.Unit;
import core.game.world.GameWorld;
import core.game.world.map.Direction;
@ -24,6 +27,9 @@ public final class FloorSpikes implements MovementHook {
final Direction dir = e.getDirection();
final Player player = (Player) e;
final Location start = l.transform(-dir.getStepX(), -dir.getStepY(), 0);
player.getPulseManager().clear();
player.getWalkingQueue().reset();
PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(player));
e.lock(5);
if(e.isPlayer())
{

View file

@ -6,6 +6,9 @@ import core.game.node.entity.Entity;
import core.game.node.entity.player.Player;
import core.game.system.task.MovementHook;
import core.game.system.task.Pulse;
import core.net.packet.PacketRepository;
import core.net.packet.context.PlayerContext;
import core.net.packet.out.ClearMinimapFlag;
import kotlin.Unit;
import core.game.world.GameWorld;
import core.game.world.map.Direction;
@ -24,6 +27,10 @@ public final class PressurePad implements MovementHook {
final Direction dir = e.getDirection();
final Player player = (Player) e;
final Location start = dest.transform(-dir.getStepX(), -dir.getStepY(), 0);
player.getPulseManager().clear();
player.getWalkingQueue().reset();
PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(player));
e.lock(5);
if(e.isPlayer())
{

View file

@ -6,6 +6,9 @@ import core.game.node.entity.Entity;
import core.game.node.entity.player.Player;
import core.game.system.task.MovementHook;
import core.game.system.task.Pulse;
import core.net.packet.PacketRepository;
import core.net.packet.context.PlayerContext;
import core.net.packet.out.ClearMinimapFlag;
import kotlin.Unit;
import core.game.world.GameWorld;
import core.game.world.map.Direction;
@ -24,6 +27,9 @@ public final class SpinningBlades implements MovementHook {
final Direction dir = e.getDirection();
final Player player = (Player) e;
final Location start = l.transform(-dir.getStepX(), -dir.getStepY(), 0);
player.getPulseManager().clear();
player.getWalkingQueue().reset();
PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(player));
e.lock(5);
if(e.isPlayer())
{
@ -70,4 +76,4 @@ public final class SpinningBlades implements MovementHook {
}
player.getPacketDispatch().sendSceneryAnimation(RegionManager.getObject(l), Animation.create(1107));
}
}
}

View file

@ -0,0 +1,68 @@
package content.global.skill.construction.decoration.skillhall
import content.global.skill.construction.HouseManager
import core.api.openDialogue
import core.game.dialogue.DialogueFile
import core.game.dialogue.FacialExpression
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.world.repository.Repository
import core.tools.END_DIALOGUE
import org.rs09.consts.NPCs
import org.rs09.consts.Scenery as Sceneries
class MountedCockatriceHead : InteractionListener {
override fun defineListeners() {
on(Sceneries.COCKATRICE_HEAD_13482, IntType.SCENERY, "talk-to") { player, _ ->
val house = player.getAttribute<HouseManager>("poh_entry", player.houseManager)
if (house == player.houseManager) {
openDialogue(player, MountedCockatriceHeadOwnerDialogue())
} else {
val ownerName = Repository.players.first { it.houseManager == house }.username.capitalize()
openDialogue(player, MountedCockatriceHeadGuestDialogue(ownerName))
}
return@on true
}
}
}
class MountedCockatriceHeadOwnerDialogue : DialogueFile() {
override fun handle(componentID: Int, buttonID: Int) {
when (stage) {
0 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_NORMAL, "You deaded me!").also { stage++ }
1 -> playerl(FacialExpression.NEUTRAL, "Well, yes.").also { stage++ }
2 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_NORMAL, "What did you do that for?").also { stage++ }
3 -> options("A Slayer Master told me to.", "So I could mount your head on my wall.", "I just wanted to.").also { stage++ }
4 -> when (buttonID) {
1 -> playerl(FacialExpression.NEUTRAL, "A Slayer Master told me to.").also { stage = 10 }
2 -> playerl(FacialExpression.NEUTRAL, "So I could mount your head on my wall.").also { stage = 20 }
3 -> playerl(FacialExpression.NEUTRAL, "I just wanted to.").also { stage = 30 }
}
10 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_SAD, "Why do the Slayer Masters all pick on poor cockatrice?").also { stage++ }
11 -> playerl(FacialExpression.NEUTRAL, "They pick on lots of other creatures too.").also { stage++ }
12 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_SAD, "Then mount one of them on your wall and let poor cockatrice rest in peace!").also { stage = END_DIALOGUE }
20 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_SAD, "Another cockatrice falls victim to the dreaded mirror shield!").also { stage++ }
21 -> playerl(FacialExpression.NEUTRAL, "Don't take it personally. You look good on my wall.").also { stage++ }
22 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_SAD, "I don't care! I think I looked better with a body!").also { stage = END_DIALOGUE }
30 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_ANGRY, "You dirty rotten swine, you!").also { stage++ }
31 -> playerl(FacialExpression.AMAZED, "Steady on.").also { stage++ }
32 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_ANGRY, "I will kill you with my paralyzing-type magic eyes look!").also { stage++ }
33 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_ANGRY, "Dots appear in air between eyes and victim. Dot! Dot! Dotty!").also { stage++ }
34 -> playerl(FacialExpression.NEUTRAL, "Er, nothing's happening...").also { stage++ }
35 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_ANGRY, "Concentrates mental power. Eyes narrow beak clenches veins on head stand out.").also { stage++ }
36 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_ANGRY, "Strain!").also { stage++ }
37 -> playerl(FacialExpression.ASKING, "You're dead, cockatrice. Your eyes are glass beads. It won't work.").also { stage++ }
38 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_ANGRY, "STRA-A-AIN!").also { stage++ }
39 -> playerl(FacialExpression.ASKING, "I think I'll leave you to it.").also { stage = END_DIALOGUE }
}
}
}
class MountedCockatriceHeadGuestDialogue(private val ownerName: String) : DialogueFile() {
override fun handle(componentID: Int, buttonID: Int) {
when (stage) {
0 -> playerl(FacialExpression.HAPPY, "Hey, a cockatrice!").also { stage++ }
1 -> npcl(NPCs.COCKATRICE_4227, FacialExpression.CHILD_SAD, "$ownerName deaded me! That wasn't very nice!").also { stage = END_DIALOGUE }
}
}
}

View file

@ -5,6 +5,7 @@ import content.global.skill.crafting.jewellery.JewelleryCrafting;
import core.game.interaction.NodeUsageEvent;
import core.game.interaction.UseWithHandler;
import core.plugin.Plugin;
import org.rs09.consts.Scenery;
/**
* Represents the plugin used to craft jewellery.
@ -17,7 +18,12 @@ public final class JewelleryCraftPlugin extends UseWithHandler {
/**
* Represents the ids to use for this plugin.
*/
private static final int[] IDS = new int[] { 4304, 6189, 11010, 11666, 12100, 12809, 18497, 26814, 30021, 30510, 36956, 37651 };
private static final int[] IDS = new int[]{
Scenery.FURNACE_4304, Scenery.FURNACE_6189, Scenery.FURNACE_11010, Scenery.FURNACE_11666,
Scenery.FURNACE_12100, Scenery.FURNACE_12809, Scenery.FURNACE_18497, Scenery.CLAY_FORGE_21303,
Scenery.FURNACE_26814, Scenery.FURNACE_30021, Scenery.FURNACE_30510, Scenery.FURNACE_36956,
Scenery.FURNACE_37651
};
/**
* Constructs a new {@code JewelleryCraftPlugin} {@code Object}.

View file

@ -79,7 +79,7 @@ class LightSourceLighter : UseWithHandler(590,36,38){
// For Temple of Ikov - if you are in the dark basement and light a light source, switch to the light basement.
// For the listener that covers the firemaking cape perk, see content.global.skill.skillcapeperks.SkillcapePerks.kt
if(event.player.location.isInRegion(10648) && event.player.location.withinDistance(Location(2639,9738,0), 8)) {
teleport(event.player, Location.create(event.player.getLocation().getX(), event.player.getLocation().getY() + 23, event.player.getLocation().getZ()))
teleport(event.player, Location.create(event.player.getLocation().x, event.player.getLocation().y + 23, event.player.getLocation().z))
closeDialogue(event.player)
// Dark basement is region 10648, min 2639 9738 0, max 2643 9744 0. Add 23 to the Y loc to tele to light basement
}

View file

@ -31,7 +31,7 @@ class SilverCraftingListener : InterfaceListener, InteractionListener {
Scenery.FURNACE_12100, Scenery.FURNACE_12809, Scenery.FURNACE_18497, Scenery.FURNACE_18525,
Scenery.FURNACE_18526, Scenery.FURNACE_21879, Scenery.FURNACE_22721, Scenery.FURNACE_26814,
Scenery.FURNACE_28433, Scenery.FURNACE_28434, Scenery.FURNACE_30021, Scenery.FURNACE_30510,
Scenery.FURNACE_36956, Scenery.FURNACE_37651
Scenery.FURNACE_36956, Scenery.FURNACE_37651, Scenery.CLAY_FORGE_21303
)
private const val ATTRIBUTE_FURNACE_ID = "crafting:silver:furnace_id"

View file

@ -270,7 +270,7 @@ class WoodcuttingListener : InteractionListener {
var amount = amount
var experience: Double = resource.getExperience()
val reward = resource.reward
if (player.getLocation().getRegionId() == 10300) {
if (player.getLocation().regionId == 10300) {
return 1.0
}

View file

@ -0,0 +1,25 @@
package content.global.skill.hunter
import core.api.getStatLevel
import core.game.node.entity.npc.NPC
import core.game.node.entity.skill.Skills
import core.game.node.item.Item
/**
* Handles the net trap node.
*/
class NetTrapNode(
npcIds: IntArray,
level: Int,
experience: Double,
rewards: Array<Item>,
private val summoningLevel: Int
) : TrapNode(npcIds, level, experience, intArrayOf(), rewards) {
override fun canCatch(wrapper: TrapWrapper, npc: NPC): Boolean {
if (getStatLevel(wrapper.player, Skills.SUMMONING) < summoningLevel) {
return false
}
return super.canCatch(wrapper, npc)
}
}

View file

@ -9,6 +9,8 @@ import core.tools.Log;
import core.tools.SystemLogger;
import core.game.world.map.Location;
import core.game.world.update.flag.context.Animation;
import org.rs09.consts.Items;
import org.rs09.consts.NPCs;
import java.util.ArrayList;
import java.util.List;
@ -33,16 +35,30 @@ public enum Traps {
}
}),
BOX_TRAP(new TrapSetting(10008, new int[] { 19187 }, new int[] { 1963, 12579, 1869, 9996, 5972, 12535 }, "lay", 19192, Animation.create(5208), new Animation(9726), 27),
new BoxTrapNode(new int[] { 5081 }, 27, 100, new Item[] { new Item(10092) }, 1),
new BoxTrapNode(new int[] { 6918, 7289, 7290, 7291, 7292 }, 27, 100, new Item[] { new Item(12184) }, 10),
new BoxTrapNode(new int[] { 1487 }, 27, 100, new Item[] { new Item(4033, 1) }, 95),
new BoxTrapNode(new int[] { 7021, 7022, 7023 }, 48, 150, new Item[] { new Item(12551, 1) }, 1),
new BoxTrapNode(new int[] { 5079 }, 53, 198, new Item[] { new Item(10033, 1) }, 1),
new BoxTrapNode(new int[] { 5428, 5430, 5449, 5450, 5451 }, 56, 150, new Item[] { new Item(12188) }, 1),
new BoxTrapNode(new int[] { 5080 }, 63, 265, new Item[] { new Item(10034, 1) }, 1),
new BoxTrapNode(new int[] { 7012, 7014 }, 66, 400, new Item[] { new Item(12535) }, 1),
new BoxTrapNode(new int[] { 8654 }, 73, 315, new Item[] { new Item(14861) }, 1),
new BoxTrapNode(new int[] { 7010, 7011 }, 77, 0, new Item[] { new Item(12539, 1) }, 1) {
new BoxTrapNode(new int[] { 5081 }, 27, 100, new Item[] { new Item(10092) }, 1), // Ferret
new BoxTrapNode(new int[] {NPCs.BABY_GECKO_6917 }, 27, 100, new Item[] { new Item(Items.BABY_GECKO_12488) }, 10), // Gecko Orange
new BoxTrapNode(new int[] {NPCs.BABY_GECKO_7285 }, 27, 100, new Item[] { new Item(Items.BABY_GECKO_12738) }, 10), // Gecko Speckled
new BoxTrapNode(new int[] {NPCs.BABY_GECKO_7286 }, 27, 100, new Item[] { new Item(Items.BABY_GECKO_12739) }, 10), // Gecko Green
new BoxTrapNode(new int[] {NPCs.BABY_GECKO_7287 }, 27, 100, new Item[] { new Item(Items.BABY_GECKO_12740) }, 10), // Gecko Blue
new BoxTrapNode(new int[] {NPCs.BABY_GECKO_7288 }, 27, 100, new Item[] { new Item(Items.BABY_GECKO_12741) }, 10), // Gecko Red
new BoxTrapNode(new int[] {NPCs.BABY_RACCOON_6997 }, 27, 100, new Item[] { new Item(Items.BABY_RACCOON_12486) }, 80), // Raccoon Gray
new BoxTrapNode(new int[] {NPCs.BABY_RACCOON_7275 }, 27, 100, new Item[] { new Item(Items.BABY_RACCOON_12734) }, 80), // Raccoon Tan
new BoxTrapNode(new int[] {NPCs.BABY_RACCOON_7276 }, 27, 100, new Item[] { new Item(Items.BABY_RACCOON_12736) }, 80), // Raccoon Red
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_6944 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12496) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7228 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12682) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7229 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12684) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7230 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12686) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7231 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12688) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7232 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12690) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7233 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12692) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7234 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12694) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7235 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12696) }, 95), // Monkey
new BoxTrapNode(new int[] { NPCs.BABY_MONKEY_7236 }, 27, 100, new Item[] { new Item(Items.BABY_MONKEY_12698) }, 95), // Monkey
new BoxTrapNode(new int[] { 7021, 7022, 7023 }, 48, 150, new Item[] { new Item(12551, 1) }, 1), // Platypus
new BoxTrapNode(new int[] { 5079 }, 53, 198, new Item[] { new Item(10033, 1) }, 1), // Chinchompa
new BoxTrapNode(new int[] { 5080 }, 63, 265, new Item[] { new Item(10034, 1) }, 1), // Red Chinchompa
new BoxTrapNode(new int[] { 7012, 7014 }, 66, 400, new Item[] { new Item(12535) }, 1), // Pawya
new BoxTrapNode(new int[] { 7010, 7011 }, 77, 0, new Item[] { new Item(12539, 1) }, 1) { // Grenwall
@Override
public boolean canCatch(TrapWrapper wrapper, final NPC npc) {
//old xp: 726
@ -59,11 +75,16 @@ public enum Traps {
new TrapNode(new int[] { 5086 }, 37, 204, new int[] { 19208, 19208, 19217 }, new Item[] { new Item(10105), new Item(526) }),
new TrapNode(new int[] { 7039 }, 44, 200, new int[] { 28939, 28940, 28941 }, new Item[] { new Item(12567), new Item(526) }),
new TrapNode(new int[] { 5087 }, 51, 200, new int[] { 19209, 19210, 19216 }, new Item[] { new Item(10109), new Item(526) })),
NET_TRAP(new NetTrapSetting(), new TrapNode(new int[] { 5117 }, 29, 152, new int[] {}, new Item[] { new Item(10149) }),
new TrapNode(new int[] { 5114 }, 47, 224, new int[] {}, new Item[] { new Item(10146) }),
new TrapNode(new int[] { 6921 }, 29, 152, new int[] {}, new Item[] { new Item(12130) }),
new TrapNode(new int[] { 5115 }, 59, 272, new int[] {}, new Item[] { new Item(10147) }),
new TrapNode(new int[] { 5116 }, 67, 304, new int[] {}, new Item[] { new Item(10148) }));
NET_TRAP(new NetTrapSetting(),
new NetTrapNode(new int[] { 5117 }, 29, 152, new Item[] { new Item(10149) }, 1), // Swamp Lizard
new NetTrapNode(new int[] { 5114 }, 47, 224, new Item[] { new Item(10146) }, 1), // Orange Salamander
new NetTrapNode(new int[] { NPCs.BABY_SQUIRREL_6921 }, 29, 152, new Item[] { new Item(Items.BABY_SQUIRREL_12490) }, 60), // Squirrel Gray
new NetTrapNode(new int[] { NPCs.BABY_SQUIRREL_7309 }, 29, 152, new Item[] { new Item(Items.BABY_SQUIRREL_12754) }, 60), // Squirrel Tan
new NetTrapNode(new int[] { NPCs.BABY_SQUIRREL_7310 }, 29, 152, new Item[] { new Item(Items.BABY_SQUIRREL_12756) }, 60), // Squirrel White
new NetTrapNode(new int[] { NPCs.BABY_SQUIRREL_7311 }, 29, 152, new Item[] { new Item(Items.BABY_SQUIRREL_12758) }, 60), // Squirrel Grayish/Brown w/e
new NetTrapNode(new int[] { NPCs.BABY_SQUIRREL_7312 }, 29, 152, new Item[] { new Item(Items.BABY_SQUIRREL_12760) }, 60), // Squirrel Brown
new NetTrapNode(new int[] { 5115 }, 59, 272, new Item[] { new Item(10147) }, 1), // Red Salamander
new NetTrapNode(new int[] { 5116 }, 67, 304, new Item[] { new Item(10148) }, 1)); // Black Salamander
/**
* The location hooks for this node.

View file

@ -37,6 +37,10 @@ import org.rs09.consts.Items
import org.rs09.consts.Sounds
import kotlin.math.floor
private const val DREAM_HEAL_INTERVAL_TICKS = 50
private const val DREAM_HARD_RESET_INTERVAL_TICKS = 90
private const val DREAM_HARD_RESET_DELAY_TICKS = 0
class LunarListeners : SpellListener("lunar"), Commands {
override fun defineListeners() {
@ -592,32 +596,46 @@ class LunarListeners : SpellListener("lunar"), Commands {
// Level 79
private fun dream(player: Player) {
if(player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) {
if (player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) {
sendMessage(player, "You have no need to cast this spell since your hitpoints are already full.")
throw IllegalStateException()
}
// https://runescape.wiki/w/Dream?oldid=880976 claims Dream makes you heal 1 hp every 20 seconds
// https://oldschool.runescape.wiki/w/Dream claims that Dream has its own timer, so the Dream heals don't need
// to align with the natural heals
val timer = getOrStartTimer<SkillRestore>(player)
val dreamStartAnimationTicks = animationDuration(Animation(Animations.LUNAR_SPELLBOOK_DREAM_START_6295))
var nextHealTick = getWorldTicks() + dreamStartAnimationTicks + DREAM_HEAL_INTERVAL_TICKS
var nextHardResetTick = -1
var waitingForHardReset = false
animate(player, Animations.LUNAR_SPELLBOOK_DREAM_START_6295)
removeRunes(player, true)
addXP(player, 82.0)
delayEntity(player, 4)
queueScript(player, 4, QueueStrength.WEAK) { stage: Int ->
delayEntity(player, dreamStartAnimationTicks)
queueScript(player, dreamStartAnimationTicks, QueueStrength.WEAK) { stage: Int ->
if (stage == 0) {
sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location)
playAudio(player, Sounds.LUNAR_SLEEP_3619)
return@queueScript delayScript(player, 5)
}
if (waitingForHardReset) {
waitingForHardReset = false
nextHardResetTick = getWorldTicks() + DREAM_HARD_RESET_INTERVAL_TICKS
} else if (nextHardResetTick != -1 && getWorldTicks() >= nextHardResetTick) {
// The 530 client eventually drops Dream back to idle if it only sees repeated 6296 updates
// Sending an explicit reset makes the next 6296 a full restart instead of a same-sequence refresh
resetAnimator(player)
waitingForHardReset = true
return@queueScript delayScript(player, DREAM_HARD_RESET_DELAY_TICKS)
} else if (nextHardResetTick == -1) {
nextHardResetTick = getWorldTicks() + DREAM_HARD_RESET_INTERVAL_TICKS
}
animate(player, Animations.LUNAR_SPELLBOOK_DREAM_MID_6296)
sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location)
// This heals 2 HP every min. Naturally you heal 1 for a total of 3
// The script steps every 5 ticks and we want 50 ticks before a heal
if (stage.mod(10) == 0) {
if (getWorldTicks() >= nextHealTick) {
val amt = timer.getHealAmount(player) //accounts for regen brace
heal(player, amt)
nextHealTick += DREAM_HEAL_INTERVAL_TICKS
if (player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) {
animate(player, Animations.LUNAR_SPELLBOOK_DREAM_END_6297)
return@queueScript stopExecuting(player)
@ -874,4 +892,3 @@ class LunarListeners : SpellListener("lunar"), Commands {
}

View file

@ -142,7 +142,7 @@ enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)?
// For Temple of Ikov - if you are in the dark basement and put on the firemaking cape with its 2009Scape light source perk, switch to the light basement.
// For the listener that teleports if you light a normal light source, see content.global.skill.crafting.lightsources.LightSourceLighter.kt
if(player.location.isInRegion(10648) && player.location.withinDistance(Location(2639,9738,0), 8)) {
teleport(player, Location.create(player.getLocation().getX(), player.getLocation().getY() + 23, player.getLocation().getZ()))
teleport(player, Location.create(player.getLocation().x, player.getLocation().y + 23, player.getLocation().z))
closeDialogue(player)
}
}

View file

@ -21,7 +21,6 @@ public enum Tasks {
ABERRANT_SPECTRES(65, new int[] { 1604, 1605, 1606, 1607, 7801, 7802, 7803, 7804 }, new String[] { "Aberrant Spectres are fetid, vile ghosts. The very", "smell of them will paralyse and harm you. A nose peg", "will help ignore their stink." }, 60, true, false),
ABYSSAL_DEMONS(85, new int[] { 1615 }, new String[] { "Abyssal Demons are nasty creatures to fight. They", "aren't really part of this realm, and are able to", "move very quickly to trap their prey."}, 85, false, false),
ANKOU(40, new int[] { 4381, 4382, 4383 }, new String[] { "Ankou are undead skeletal ghosts. They'll fight you", "up close but make sure to take advantage out of their", "limited defence." }, 1, true, false),
AVIANSIES(60, new int[] { 6245, 6243, 6235, 6232, 6244, 6246, 6233, 6241, 6238, 6237, 6240, 6242, 6239, 6234 }, new String[] { "Aviansies are bird-like creatures found in the icy", "dungeons of the north. Melee weapons can't reach them,", "so use Magic or Ranged attacks." }, 1, false, false),
BANSHEE(20, new int[] { 1612 }, new String[] { "Banshees use a piercing scream to shock their enemies.", "You'll need some earmuffs to protect yourself from them." }, 15, true, false),
BASILISKS(40, new int[] { 1616, 1617 }, new String[] { "Basilisks, like Cockatrice, have a gaze which will", "paralyse and harm their prey. You'll need a Mirror", "Shield to protect you." }, 40, false, false),
BATS(5, new int[] { 412, 78, 3711 }, new String[] { "Bats are rarely found on the ground, so you'll have", "to fight them while they're airborne, which won't be", "easy for melee." }, 1, false, false),
@ -107,12 +106,10 @@ public enum Tasks {
SPIRTUAL_WARRIORS(60, new int[] { 6219, 6229, 6255, 6277, }, new String[] { "Spiritual warriors can be found in the icy caverns near", "Trollheim, supporting the cause of their chosen god." }, 68, Quests.DEATH_PLATEAU),
STEEL_DRAGONS( 85,new int[] { 1592, 3590 }, new String[] { "Steel dragons are dangerous and metallic, with steel", "scales that are far thicker than normal steel armour. As", "you are an accomplished slayer, I am sure you'll be", "able to deal with them easily."}, 1, false, true),
SUQAHS (65, new int[] { 4527, 4528, 4529, 4530, 4531, 4532, 4533 }, new String[] { "Suqahs can only be found on the mystical Lunar Isle.", "They are capable of melee and magic attacks and often", "drop hide, teeth and herbs!" }, 1, Quests.LUNAR_DIPLOMACY),
// No access to Lair of Tarn Razorlor but this should be added as a task when there is access
TERROR_DOGS(1, new int[] { 5417, 5418 }, new String[] { "Terror dogs are the personal pets of Tarn Razorlor.", "Wherever you find him, you will find them. They are", "bad-tempered and generally unfriendly." }, 40, Quests.HAUNTED_MINE),
TROLLS(60, new int[] { 72, 3584, 1098, 1096, 1097, 1095, 1101, 1105, 1102, 1103, 1104, 1130, 1131, 1132, 1133, 1134, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1138, 1560, 1561, 1562, 1563, 1564, 1565, 1566, 1935, 1936, 1937, 1938, 1939, 1940, 1941, 1942, 3840, 3841, 3842, 3843, 3845, 1933, 1934, 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 391, 392, 393, 394, 395, 396}, new String[] { "Trolls regenerate damage quickly but are still", "vulnerable to poisons, they usually use crushing", "weapons." }, 1, false, false),
TUROTHS(60, new int[] { 1622, 1611, 1623, 1626, 1627, 1628, 1629, 1630, 7800}, new String[] { "Turoth are large vicious creatures with thick hides.", "You'll need a Leaf-Tipped Spear Sword or Battle-axe,", "Broad Arrows, or a Magic Dart to harm them." }, 55, false, false),
VAMPIRES(35, new int[] { 1220, 1223, 1225, 6214 }, new String[] { "Vampires are extremely powerful beings. They feed on", "the blood of the living so watch out you don't", "get bitten." }, 1, false, false),
// Waiting for the wall beast movement bug to be fixed before adding this as a task
WALL_BEASTS(1, new int[] { 7823 }, new String[] { "Wall Beasts are really much larger creatures but", "you'll only see their arms. You'll want something", "sharp on your head to stop them grabbing you." }, 35, false, false ),
// No way to get to Poison Swamp Cave
// If a grapple is implemented from W Castle Wars to E Posion Swamps
@ -124,15 +121,6 @@ public enum Tasks {
WEREWOLVES(60, new int[] { 6006, 6007, 6008, 6009, 6010, 6011, 6012, 6013, 6014, 6015, 6016, 6017, 6018, 6019, 6020, 6021, 6022, 6023, 6024, 6025, 6212, 6213, 6607, 6609, 6614, 6617, 6625, 6632, 6644, 6663, 6675, 6686, 6701, 6712, 6724, 6728, }, new String[] { "Werewolves are feral creatures, they are strong and", "tough with sharp claws and teeth." }, 1, false, false),
WOLVES(20, new int[] { 95, 96, 97, 141, 142, 143, 839, 1198, 1330, 1558, 1559, 1951, 1952, 1953, 1954, 1955, 1956, 4413, 4414, 6046, 6047, 6048, 6049, 6050, 6051, 6052, 6829, 6830, 7005 }, new String[] { "Wolves are pack animals, so you'll always find them", "in groups. Watch out for their bite, it can be nasty." }, 1, false, false),
ZOMBIES(10, new int[] { 73, 74, 75, 76, 2060, 2714, 2863, 2866, 2869, 2878, 3622, 4392, 4393, 4394, 5293, 5294, 5295, 5296, 5297, 5298, 5299, 5300, 5301, 5302, 5303, 5304, 5305, 5306, 5307, 5308, 5309, 5310, 5311, 5312, 5313, 5314, 5315, 5316, 5317, 5318, 5319, 5320, 5321, 5322, 5323, 5324, 5325, 5326, 5327, 5328, 5329, 5330, 5331, 5375, 5376, 5377, 5378, 5379, 5380, 5393, 5394, 5395, 5396, 5397, 5398, 5399, 5400, 5401, 5402, 5403, 5404, 5405, 5406, 5407, 5408, 5409, 5410, 6099, 6100, 6131, 8149, 8150, 8151, 8152, 8153, 8159, 8160, 8161, 8162, 8163, 8164, 2044, 2045, 2046, 2047, 2048, 2049, 2050, 2051, 2052, 2053, 2054, 2055, 7641, 1465, 1466, 1467, 2837, 2838, 2839, 2840, 2841, 2842, 5629, 5630, 5631, 5632, 5633, 5634, 5635, 5636, 5637, 5638, 5639, 5640, 5641, 5642, 5643, 5644, 5645, 5646, 5647, 5648, 5649, 5650, 5651, 5652, 5653, 5654, 5655, 5656, 5657, 5658, 5659, 5660, 5661, 5662, 5663, 5664, 5665, 2843, 2844, 2845, 2846, 2847, 2848}, new String[] { "Zombies are undead so magic is your best bet against", "them, there is even a spell specially for", "fighting the undead." }, 1, true, false),
// Bosses need to be handled differently/have a source. They should probably be removed from here
JAD(90, new int[] { }, new String[] { "You must complete the entire fight cave, including", "TzTok-Jad. Beware, only those skilled in combat should", "attempt this and death will reset your task." }, 1, false, false),
CHAOS_ELEMENTAL(90, new int[] { 3200 }, new String[] { "The Chaos Elemental roams the deepest Wilderness. It", "can teleport you and make you unequip items randomly." }, 1, false, false),
GIANT_MOLE(75, new int[] { 3340 }, new String[] { "Dig on the mole-hills in Falador Park to enter the", "mole cave, and bring a lantern he can't extinguish." }, 1, false, false),
KING_BLACK_DRAGON(75, new int[] { 50 }, new String[] { "The King Black Dragon's cave is reached by pulling a", "lever in the Wilderness. An anti-fire shield is", "strongly recommended." }, 1, false, true),
COMMANDER_ZILYANA(90, new int[] { 6247 }, new String[] { "Commander Zilyana is in the God Wars Dungeon.", "She frequently uses magic attacks." }, 1, false, false),
GENERAL_GRARDOOR(90, new int[] { 6260 }, new String[] { "General Graardor is in the God Wars Dungeon.", "He uses melee and ranged attacks." }, 1, false, false),
KRIL_TSUTSAROTH(90, new int[] { 6203 }, new String[] { "K'ril Tsutsaroth is in the God Wars Dungeon.", "He's poisonous and he can hit through prayers." }, 1, false, false),
KREE_ARRA(90, new int[] { 6222 }, new String[] { "Kree'arra roosts in the God Wars Dungeon.", "Melee attacks don't work on flying creatures,", "and you'll need a grappling hook to get in." }, 1, false, false),
;
static final HashMap<Integer, Tasks> taskMap = new HashMap<>();

View file

@ -106,7 +106,7 @@ public enum SmithingType {
/**
* Grapple Tips
*/
TYPE_GRAPPLE_TIP(1, 170, 171, new int[] { 175, 176, 175, 174, 173 }, 1),
TYPE_GRAPPLE_TIP(1, 170, 171, new int[] { 176, 175, 174, 173 }, 1),
/**
* The studs type.

View file

@ -82,6 +82,8 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
* The amount of special points left.
*/
protected int specialPoints = 60;
public static final String INFINITE_SPECIAL_MOVE_ATTRIBUTE = "infinite-special-move";
/**
* The pouch id.
@ -494,7 +496,9 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
owner.getInventory().remove(new Item(scroll.getItemId()));
playAudio(owner, Sounds.SPELL_4161);
visualizeSpecialMove();
updateSpecialPoints(specialCost);
if (!owner.getAttribute(INFINITE_SPECIAL_MOVE_ATTRIBUTE, false)) {
updateSpecialPoints(specialCost);
}
owner.getSkills().addExperience(Skills.SUMMONING, scroll.getExperience(), true);
}
return true;

View file

@ -1,5 +1,6 @@
package content.global.skill.summoning.familiar;
import content.data.Quests;
import content.global.skill.summoning.pet.Pet;
import content.global.skill.summoning.pet.Pets;
import core.cache.def.impl.ItemDefinition;
@ -31,6 +32,15 @@ import static core.api.ContentAPIKt.*;
*/
public final class FamiliarManager {
/**
* These varbits are packed into varp 1160. Legacy code added 243269632 directly to the raw varp,
* which toggled the Summoning orb visibility on every login.
* They are now written as individual varbits, but two of them remain unknown.
*/
private static final int VARBIT_SUMMONING_ORB_VISIBILITY = 4280;
private static final int VARBIT_SUMMONING_UNKNOWN1 = 4281;
private static final int VARBIT_SUMMONING_UNKNOWN2 = 4282;
/**
* The familiars mapping.
*/
@ -158,7 +168,11 @@ public final class FamiliarManager {
if (hasFamiliar()) {
familiar.init();
}
player.getFamiliarManager().setConfig(243269632);
boolean unlocked = player.getQuestRepository().isComplete(Quests.WOLF_WHISTLE);
setVarbit(player, VARBIT_SUMMONING_ORB_VISIBILITY, unlocked ? 1 : 0);
setVarbit(player, VARBIT_SUMMONING_UNKNOWN1, 0);
setVarbit(player, VARBIT_SUMMONING_UNKNOWN2, 7);
}
/**
@ -456,16 +470,6 @@ public final class FamiliarManager {
return true;
}
/**
* Sets a config value.
* @param value the value.
*/
public void setConfig(int value) {
int current = getVarp(player, 1160);
int newVal = current + value;
setVarp(player, 1160, newVal);
}
/**
* Gets the familiar.
* @return The familiar.

View file

@ -151,7 +151,6 @@ public final class BarrowsActivityPlugin extends ActivityPlugin {
Player player = (Player) e;
PacketRepository.send(MinimapState.class, new MinimapStateContext(player, 2));
player.getInterfaceManager().openOverlay(OVERLAY);
setVarp(player, 0, 1);
if (getVarp(player, 452) == 0) {
shuffleCatacombs(player);
}

View file

@ -78,7 +78,7 @@ object RewardChest {
InterfaceContainer.generateItems(player, rewards.toTypedArray(), arrayOf("Examine"), 364, 4, 3, 4)
player.interfaceManager.open(Component(Components.TRAIL_REWARD_364))
BossKillCounter.addtoBarrowsCount(player)
BossKillCounter.addToBarrowsChestCount(player)
for (item in rewards) {
announceIfRare(player, item)
if (!player.inventory.add(item)) {

View file

@ -1,11 +1,13 @@
package content.region.asgarnia.burthorpe.quest.deathplateau
import content.data.Quests
import content.global.handlers.iface.ScrollInterface
import core.api.*
import core.game.global.action.DoorActionHandler
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.item.GroundItemManager
import org.rs09.consts.Components
import org.rs09.consts.Items
import org.rs09.consts.Scenery
@ -23,6 +25,11 @@ class DeathPlateauInteractionListener : InteractionListener {
Scenery.STONE_MECHANISM_3676, // 4 Outer stone plates
Scenery.STONE_MECHANISM_3677 // 2 Inner stone plates
)
val combinationScroll = arrayOf(
"Red is North of Blue. Yellow is South of Purple.",
"Green is North of Purple. Blue is West of",
"Yellow. Purple is East of Red."
)
}
override fun defineListeners() {
on(Scenery.DOOR_3747, SCENERY, "open") { player, node ->
@ -48,10 +55,10 @@ class DeathPlateauInteractionListener : InteractionListener {
}
on(Items.COMBINATION_3102, ITEM, "read") { player, _ ->
openInterface(player, 220)
setInterfaceText(player, "<col=3D1E00>Red is North of Blue. Yellow is South of Purple.", 220, 7)
setInterfaceText(player, "<col=3D1E00>Green is North of Purple. Blue is West of", 220, 8)
setInterfaceText(player, "<col=3D1E00>Yellow. Purple is East of Red.", 220, 9)
// https://youtu.be/C8n8Yi-J8wU?t=466
openInterface(player, 222)
setInterfaceText(player, combinationScroll.joinToString("<br>"), 222, 5)
return@on true
}

View file

@ -4,6 +4,9 @@ import content.data.Quests
import core.api.*
import core.game.dialogue.DialogueFile
import core.game.dialogue.FacialExpression
import core.game.interaction.IntType
import core.game.interaction.InteractionListeners
import core.game.node.item.Item
import core.tools.END_DIALOGUE
import org.rs09.consts.Items
@ -26,10 +29,7 @@ class IOUNoteDialogueFile : DialogueFile() {
4 -> {
end()
stage = END_DIALOGUE
openInterface(player!!, 220)
setInterfaceText(player!!, "<col=3D1E00>Red is North of Blue. Yellow is South of Purple.", 220, 7)
setInterfaceText(player!!, "<col=3D1E00>Green is North of Purple. Blue is West of", 220, 8)
setInterfaceText(player!!, "<col=3D1E00>Yellow. Purple is East of Red.", 220, 9)
InteractionListeners.run(Items.COMBINATION_3102, IntType.ITEM, "read", player!!, Item(Items.COMBINATION_3102))
}
}
}

View file

@ -299,7 +299,7 @@ public final class GiantMoleNPC extends AbstractNPC {
super.finalizeDeath(killer);
if (killer instanceof Player) {
Player player = killer.asPlayer();
BossKillCounter.addtoKillcount(player, this.getId());
BossKillCounter.addToBossKillCount(player, this.getId());
}
}

View file

@ -14,6 +14,7 @@ import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import core.plugin.Initializable
import core.tools.RandomFunction
import core.tools.colorize
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import org.rs09.consts.Scenery
@ -119,9 +120,8 @@ class MudSkipperPointListeners : InteractionListener {
}
on(Scenery.SIGNPOST_10090, IntType.SCENERY, "read") { player, _ ->
setInterfaceText(player, "Mudskipper Point.", 220, 2)
setInterfaceText(player, "WARNING! BEWARE OF THE MUDSKIPPERS!", 220, 4)
openInterface(player, 220)
// https://runescape.wiki/w/Mudskipper_Point?oldid=969991
sendDialogue(player, colorize("%RBEWARE OF THE MUDSKIPPERS!"))
return@on true
}

View file

@ -23,6 +23,11 @@ public class WolfWhistle extends Quest {
*/
public static final Item WOLF_BONES = new Item(2859, 2);
/**
* Gates the visibility of the Summoning orb next to the minimap behind quest completion.
*/
private static final int VARBIT_SUMMONING_ORB_VISIBILITY = 4280;
/**
* Constructs a new {@code WolfWhistle} {@code Object}.
*/
@ -164,6 +169,7 @@ public class WolfWhistle extends Quest {
player.getSkills().addExperience(Skills.SUMMONING, 276);
player.getInventory().add(new Item(12158, 275), player);
player.removeAttribute("searched-body");
setVarbit(player, VARBIT_SUMMONING_ORB_VISIBILITY, 1);
player.getQuestRepository().syncronizeTab(player);
player.getInterfaceManager().openInfoBars();
}

View file

@ -202,7 +202,7 @@ public final class TrollheimPlugin extends OptionHandler {
switch (id) {
case 32738:
if (loc.equals(new Location(2892, 10072, 0))) {
player.getProperties().setTeleportLocation(LOCATIONS[0]);
player.getProperties().setTeleportLocation(LOCATIONS[9]);
return true;
}
player.teleport(LOCATIONS[1]);

View file

@ -231,7 +231,7 @@ public final class GodwarsBossNPC extends AbstractNPC {
public void finalizeDeath(Entity killer) {
super.finalizeDeath(killer);
if (getId() == 6222 || getId() == 6260 || getId() == 6247 || getId() == 6203) {
BossKillCounter.addtoKillcount((Player) killer, this.getId());
BossKillCounter.addToBossKillCount((Player) killer, this.getId());
}
if (minions == null) {
return;

View file

@ -97,10 +97,10 @@ public final class KalphiteQueenNPC extends AbstractNPC {
removeAttribute("disable:drop");
super.finalizeDeath(killer);
reTransform();
BossKillCounter.addtoKillcount((Player) killer, 1160);
BossKillCounter.addToBossKillCount((Player) killer, 1160);
return;
}
BossKillCounter.addtoKillcount((Player) killer, this.getId());
BossKillCounter.addToBossKillCount((Player) killer, this.getId());
setAttribute("disable:drop", true);
super.finalizeDeath(killer);
super.setRespawnTick(-1);

View file

@ -74,7 +74,7 @@ class MysteriousGhostRennardDialogueFile : DialogueLabeller() {
player("Why don't you tell me what happened? I might be able to help...")
npc("Well, I was making me merry way along, having just pulled off a glorious jewellery heist from a bunch of stinking dwarves...")
player("Hey, that's no way to talk about dwarves! Some of my best friends are short!")
npc("Ah, yer misunderstand me [lad/lass], I wasn't generalising about the whole dwarf species, I had just stolen a bundle of jewels from a very specific group of dwarves who happened to have an odious stench about them!")
npc("Ah, yer misunderstand me @g[lad,lass], I wasn't generalising about the whole dwarf species, I had just stolen a bundle of jewels from a very specific group of dwarves who happened to have an odious stench about them!")
player("Oh. Well I guess that's okay then. Please continue.")
npc("Well, as I headed on me merry way, hoping the foul odours that lingered in me nostrils would soon pass, I see in front of me this explorer fella, all decked out in in his fine clothing, and carrying some long package")
npc("bundled in rags.")

View file

@ -175,7 +175,7 @@ class MysteriousGhostViggoraDialogueFile : DialogueLabeller() {
npc("Hello yourself.")
player("I really liked your little story. Can you tell me another one?")
player("Preferably something with a big fight and lots of explosions!")
npc("...You are a very strange young @g[man/woman].")
npc("...You are a very strange young @g[man,woman].")
label("lostghostlything")
player(ChatAnim.SAD, "Can I have that cloak back?")

View file

@ -9,6 +9,7 @@ import org.rs09.consts.NPCs
import core.game.dialogue.DialogueBuilder
import core.game.dialogue.DialogueBuilderFile
import content.data.Quests
import content.global.handlers.iface.ScrollLine
@Initializable
public final class ClayGolemDialoguePlugin(player: Player? = null) : DialoguePlugin(player) {
@ -107,19 +108,18 @@ class CuratorHaigHalenGolemDialogue : DialogueBuilderFile() {
}
}
// https://youtu.be/owPSLUB9E8k?t=261
val LETTER_LINES = arrayOf(
"",
"",
"Dearest Varmen,",
"I hope this finds you well. Here are the books you asked for",
"There has been an exciting development closer to home --",
"another city from the same period has been discovered east",
"of Varrock, and we are starting a huge excavation project",
"here. I don't know if the museum will be able to finance your",
"expedition as well as this one, so I fear your current trip will be",
"the last.",
"May Saradomin grant you a safe journey home",
"Your loving Elissa.",
ScrollLine("Dearest Varmen,", 3),
ScrollLine("I hope this finds you well. Here are the books you asked for", 4),
ScrollLine("There has been an exciting development closer to home --", 5),
ScrollLine("another city from the same period has been discovered east", 6),
ScrollLine("of Varrock, and we are starting a huge excavation project", 7),
ScrollLine("here. I don't know if the museum will be able to finance your", 8),
ScrollLine("expedition as well as this one, so I fear your current trip will be", 9),
ScrollLine("the last.", 10),
ScrollLine("May Saradomin grant you a safe journey home", 11),
ScrollLine("Your loving Elissa.", 12)
)
val DISPLAY_CASE_TEXT = arrayOf("3rd age - yr 3000-4000",

View file

@ -20,6 +20,8 @@ import core.game.interaction.InteractionListener
import core.game.interaction.InterfaceListener
import core.game.world.GameWorld
import content.data.Quests
import content.global.handlers.iface.ScrollInterface
import org.rs09.consts.Components
@Initializable
class TheGolemQuest : Quest(Quests.THE_GOLEM, 70, 69, 1, 437, 0, 1, 10) {
@ -112,19 +114,6 @@ class ClayGolemNPC : AbstractNPC {
}
}
class LetterListener : InterfaceListener {
override fun defineInterfaceListeners() {
onOpen(220) { player, component ->
val lines: Array<String> = player.getAttribute("ifaces:220:lines", arrayOf())
for(i in 0 until Math.min(lines.size, 15)) {
setInterfaceText(player, lines[i], 220, i+1)
//setInterfaceText(player, "${i}", 220, i+1)
}
return@onOpen true
}
}
}
class DisplayCaseListener : InterfaceListener {
override fun defineInterfaceListeners() {
onOpen(534) { player, component ->
@ -372,9 +361,8 @@ class TheGolemListeners : InteractionListener {
on(34978, IntType.SCENERY, "climb-down") { player, node -> core.game.global.action.ClimbActionHandler.climb(player, core.game.global.action.ClimbActionHandler.CLIMB_DOWN, core.game.global.action.SpecialLadders.getDestination(node.location)); return@on true }
on(6372, IntType.SCENERY, "climb-up") { player, node -> core.game.global.action.ClimbActionHandler.climb(player, core.game.global.action.ClimbActionHandler.CLIMB_UP, core.game.global.action.SpecialLadders.getDestination(node.location)); return@on true }
on(4615, IntType.ITEM, "read") { player, node ->
player.setAttribute("ifaces:220:lines", LETTER_LINES)
player.setAttribute("/save:the-golem:read-elissa-letter", true)
openInterface(player, 220)
ScrollInterface.scrollSetup(player, Components.MESSAGESCROLL_220, LETTER_LINES)
return@on true
}
on(35226, IntType.SCENERY, "search") { player, node ->

View file

@ -97,7 +97,7 @@ public final class DagannothKingNPC extends AbstractNPC {
public void finalizeDeath(Entity killer) {
super.finalizeDeath(killer);
if (getId() == 2881 || getId() == 2882 || getId() == 2883) {
BossKillCounter.addtoKillcount((Player) killer, this.getId());
BossKillCounter.addToBossKillCount((Player) killer, this.getId());
}
}

View file

@ -406,16 +406,17 @@ class PlagueCityListeners : InteractionListener {
}
}
// https://youtu.be/AG81LH51WME?t=302
private fun scruffyNote(player: Player) {
val scruffynotes =
arrayOf(
"Got a bncket of nnilk",
"Tlen grind sorne lhoculate",
"Tlen qrind sorne lhoculate",
"vnith a pestal and rnortar",
"ald the grourd dlocolate to tho milt",
"finales add 5cme snape gras5",
"fnales add 5cme snape gras5",
)
setInterfaceText(player, scruffynotes.joinToString("<br>"), 222, 5)
setInterfaceText(player, scruffynotes.joinToString("<br>"), 222, 4)
}
override fun defineDestinationOverrides() {

View file

@ -44,6 +44,7 @@ public class DwarfCannonPlugin extends OptionHandler {
SceneryDefinition.forId(3).getHandlers().put("option:open", this);
SceneryDefinition.forId(5).getHandlers().put("option:inspect", this);
SceneryDefinition.forId(2).getHandlers().put("option:enter", this);
SceneryDefinition.forId(11).getHandlers().put("option:climb-up", this);
SceneryDefinition.forId(13).getHandlers().put("option:climb-over", this);
SceneryDefinition.forId(15601).getHandlers().put("option:inspect", this);
for (int i = 15; i < 21; i++) {
@ -110,6 +111,13 @@ public class DwarfCannonPlugin extends OptionHandler {
player.animate(Animation.create(845));
player.teleport(new Location(2619, 9797, 0), 2);
break;
case 11:
if (node.getLocation().equals(new Location(2570, 3443, 1))
&& quest.getStage(player) == 20
&& !player.hasItem(DwarfCannon.DWARF_REMAINS)) {
setVarp(player, 0, 3, true);
}
return ClimbActionHandler.climbLadder(player, node.asScenery(), option);
case 13:
ClimbActionHandler.climb(player, new Animation(828), new Location(2623, 3391, 0));
break;

View file

@ -4,7 +4,6 @@ import content.global.handlers.iface.BookInterface
import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player

View file

@ -15,6 +15,9 @@ import org.rs09.consts.Items
import org.rs09.consts.NPCs
import org.rs09.consts.Sounds
import content.data.Quests
import content.global.handlers.iface.ScrollInterface
import content.global.handlers.iface.ScrollLine
import org.rs09.consts.Components
class GrandTreeListeners: InteractionListener {
@ -26,12 +29,11 @@ class GrandTreeListeners: InteractionListener {
Location(2473,9897,0),
)
val hazelmerescroll = Items.HAZELMERES_SCROLL_786
val hazelmerescrollText = arrayOf(
"<col=FFF900>Es lemanto meso pro eis prit ta Cinqo mond.</col>",
"<col=FFF900>Mi lovos ta lemanto Daconia arpos</col>",
"<col=FFF900>et Daconia arpos eto meriz ta priw!</col>",
// https://youtu.be/-jfueZpRhzU?t=184
val hazelmereScrollText = arrayOf(
ScrollLine("Es lemanto meso pro eis prit ta Cinqo mond.", 6),
ScrollLine("Mi lovos ta lemanto Daconia arpos", 7),
ScrollLine("et Daconia arpos eto meriz ta priw!", 8)
)
fun unlockTUZODoor(player: Player) {
@ -84,8 +86,8 @@ class GrandTreeListeners: InteractionListener {
openDialogue(player, AnitaDialogue(), npc)
return@on true
}
on(hazelmerescroll, IntType.ITEM, "read") { player, node ->
hazelmereScroll(player, node.asItem())
on(Items.HAZELMERES_SCROLL_786, IntType.ITEM, "read") { player, node ->
ScrollInterface.scrollSetup(player, Components.MESSAGESCROLL_220, hazelmereScrollText)
return@on true
}
@ -209,14 +211,5 @@ class GrandTreeListeners: InteractionListener {
return@on true
}
}
private fun hazelmereScroll(player: Player, item: Item) {
val id = item.id
openInterface(player, 222).also {
when (id) {
hazelmerescroll -> setInterfaceText(player, hazelmerescrollText.joinToString("<br>"), 222, 6)
}
}
}
}

View file

@ -34,7 +34,7 @@ class TribalTotemListeners : InteractionListener {
on(realCrate, IntType.SCENERY, "Investigate"){ player, node ->
if(player.questRepository.getStage(Quests.TRIBAL_TOTEM) in 1..19 && !player.inventory.containsAtLeastOneItem(Items.ADDRESS_LABEL_1858)){
sendDialogue(player,"There is a label on this crate. It says; To Lord Handelmort, Handelmort Mansion Ardogune.You carefully peel it off and take it.")
sendDialogue(player,"There is a label on this crate. It says; To Lord Handelmort, Handelmort Mansion, Ardougne.You carefully peel it off and take it.")
addItemOrDrop(player,Items.ADDRESS_LABEL_1858,1)
}
else if(player.questRepository.getStage(Quests.TRIBAL_TOTEM) in 1..19 && player.inventory.containsAtLeastOneItem(Items.ADDRESS_LABEL_1858)){

View file

@ -1,6 +1,5 @@
package content.region.karamja.tzhaar.handlers;
import content.global.skill.slayer.SlayerManager;
import core.game.event.NPCKillEvent;
import core.game.activity.ActivityPlugin;
import content.data.BossKillCounter;
@ -10,8 +9,6 @@ import core.game.node.entity.Entity;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.diary.DiaryType;
import core.game.node.entity.skill.Skills;
import content.global.skill.slayer.Tasks;
import core.game.node.item.GroundItemManager;
import core.game.node.item.Item;
import core.game.node.scenery.Scenery;
@ -23,6 +20,7 @@ import core.plugin.Initializable;
import core.tools.RandomFunction;
import core.game.world.GameWorld;
import core.game.world.repository.Repository;
import org.rs09.consts.NPCs;
import java.util.ArrayList;
import java.util.List;
@ -147,11 +145,6 @@ public final class TzhaarFightCavesPlugin extends ActivityPlugin {
public void leave(Player player, int wave) {
activeNPCs.clear();
player.getProperties().setTeleportLocation(getSpawnLocation());
player.getSkills().restore();
player.timers.removeTimer("poison");
player.timers.removeTimer("disease");
player.timers.removeTimer("teleblock");
player.getSettings().updateRunEnergy(-100);
boolean practice = player.getAttribute("fc_practice_jad", false);
if (wave == 63) {
if (!practice) {
@ -167,12 +160,7 @@ public final class TzhaarFightCavesPlugin extends ActivityPlugin {
}
player.getPacketDispatch().sendMessage("You were victorious!");
if (!practice) {
BossKillCounter.addtoKillcount(player, 2745);
if (SlayerManager.getInstance(player).getTask() == Tasks.JAD) {
player.getSkills().addExperience(Skills.SLAYER, 25000);
SlayerManager.getInstance(player).clear();
player.sendMessage("You receive 25,000 slayer experience for defeating TzTok-Jad.");
}
BossKillCounter.addToBossKillCount(player, NPCs.TZTOK_JAD_2745);
player.getDialogueInterpreter().sendDialogues(2617, null, "You even defeated TzTok-Jad, I am most impressed!", "Please accept this gift as a reward.");
Repository.sendNews(player.getUsername() + " has been victorious in defeating TzTok-Jad for a firecape!");
} else {

View file

@ -178,8 +178,8 @@ public final class CharacterDesign {
case 40:
player.getSettings().toggleMouseButton();
break;
case 92://hair style
case 93:
case 92://hair style left arrow
case 93://hair style right arrow
changeLook(player, 0, buttonId == 93);
break;
case 97:
@ -348,18 +348,23 @@ public final class CharacterDesign {
private static int getValue(Player player, String key, int index, int currentIndex, boolean increment) {
int[] array = player.getAttribute("male", player.getAppearance().isMale()) ? MALE_LOOK_IDS[index] : FEMALE_LOOK_IDS[index];
int val = 0;
if (increment && currentIndex + 1 > array.length -1) {
val = (int) array[0];
currentIndex = 0;
} else if (!increment && currentIndex -1 < 0) {
val = (int) array[array.length -1];
currentIndex = array.length -1;
} else if (increment) {
val = (int) array[currentIndex + 1];
currentIndex++;
if (player.isArtificial() && index <= 1) {
currentIndex = RandomFunction.random(array.length -1);
val = array[currentIndex];
} else {
val = (int) array[currentIndex - 1];
currentIndex--;
if (increment && currentIndex + 1 > array.length -1) {
val = (int) array[0];
currentIndex = 0;
} else if (!increment && currentIndex -1 < 0) {
val = (int) array[array.length -1];
currentIndex = array.length -1;
} else if (increment) {
val = (int) array[currentIndex + 1];
currentIndex++;
} else {
val = (int) array[currentIndex - 1];
currentIndex--;
}
}
player.setAttribute(key + ":" + index, currentIndex);
return val;

View file

@ -31,7 +31,7 @@ package content.region.misthalin.barbvillage.stronghold.playersafety
SceneryConst.JAIL_DOOR_29601 to 698,
)
private val creviceClimbedAttribute = "player_strong:crevice_climbed"
private val creviceClimbedAttribute = "/save:player_strong:crevice_climbed"
}
override fun defineListeners() {

View file

@ -29,7 +29,7 @@ class CaveGoblinMinerNPC(id: Int = 0, location: Location? = null) : AbstractNPC(
mining = (id > 2074)
if(properties.combatPulse.isAttacking && mining){
transform(id - 6)
transformWithHpCarryover(id - 6)
this.isWalks = true
this.walkRadius = 4
this.isNeverWalks = false

View file

@ -31,10 +31,10 @@ class DraynorManorHouseZone : MapZone("Draynor Manor House", true), Plugin<Any?>
if (n.shouldPreventStacking(e)) {
val s1 = e.size()
val s2 = n.size()
val x = destination.getX()
val y = destination.getY()
val x = destination.x
val y = destination.y
val l = n.getLocation()
if (Pathfinder.isStandingIn(x, y, s1, s1, l.getX(), l.getY(), s2, s2)) {
if (Pathfinder.isStandingIn(x, y, s1, s1, l.x, l.y, s2, s2)) {
return false
}
}

View file

@ -184,7 +184,7 @@ public class TormentedDemonNPC extends AbstractNPC {
super.finalizeDeath(killer);
reTransform();
fireShield = true;
BossKillCounter.addtoKillcount((Player) killer, this.getId());
BossKillCounter.addToBossKillCount((Player) killer, this.getId());
}
@Override

View file

@ -3,6 +3,7 @@ package content.region.morytania.canifis.handlers
import core.api.*
import core.game.interaction.QueueStrength
import core.game.node.entity.Entity
import core.game.node.entity.skill.Skills
import core.game.node.entity.combat.BattleState
import core.game.node.entity.combat.DeathTask
import core.game.node.entity.npc.NPC
@ -38,7 +39,9 @@ class WerewolfBehavior : NPCBehavior(*HUMAN_NPCS) {
return@queueScript delayScript(self, WEREWOLF_IN_ANIMATION.duration)
}
1 -> {
transformNpc(self, WEREWOLF_NPCS[self.id - 6026], 200)
val humanMaxLp = self.getSkills().getMaximumLifepoints()
transformNpcWithHpCarryover(self, WEREWOLF_NPCS[self.id - 6026], 200)
self.getSkills().heal(self.getSkills().getMaximumLifepoints() - humanMaxLp)
return@queueScript delayScript(self, 1)
}
2 -> {

View file

@ -14,6 +14,8 @@ import core.game.interaction.InteractionListener
import core.game.interaction.IntType
import kotlin.random.Random
import content.data.Quests
import core.game.interaction.InterfaceListener
import org.rs09.consts.Components
/**
* File to be used for anything Morytania related.
@ -40,12 +42,15 @@ class MorytaniaListeners : InteractionListener {
on(SWAMP_GATES, IntType.SCENERY, "open"){ player, node ->
if(player.location.y == 3457){
core.game.global.action.DoorActionHandler.handleAutowalkDoor(player, node.asScenery())
sendMessage(player, "You skip gladly out of murky Mort Myre.")
removeTimer<SwampDecayTimer>(player)
GlobalScope.launch {
findLocalNPC(player, NPCs.ULIZIUS_1054)?.sendChat("Oh my! You're still alive!", 2)
}
} else {
if (player.questRepository.hasStarted(Quests.NATURE_SPIRIT)) {
core.game.global.action.DoorActionHandler.handleAutowalkDoor(player, node.asScenery())
setAttribute(player, "swampgate", node)
openInterface(player, Components.CWS_WARNING_20_580)
} else {
sendNPCDialogue(
player,
@ -97,4 +102,24 @@ class MorytaniaListeners : InteractionListener {
return@on true
}
}
}
class warningInterface : InterfaceListener {
override fun defineInterfaceListeners() {
on(Components.CWS_WARNING_20_580) { player, _, _, buttonID, _, _ ->
when(buttonID) {
17 -> {
val gate = getAttribute(player, "swampgate", null) as? core.game.node.scenery.Scenery
if (gate != null) {
core.game.global.action.DoorActionHandler.handleAutowalkDoor(player, gate)
sendMessage(player, "You walk into the gloomy atmosphere of Mort Myre.")
}
closeInterface(player)
}
18 -> closeInterface(player)
}
removeAttribute(player, "swampgate")
return@on true
}
}
}

View file

@ -1,30 +0,0 @@
package content.region.morytania.handlers;
import core.cache.def.impl.SceneryDefinition;
import core.game.interaction.OptionHandler;
import core.game.node.Node;
import core.game.node.entity.player.Player;
import core.plugin.Initializable;
import core.plugin.Plugin;
/**
* Represents the mortynia swamp plugin.
* @author 'Vexia
* @version 1.0
*/
@Initializable
public final class MoyrtniaSwampPlugin extends OptionHandler {
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
SceneryDefinition.forId(3506).getHandlers().put("option:open", this);
return this;
}
@Override
public boolean handle(Player player, Node node, String option) {
player.getDialogueInterpreter().sendDialogue("There's a message attached to this gate, it reads:~", "~ Mort Myre is a dangerous Ghast infested swamp. ~", "~ Do not enter if you value your life. ~", "~ All persons wishing to enter must see Drezel. ~");
return true;
}
}

View file

@ -0,0 +1,148 @@
package content.region.morytania.handlers
import core.api.*
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.system.timer.PersistTimer
import core.game.system.timer.RSTimer
import core.game.system.timer.TimerFlag
import core.game.world.map.zone.ZoneBorders
import org.rs09.consts.Items
import kotlin.random.Random
/**
* The area for swamp decay
*/
class SwampDecayArea : MapArea {
override fun defineAreaBorders(): Array<ZoneBorders> {
return arrayOf(
ZoneBorders(3392, 3328, 3519, 3455)
)
}
/**
* Sources:
* General mechanic: https://runescape.wiki/w/Swamp_decay
* Historic damage: https://runescape.wiki/w/Mort_Myre_Swamp?oldid=1961846
* Precise area: https://oldschool.runescape.wiki/w/Swamp_decay
* Video: https://www.youtube.com/watch?v=gkw959x8Q6s
* Video: https://www.youtube.com/watch?v=N86RywmEyEU
*/
override fun areaEnter(entity: Entity) {
if (entity is Player) {
getOrStartTimer<SwampDecayTimer>(entity)
}
super.areaEnter(entity)
}
}
/**
* The timer for swamp decay
*/
class SwampDecayTimer : PersistTimer (200, "swampdecay", isSoft = true, flags = arrayOf(TimerFlag.ClearOnDeath)) {
companion object {
// Silver sickle, Rod of Ivandis, or Ivandis Flail in equip or inv will protect the player.
private val PROTECTIVE_ITEMS = intArrayOf(
Items.SILVER_SICKLEB_2963,
Items.ROD_OF_IVANDIS10_7639,
Items.ROD_OF_IVANDIS9_7640,
Items.ROD_OF_IVANDIS8_7641,
Items.ROD_OF_IVANDIS7_7642,
Items.ROD_OF_IVANDIS6_7643,
Items.ROD_OF_IVANDIS5_7644,
Items.ROD_OF_IVANDIS4_7645,
Items.ROD_OF_IVANDIS3_7646,
Items.ROD_OF_IVANDIS2_7647,
Items.ROD_OF_IVANDIS1_7648,
Items.IVANDIS_FLAIL_30_13117,
Items.IVANDIS_FLAIL_29_13118,
Items.IVANDIS_FLAIL28_13119,
Items.IVANDIS_FLAIL_27_13120,
Items.IVANDIS_FLAIL_26_13121,
Items.IVANDIS_FLAIL_25_13122,
Items.IVANDIS_FLAIL_24_13123,
Items.IVANDIS_FLAIL_23_13124,
Items.IVANDIS_FLAIL_22_13125,
Items.IVANDIS_FLAIL_21_13126,
Items.IVANDIS_FLAIL_20_13127,
Items.IVANDIS_FLAIL_19_13128,
Items.IVANDIS_FLAIL_18_13129,
Items.IVANDIS_FLAIL_17_13130,
Items.IVANDIS_FLAIL_16_13131,
Items.IVANDIS_FLAIL_15_13132,
Items.IVANDIS_FLAIL_14_13133,
Items.IVANDIS_FLAIL_13_13134,
Items.IVANDIS_FLAIL_12_13135,
Items.IVANDIS_FLAIL_11_13136,
Items.IVANDIS_FLAIL_10_13137,
Items.IVANDIS_FLAIL_9_13138,
Items.IVANDIS_FLAIL_8_13139,
Items.IVANDIS_FLAIL_7_13140,
Items.IVANDIS_FLAIL_6_13141,
Items.IVANDIS_FLAIL_5_13142,
Items.IVANDIS_FLAIL_4_13143,
Items.IVANDIS_FLAIL_3_13144,
Items.IVANDIS_FLAIL_2_13145,
Items.IVANDIS_FLAIL_1_13146
)
}
// returning false stops the timer. returning true keeps it running.
override fun run(entity: Entity): Boolean {
if (entity !is Player) return false
// player is outside the zone
if (entity.location.x !in 3392..3519 || entity.location.y !in 3328..3455) {
sendMessage(entity, "The swamp decay effect is now over.")
return false
}
// the nature spirit camp zone is safe
if (entity.location.x in 3435..3445 && entity.location.y in 3331..3442) {
sendMessage(entity, "The aura of Filliman's camp protects you from the swamp.")
return true
}
// protected
if (hasProtection(entity)) {
return true
}
// decayed (2-4 random damage)
sendMessage(entity, "The swamp decays you!")
impact(entity, Random.nextInt(2, 5))
visualize(entity, -1, 255)
return true
}
override fun onRegister(entity: Entity) {
if (entity !is Player) return
}
override fun getTimer(vararg args: Any): RSTimer {
val t = SwampDecayTimer()
t.runInterval = args.getOrNull(0) as? Int ?: 200
return t
}
// always remember your protection
private fun hasProtection(player: Player): Boolean {
for (itemId in PROTECTIVE_ITEMS) {
if (inEquipmentOrInventory(player, itemId)) {
val prot = when(itemId) {
2963 -> "blessed silver sickle"
in 7639..7648 -> "rod of Ivandis"
in 13117..13146 -> "Ivandis flail"
else -> "item" // fallback
}
sendMessage(player, "The power of the $prot prevents the swamp decay.")
visualize(player, -1, 259)
return true
}
}
return false
}
}

View file

@ -18,7 +18,7 @@ class GardenerGhostDialogue (player: Player? = null) : DialoguePlugin(player) {
if (gardenerGhost.location.withinDistance(gardenerGhost.graveLocation, 5)) {
sendChat(gardenerGhost, "Here is the place where I met me' maker.")
} else {
sendChat(gardenerGhost, "Go " + gardenerGhost.location.deriveDirection(gardenerGhost.graveLocation).name.lowercase(Locale.getDefault()).replace('_', '-') + ", mate")
sendChat(gardenerGhost, "Go " + gardenerGhost.location.deriveDirection(gardenerGhost.graveLocation)!!.name.lowercase(Locale.getDefault()).replace('_', '-') + ", mate")
gardenerGhost.continueFollowing(player)
}
} else {

View file

@ -118,7 +118,7 @@ public class BorkNPC extends AbstractNPC {
@Override
public void finalizeDeath(Entity killer){
super.finalizeDeath(killer);
BossKillCounter.addtoKillcount((Player) killer, this.getId());
BossKillCounter.addToBossKillCount((Player) killer, this.getId());
}
@Override

View file

@ -82,7 +82,7 @@ public class ChaosElementalNPC extends AbstractNPC {
@Override
public void finalizeDeath(Entity killer) {
super.finalizeDeath(killer);
BossKillCounter.addtoKillcount((Player) killer, this.getId());
BossKillCounter.addToBossKillCount((Player) killer, this.getId());
}
/**

View file

@ -91,7 +91,7 @@ public final class CorporealBeastNPC extends NPCBehavior {
@Override
public void onDeathFinished(NPC self, Entity killer) {
BossKillCounter.addtoKillcount((Player) killer, NPCs.CORPOREAL_BEAST_8133);
BossKillCounter.addToBossKillCount((Player) killer, NPCs.CORPOREAL_BEAST_8133);
if (darkEnergyCore != null) {
darkEnergyCore.clear();
darkEnergyCore = null;

View file

@ -14,6 +14,7 @@ import core.game.node.entity.impl.Animator.Priority;
import core.game.node.entity.npc.AbstractNPC;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.world.GameWorld;
import core.game.world.map.Location;
import core.game.world.update.flag.context.Animation;
import core.plugin.Initializable;
@ -48,7 +49,7 @@ public final class KingBlackDragonNPC extends AbstractNPC {
@Override
public void finalizeDeath(Entity killer) {
super.finalizeDeath(killer);
BossKillCounter.addtoKillcount((Player) killer, this.getId());
BossKillCounter.addToBossKillCount((Player) killer, this.getId());
}
/**
@ -127,7 +128,6 @@ public final class KingBlackDragonNPC extends AbstractNPC {
@Override
public void adjustBattleState(Entity entity, Entity victim, BattleState state) {
if (style == CombatStyle.RANGE) {
fireType.getTask().exec(victim, entity);
state.setStyle(null);
DRAGONFIRE.adjustBattleState(entity, victim, state);
state.setStyle(CombatStyle.RANGE);
@ -184,6 +184,9 @@ public final class KingBlackDragonNPC extends AbstractNPC {
@Override
public void impact(Entity entity, Entity victim, BattleState state) {
if (style != CombatStyle.MELEE && victim.getImpactHandler().getDisabledTicks() <= GameWorld.getTicks()) {
fireType.getTask().exec(victim, entity);
}
style.getSwingHandler().impact(entity, victim, state);
}

View file

@ -6,6 +6,8 @@ import core.game.system.SystemState
import core.game.system.config.ServerConfigParser
import core.game.world.GameWorld
import core.net.NioReactor
import core.net.websocket.GameWebSocketServer
import core.net.websocket.WebSocketTls
import core.tools.Log
import core.tools.NetworkReachability
import core.tools.TimeStamp
@ -44,6 +46,9 @@ object Server {
@JvmStatic
var reactor: NioReactor? = null
@JvmStatic
var webSocketServer: GameWebSocketServer? = null
var networkReachability = NetworkReachability.Reachable
/**
@ -70,6 +75,16 @@ object Server {
try {
reactor = NioReactor.configure(43594 + GameWorld.settings?.worldId!!)
reactor!!.start()
if (ServerConstants.WEBSOCKET_ENABLED) {
val websocketPort = if (ServerConstants.WEBSOCKET_PORT > 0) {
ServerConstants.WEBSOCKET_PORT
} else {
53594 + GameWorld.settings?.worldId!!
}
webSocketServer = GameWebSocketServer(websocketPort, 1)
WebSocketTls.configure(webSocketServer!!)
webSocketServer!!.start()
}
} catch (e: BindException) {
log(this::class.java, Log.ERR, "Port " + (43594 + GameWorld.settings?.worldId!!) + " is already in use!")
throw e

View file

@ -374,5 +374,20 @@ class ServerConstants {
@JvmField
var CONNECTIVITY_TIMEOUT = 500
@JvmField
var WEBSOCKET_ENABLED = false
@JvmField
var WEBSOCKET_PORT = 0
@JvmField
var WEBSOCKET_TLS_ENABLED = false
@JvmField
var WEBSOCKET_TLS_KEYSTORE_PATH = ""
@JvmField
var WEBSOCKET_TLS_KEYSTORE_PASSWORD = ""
}
}

View file

@ -1343,6 +1343,25 @@ fun transformNpc(npc: NPC, transformTo: Int, restoreTicks: Int) {
})
}
/**
* Transforms an NPC for the given number of ticks with hp carryover
* @param npc the NPC object to transform
* @param transformTo the ID of the NPC to turn into
* @param restoreTicks the number of ticks until the NPC returns to normal
*/
fun transformNpcWithHpCarryover(npc: NPC, transformTo: Int, restoreTicks: Int) {
npc.transformWithHpCarryover(transformTo)
queueScript(npc, 0, QueueStrength.SOFT) { stage: Int ->
when (stage) {
0 -> return@queueScript delayScript(npc, restoreTicks)
else -> {
npc.reTransform()
return@queueScript stopExecuting(npc)
}
}
}
}
/**
* Produces a Location object using the given x,y,z values
*/

View file

@ -91,7 +91,7 @@ public final class AIPBuilder {
//Varrock Ge bank
RandomAfkPlayer(new Location(3189, 3439));
//Home Bank
// Lunar Island Bank
RandomAfkPlayer(new Location(2099, 3918));
}

View file

@ -109,7 +109,7 @@ public class AIPlayer extends Player {
* Generates bot stats/equipment/etc based on OSRScopyLine
*/
public void updateRandomValues() {
this.getAppearance().setGender(RandomFunction.random(5) == 1 ? Gender.FEMALE : Gender.MALE);
this.getAppearance().setGender(RandomFunction.random(3) == 1 ? Gender.FEMALE : Gender.MALE);
int setTo = RandomFunction.random(0,10);
CharacterDesign.randomize(this,true);
this.setDirection(Direction.values()[new Random().nextInt(Direction.values().length)]); //Random facing dir

View file

@ -2,6 +2,7 @@ package core.game.bots
import core.game.node.item.Item
import core.game.world.map.Location
import org.rs09.consts.Items
class SkillingBotAssembler {
fun produce(type: Wealth, loc: Location): AIPlayer {
@ -39,7 +40,161 @@ class SkillingBotAssembler {
RICH
}
val POORSETS = arrayOf(listOf(542,544), listOf(581), listOf(6654,6655,6656), listOf(6654,6656), listOf(636,646), listOf(638,648), listOf(), listOf(), listOf())
val AVGSETS = arrayOf(listOf(2649,342,344), listOf(2651,542,544), listOf(6654,6655,6656), listOf(6139,6141), listOf(9923,9924,9925), listOf(10400,10402,2649), listOf(10404,10406), listOf(12971,12978))
val RICHSETS = arrayOf(listOf(10330,10332,2649), listOf(12873,12880,1046), listOf(13858,13861,13864), listOf(13887,13893), listOf(3481,3483), listOf(2653,2655), listOf(2661,2663), listOf(2591,2593), listOf(14490,14492))
val POORSETS = arrayOf(
listOf(Items.BLACK_ROBE_581, Items.BLACK_SKIRT_1015, Items.STAFF_OF_AIR_1381),
listOf(Items.BLACK_ROBE_581, Items.BLACK_SKIRT_1015),
listOf(Items.BLACK_ROBE_581, Items.BLACK_SKIRT_1015, Items.WIZARD_HAT_1017),
listOf(), // No equipment
listOf(Items.WIZARD_HAT_579),
listOf(Items.WIZARD_HAT_579, Items.STAFF_OF_AIR_1381),
listOf(Items.WIZARD_HAT_579, Items.STAFF_1379),
listOf(Items.WIZARD_HAT_579, Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011),
listOf(Items.WIZARD_HAT_579, Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011, Items.TIARA_5525),
listOf(Items.WIZARD_HAT_579, Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011, Items.TEAM_1_CAPE_4315),
listOf(Items.WIZARD_HAT_579, Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011, Items.STAFF_OF_AIR_1381),
listOf(Items.WIZARD_HAT_579, Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011, Items.STAFF_OF_WATER_1383),
listOf(Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011),
listOf(Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011, Items.TIARA_5525),
listOf(Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011, Items.TEAM_1_CAPE_4315),
listOf(Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011, Items.STAFF_OF_AIR_1381),
listOf(Items.WIZARD_ROBE_577, Items.BLUE_SKIRT_1011, Items.STAFF_OF_WATER_1383),
listOf(), // No equipment
listOf(Items.STEEL_PLATELEGS_1069, Items.STEEL_PLATEBODY_1119, Items.STEEL_KITESHIELD_1193, Items.STEEL_LONGSWORD_1295, Items.TEAM_29_CAPE_4371),
listOf(Items.STEEL_PLATESKIRT_1083, Items.STEEL_PLATEBODY_1119, Items.STEEL_KITESHIELD_1193, Items.STEEL_LONGSWORD_1295, Items.TEAM_30_CAPE_4373),
listOf(Items.STEEL_PLATESKIRT_1083, Items.STEEL_PLATEBODY_1119, Items.STEEL_FULL_HELM_1157, Items.TEAM_30_CAPE_4373),
listOf(Items.STEEL_PLATELEGS_1069, Items.STEEL_CHAINBODY_1105, Items.STEEL_FULL_HELM_1157, Items.TEAM_31_CAPE_4375),
listOf(Items.STEEL_PLATELEGS_1069, Items.STEEL_PLATEBODY_1119, Items.TEAM_29_CAPE_4371),
listOf(Items.STEEL_PLATESKIRT_1083, Items.STEEL_PLATEBODY_1119, Items.TEAM_30_CAPE_4373),
listOf(Items.STEEL_PLATELEGS_1069, Items.STEEL_CHAINBODY_1105, Items.TEAM_31_CAPE_4375),
listOf(Items.STEEL_PLATELEGS_1069, Items.STEEL_CHAINBODY_1105, Items.STEEL_2H_SWORD_1311, Items.TEAM_34_CAPE_4381),
listOf(Items.STEEL_PLATELEGS_1069, Items.STEEL_CHAINBODY_1105, Items.STEEL_SCIMITAR_1325, Items.TEAM_35_CAPE_4383),
listOf(Items.STEEL_PLATELEGS_1069, Items.STEEL_CHAINBODY_1105, Items.STEEL_BATTLEAXE_1365, Items.TEAM_35_CAPE_4383),
listOf(Items.STEEL_PLATELEGS_1069, Items.STEEL_CHAINBODY_1105, Items.STEEL_BATTLEAXE_1365, Items.STEEL_SQ_SHIELD_1177, Items.TEAM_35_CAPE_4383),
listOf(), // No equipment
listOf(Items.IRON_PLATELEGS_1067, Items.IRON_PLATEBODY_1115, Items.IRON_FULL_HELM_1153, Items.IRON_KITESHIELD_1191, Items.IRON_LONGSWORD_1293, Items.TEAM_36_CAPE_4385),
listOf(Items.IRON_PLATELEGS_1067, Items.IRON_PLATEBODY_1115, Items.IRON_FULL_HELM_1153, Items.IRON_2H_SWORD_1309, Items.TEAM_37_CAPE_4387),
listOf(Items.IRON_PLATESKIRT_1081, Items.IRON_PLATEBODY_1115, Items.IRON_FULL_HELM_1153, Items.IRON_SCIMITAR_1323, Items.TEAM_32_CAPE_4377),
listOf(Items.IRON_PLATESKIRT_1081, Items.IRON_PLATEBODY_1115, Items.IRON_FULL_HELM_1153, Items.IRON_BATTLEAXE_1363, Items.TEAM_32_CAPE_4377),
listOf(Items.IRON_PLATELEGS_1067, Items.IRON_CHAINBODY_1101, Items.IRON_KITESHIELD_1191, Items.IRON_LONGSWORD_1293, Items.TEAM_33_CAPE_4379),
listOf(Items.IRON_PLATESKIRT_1081, Items.IRON_PLATEBODY_1115, Items.IRON_FULL_HELM_1153, Items.TEAM_39_CAPE_4391),
listOf(Items.IRON_PLATELEGS_1067, Items.IRON_PLATEBODY_1115, Items.IRON_FULL_HELM_1153, Items.TEAM_37_CAPE_4387),
listOf(Items.IRON_PLATELEGS_1067, Items.IRON_PLATEBODY_1115, Items.IRON_SCIMITAR_1323, Items.TEAM_32_CAPE_4377),
listOf(Items.IRON_PLATESKIRT_1081, Items.IRON_PLATEBODY_1115, Items.TEAM_32_CAPE_4377),
listOf(Items.IRON_PLATELEGS_1067, Items.IRON_CHAINBODY_1101, Items.TEAM_33_CAPE_4379),
listOf(Items.IRON_PLATESKIRT_1081, Items.IRON_PLATEBODY_1115, Items.IRON_FULL_HELM_1153, Items.TEAM_32_CAPE_4377),
listOf(Items.IRON_PLATESKIRT_1081, Items.IRON_CHAINBODY_1101, Items.IRON_FULL_HELM_1153, Items.TEAM_32_CAPE_4377),
listOf(Items.IRON_PLATELEGS_1067, Items.IRON_CHAINBODY_1101, Items.IRON_FULL_HELM_1153, Items.TEAM_33_CAPE_4379),
listOf(), // No equipment
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_PLATEBODY_1117,Items.BRONZE_FULL_HELM_1155,Items.BRONZE_KITESHIELD_1189,Items.BRONZE_LONGSWORD_1291,Items.TEAM_27_CAPE_4367),
listOf(Items.BRONZE_PLATESKIRT_1087,Items.BRONZE_PLATEBODY_1117,Items.BRONZE_FULL_HELM_1155,Items.BRONZE_KITESHIELD_1189,Items.BRONZE_LONGSWORD_1291,Items.TEAM_27_CAPE_4367),
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_PLATEBODY_1117,Items.BRONZE_FULL_HELM_1155,Items.BRONZE_2H_SWORD_1307, Items.TEAM_28_CAPE_4369),
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_CHAINBODY_1103),
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_CHAINBODY_1103,Items.BRONZE_2H_SWORD_1307),
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_CHAINBODY_1103,Items.BRONZE_SCIMITAR_1321),
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_CHAINBODY_1103,Items.BRONZE_BATTLEAXE_1375),
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_CHAINBODY_1103,Items.BRONZE_BATTLEAXE_1375,Items.BRONZE_SQ_SHIELD_1173),
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_PLATEBODY_1117, Items.TEAM_27_CAPE_4367),
listOf(Items.BRONZE_PLATELEGS_1075,Items.BRONZE_PLATEBODY_1117,Items.BRONZE_FULL_HELM_1155, Items.TEAM_27_CAPE_4367),
listOf(Items.BRONZE_PLATESKIRT_1087,Items.BRONZE_CHAINBODY_1103),
listOf(Items.BRONZE_PLATESKIRT_1087,Items.BRONZE_CHAINBODY_1103,Items.BRONZE_FULL_HELM_1155),
listOf(Items.BRONZE_PLATESKIRT_1087,Items.BRONZE_CHAINBODY_1103,Items.BRONZE_FULL_HELM_1155, Items.TEAM_28_CAPE_4369),
listOf(Items.BRONZE_PLATESKIRT_1087,Items.BRONZE_PLATEBODY_1117, Items.TEAM_28_CAPE_4369),
listOf(), // No equipment
listOf(Items.LEATHER_BODY_1129,Items.LEATHER_CHAPS_1095),
listOf(), // No equipment
listOf(Items.STUDDED_CHAPS_1097,Items.STUDDED_BODY_1133),
listOf(Items.MIME_LEGS_3059,Items.MIME_TOP_3058,Items.MIME_BOOTS_3061,Items.MIME_GLOVES_3060,Items.MIME_MASK_3057),
listOf(Items.MIME_LEGS_3059,Items.MIME_TOP_3058,Items.MIME_BOOTS_3061,Items.MIME_GLOVES_3060),
listOf(Items.MONKS_ROBE_542,Items.MONKS_ROBE_544),
listOf(Items.CHICKEN_LEGS_11022,Items.CHICKEN_WINGS_11020,Items.CHICKEN_HEAD_11021,Items.CHICKEN_FEET_11019),
listOf(Items.TOY_KITE_12844,Items.CHOCATRICE_CAPE_12634),
listOf(), // No equipment
listOf(Items.WHITE_APRON_1005, Items.CHEFS_HAT_1949),
listOf(Items.TEAM_10_CAPE_4333),
listOf(Items.TEAM_2_CAPE_4317),
listOf(), // No equipment
listOf(), // No equipment
listOf(), // No equipment
listOf())
val AVGSETS = arrayOf(
listOf(Items.SKELETAL_TOP_6139, Items.SKELETAL_BOTTOMS_6141),
listOf(Items.SKELETON_LEGGINGS_9923, Items.SKELETON_SHIRT_9924, Items.SKELETON_MASK_9925),
listOf(Items.BLACK_ELE_SHIRT_10400,Items.BLACK_ELE_LEGS_10402,Items.BROWN_HEADBAND_2649),
listOf(Items.RED_ELE_SHIRT_10404,Items.RED_ELE_LEGS_10406),
listOf(Items.COMBAT_ROBE_TOP_100_12971, Items.COMBAT_ROBE_BOTTOM_100_12978),
listOf(Items.COMBAT_ROBE_TOP_100_12971, Items.COMBAT_ROBE_BOTTOM_100_12978, Items.COMBAT_HOOD_100_12964),
listOf(Items.COMBAT_ROBE_TOP_100_12971, Items.COMBAT_ROBE_BOTTOM_100_12978, Items.RUNE_BERSERKER_SHIELD_100_12929),
listOf(Items.COMBAT_ROBE_TOP_100_12971, Items.COMBAT_ROBE_BOTTOM_100_12978, Items.ADAMANT_BERSERKER_SHIELD_100_12915),
listOf(Items.DRUIDIC_MAGE_TOP_100_12894, Items.DRUIDIC_MAGE_BOTTOM_100_12901, Items.DRUIDIC_MAGE_HOOD_100_12887),
listOf(), // No equipment
listOf(Items.MITHRIL_PLATELEGS_1071, Items.MITHRIL_PLATEBODY_1121, Items.MITHRIL_KITESHIELD_1197, Items.MITHRIL_LONGSWORD_1299, Items.TEAM_29_CAPE_4371),
listOf(Items.MITHRIL_PLATESKIRT_1085, Items.MITHRIL_PLATEBODY_1121, Items.MITHRIL_KITESHIELD_1197, Items.MITHRIL_LONGSWORD_1299, Items.TEAM_30_CAPE_4373),
listOf(Items.MITHRIL_PLATELEGS_1071, Items.MITHRIL_PLATEBODY_1121, Items.TEAM_29_CAPE_4371),
listOf(Items.MITHRIL_PLATESKIRT_1085, Items.MITHRIL_PLATEBODY_1121, Items.TEAM_30_CAPE_4373),
listOf(Items.MITHRIL_PLATESKIRT_1085, Items.MITHRIL_CHAINBODY_1109, Items.TEAM_30_CAPE_4373),
listOf(Items.MITHRIL_PLATELEGS_1071, Items.MITHRIL_CHAINBODY_1109, Items.TEAM_31_CAPE_4375),
listOf(Items.MITHRIL_PLATELEGS_1071, Items.MITHRIL_CHAINBODY_1109, Items.MITHRIL_2H_SWORD_1315, Items.TEAM_34_CAPE_4381),
listOf(Items.MITHRIL_PLATELEGS_1071, Items.MITHRIL_CHAINBODY_1109, Items.MITHRIL_SCIMITAR_1329, Items.TEAM_35_CAPE_4383),
listOf(Items.MITHRIL_PLATELEGS_1071, Items.MITHRIL_CHAINBODY_1109, Items.MITHRIL_BATTLEAXE_1369, Items.TEAM_36_CAPE_4385),
listOf(), // No equipment
listOf(Items.ADAMANT_PLATELEGS_1073,Items.ADAMANT_PLATEBODY_1124,Items.ADAMANT_KITESHIELD_1199,Items.ADAMANT_LONGSWORD_1301, Items.TEAM_29_CAPE_4371),
listOf(Items.ADAMANT_PLATESKIRT_1091,Items.ADAMANT_PLATEBODY_1124,Items.ADAMANT_KITESHIELD_1199,Items.ADAMANT_LONGSWORD_1301, Items.TEAM_30_CAPE_4373),
listOf(Items.ADAMANT_PLATELEGS_1073,Items.ADAMANT_PLATEBODY_1124, Items.TEAM_29_CAPE_4371),
listOf(Items.ADAMANT_PLATESKIRT_1091,Items.ADAMANT_PLATEBODY_1124, Items.TEAM_30_CAPE_4373),
listOf(Items.ADAMANT_PLATESKIRT_1091,Items.ADAMANT_CHAINBODY_1111, Items.TEAM_30_CAPE_4373),
listOf(Items.ADAMANT_PLATELEGS_1073,Items.ADAMANT_CHAINBODY_1111, Items.TEAM_31_CAPE_4375),
listOf(Items.ADAMANT_PLATESKIRT_1091,Items.ADAMANT_CHAINBODY_1111,Items.ADAMANT_FULL_HELM_1161, Items.TEAM_30_CAPE_4373),
listOf(Items.ADAMANT_PLATELEGS_1073,Items.ADAMANT_CHAINBODY_1111,Items.ADAMANT_FULL_HELM_1161, Items.TEAM_31_CAPE_4375),
listOf(Items.ADAMANT_PLATELEGS_1073,Items.ADAMANT_CHAINBODY_1111,Items.ADAMANT_2H_SWORD_1317, Items.TEAM_34_CAPE_4381),
listOf(Items.ADAMANT_PLATELEGS_1073,Items.ADAMANT_CHAINBODY_1111,Items.ADAMANT_SCIMITAR_1331, Items.TEAM_35_CAPE_4383),
listOf(Items.ADAMANT_PLATELEGS_1073,Items.ADAMANT_CHAINBODY_1111,Items.ADAMANT_BATTLEAXE_1371, Items.TEAM_35_CAPE_4383),
listOf(), // No equipment
listOf(Items.GREEN_DHIDE_BODY_1135,Items.GREEN_DHIDE_CHAPS_1099,Items.GREEN_DHIDE_VAMB_1065,Items.GREEN_DHIDE_COIF_100_12936,Items.AVAS_ACCUMULATOR_10499),
listOf(Items.GREEN_DHIDE_BODY_1135,Items.GREEN_DHIDE_CHAPS_1099,Items.GREEN_DHIDE_VAMB_1065,Items.GREEN_DHIDE_COIF_100_12936,Items.ADAMANT_CROSSBOW_9183,Items.AVAS_ACCUMULATOR_10499),
listOf(Items.TEAM_10_CAPE_4333),
listOf(Items.TEAM_1_CAPE_4315),
listOf(), // No equipment
listOf(), // No equipment
listOf())
val RICHSETS = arrayOf(
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_PLATEBODY_1127,Items.RUNE_KITESHIELD_1201,Items.RUNE_FULL_HELM_1163,Items.RUNE_LONGSWORD_1303,Items.TEAM_29_CAPE_4371),
listOf(Items.RUNE_PLATESKIRT_1093,Items.RUNE_PLATEBODY_1127,Items.RUNE_KITESHIELD_1201,Items.RUNE_FULL_HELM_1163,Items.RUNE_LONGSWORD_1303,Items.TEAM_30_CAPE_4373),
listOf(Items.RUNE_PLATESKIRT_1093,Items.RUNE_PLATEBODY_1127, Items.TEAM_29_CAPE_4371),
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_PLATEBODY_1127,Items.RUNE_FULL_HELM_1163,Items.TEAM_29_CAPE_4371),
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_PLATEBODY_1127,Items.RUNE_BERSERKER_SHIELD_100_12929,Items.TEAM_29_CAPE_4371),
listOf(Items.RUNE_PLATESKIRT_1093,Items.RUNE_PLATEBODY_1127,Items.TEAM_30_CAPE_4373),
listOf(Items.RUNE_PLATESKIRT_1093,Items.RUNE_CHAINBODY_1113,Items.TEAM_30_CAPE_4373),
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_CHAINBODY_1113,Items.TEAM_31_CAPE_4375),
listOf(Items.RUNE_PLATESKIRT_1093,Items.RUNE_CHAINBODY_1113,Items.RUNE_FULL_HELM_1163,Items.TEAM_30_CAPE_4373),
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_CHAINBODY_1113,Items.RUNE_FULL_HELM_1163,Items.TEAM_31_CAPE_4375),
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_CHAINBODY_1113,Items.RUNE_2H_SWORD_1319,Items.TEAM_34_CAPE_4381),
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_CHAINBODY_1113,Items.RUNE_SCIMITAR_1333,Items.TEAM_35_CAPE_4383),
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_CHAINBODY_1113,Items.RUNE_BATTLEAXE_1373,Items.TEAM_35_CAPE_4383),
listOf(Items.RUNE_PLATELEGS_1079,Items.RUNE_PLATEBODY_1127,Items.ABYSSAL_WHIP_4151,Items.TEAM_29_CAPE_4371),
listOf(), // No equipment
listOf(Items.RUNE_PLATEBODY_G_2615,Items.RUNE_PLATELEGS_G_2618,Items.RUNE_KITESHIELD_G_2621,Items.RUNE_FULL_HELMG_2619, Items.TEAM_27_CAPE_4367),
listOf(Items.RUNE_PLATEBODY_T_2623,Items.RUNE_PLATELEGS_T_2625,Items.RUNE_KITESHIELD_T_2629,Items.RUNE_FULL_HELM_T_2627, Items.TEAM_27_CAPE_4367),
listOf(Items.GILDED_PLATEBODY_3481,Items.GILDED_PLATELEGS_3483,Items.CAPE_1030),
listOf(Items.ZAMORAK_PLATEBODY_2653,Items.ZAMORAK_PLATELEGS_2655,Items.ZAMORAK_FULL_HELM_2657),
listOf(Items.SARADOMIN_PLATEBODY_2661,Items.SARADOMIN_PLATELEGS_2663,Items.SARADOMIN_FULL_HELM_2665),
listOf(Items.GUTHIX_PLATEBODY_2669,Items.GUTHIX_PLATELEGS_2671,Items.GUTHIX_FULL_HELM_2673),
listOf(Items.ZAMORAK_PLATEBODY_2653,Items.ZAMORAK_PLATELEGS_2655,Items.ZAMORAK_FULL_HELM_2657,Items.ZAMORAK_CAPE_2414),
listOf(Items.SARADOMIN_PLATEBODY_2661,Items.SARADOMIN_PLATELEGS_2663,Items.SARADOMIN_FULL_HELM_2665,Items.SARADOMIN_CAPE_2412),
listOf(Items.GUTHIX_PLATEBODY_2669,Items.GUTHIX_PLATELEGS_2671,Items.GUTHIX_FULL_HELM_2673,Items.GUTHIX_CAPE_2413),
listOf(Items.THIRD_AGE_ROBE_10340,Items.THIRD_AGE_ROBE_TOP_10338,Items.SANTA_HAT_1050),
listOf(), // No equipment
listOf(Items.DRAGON_PLATELEGS_4087,Items.DRAGON_CHAINBODY_3140,Items.ABYSSAL_WHIP_4151,Items.DRAGON_FULL_HELM_11335),
listOf(Items.DRAGON_PLATELEGS_4087,Items.DRAGON_CHAINBODY_3140,Items.DRAGONFIRE_SHIELD_11283,Items.DRAGON_FULL_HELM_11335),
listOf(), // No equipment
listOf(Items.BLACK_DHIDE_BODY_2503,Items.BLACK_DHIDE_CHAPS_2497,Items.BLACK_DHIDE_VAMB_2491,Items.RUNE_CROSSBOW_9185,Items.AVAS_ACCUMULATOR_10499),
listOf(Items.BLACK_DHIDE_BODY_2503,Items.BLACK_DHIDE_CHAPS_2497,Items.BLACK_DHIDE_VAMB_2491,Items.BLACK_DHIDE_COIF_100_12957,Items.RUNE_CROSSBOW_9185,Items.RUNE_KITESHIELD_1201),
listOf(Items.ELITE_BLACK_PLATEBODY_14492,Items.ELITE_BLACK_PLATELEGS_14490, Items.ELITE_BLACK_FULL_HELM_14494),
listOf(Items.BATTLE_ROBE_BOTTOM_100_12880,Items.BATTLE_ROBE_TOP_100_12873,Items.PURPLE_PARTYHAT_1046),
listOf(Items.ZURIELS_ROBE_BOTTOM_13862,Items.ZURIELS_ROBE_TOP_13858,Items.ZURIELS_HOOD_13864,Items.ZURIELS_HOOD_13864,Items.ZURIELS_STAFF_13867),
listOf(Items.VESTAS_CHAINBODY_13887,Items.VESTAS_PLATESKIRT_13893),
listOf(), // No equipment
listOf(), // No equipment
listOf())
}

View file

@ -9,6 +9,7 @@ import core.game.node.entity.combat.spell.CombatSpell;
/**
* Represents an entity's current battle state.
*
* @author Emperor
*/
public final class BattleState {
@ -99,6 +100,7 @@ public final class BattleState {
/**
* Constructs a new {@code BattleState} {@Code Object}
*
* @param victim The victim entity.
*/
public BattleState(Entity entity, Entity victim) {
@ -134,6 +136,7 @@ public final class BattleState {
/**
* Gets the estimatedHit.
*
* @return The estimatedHit.
*/
public int getEstimatedHit() {
@ -142,6 +145,7 @@ public final class BattleState {
/**
* Sets the estimated hit.
*
* @param estimatedHit The estimated hit to set.
*/
public void setEstimatedHit(int estimatedHit) {
@ -164,6 +168,7 @@ public final class BattleState {
/**
* Gets the recoilDamage.
*
* @return The recoilDamage.
*/
public int getRecoilDamage() {
@ -172,6 +177,7 @@ public final class BattleState {
/**
* Sets the recoilDamage.
*
* @param recoilDamage The recoilDamage to set.
*/
public void setRecoilDamage(int recoilDamage) {
@ -236,6 +242,7 @@ public final class BattleState {
/**
* Gets the maximumHit.
*
* @return The maximumHit.
*/
public int getMaximumHit() {
@ -244,6 +251,7 @@ public final class BattleState {
/**
* Sets the maximumHit.
*
* @param maximumHit The maximumHit to set.
*/
public void setMaximumHit(int maximumHit) {
@ -252,6 +260,7 @@ public final class BattleState {
/**
* Gets the rangeWeapon.
*
* @return The rangeWeapon.
*/
public RangeWeapon getRangeWeapon() {
@ -260,6 +269,7 @@ public final class BattleState {
/**
* Sets the rangeWeapon.
*
* @param rangeWeapon The rangeWeapon to set.
*/
public void setRangeWeapon(RangeWeapon rangeWeapon) {
@ -268,6 +278,7 @@ public final class BattleState {
/**
* Gets the ammunition.
*
* @return The ammunition.
*/
public Ammunition getAmmunition() {
@ -276,6 +287,7 @@ public final class BattleState {
/**
* Sets the ammunition.
*
* @param ammunition The ammunition to set.
*/
public void setAmmunition(Ammunition ammunition) {
@ -284,6 +296,7 @@ public final class BattleState {
/**
* Gets the frozen.
*
* @return The frozen.
*/
public boolean isFrozen() {
@ -292,6 +305,7 @@ public final class BattleState {
/**
* Sets the frozen.
*
* @param frozen The frozen to set.
*/
public void setFrozen(boolean frozen) {
@ -300,6 +314,7 @@ public final class BattleState {
/**
* Gets the style.
*
* @return The style.
*/
public CombatStyle getStyle() {
@ -308,6 +323,7 @@ public final class BattleState {
/**
* Sets the style.
*
* @param style The style to set.
*/
public void setStyle(CombatStyle style) {
@ -316,6 +332,7 @@ public final class BattleState {
/**
* Gets the armourEffect.
*
* @return The armourEffect.
*/
public ArmourSet getArmourEffect() {
@ -324,6 +341,7 @@ public final class BattleState {
/**
* Sets the armourEffect.
*
* @param armourEffect The armourEffect to set.
*/
public void setArmourEffect(ArmourSet armourEffect) {
@ -332,6 +350,7 @@ public final class BattleState {
/**
* Gets the attacking entity.
*
* @return The entity.
*/
public Entity getAttacker() {
@ -340,10 +359,9 @@ public final class BattleState {
public int getTotalDamage() {
int hit = Math.max(estimatedHit, 0) + Math.max(secondaryHit, 0);
if (targets != null) {
for (BattleState s : targets) {
if (s != null) {
if (s != null && s != this) {
hit += Math.max(s.getEstimatedHit(), 0) + Math.max(s.getSecondaryHit(), 0);
}
}

View file

@ -254,9 +254,15 @@ abstract class CombatSwingHandler(var type: CombatStyle?) {
?: return InteractionType.STILL_INTERACT //if we cannot derive a direction, it's because both tiles are the same, so hand off control to the main logic which already handles this case
var next = closestEntityTile
while (next.getDistance(closestVictimTile) > 3) { //skip the initial gap in distance if it exists, because standard pathfinding would already stop us before this point if something was between us and the NPC or vice versa
//Skip the initial gap in distance if it exists, because standard pathfinding would already stop us before this point if something was between us and the NPC or vice versa.
//A fixed-direction walk can only arrive within its starting distance in steps; on oblique approaches it
//passes beside the target and never converges, so the walk is hard-capped at that many steps.
val maxSkipSteps = next.getDistance(closestVictimTile).toInt() + 1
for (i in 0 until maxSkipSteps) {
if (next.getDistance(closestVictimTile) <= 3) break
next = next.transform(dir)
}
if (next.getDistance(closestVictimTile) > 3) return InteractionType.STILL_INTERACT //never converged (oblique approach), so defer to the range checks instead
var result: InteractionType = InteractionType.STILL_INTERACT
val maxIterations = next.getDistance(closestVictimTile).toInt()

View file

@ -253,10 +253,14 @@ open class MeleeSwingHandler (vararg flags: SwingHandlerFlag)
return 1.0 + (e!!.skills.maximumLifepoints - e.skills.lifepoints) * 0.01
}
var multiplier = 1.0
if(e is Player && e.isWearingVoid(CombatStyle.MELEE) && (skillId == Skills.ATTACK || skillId == Skills.STRENGTH)) {
return 1.1
multiplier += 0.1
}
return 1.0
if (e is Player && e.properties.armourSet === ArmourSet.BERSERKER_NECKLACE_SETS && skillId == Skills.STRENGTH) {
multiplier += 0.2
}
return multiplier
}
override fun getArmourSet(e: Entity?): ArmourSet? {
@ -269,6 +273,9 @@ open class MeleeSwingHandler (vararg flags: SwingHandlerFlag)
if (ArmourSet.VERAC.isUsing(e)) {
return ArmourSet.VERAC
}
if (ArmourSet.BERSERKER_NECKLACE_SETS.isUsing(e)) {
return ArmourSet.BERSERKER_NECKLACE_SETS
}
return if (ArmourSet.TORAG.isUsing(e)) {
ArmourSet.TORAG
} else super.getArmourSet(e)

View file

@ -8,6 +8,7 @@ import core.game.node.entity.player.Player;
import core.game.node.item.Item;
import core.game.world.update.flag.context.Graphics;
import core.tools.RandomFunction;
import org.rs09.consts.Items;
/**
* Represents the types of armour sets.
@ -129,6 +130,15 @@ public enum ArmourSet {
}
return super.isUsing(e);
}
},
/**
* Berserker Necklace sets
*/
BERSERKER_NECKLACE_SETS(null, new int[][] { { Items.BERSERKER_NECKLACE_11128 }, { Items.TOKTZ_XIL_AK_6523, Items.TOKTZ_XIL_EK_6525, Items.TZHAAR_KET_EM_6527, Items.TZHAAR_KET_OM_6528 } }) {
@Override
public boolean isUsing(Entity e) {
return super.isUsing(e);
}
};
/**
@ -148,8 +158,9 @@ public enum ArmourSet {
*/
private ArmourSet(Graphics endGraphic, int[][] set) {
this.endGraphic = endGraphic;
this.sets = new Item[4][5];
this.sets = new Item[set.length][];
for (int i = 0; i < set.length; i++) {
sets[i] = new Item[set[i].length];
for (int k = 0; k < sets[i].length; k++) {
sets[i][k] = new Item(set[i][k]);
}
@ -197,7 +208,7 @@ public enum ArmourSet {
}
}
}
return hits == 4;
return hits == sets.length;
}
}
}

View file

@ -751,6 +751,18 @@ public class NPC extends Entity {
return this;
}
/**
* Transforms this NPC with HP carried over from pre-transformed entity.
* HP adjustments (ie healing/scaling HP to new maximum) should be done manually per-NPC
* @param id The new NPC id.
*/
public NPC transformWithHpCarryover(int id) {
int lp = getSkills().getLifepoints();
this.transform(id);
getSkills().setLifepoints(lp);
return this;
}
/**
* Transforms this NPC back to original.
*/

View file

@ -45,6 +45,10 @@ public final class SystemTermination {
log(this.getClass(), Log.INFO, "Stopping all bots...");
AIRepository.clearAllBots();
Server.getReactor().terminate();
if (Server.getWebSocketServer() != null) {
Server.getWebSocketServer().stop(1000, "Server shutting down");
Server.setWebSocketServer(null);
}
log(this.getClass(), Log.INFO, "Stopping all pulses...");
GameWorld.getMajorUpdateWorker().stop();
for (Iterator<Player> it = Repository.getPlayers().iterator(); it.hasNext();) {

View file

@ -197,7 +197,7 @@ class VisualCommand : CommandPlugin() {
return true
}
location = if (args.size > 2) Location.create(args[1]!!.toInt(), args[2]!!.toInt(), player!!.location.z) else player!!.location
`object` = RegionManager.getObject(location)
`object` = RegionManager.getObject(location!!)
if (`object` == null) {
player!!.debug("error: object not found in region cache.")
return true

View file

@ -1,6 +1,7 @@
package core.game.system.command.sets
import content.global.handlers.iface.*
import content.global.skill.summoning.familiar.Familiar
import content.global.skill.cooking.fermenting.BrewGrowth
import content.global.skill.farming.timers.*
import content.minigame.fishingtrawler.TrawlerLoot
@ -40,6 +41,27 @@ import java.awt.HeadlessException
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
private const val INFINITE_SPECIAL_ATTRIBUTE = "infinite-special"
private const val ALLOW_ADMIN_AGGRESSION_ATTRIBUTE = "/save:allow_admin_aggression"
private fun resolveBooleanToggle(player: Player, args: Array<String>, attribute: String): Boolean? {
return when {
args.size < 2 -> !player.getAttribute(attribute, false)
args[1].equals("true", ignoreCase = true) -> true
args[1].equals("false", ignoreCase = true) -> false
else -> null
}
}
private fun setBooleanAttribute(player: Player, attribute: String, enabled: Boolean, label: String) {
if (enabled) {
setAttribute(player, attribute, true)
} else {
removeAttribute(player, attribute)
}
sendMessage(player, "$label is now ${if (enabled) "enabled" else "disabled"}.")
}
@Initializable
class MiscCommandSet : CommandSet(Privilege.ADMIN){
override fun defineCommands() {
@ -662,29 +684,49 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){
notify(player,"No parent NPC found.")
}
}
define("infinitespecial", Privilege.ADMIN, "::infinitespecial <true|false>", "Turns the infinite special-attack flag on or off for your player."){ player, args ->
val usageStr = "Usage: ::infinitespecial true|false"
if(args.size < 2){
reject(player, usageStr)
}
when(args[1]){
"true" -> player.setAttribute("infinite-special", true)
"false" -> player.removeAttribute("infinite-special")
else -> reject(player, usageStr)
}
}
define("allowaggro", Privilege.ADMIN, "allowaggro true | false", "Toggle NPCs aggroing on you") { player, args ->
val usageStr = "Usage: ::allowaggro true | false"
if(args.size < 2) {
notify(player, "Allow admin aggression is currently ${player.getAttribute("/save:allow_admin_aggression", false)}")
define(
"infinitespecial",
Privilege.ADMIN,
"::infinitespecial [true|false]",
"Toggles infinite special attack energy for your player (special-attack flag)."
) { player, args ->
val enabled = resolveBooleanToggle(player, args, INFINITE_SPECIAL_ATTRIBUTE)
if (enabled == null) {
reject(player, "Usage: ::infinitespecial [true|false]")
return@define
}
when(args[1]) {
"true" -> player.setAttribute("/save:allow_admin_aggression", true)
"false" -> player.setAttribute("/save:allow_admin_aggression", false)
else -> reject(player, usageStr)
setBooleanAttribute(player, INFINITE_SPECIAL_ATTRIBUTE, enabled, "Infinite special attack")
}
define(
"infinitespecialmove",
Privilege.ADMIN,
"::infinitespecialmove [true|false]",
"Toggles infinite Summoning familiar special move points for your player."
) { player, args ->
val enabled = resolveBooleanToggle(player, args, Familiar.INFINITE_SPECIAL_MOVE_ATTRIBUTE)
if (enabled == null) {
reject(player, "Usage: ::infinitespecialmove [true|false]")
return@define
}
notify(player, "Setting aggro ${args[1]}")
setBooleanAttribute(
player,
Familiar.INFINITE_SPECIAL_MOVE_ATTRIBUTE,
enabled,
"Infinite familiar special move energy"
)
}
define(
"allowaggro",
Privilege.ADMIN,
"::allowaggro [true|false]",
"Toggles NPCs aggroing on you as an admin."
) { player, args ->
val enabled = resolveBooleanToggle(player, args, ALLOW_ADMIN_AGGRESSION_ATTRIBUTE)
if (enabled == null) {
reject(player, "Usage: ::allowaggro [true|false]")
return@define
}
setBooleanAttribute(player, ALLOW_ADMIN_AGGRESSION_ATTRIBUTE, enabled, "Admin NPC aggression")
}
define("rules", Privilege.STANDARD, "", "Shows the rules."){ player, _ ->

View file

@ -29,20 +29,38 @@ class QuestCommandSet : CommandSet(Privilege.ADMIN){
/**
* Displays the currently implemented quests with debug information
*/
define("quest", usage = "::quest [player-name]", description = "Shows quest stages for you, or for [player-name] if provided."){player,args ->
if (args.size < 3) {
val lookupP = if (args.size == 1) {
player
} else if (Repository.getPlayerByName(args[1]) != null) {
Repository.getPlayerByName(args[1]) ?: return@define
define("quest",
usage = "::quest [-p username] [quest-filter]",
description = "Shows quest stages for you, or for [username] if -p is used. Add optional text to filter by quest name."
) { player, args ->
var target = player
var filter: String? = null
if (args.size > 1) {
if (args[1].equals("-p", ignoreCase = true)) {
// need at least 3 arguments if -p is used: command, -p, username
if (args.size < 3) {
reject(player, "ERROR: Missing player name. Usage: ::quest -p <username> [quest-filter]")
return@define
}
val playerName = args[2]
target = Repository.getPlayerByName(playerName) ?: run {
reject(player, "ERROR: Username '$playerName' not found.")
return@define
}
// arguments after the player name are the quest filter
if (args.size > 3) {
filter = args.drop(3).joinToString(" ")
}
} else {
reject(player, "ERROR: Username not found. Usage: ::quest <username>")
return@define
// no -p, so the arguments are the quest filter
filter = args.drop(1).joinToString(" ")
}
sendQuestsDebug(player, lookupP)
} else {
reject(player, "Usage: ::quest || ::quest <username>")
}
sendQuestsDebug(player, target, filter)
}
/**
@ -96,24 +114,49 @@ class QuestCommandSet : CommandSet(Privilege.ADMIN){
/**
* Sends the list of quests with debug information
* @param admin the player.
* @param lookupUser the target player to view quests for.
* @param questFilter optional string to filter quests by name.
*/
private fun sendQuestsDebug(admin: Player?, lookupUser: Player?) {
private fun sendQuestsDebug(admin: Player?, lookupUser: Player?, questFilter: String? = null) {
admin!!.interfaceManager.open(Component(275))
// clear interface lines
for (i in 0..310) {
admin.packetDispatch.sendString("", 275, i)
}
var lineId = 11
// title
admin.packetDispatch.sendString("<col=ecf0f1>${lookupUser!!.username}'s Quest Debug</col>", 275, 2)
for (q in QuestRepository.getQuests().values) {
// Add a space to beginning and end of string for the strikethrough
// filtered quests
val questsToDisplay = QuestRepository.getQuests().values.filter { q ->
questFilter.isNullOrBlank() || q.quest.toString().contains(questFilter, ignoreCase = true)
}
// no quests to filter
if (questsToDisplay.isEmpty()) {
admin.packetDispatch.sendString("<col=ff0000>No quests found matching: $questFilter</col>", 275, lineId++)
return
}
for (q in questsToDisplay) {
// can't display more than 308 lines
if (lineId >= 308) {
admin.packetDispatch.sendString("<col=ff0000>List truncated (too many results).</col>", 275, lineId)
break
}
val stage = lookupUser.questRepository.getStage(q)
val statusColor = when {
stage >= 100 -> "80ff00"
stage in 1..99 -> "ff8400"
else -> "ff0000"
}
admin.packetDispatch.sendString("<col=ecf0f1>${q.quest}</col>", 275, lineId++)
admin.packetDispatch.sendString("<col=ecf0f1>Index: </col><col=ff1f1f><shad=2>${q.index}</shad></col> | <col=ecf0f1>Stage:</col> <col=$statusColor><shad=2>${lookupUser.questRepository.getStage(q)}</shad></col>", 275, lineId++)
admin.packetDispatch.sendString("<col=ecf0f1>Index: </col><col=ff1f1f><shad=2>${q.index}</shad></col> | <col=ecf0f1>Stage:</col> <col=$statusColor><shad=2>${stage}</shad></col>", 275, lineId++)
admin.packetDispatch.sendString("<str> ", 275, lineId++)
}
}

View file

@ -67,6 +67,7 @@ class GroundSpawnLoader {
* Method used to initialize this spawn.
*/
fun init(): GroundItem {
if (respawnRate shr 16 == 0) respawnRate = respawnRate or (respawnRate shl 16)
return GroundItemManager.create(this)
}

View file

@ -176,6 +176,11 @@ object ServerConfigParser {
ServerConstants.BOOSTED_TRAWLER_REWARDS = data.getBoolean("world.boosted_trawler_rewards", false)
ServerConstants.CONNECTIVITY_CHECK_URL = data.getString("server.connectivity_check_url", "https://google.com,https://2009scape.org")
ServerConstants.CONNECTIVITY_TIMEOUT = data.getLong("server.connectivity_timeout", 500L).toInt()
ServerConstants.WEBSOCKET_ENABLED = data.getBoolean("server.websocket_enabled", false)
ServerConstants.WEBSOCKET_PORT = data.getLong("server.websocket_port", 0L).toInt()
ServerConstants.WEBSOCKET_TLS_ENABLED = data.getBoolean("server.websocket_tls_enabled", false)
ServerConstants.WEBSOCKET_TLS_KEYSTORE_PATH = data.getString("server.websocket_tls_keystore_path", "")
ServerConstants.WEBSOCKET_TLS_KEYSTORE_PASSWORD = data.getString("server.websocket_tls_keystore_password", "")
val logLevel = data.getString("server.log_level", "VERBOSE").uppercase()
ServerConstants.LOG_LEVEL = parseEnumEntry<LogLevel>(logLevel) ?: LogLevel.VERBOSE

View file

@ -1,578 +0,0 @@
package core.game.world.map;
import core.game.interaction.DestinationFlag;
import core.game.node.Node;
import core.game.world.map.path.Path;
import core.game.world.map.path.Pathfinder;
import core.tools.RandomFunction;
import org.jetbrains.annotations.NotNull;
import core.api.utils.Vector;
import java.util.ArrayList;
import java.util.List;
import java.lang.Math;
/**
* Represents a location on the world map.
* @author Emperor
*/
public final class Location extends Node {
/**
* The x-coordinate.
*/
private int x;
/**
* The y-coordinate.
*/
private int y;
/**
* The plane.
*/
private int z;
/**
* Constructs a new {@code Location} {@code Object}.
* @param x The x-coordinate.
* @param y The y-coordinate.
* @param z The z-coordinate.
*/
public Location(int x, int y, int z) {
super(null, null);
super.destinationFlag = DestinationFlag.LOCATION;
this.x = x;
this.y = y;
if (z < 0) {
z += 4;
}
this.z = z;
}
/**
* Constructs a new {@code Location} {@code Object}
* @param x The x-coordinate.
* @param y The y-coordinate.
*/
public Location(int x, int y) {
this(x, y, 0);
}
/**
* Constructs a new {@code Location} {@code Object}.
* @param x The x-coordinate.
* @param y The y coordinate.
* @param z The z-coordinate.
* @param randomizer The amount we should randomize the x and y coordinates
* with (x + random(randomizer), y + random(randomizer)).
*/
public Location(int x, int y, int z, int randomizer) {
this(x + RandomFunction.getRandom(randomizer), y + RandomFunction.getRandom(randomizer), z);
}
/**
* Construct a new Location.
* @param x The x-coordinate.
* @param y The y-coordinate.
* @param z The z-coordinate.
* @return The constructed location.
*/
public static Location create(int x, int y, int z) {
return new Location(x, y, z);
}
public static Location create(int x, int y) {
return new Location(x, y, 0);
}
/**
* Creates a new instance of the given location.
* @param location The given location.
* @return The new instance.
*/
public static Location create(Location location) {
return create(location.getX(), location.getY(), location.getZ());
}
/**
* Creates a location instance with coordinates being the difference between
* {@code other} & {@code location}.
* @param location The first location.
* @param other The other location.
* @return The delta location.
*/
public static Location getDelta(Location location, Location other) {
return Location.create(other.x - location.x, other.y - location.y, other.z - location.z);
}
/**
* Gets a random location near the main location.
* @param main The main location.
* @param radius The radius.
* @param reachable If the locations should be able to reach eachother.
* @return The location.
*/
public static Location getRandomLocation(Location main, int radius, boolean reachable) {
Location location = RegionManager.getTeleportLocation(main, radius);
if (!reachable) {
return location;
}
Path path = Pathfinder.find(main, location, false, Pathfinder.DUMB);
if (!path.isSuccessful()) {
location = main;
if (!path.getPoints().isEmpty()) {
Point p = path.getPoints().getLast();
location = Location.create(p.getX(), p.getY(), main.getZ());
}
}
return location;
}
@Override
public Location getLocation() {
return this;
}
/**
* Checks if this location is right next to the node (assuming the node is
* size 1x1).
* @param node The node to check.
* @return {@code True} if this location is 1 tile north, west, south or
* east of the node location.
*/
public boolean isNextTo(Node node) {
Location l = node.getLocation();
if (l.getY() == y) {
return l.getX() - x == -1 || l.getX() - x == 1;
}
if (l.getX() == x) {
return l.getY() - y == -1 || l.getY() - y == 1;
}
return false;
}
/**
* Gets the region id.
* @return The region id.
*/
public int getRegionId() {
return (x >> 6) << 8 | (y >> 6);
}
/**
* Compares the users region with the one given
* @return True if user is in given region
*/
public boolean isInRegion(int region) {
return getRegionId() == region;
}
/**
* Gets the location incremented by the given coordinates.
* @param dir The direction to transform this location.
* @return The location.
*/
public Location transform(Direction dir) {
return transform(dir, 1);
}
/**
* Gets the location incremented by the given coordinates.
* @param dir The direction to transform this location.
* @param steps The amount of steps to move in this direction.
* @return The location.
*/
public Location transform(Direction dir, int steps) {
return new Location(x + (dir.getStepX() * steps), y + (dir.getStepY() * steps), this.z);
}
/**
* Gets the location incremented by the given coordinates.
* @param l incremental location
* @return The location.
*/
public Location transform(Location l) {
return new Location(x + l.getX(), y + l.getY(), this.z + l.getZ());
}
/**
* Gets the location incremented by the given coordinates.
* @param diffX The x-difference.
* @param diffY The y-difference.
* @param z The height difference.
* @return The location.
*/
public Location transform(int diffX, int diffY, int z) {
return new Location(x + diffX, y + diffY, this.z + z);
}
/**
* Checks if the other location is within viewing distance.
* @param other The other location.
* @return If you're within the other distance.
*/
public boolean withinDistance(Location other) {
return withinDistance(other, MapDistance.RENDERING.getDistance());
}
/**
* Returns if a player is within a specified distance.
* @param other The other location.
* @param dist The amount of distance.
* @return If you're within the other distance.
*/
public boolean withinDistance(Location other, int dist) {
if (other.z != z) {
return false;
}
int a = (other.x - x);
int b = (other.y - y);
double product = Math.sqrt((a*a) + (b*b));
return product <= dist;
}
/**
* Returns if a player is within a specified distance using max norm distance.
* @param other The other location.
* @param dist The amount of distance.
* @return If you're within the other distance.
*/
public boolean withinMaxnormDistance(Location other, int dist) {
if (other.z != z) {
return false;
}
int a = Math.abs(other.x - x);
int b = Math.abs(other.y - y);
double max = Math.max(a, b);
return max <= dist;
}
/**
* Returns the distance between you and the other.
* @param other The other location.
* @return The amount of distance between you and other.
*/
public double getDistance(Location other) {
int xdiff = this.getX() - other.getX();
int ydiff = this.getY() - other.getY();
return Math.sqrt(xdiff * xdiff + ydiff * ydiff);
}
/**
* Returns the distance between the first and the second specified distance.
* @param first The first location.
* @param second The other location.
* @return The amount of distance between first and other.
*/
public static double getDistance(Location first, Location second) {
int xdiff = first.getX() - second.getX();
int ydiff = first.getY() - second.getY();
return Math.sqrt(xdiff * xdiff + ydiff * ydiff);
}
/**
* Gets the 8 tiles surrounding this location as an ArrayList<Location>
*/
public ArrayList<Location> getSurroundingTiles() {
ArrayList<Location> locs = new ArrayList<>();
locs.add(transform(-1,-1,0));//SW
locs.add(transform(0,-1,0)); //S
locs.add(transform(1,-1,0)); //SE
locs.add(transform(1,0,0)); //E
locs.add(transform(1,1,0)); //NE
locs.add(transform(0,1,0)); //N
locs.add(transform(-1,1,0));//NW
locs.add(transform(-1,0,0));//W
return locs;
}
public ArrayList<Location> getCardinalTiles() {
ArrayList<Location> locs = new ArrayList<>();
locs.add(transform(-1, 0, 0));
locs.add(transform(0, -1, 0));
locs.add(transform(1, 0, 0));
locs.add(transform(0, 1, 0));
return locs;
}
/**
* Gets a square of 3 x 3 tiles as an ArrayList<Location>
*/
public ArrayList<Location> get3x3Tiles() {
ArrayList<Location> locs = new ArrayList<>();
locs.add(transform(0,0,0)); //Center
locs.add(transform(0,1,0)); //N
locs.add(transform(1,1,0)); //NE
locs.add(transform(1,0,0)); //E
locs.add(transform(1,-1,0)); //SE
locs.add(transform(0,-1,0)); //S
locs.add(transform(-1,-1,0));//SW
locs.add(transform(-1,0,0));//W
locs.add(transform(-1,1,0));//NW
return locs;
}
/**
* Gets the x position on the region chunk.
* @return The x position on the region chunk.
*/
public int getChunkOffsetX() {
int x = getLocalX();
//return x - ((x / RegionChunk.SIZE) * RegionChunk.SIZE);
return x & 7;
}
/**
* Gets the y position on the region chunk.
* @return The y position on the region chunk.
*/
public int getChunkOffsetY() {
int y = getLocalY();
//return y - ((y / RegionChunk.SIZE) * RegionChunk.SIZE);
return y & 7;
}
/**
* Gets the base location for the chunk this location is in.
* @return The base location.
*/
public Location getChunkBase() {
return create(getRegionX() << 3, getRegionY() << 3, z);
}
/**
* Gets the region x-coordinate.
* @return The region x-coordinate.
*/
public int getRegionX() {
return x >> 3;
}
/**
* Gets the region y-coordinate.
* @return The region y-coordinate.
*/
public int getRegionY() {
return y >> 3;
}
/**
* Gets the local x-coordinate on the current region in [0, 64).
* @return The local x-coordinate.
*/
public int getLocalX() {
return x & 63;
}
/**
* Gets the local y-coordinate on the current region in [0, 64).
* @return The local y-coordinate.
*/
public int getLocalY() {
return y & 63;
}
/**
* Gets the scene x-coordinate in [48, 55] (note that 104/2 = 52).
* @return The local x-coordinate.
*/
public int getSceneX() {
return x - ((getRegionX() - 6) << 3);
}
/**
* Gets the local y-coordinate in [48, 55] (note that 104/2 = 52).
* @return The local y-coordinate.
*/
public int getSceneY() {
return y - ((getRegionY() - 6) << 3);
}
/**
* Gets the local x-coordinate.
* @param loc The location containing the regional coordinates.
* @return The local x-coordinate.
*/
public int getSceneX(Location loc) {
return x - ((loc.getRegionX() - 6) << 3);
}
/**
* Gets the local y-coordinate.
* @param loc The location containing the regional coordinates.
* @return The local y-coordinate.
*/
public int getSceneY(Location loc) {
return y - ((loc.getRegionY() - 6) << 3);
}
/**
* Gets the chunk's x-coordinate (0-7).
* @return The x in the (8x8) region.
*/
public int getChunkX() {
return getLocalX() >> 3;
}
/**
* Gets the chunk's y-coordinate (0-7).
* @return The y in the (8x8) region.
*/
public int getChunkY() {
return getLocalY() >> 3;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Location)) {
return false;
}
Location loc = (Location) other;
return loc.x == x && loc.y == y && loc.z == z;
}
/**
* Checks if these coordinates equal this location.
* @param x the x.
* @param y the y.
* @param z the x.
* @return {@code True} if so.
*/
public boolean equals(int x, int y, int z) {
return equals(new Location(x, y, z));
}
@Override
public String toString() {
return "[" + x + ", " + y + ", " + z + "]";
}
public static Location fromString(String locString) {
String trimmed = locString.replace("[", "").replace("]", "");
String[] tokens = trimmed.split(",");
return Location.create(
Integer.parseInt(tokens[0].trim()),
Integer.parseInt(tokens[1].trim()),
Integer.parseInt(tokens[2].trim())
);
}
@Override
public int hashCode() {
return z << 30 | x << 15 | y;
}
/**
* Gets the x.
* @return The x.
*/
public int getX() {
return x;
}
/**
* Sets the x.
* @param x The x to set.
*/
public void setX(int x) {
this.x = x;
}
/**
* Gets the y.
* @return The y.
*/
public int getY() {
return y;
}
/**
* Sets the y.
* @param y The y to set.
*/
public void setY(int y) {
this.y = y;
}
/**
* Gets the z.
* @return The z.
*/
public int getZ() {
return z % 4;
}
/**
* Sets the z.
* @param z The z to set.
*/
public void setZ(int z) {
this.z = z;
}
@NotNull
public List<Location> getStepComponents(Direction dir) {
List<Location> output = new ArrayList<>(2);
int stepX = dir.getStepX();
int stepY = dir.getStepY();
// alright ill break it down real simple
//
// we can move diagonally but theres no melee attacking diagonally. its gotta be from N, W, S or E.
// if we are moving diagonally, we then need to check if we can attack from one of the valid directions.
// in other words we need to check the two tiles that share a corner with our destination and make sure they can be attacked from
//
// picture this: you are at (0,0). im at (1,1). you want to strangle my head off so you gotta check
// 1. to the west of destination: (1,1) + (-1,0) = (0,1) or north from where you started
// 2. to the south of the destination: (1,1) + (0,-1) = (1,0) or east from where you started
// this function is used only by CombatSwingHandler.kt and assumes that we return the X tiles, west or east, FIRST
// and then the Y tiles South and North.
//
// idk what else to say, if youre reading this youre either crazy or crazy good at weird ass shit like this
// and dont even need letters to understand, theres just numbers and arrows and fking formulas or something in ur head (and a lot of work to do here)
if (stepX != 0) output.add(transform(-stepX, 0, 0)); // Ive never dealt with coordinate systems at this level, but it feels like Im in a cold, damp, deep and dark cave with fking goblins etc
if (stepY != 0) output.add(transform(0, -stepY, 0)); // and minotaurs
return output;
}
public Direction deriveDirection(Location location) {
int diffX = location.x - this.x;
int diffY = location.y - this.y;
diffX = diffX >= 0 ? Math.min(diffX, 1) : -1;
diffY = diffY >= 0 ? Math.min(diffY, 1) : -1;
StringBuilder sb = new StringBuilder();
if (diffY != 0) {
if (diffY > 0) {
sb.append("NORTH");
} else {
sb.append("SOUTH");
}
}
if (diffX != 0) {
if (sb.length() > 0) sb.append("_");
if (diffX > 0) {
sb.append("EAST");
} else {
sb.append("WEST");
}
}
if (sb.length() == 0) return null;
return Direction.valueOf(sb.toString());
}
public Location transform (Vector vector) {
return Location.create(this.x + (int) Math.floor(vector.getX()), this.y + (int) Math.floor(vector.getY()));
}
}

View file

@ -0,0 +1,257 @@
package core.game.world.map
import core.api.utils.Vector
import core.game.interaction.DestinationFlag
import core.game.node.Node
import core.game.world.map.RegionManager.getTeleportLocation
import core.game.world.map.path.Pathfinder
import core.tools.RandomFunction
import kotlin.math.*
/**
* Specify a position (x,y,z), implements [Node].
* - Coordinates x and y can be negative for delta values. Values ±16,383.
* - Coordinate z (level) has a quirk, this class doesn't let you init with a negative z. Values 0 - 3.
*/
/*
Official Terms
- Zones: 8x8 tiles
- Map Squares: 64x64 tiles
- Build Area: 104x104 tiles (13 x 13 chunks centered around player's chunk) - client's loaded area
Some Definitions:
- Cardinal: Four Base Direction (N E S W)
- Diagonal: Four Diagonal directions (NE SE SW NW)
- Compass: All 8 directions (For this game, compass in relation to 8-point Cardinal+Diagonal)
- Euclidean distance: Straight-line length of two points. Direct calculation. e.g. [2,3] distance=13(3.61)
- Manhattan distance: Cardinal length of two points. Count of steps along grid lines. e.g. [2,3] distance=5
- Chebyshev distance: Cardinal+Diagonal length of two points. Count of king moves to make. e.g. [2,3] distance=3
*/
class Location(var hash: LocationHash) : Node(null, null) {
@JvmOverloads
constructor(x: Int, y: Int, z: Int = 0) : this(
LocationHash(x, y, (z % 4 + 4) % 4) // The previous z calc didn't make sense since they play around with negative numbers.
)
constructor(location: Location) : this(location.hash)
constructor(x: Int, y: Int, z: Int, randomizer: Int) :
this(x + RandomFunction.getRandom(randomizer), y + RandomFunction.getRandom(randomizer), z)
init { super.destinationFlag = DestinationFlag.LOCATION } // This is to set the type of [Node].
// Map properties directly to the underlying hash for convenience
var x: Int
get() = hash.x
set(value) { hash = LocationHash(value, y, z) }
var y: Int
get() = hash.y
set(value) { hash = LocationHash(x, value, z) }
var z: Int
get() = hash.z
set(value) { hash = LocationHash(x, y, (value % 4 + 4) % 4) }
companion object {
// Alternate static constructors
@JvmStatic @JvmOverloads fun create(x: Int, y: Int, z: Int = 0): Location = Location(x, y, z)
@JvmStatic fun create(location: Location): Location = Location(location)
/** @return new Location parsed from a string format "[x, y, z]". */
fun fromString(locString: String): Location {
val coordinates = locString.removeSurrounding("[", "]").split(",").map { it.trim().toInt() }
return create(coordinates[0], coordinates[1], coordinates[2])
}
/** @return Straight-line(Euclidean) distance between two locations. */
@JvmStatic fun getDistance(first: Location, second: Location): Double {
val xDiff: Int = first.x - second.x
val yDiff: Int = first.y - second.y
return sqrt((xDiff * xDiff + yDiff * yDiff).toDouble())
}
/** @return A location representing the delta between two locations. This is not a good function because it doesn't care about z. */
@JvmStatic fun getDelta(location: Location, other: Location): Location = create(other.x - location.x, other.y - location.y, other.z - location.z)
/** @return A random location within radius, optionally verifying reachability. */
fun getRandomLocation(main: Location, radius: Int, reachable: Boolean): Location {
val loc = getTeleportLocation(main, radius)
if (!reachable) return loc
val path = Pathfinder.find(main, loc, false, Pathfinder.DUMB)
if (!path.isSuccessful && path.points.isNotEmpty()) {
return create(path.points.last.x, path.points.last.y, main.z)
} else if (path.isSuccessful) {
return loc
} else {
return main
}
}
}
// Function overrides of Node class and Java class
override fun getLocation(): Location = this
override fun toString(): String = "[$x, $y, $z]"
override fun hashCode(): Int = hash.hash
override fun equals(other: Any?): Boolean {
return when (other) {
is Location -> other.hash == this.hash
is LocationHash -> other == this.hash
else -> false
}
}
fun equals(x: Int, y: Int, z: Int): Boolean = hash == LocationHash(x, y, z)
// Derived variables. Please note the following isn't a 1-1 representation of what is officially used.
// Region - (Officially: Map Square) 64x64
/** Unique index "key" (Officially: region_uid) for a 64x64 area. Made of x and y joined bitwise together. Used in xteas.json. */
val regionId: Int get() = x shr 6 shl 8 or (y shr 6) // Cuts off the end 6 bits of both x y and joins them together. E.g. 10392
/** The chunk x index for this position (x/8). For sets of 8x8 area. THIS IS NAMED WRONG, THIS IS A CHUNK(8) NOT REGION(64). */
val regionX: Int get() = x shr 3 // shr 3 is essentially divide by 8
/** The chunk y index for this position (y/8). For sets of 8x8 area. THIS IS NAMED WRONG, THIS IS A CHUNK(8) NOT REGION(64). */
val regionY: Int get() = y shr 3 // shr 3 is essentially divide by 8
// Local - x,y position inside a region
/** The relative x position inside a region(64x64). */
val localX: Int get() = x and 63 // and 63 is essentially modulo 64
/** The relative y position inside a region(64x64). */
val localY: Int get() = y and 63 // and 63 is essentially modulo 64
/** @return Check if it is in the same region id. */
fun isInRegion(region: Int): Boolean = regionId == region
// Chunk - (Officially: Zones) 8x8
val chunkBase: Location get() = create(regionX shl 3, regionY shl 3, z)
/** The chunk x index for this position RELATIVE to a region[localX](x/8). THIS IS NOT GLOBAL CHUNK X. */
val chunkX: Int get() = localX shr 3 // shr 3 is essentially divide by 8 (note this is localX)
/** The chunk y index for this position RELATIVE to a region[localY](y/8). THIS IS NOT GLOBAL CHUNK Y. */
val chunkY: Int get() = localY shr 3 // shr 3 is essentially divide by 8 (note this is localX)
/** The relative x position inside a chunk(8x8). */
val chunkOffsetX: Int get() = localX and 7 // and 7 is essentially modulo 8
/** The relative y position inside a chunk(8x8). */
val chunkOffsetY: Int get() = localY and 7 // and 7 is essentially modulo 8
// Scene - (Officially: Build Area) 104x104 - Made of 13x13 chunks(8x8)
/** The relative x position inside a scene(104x104). Used for client viewport rendering. */
val sceneX: Int get() = x - (regionX - 6 shl 3) // Centers the current region at [7chunk,7chunk]
/** The relative y position inside a scene(104x104). Used for client viewport rendering. */
val sceneY: Int get() = y - (regionY - 6 shl 3) // Centers the current region at [7chunk,7chunk]
/** @return The local scene x-coordinate relative to another location's regionX. */
fun getSceneX(loc: Location): Int = x - (loc.regionX - 6 shl 3)
/** @return The local scene y-coordinate relative to another location's regionY. */
fun getSceneY(loc: Location): Int = y - (loc.regionY - 6 shl 3)
/** @return The location incremented by another location's coordinates. (POTENTIAL Z PROBLEMS HERE) */
// TODO: THE FOLLOWING TRANSFORM HAS SHIT Z HANDLING.
fun transform(l: Location): Location = Location(x + l.x, y + l.y, (z + l.z) % 4)
/** @return The location incremented by specific x, y, z differences. */
fun transform(diffX: Int, diffY: Int, z: Int): Location = Location(x + diffX, y + diffY, (this.z + z) % 4)
/** @return The location incremented by direction and steps. */
@JvmOverloads fun transform(dir: Direction, steps: Int = 1): Location = Location(x + dir.stepX * steps, y + dir.stepY * steps, z)
/** Location transformed by a Vector. Limit usage to more complicated functionality. */
fun transform(vector: Vector): Location = create(x + floor(vector.x).toInt(), y + floor(vector.y).toInt())
/** @return True if this location is 1 tile N, W, S, or E of the node. THIS CAN BE REFACTORED OUT. */
fun isNextTo(node: Node): Boolean {
val l = node.location
return if (l.y == y) abs(l.x - x) == 1 else if (l.x == x) abs(l.y - y) == 1 else false
}
/** @return True if this other location is within dist of this location. */
@JvmOverloads fun withinDistance(other: Location, dist: Int = MapDistance.RENDERING.distance): Boolean {
if (other.z != z) return false
return getDistance(this, other) <= dist
}
/** @return True if within specified distance using max norm (Chebyshev) distance. */
fun withinMaxnormDistance(other: Location, dist: Int): Boolean = if (other.z != z) false else max(abs(other.x - x), abs(other.y - y)) <= dist
/** @return The straight-line(Euclidean) distance between this and another location. */
fun getDistance(other: Location): Double = getDistance(this, other)
/** @returns ArrayList of the 8 cardinal and diagonal tiles. Order is important and follows [Direction] indexing **/
val surroundingTiles: ArrayList<Location> get() {
val locations = ArrayList<Location>()
locations.add(transform(-1, -1, 0))
locations.add(transform(0, -1, 0))
locations.add(transform(1, -1, 0))
locations.add(transform(1, 0, 0))
locations.add(transform(1, 1, 0))
locations.add(transform(0, 1, 0))
locations.add(transform(-1, 1, 0))
locations.add(transform(-1, 0, 0))
return locations
}
/** @returns ArrayList of the 4 cardinal direction tiles. Order is important and follows [Direction] indexing **/
val cardinalTiles: ArrayList<Location> get() {
val locations = ArrayList<Location>()
locations.add(transform(-1, 0, 0))
locations.add(transform(0, -1, 0))
locations.add(transform(1, 0, 0))
locations.add(transform(0, 1, 0))
return locations
}
/**
* Gets the directional components of a movement step.
* TODO: would prefer this in the other place. This doesn't exactly belong here since this isn't a common function for other instances of Location
*
* we can move diagonally but there's no melee attacking diagonally. its gotta be from N, W, S or E.
* if we are moving diagonally, we then need to check if we can attack from one of the valid directions.
* in other words we need to check the two tiles that share a corner with our destination and make sure they can be attacked from
*
* picture this: you are at (0,0). im at (1,1). you want to strangle my head off, so you got to check
* 1. to the west of destination: (1,1) + (-1,0) = (0,1) or north from where you started
* 2. to the south of the destination: (1,1) + (0,-1) = (1,0) or east from where you started
* this function is used only by CombatSwingHandler.kt and assumes that we return the X tiles, west or east, FIRST
* and then the Y tiles South and North.
*/
fun getStepComponents(dir: Direction): List<Location> {
val output = ArrayList<Location>(2)
if (dir.stepX != 0) output.add(transform(-dir.stepX, 0, 0))
if (dir.stepY != 0) output.add(transform(0, -dir.stepY, 0))
return output
}
/** @return the Direction of the another location relative to this one. */
fun deriveDirection(location: Location): Direction? {
val dx = (location.x - x).let { if (it >= 0) min(it, 1) else -1 }
val dy = (location.y - y).let { if (it >= 0) min(it, 1) else -1 }
val sb = StringBuilder()
if (dy != 0) sb.append(if (dy > 0) "NORTH" else "SOUTH")
if (dx != 0) { if (sb.isNotEmpty()) sb.append("_"); sb.append(if (dx > 0) "EAST" else "WEST") }
return if (sb.isEmpty()) null else Direction.valueOf(sb.toString())
}
/**
* Locations as an Integer rather than a Location object. This is for space efficiency.
* Inner class, because this is only used here, and to avoid adding another standard until this replaces Location
*
* 32 bits for an int (z z x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y)
* - Rightmost set of 15 bits is for y (can handle negative numbers)
* - Middle set of 15 bits is for x (can handle negative numbers)
* - Leftmost set of 2 bits is for z
*
* Since x and y occupy 15 bits (0 to 32,767) it becomes => 1 sign + 14 bits (e.g., 16,384) for range of ±16,383.
*/
@JvmInline
value class LocationHash(val hash: Int) {
constructor(x: Int, y: Int, z: Int = 0) : this(
((z and 0x3) shl 30) or
((x and 0x7FFF) shl 15) or
(y and 0x7FFF)
)
val x: Int get() {
val raw = (hash ushr 15) and 0x7FFF
// If the 15th bit is set, it's a negative number in 15-bit range
return if (raw >= 0x4000) raw - 0x8000 else raw
}
val y: Int get() {
val raw = hash and 0x7FFF
// If the 15th bit is set, it's a negative number
return if (raw >= 0x4000) raw - 0x8000 else raw
}
val z: Int get() = hash ushr 30
}
}

View file

@ -783,10 +783,10 @@ object RegionManager {
for (regionX in ((l.regionX - 6) shr 3)..((l.regionX + 6) shr 3)) {
for (regionY in ((l.regionY - 6) shr 3)..((l.regionY + 6) shr 3)) {
for (player in forId((regionX shl 8) or regionY).planes[l.z].players) {
if (player.location.x >= l.getX() - xdist &&
player.location.x <= l.getX() + xdist &&
player.location.y >= l.getY() - ydist &&
player.location.y <= l.getY() + ydist) {
if (player.location.x >= l.x - xdist &&
player.location.x <= l.x + xdist &&
player.location.y >= l.y - ydist &&
player.location.y <= l.y + ydist) {
players.add(player)
}
}

Some files were not shown because too many files have changed in this diff Show more