diff --git a/data/config/npcs/ardougne.json b/data/config/npcs/ardougne.json index b1dcddbec..5e493dd84 100644 --- a/data/config/npcs/ardougne.json +++ b/data/config/npcs/ardougne.json @@ -2,29 +2,56 @@ "rs:ardougne_baker": { "variations": [ { - "suffix": 0, - "game_id": 571 + "suffix": 0, + "game_id": 571 }, { - "suffix": 1, - "game_id": 571 + "suffix": 1, + "game_id": 571 } ] }, "rs:knight": { "variations": [ { - "suffix": 0, - "game_id": 23 + "suffix": 0, + "game_id": 23 }, { - "suffix": 1, - "game_id": 26 + "suffix": 1, + "game_id": 26 } ] }, "rs:hero": { - "game_id": 21 + "game_id": 21, + "killable": true, + "respawn_time": 50, + "skills": { + "hitpoints": 82 + }, + "offensive_stats": { + "speed": 5 + }, + "defensive_stats": { + "stab": 87, + "slash": 84, + "crush": 76, + "magic": -10, + "ranged": 79 + }, + "animations": { + "attack": 422, + "defend": 424, + "death": 512 + }, + "drop_table": [ + { + "itemKey": "rs:bones", + "frequency": "always", + "amount": 1 + } + ] }, "rs:silk_merchant": { "game_id": 574 diff --git a/data/config/npcs/general.json b/data/config/npcs/general.json index c2fb0ff6e..c1b2806b9 100644 --- a/data/config/npcs/general.json +++ b/data/config/npcs/general.json @@ -2,14 +2,21 @@ "rs:rat": { "game_id": 47, "drop_table": [ - { "itemKey": "rs:rats_tail", "frequency": "always", "amount": 1, + { + "itemKey": "rs:rats_tail", + "frequency": "always", + "amount": 1, "amountMax": 1, "questRequirement": { "questId": "rs:witchs_potion", "stage": 50 } }, - { "itemKey": "rs:bones", "frequency": "always", "amount": 1 } + { + "itemKey": "rs:bones", + "frequency": "always", + "amount": 1 + } ], "skills": { "hitpoints": 1 diff --git a/data/config/npcs/generic-humans.json b/data/config/npcs/generic-humans.json index 88dd1c704..36dbd813b 100644 --- a/data/config/npcs/generic-humans.json +++ b/data/config/npcs/generic-humans.json @@ -22,12 +22,19 @@ "death": 512 }, "drop_table": [ - [ "rs:bones", "always", 1 ], - [ "rs:bronze_dagger", "1/128", 1, 12 ] + { + "itemKey": "rs:bones", + "frequency": "always", + "amount": 1 + }, + { + "itemKey": "rs:bronze_dagger", + "frequency": "1/128", + "amount": 1 + } ] } }, - "rs:man": { "extends": "rs:human_male", "game_id": 1, diff --git a/data/config/npcs/goblins.json b/data/config/npcs/goblins.json index c7a92208b..3ada09481 100644 --- a/data/config/npcs/goblins.json +++ b/data/config/npcs/goblins.json @@ -23,16 +23,22 @@ "ranged": -15 }, "animations": { - "attack": [ 310, 309 ], + "attack": [ + 310, + 309 + ], "defend": 312, "death": 313 }, "drop_table": [ - [ "rs:bones", "always", 1 ] + { + "itemKey": "rs:bones", + "frequency": "always", + "amount": 1 + } ] } }, - "rs:goblin": { "extends": "rs:goblin_base_l2", "game_id": 100, diff --git a/data/config/npcs/guards.json b/data/config/npcs/guards.json index 957afa01a..52a6ed0c7 100644 --- a/data/config/npcs/guards.json +++ b/data/config/npcs/guards.json @@ -11,7 +11,95 @@ }, { "suffix": 2, - "game_id": 32 + "game_id": 32, + "killable": true, + "respawn_time": 3, + "skills": { + "hitpoints": 22, + "attack": 17, + "strength": 18, + "defence": 13, + "magic": 1, + "ranged": 1 + }, + "offensive_stats": { + "speed": 5, + "attack": 9, + "strength": 7, + "magic": 0, + "magic_strength": 0, + "ranged": 0, + "ranged_strength": 0 + }, + "defensive_stats": { + "stab": 24, + "slash": 14, + "crush": 19, + "magic": 4, + "ranged": 16 + }, + "animations": { + "attack": 422, + "defend": 424, + "death": 512 + }, + "drop_table": [ + { + "itemKey": "rs:bones", + "frequency": "always", + "amount": 1 + }, + { + "itemKey": "rs:steel_arrow", + "frequency": "1/32", + "amount": 1 + }, + { + "itemKey": "rs:steel_arrow", + "frequency": "1/42.67", + "amount": 1 + }, + { + "itemKey": "rs:air_rune", + "frequency": "1/64", + "amount": 6 + }, + { + "itemKey": "rs:earth_rune", + "frequency": "1/64", + "amount": 3 + }, + { + "itemKey": "rs:fire_rune", + "frequency": "1/64", + "amount": 2 + }, + { + "itemKey": "rs:bronze_arrow", + "frequency": "1/64", + "amount": 2 + }, + { + "itemKey": "rs:blood_rune", + "frequency": "1/128", + "amount": 1 + }, + { + "itemKey": "rs:chaos_rune", + "frequency": "1/128", + "amount": 1 + }, + { + "itemKey": "rs:nature_rune", + "frequency": "1/128", + "amount": 1 + }, + { + "itemKey": "rs:steel_arrow", + "frequency": "1/128", + "amount": 5 + } + ] }, { "suffix": 3, diff --git a/src/engine/world/actor/player/attack.ts b/src/engine/world/actor/player/attack.ts index 25fd0e5d1..d203935f5 100644 --- a/src/engine/world/actor/player/attack.ts +++ b/src/engine/world/actor/player/attack.ts @@ -2,10 +2,10 @@ export class Attack { damageType: AttackDamageType; - attackRoll: number =0; + attackRoll: number = 0; defenseRoll: number = 0; - hitChance: number =0; - damage: number =0; + hitChance: number = 0; + damage: number = 0; maximumHit: number; } @@ -15,4 +15,4 @@ export enum AttackDamageType { Crush, Magic, Range -} \ No newline at end of file +} diff --git a/src/engine/world/actor/player/sync/npc-sync-task.ts b/src/engine/world/actor/player/sync/npc-sync-task.ts index ee8268228..f91fba5ee 100644 --- a/src/engine/world/actor/player/sync/npc-sync-task.ts +++ b/src/engine/world/actor/player/sync/npc-sync-task.ts @@ -74,6 +74,17 @@ export class NpcSyncTask extends SyncTask { }); } + /** + * As of 2024-09-03 this has been modified to include an extra `short` if + * any updates are required. This extra `short` includes the `worldIndex` + * for this NPC, which helps the client figure out which NPC to apply the + * updates to. + * + * For the sake of efficiency, this `short` will only be added once, and it + * will always be added after the first updated value's data is processed. + * + * Make sure to keep your client updated so that it can handle it. + */ private appendUpdateMaskData(npc: Npc, updateMaskData: ByteBuffer): void { const updateFlags = npc.updateFlags; if(!updateFlags.updateBlockRequired) { @@ -103,12 +114,23 @@ export class NpcSyncTask extends SyncTask { updateMaskData.put(mask, 'BYTE'); + let alreadyPutWorldIndex = false; + const putWorldIndex = () => { + if (alreadyPutWorldIndex) { + return; + } + updateMaskData.put(npc.worldIndex, 'SHORT'); + alreadyPutWorldIndex = true; + } + if(updateFlags.damage !== null) { const damage = updateFlags.damage; updateMaskData.put(damage.damageDealt); updateMaskData.put(damage.damageType.valueOf()); updateMaskData.put(damage.remainingHitpoints); updateMaskData.put(damage.maxHitpoints); + + putWorldIndex(); } if(updateFlags.faceActor !== undefined) { @@ -129,6 +151,8 @@ export class NpcSyncTask extends SyncTask { updateMaskData.put(worldIndex, 'SHORT'); } + + putWorldIndex(); } if(updateFlags.chatMessages.length !== 0) { @@ -139,16 +163,20 @@ export class NpcSyncTask extends SyncTask { } else { updateMaskData.putString('Undefined Message'); } + + putWorldIndex(); } if(updateFlags.appearanceUpdateRequired) { updateMaskData.put(npc.id, 'SHORT'); + putWorldIndex(); } if(updateFlags.facePosition) { const position = updateFlags.facePosition; updateMaskData.put(position.x * 2 + 1, 'SHORT'); updateMaskData.put(position.y * 2 + 1, 'SHORT', 'LITTLE_ENDIAN'); + putWorldIndex(); } if(updateFlags.animation) { @@ -163,6 +191,7 @@ export class NpcSyncTask extends SyncTask { updateMaskData.put(animation.id, 'SHORT'); updateMaskData.put(delay); } + putWorldIndex(); } } diff --git a/src/plugins/combat/attack.plugin.ts b/src/plugins/combat/attack.plugin.ts new file mode 100644 index 000000000..3146786c3 --- /dev/null +++ b/src/plugins/combat/attack.plugin.ts @@ -0,0 +1,39 @@ +import { npcInteractionActionHandler } from '@engine/action'; +import { DamageType } from '@engine/world/actor'; + +/** + * This is a placeholder for attacking/initiating combat with any NPC. + * + * It is meant to demonstrate basic NPC interactions and to validate that + * client-side NPC tracking works, specifically for chat messages and HP bars. + * + * It is also meant to help new users be less confused when they see that + * combat isn't implemented yet. See GitHub issue #429. + */ +export const action: npcInteractionActionHandler = (details) => { + const { player, option } = details; + + switch (option) { + case 'attack': + break; + default: + player.sendMessage(`This has not been implemented.`); + return; + } + + details.npc.say(`Hi there! Combat is not implemented yet. My worldIndex is: ${details.npc.worldIndex}`); + details.npc.updateFlags.addDamage(0, DamageType.NO_DAMAGE, 1, 1); +}; + +export default { + pluginId: 'rs:attack', + hooks: [ + { + type: 'npc_interaction', + objectIds: [], + options: [ 'attack' ], + walkTo: false, + handler: action + }, + ] +};