diff --git a/src/net/data-parser/client-packet-data-parser.ts b/src/net/data-parser/client-packet-data-parser.ts index 137b4f918..9d8d0d1d6 100644 --- a/src/net/data-parser/client-packet-data-parser.ts +++ b/src/net/data-parser/client-packet-data-parser.ts @@ -67,14 +67,12 @@ export class ClientPacketDataParser extends DataParser { return; } - if(this.activePacketSize !== 0) { - // read packet data - const packetData = this.activeBuffer.readBytes(this.activePacketSize); - handlePacket(this.clientConnection.player, this.activePacketId, this.activePacketSize, packetData); + // read packet data + const packetData = this.activePacketSize !== 0 ? this.activeBuffer.readBytes(this.activePacketSize) : null; + handlePacket(this.clientConnection.player, this.activePacketId, this.activePacketSize, packetData); - if(clearBuffer) { - this.activeBuffer = null; - } + if(clearBuffer) { + this.activeBuffer = null; } this.activePacketId = null; diff --git a/src/net/incoming-packet-directory.ts b/src/net/incoming-packet-directory.ts index c1197c268..6f674db1b 100644 --- a/src/net/incoming-packet-directory.ts +++ b/src/net/incoming-packet-directory.ts @@ -22,7 +22,7 @@ import { itemOnObjectPacket } from '@server/net/incoming-packets/item-on-object- import { numberInputPacket } from '@server/net/incoming-packets/number-input-packet'; import { itemOnNpcPacket } from '@server/net/incoming-packets/item-on-npc-packet'; -const ignore = [ 234, 160, 58 /* camera move */ ]; +const ignore = [ 234, 160, 216, 13, 58 /* camera move */ ]; const packets: { [key: number]: incomingPacket } = { 75: chatPacket, diff --git a/src/net/incoming-packets/character-design-packet.ts b/src/net/incoming-packets/character-design-packet.ts index 51c5810e1..aaf66e5a4 100644 --- a/src/net/incoming-packets/character-design-packet.ts +++ b/src/net/incoming-packets/character-design-packet.ts @@ -39,5 +39,5 @@ export const characterDesignPacket: incomingPacket = (player: Player, packetId: }; player.updateFlags.appearanceUpdateRequired = true; - player.closeActiveWidget(); + player.closeActiveWidgets(); }; diff --git a/src/net/incoming-packets/widgets-closed-packet.ts b/src/net/incoming-packets/widgets-closed-packet.ts index 7c92d0da8..2ebea7a94 100644 --- a/src/net/incoming-packets/widgets-closed-packet.ts +++ b/src/net/incoming-packets/widgets-closed-packet.ts @@ -3,5 +3,5 @@ import { Player } from '../../world/actor/player/player'; import { RsBuffer } from '@server/net/rs-buffer'; export const widgetsClosedPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: RsBuffer): void => { - player.closeActiveWidget(false); + player.closeActiveWidgets(false); }; diff --git a/src/plugins/dialogue/dialogue-option-plugin.ts b/src/plugins/dialogue/dialogue-option-plugin.ts index 484d33ef5..2a71053c6 100644 --- a/src/plugins/dialogue/dialogue-option-plugin.ts +++ b/src/plugins/dialogue/dialogue-option-plugin.ts @@ -5,18 +5,11 @@ const dialogueIds = [ 64, 65, 66, 67, 241, 242, 243, 244, 228, 230, 232, 234, - 158, 161, 175, - 167, 171, 170, - 168, 159, 177, - 165, 164, 163, - 160, 174, 169, - 166, 157, 176, - 173, 162, 172, 210, 211, 212, 213, 214, ]; /** - * Handles a basic NPC/Player/Option/level-up dialogue choice/action. + * Handles a basic NPC/Player/Option/Text dialogue choice/action. */ export const action: widgetAction = (details) => { const { player, childId } = details; diff --git a/src/plugins/skills/crafting/spinning-wheel-plugin.ts b/src/plugins/skills/crafting/spinning-wheel-plugin.ts index 5ffe487b5..1b8cfd81a 100644 --- a/src/plugins/skills/crafting/spinning-wheel-plugin.ts +++ b/src/plugins/skills/crafting/spinning-wheel-plugin.ts @@ -146,7 +146,7 @@ export const buttonClicked: buttonAction = (details) => { const product = widgetButtonIds.get(details.buttonId); // Close the widget as it is no longer needed - details.player.closeActiveWidget(); + details.player.closeActiveWidgets(); if (!details.player.skills.hasSkillLevel(Skill.CRAFTING, product.spinnable.requiredLevel)) { details.player.sendMessage(`You need a crafting level of ${product.spinnable.requiredLevel} to craft ${gameCache.itemDefinitions.get(product.spinnable.output).name.toLowerCase()}.`, true); diff --git a/src/plugins/skills/level-up-dialogue-plugin.ts b/src/plugins/skills/level-up-dialogue-plugin.ts new file mode 100644 index 000000000..40664e246 --- /dev/null +++ b/src/plugins/skills/level-up-dialogue-plugin.ts @@ -0,0 +1,22 @@ +import { widgetAction } from '@server/world/actor/player/action/widget-action'; +import { ActionType, RunePlugin } from '@server/plugins/plugin'; + +const widgetIds = [ + 158, 161, 175, + 167, 171, 170, + 168, 159, 177, + 165, 164, 163, + 160, 174, 169, + 166, 157, 176, + 173, 162, 172, +]; + +/** + * Handles a level-up dialogue action. + */ +export const action: widgetAction = (details) => { + const { player } = details; + player.closeActiveWidgets(); +}; + +export default new RunePlugin({ type: ActionType.WIDGET_ACTION, widgetIds, action, cancelActions: false }); diff --git a/src/world/actor/player/action/dialogue-action.ts b/src/world/actor/player/action/dialogue-action.ts index 27ec1fc7b..00a9500f7 100644 --- a/src/world/actor/player/action/dialogue-action.ts +++ b/src/world/actor/player/action/dialogue-action.ts @@ -1,7 +1,6 @@ import { Player } from '@server/world/actor/player/player'; import { gameCache } from '@server/game-server'; import { Npc } from '@server/world/actor/npc/npc'; -import { skillDetails } from '@server/world/actor/skills'; export const dialogueWidgetIds = { PLAYER: [ 64, 65, 66, 67 ], @@ -17,7 +16,6 @@ const lineConstraints = { PLAYER: [ 1, 4 ], NPC: [ 1, 4 ], OPTIONS: [ 2, 5 ], - LEVEL_UP: [ 2, 2 ], TEXT: [ 1, 5 ] }; @@ -54,7 +52,7 @@ export enum DialogueEmote { ANGRY_4 = 617 } -export type DialogueType = 'PLAYER' | 'NPC' | 'OPTIONS' | 'LEVEL_UP' | 'TEXT'; +export type DialogueType = 'PLAYER' | 'NPC' | 'OPTIONS' | 'TEXT'; export interface DialogueOptions { type: DialogueType; @@ -94,10 +92,6 @@ export class DialogueAction { throw 'NPC not supplied.'; } - if(options.type === 'LEVEL_UP' && options.skillId === undefined) { - throw 'Skill ID not supplied.'; - } - this._action = null; let widgetIndex = options.lines.length - 1; @@ -105,13 +99,7 @@ export class DialogueAction { widgetIndex--; } - let widgetId = -1; - - if(options.type === 'LEVEL_UP') { - widgetId = skillDetails.map(skill => !skill || !skill.advancementWidgetId ? -1 : skill.advancementWidgetId)[options.skillId]; - } else { - widgetId = dialogueWidgetIds[options.type][widgetIndex]; - } + const widgetId = dialogueWidgetIds[options.type][widgetIndex]; if(widgetId === undefined || widgetId === null || widgetId === -1) { return Promise.resolve(this); @@ -137,7 +125,7 @@ export class DialogueAction { } else if(options.type === 'OPTIONS') { this.p.outgoingPackets.updateWidgetString(widgetId, 0, options.title); textOffset = 1; - } else if(options.type === 'LEVEL_UP' || options.type === 'TEXT') { + } else if(options.type === 'TEXT') { textOffset = 0; } diff --git a/src/world/actor/player/action/item-selection-action.ts b/src/world/actor/player/action/item-selection-action.ts index 4b2150748..9bb7bab06 100644 --- a/src/world/actor/player/action/item-selection-action.ts +++ b/src/world/actor/player/action/item-selection-action.ts @@ -141,17 +141,17 @@ export async function itemSelectionAction(player: Player, type: 'COOKING' | 'MAK interactionSub.unsubscribe(); if(input < 1 || input > 2147483647) { - player.closeActiveWidget(); + player.closeActiveWidgets(); reject('Invalid User Amount Input'); } else { - player.closeActiveWidget(); + player.closeActiveWidgets(); resolve({itemId, amount: input} as ItemSelection); } }); } else { actionsSub.unsubscribe(); interactionSub.unsubscribe(); - player.closeActiveWidget(); + player.closeActiveWidgets(); resolve({itemId, amount} as ItemSelection); } }); diff --git a/src/world/actor/player/player.ts b/src/world/actor/player/player.ts index b5550454c..95928bc9d 100644 --- a/src/world/actor/player/player.ts +++ b/src/world/actor/player/player.ts @@ -13,7 +13,7 @@ import { PlayerSave, PlayerSettings, QuestProgress, savePlayerData } from './player-data'; -import { ActiveWidget, widgets, widgetScripts } from '../../config/widget'; +import { PlayerWidget, widgets, widgetScripts } from '../../config/widget'; import { ContainerUpdateEvent, ItemContainer } from '../../items/item-container'; import { EquipmentBonuses, ItemDetails } from '../../config/item-data'; import { Item } from '../../items/item'; @@ -78,7 +78,8 @@ export class Player extends Actor { public trackedPlayers: Player[]; public trackedNpcs: Npc[]; private _appearance: Appearance; - private _activeWidget: ActiveWidget; + private _activeWidget: PlayerWidget; + private queuedWidgets: PlayerWidget[]; private readonly _equipment: ItemContainer; private _bonuses: EquipmentBonuses; private _carryWeight: number; @@ -108,6 +109,7 @@ export class Player extends Actor { this.trackedPlayers = []; this.trackedNpcs = []; this._activeWidget = null; + this.queuedWidgets = []; this._carryWeight = 0; this._equipment = new ItemContainer(14); this.dialogueInteractionEvent = new Subject(); @@ -463,8 +465,6 @@ export class Player extends Actor { questData.completion.modelRotationX || 0, questData.completion.modelRotationY || 0, questData.completion.modelZoom || 0); - questData.completion.onComplete(this); - this.activeWidget = { widgetId: widgets.questReward, type: 'SCREEN', @@ -472,6 +472,8 @@ export class Player extends Actor { }; this.modifyWidget(widgets.questTab, { childId: questData.questTabId, textColor: colors.green }); + + questData.completion.onComplete(this); } playerQuest.stage = stage; @@ -485,7 +487,7 @@ export class Player extends Actor { public modifyWidget(widgetId: number, options: { childId?: number, text?: string, hidden?: boolean, textColor?: number }): void { const { childId, text, hidden, textColor } = options; - if(childId) { + if(childId !== undefined) { if(text !== undefined) { this.outgoingPackets.updateWidgetString(widgetId, childId, text); } @@ -612,6 +614,10 @@ export class Player extends Actor { this.updateCarryWeight(); } + /** + * Updates the player's carry weight based off of their held items (inventory + equipment). + * @param force Whether or not to force send an updated carry weight to the game client. + */ public updateCarryWeight(force: boolean = false): void { const oldWeight = this._carryWeight; this._carryWeight = Math.round(this.inventory.weight() + this.equipment.weight()); @@ -621,6 +627,11 @@ export class Player extends Actor { } } + /** + * Updates a player's client settings based off of which setting button they've clicked. + * @param buttonId The ID of the setting button. + * @TODO refactor to better match the 400+ widget system + */ public settingChanged(buttonId: number): void { const settingsMappings = { 0: {setting: 'runEnabled', value: !this.settings['runEnabled']}, @@ -661,6 +672,9 @@ export class Player extends Actor { this.settings[config.setting] = config.value; } + /** + * Updates the player's combat bonuses based off of their equipped items. + */ public updateBonuses(): void { this.clearBonuses(); @@ -709,15 +723,46 @@ export class Player extends Actor { }; } - public closeActiveWidget(notifyClient: boolean = true): void { + /** + * Queues up a widget to be displayed when the active widget is closed. + * If there is no active widget, the provided widget will be automatically displayed. + * @param widget The widget to queue. + */ + public queueWidget(widget: PlayerWidget): void { + if(this.activeWidget === null) { + this.activeWidget = widget; + } else { + this.queuedWidgets.push(widget); + console.log(this.queuedWidgets); + } + } + + /** + * Closes the currently active widget or widget pair. + * @param notifyClient [optional] Whether or not to notify the game client that widgets should be cleared. Defaults to true. + */ + public closeActiveWidgets(notifyClient: boolean = true): void { if(notifyClient) { - this.activeWidget = null; + if(this.queuedWidgets.length !== 0) { + this.activeWidget = this.queuedWidgets.shift(); + } else { + this.activeWidget = null; + } } else { - this.actionsCancelled.next(true); this._activeWidget = null; + + if(this.queuedWidgets.length !== 0) { + this.activeWidget = this.queuedWidgets.shift(); + } else { + this.actionsCancelled.next(true); + } } } + /** + * Checks to see if the player has the specified widget ID open on their screen or not. + * @param widgetId The ID of the widget to look for. + */ public hasWidgetOpen(widgetId: number): boolean { return this.activeWidget && this.activeWidget.widgetId === widgetId; } @@ -777,12 +822,16 @@ export class Player extends Actor { this._appearance = value; } - public get activeWidget(): ActiveWidget { + public get activeWidget(): PlayerWidget { return this._activeWidget; } - public set activeWidget(value: ActiveWidget) { + public set activeWidget(value: PlayerWidget) { if(value !== null) { + if(value.beforeOpened !== undefined) { + value.beforeOpened(); + } + if(value.type === 'SCREEN') { this.outgoingPackets.showScreenWidget(value.widgetId); } else if(value.type === 'CHAT') { @@ -792,6 +841,10 @@ export class Player extends Actor { } else if(value.type === 'SCREEN_AND_TAB') { this.outgoingPackets.showScreenAndTabWidgets(value.widgetId, value.secondaryWidgetId); } + + if(value.afterOpened !== undefined) { + value.afterOpened(); + } } else { this.outgoingPackets.closeActiveWidgets(); } diff --git a/src/world/actor/skills.ts b/src/world/actor/skills.ts index 3cacb6a54..6455a5ca1 100644 --- a/src/world/actor/skills.ts +++ b/src/world/actor/skills.ts @@ -120,7 +120,7 @@ export class Skills { if(currentLevel !== finalLevel) { this.setLevel(skillId, finalLevel); - this.actor.playGraphics({ id: 199, delay: 0, height: 125 }); + // this.actor.playGraphics({ id: 199, delay: 0, height: 125 }); if(this.actor instanceof Player) { const achievementDetails = skillDetails[skillId]; @@ -128,19 +128,44 @@ export class Skills { return; } - this.actor.outgoingPackets.chatboxMessage(`Congratulations, you just advanced a ${achievementDetails.name.toLowerCase()} level.`); - - if(achievementDetails.advancementWidgetId) { - dialogueAction(this.actor, { type: 'LEVEL_UP', skillId, lines: [ - `Congratulations, you just advanced ${startsWithVowel(achievementDetails.name) ? 'an' : 'a'} ` + - `${achievementDetails.name.toLowerCase()} level.`, - `Your ${achievementDetails.name.toLowerCase()} level is now ${finalLevel}.` ] }).then(d => d.close()); - // @TODO sounds - } + this.actor.sendMessage(`Congratulations, you just advanced a ${achievementDetails.name.toLowerCase()} level.`); + this.showLevelUpDialogue(skillId, finalLevel); } } } + public showLevelUpDialogue(skillId: number, level: number): void { + if(!(this.actor instanceof Player)) { + return; + } + + const player = this.actor as Player; + const achievementDetails = skillDetails[skillId]; + const widgetId = achievementDetails.advancementWidgetId; + + if(!widgetId) { + return; + } + + const skillName = achievementDetails.name.toLowerCase(); + + player.queueWidget({ + widgetId, + type: 'CHAT', + closeOnWalk: true, + beforeOpened: () => { + player.modifyWidget(widgetId, { childId: 0, + text: `Congratulations, you just advanced ${startsWithVowel(skillName) ? 'an' : 'a'} ${skillName} level.` }); + player.modifyWidget(widgetId, { childId: 1, + text: `Your ${skillName} level is now ${level}.` }); + }, + afterOpened: () => { + player.playGraphics({ id: 199, delay: 0, height: 125 }); + // @TODO sounds + } + }); + } + public setExp(skillId: number, exp: number): void { this._values[skillId].exp = exp; } diff --git a/src/world/config/widget.ts b/src/world/config/widget.ts index dc9a7d419..56bde6b80 100644 --- a/src/world/config/widget.ts +++ b/src/world/config/widget.ts @@ -67,11 +67,13 @@ export const widgetScripts = { questPoints: 101 }; -export interface ActiveWidget { +export interface PlayerWidget { widgetId: number; secondaryWidgetId?: number; type: 'SCREEN' | 'CHAT' | 'FULLSCREEN' | 'SCREEN_AND_TAB'; disablePlayerMovement?: boolean; closeOnWalk?: boolean; forceClosed?: Function; + beforeOpened?: Function; + afterOpened?: Function; }