Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working on a better dialogue system. #29

Merged
merged 1 commit into from
Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 87 additions & 31 deletions src/world/mob/player/action/dialogue/dialogue-action.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { Player } from '@server/world/mob/player/player';
import { gameCache } from '@server/game-server';
import { Npc } from '@server/world/mob/npc/npc';

const interfaceIds = {
PLAYER: [ 968, 973, 979, 986 ],
NPC: [ 4882, 4887, 4893, 4900 ]
NPC: [ 4882, 4887, 4893, 4900 ],
OPTIONS: [ 2459, 2469, 2480, 2492 ]
};

const lineConstraints = {
PLAYER: [ 1, 4 ],
NPC: [ 1, 4 ],
OPTIONS: [ 2, 5 ]
};

export enum DialogueEmote {
Expand Down Expand Up @@ -39,57 +47,105 @@ export enum DialogueEmote {
ANGRY_4 = 617
}

export type DialogueType = 'PLAYER' | 'NPC';
export type DialogueType = 'PLAYER' | 'NPC' | 'OPTIONS';

export interface DialogueOptions {
type: DialogueType;
npc?: number;
emote?: DialogueEmote;
title?: string;
lines: string[];
}

export const closeDialogue = (player: Player): void => {
player.packetSender.closeActiveInterfaces();
};
export class DialogueAction {

private _action: number = null;

public constructor(private readonly p: Player) {
}

export const dialogueAction = (player: Player, options: DialogueOptions): Promise<number> => {
if(options.lines.length === 0 || options.lines.length > 4) {
throw 'Invalid line length.';
public player(emote: DialogueEmote, lines: string[]): Promise<DialogueAction> {
return this.dialogue({ emote, lines, type: 'PLAYER' });
}

if(options.type === 'NPC' && options.npc === undefined) {
throw 'NPC not supplied.';
public npc(npc: Npc, emote: DialogueEmote, lines: string[]): Promise<DialogueAction> {
return this.dialogue({ emote, lines, type: 'NPC', npc: npc.id });
}

const interfaceId = interfaceIds[options.type][options.lines.length - 1];
let textOffset = 1;
public options(title: string, options: string[]): Promise<DialogueAction> {
return this.dialogue({ type: 'OPTIONS', title, lines: options });
}

public dialogue(options: DialogueOptions): Promise<DialogueAction> {
if(options.lines.length < lineConstraints[options.type][0] || options.lines.length > lineConstraints[options.type][1]) {
throw 'Invalid line length.';
}

if(options.type === 'PLAYER' || options.type === 'NPC') {
if(!options.emote) {
options.emote = DialogueEmote.DEFAULT;
if(options.type === 'NPC' && options.npc === undefined) {
throw 'NPC not supplied.';
}

if(options.type === 'NPC') {
player.packetSender.setInterfaceModel2(interfaceId + 1, options.npc);
player.packetSender.updateInterfaceString(interfaceId + 2, gameCache.npcDefinitions.get(options.npc).name);
} else if(options.type === 'PLAYER') {
// @TODO
player.packetSender.updateInterfaceString(interfaceId + 2, player.username);
this._action = null;

let interfaceIndex = options.lines.length - 1;
if(options.type === 'OPTIONS') {
interfaceIndex--;
}

player.packetSender.playInterfaceAnimation(interfaceId + 1, options.emote);
textOffset += 2;
const interfaceId = interfaceIds[options.type][interfaceIndex];
let textOffset = 1;

if(options.type === 'PLAYER' || options.type === 'NPC') {
if(!options.emote) {
options.emote = DialogueEmote.DEFAULT;
}

if(options.type === 'NPC') {
this.p.packetSender.setInterfaceModel2(interfaceId + 1, options.npc);
this.p.packetSender.updateInterfaceString(interfaceId + 2, gameCache.npcDefinitions.get(options.npc).name);
} else if(options.type === 'PLAYER') {
this.p.packetSender.setInterfacePlayerHead(interfaceId + 1);
this.p.packetSender.updateInterfaceString(interfaceId + 2, this.p.username);
}

this.p.packetSender.playInterfaceAnimation(interfaceId + 1, options.emote);
textOffset += 2;
} else if(options.type === 'OPTIONS') {
this.p.packetSender.updateInterfaceString(interfaceId + 1, options.title);
textOffset += 1;
}

for(let i = 0; i < options.lines.length; i++) {
this.p.packetSender.updateInterfaceString(interfaceId + textOffset + i, options.lines[i]);
}

this.p.packetSender.showChatboxInterface(interfaceId);

return new Promise<DialogueAction>(resolve => {
this.p.dialogueInteractionEvent.subscribe(action => {
this._action = action;
resolve(this);
})
});
}

for(let i = 0; i < options.lines.length; i++) {
player.packetSender.updateInterfaceString(interfaceId + textOffset + i, options.lines[i]);
public close(): void {
this.p.packetSender.closeActiveInterfaces();
}

player.packetSender.showChatboxInterface(interfaceId);
public get action(): number {
return this._action;
}

return new Promise<number>(resolve => {
player.dialogueInteractionEvent.subscribe(value => {
resolve(value);
})
});
public set action(value: number) {
this._action = value;
}
}

export const dialogueAction = (player: Player, options?: DialogueOptions): Promise<DialogueAction> => {
if(options) {
return new DialogueAction(player).dialogue(options);
} else {
return Promise.resolve(new DialogueAction(player));
}
};
20 changes: 16 additions & 4 deletions src/world/mob/player/action/input-command-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Player } from '../player';
import { logger } from '@runejs/logger/dist/logger';
import { world } from '@server/game-server';
import { interfaceIds } from '../game-interface';
import { closeDialogue, dialogueAction, DialogueEmote } from '@server/world/mob/player/action/dialogue/dialogue-action';
import { npcAction } from '@server/world/mob/player/action/npc-action';

type commandHandler = (player: Player, args?: string[]) => void;

Expand Down Expand Up @@ -69,9 +69,21 @@ const commands: { [key: string]: commandHandler } = {
},

chat: (player: Player) => {
dialogueAction(player, { type: 'NPC', emote: DialogueEmote.CALM_TALK_1, npc: 0, lines: [ 'Welcome to RuneScape!' ] })
.then(() => dialogueAction(player, { type: 'NPC', emote: DialogueEmote.CALM_TALK_2, npc: 0, lines: [ 'How do you feel about Rune.JS so far?', 'Let us know what you think!' ] }))
.then(() => closeDialogue(player));
npcAction(player, world.npcList[0]);
},

chati: (player: Player, args: string[]) => {
if(args.length !== 1) {
throw `chati interfaceId`;
}

const interfaceId: number = parseInt(args[0]);

if(isNaN(interfaceId)) {
throw `chati interfaceId`;
}

player.packetSender.showChatboxInterface(interfaceId);
}

};
Expand Down
44 changes: 44 additions & 0 deletions src/world/mob/player/action/npc-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Player } from '@server/world/mob/player/player';
import { Npc } from '@server/world/mob/npc/npc';
import { dialogueAction, DialogueEmote } from '@server/world/mob/player/action/dialogue/dialogue-action';

export const npcAction = (player: Player, npc: Npc): void => {
if(npc.id === 0) {
dialogueAction(player, { type: 'NPC', emote: DialogueEmote.CALM_TALK_1, npc: 0, lines: [ 'Welcome to RuneScape!' ] })
.then(d => d.npc(npc, DialogueEmote.CALM_TALK_2, [ 'How do you feel about Rune.JS so far?', 'Please take a moment to let us know what you think!' ]))
.then(d => d.options('Thoughts?', [ 'Love it!', 'Kind of cool.', `Eh, I don't know...`, `Not my cup of tea, honestly.`, `It's literally the worst.` ]))
.then(d => {
switch(d.action) {
case 1:
return d.player(DialogueEmote.JOYFUL, [ 'Loving it so far, thanks for asking!' ])
.then(d => d.npc(npc, DialogueEmote.JOYFUL, [ `You're very welcome! Glad to hear it.` ]));
case 2:
return d.player(DialogueEmote.DEFAULT, [ `It's kind of cool, I guess.`, 'Bit of a weird gimmick.' ])
.then(d => d.npc(npc, DialogueEmote.DEFAULT, [ `Please let us know if you have any suggestions.` ]));
case 3:
return d.player(DialogueEmote.NOT_INTERESTED, [ `Ehhh... I don't know...` ])
.then(d => d.npc(npc, DialogueEmote.CALM_TALK_1, [ `We're always open to feedback or`, `Pull Requests anytime you like.` ]))
.then(d => d.player(DialogueEmote.CALM_TALK_1, [ `I'll keep that in mind, thanks.` ]));
case 4:
return d.player(DialogueEmote.CALM_TALK_2, [ `Not really my cup of tea, but keep at it.` ])
.then(d => d.npc(npc, DialogueEmote.JOYFUL, [ `Thanks for the support!` ]));
case 5:
return d.player(DialogueEmote.ANGRY_1, [ `Literally the worst thing I've ever seen.`, 'You disgust me on a personal level.' ])
.then(d => d.npc(npc, DialogueEmote.SAD_3, [ `I-is that so?...`, `Well I'm... I'm sorry to hear that.` ]))
.then(d => {
d.action = 1;
return d;
});
}
})
.then(d => {
d.close();

if(d.action === 1) {
player.packetSender.chatboxMessage('Hans wanders off rather dejectedly.');
} else {
player.packetSender.chatboxMessage('Hans wanders off aimlessly through the courtyard.');
}
});
}
};
9 changes: 9 additions & 0 deletions src/world/mob/player/packet/impl/button-click-packet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,22 @@ const settingButtons: number[] = [
150, 151, // auto retaliate
];

const dialogueActions: { [key: number]: number } = {
2494: 1, 2495: 2, 2496: 3, 2497: 4, 2498: 5,
2482: 1, 2483: 2, 2484: 3, 2485: 4,
2471: 1, 2472: 2, 2473: 3,
2461: 1, 2462: 2
};

export const buttonClickPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: RsBuffer): void => {
const buttonId = packet.readShortBE();

if(buttonId === 2458) {
player.logout();
} else if(settingButtons.indexOf(buttonId) !== -1) {
player.settingChanged(buttonId);
} else if(dialogueActions.hasOwnProperty(buttonId)) {
player.dialogueInteractionEvent.next(dialogueActions[buttonId]);
} else if(ignoreButtons.indexOf(buttonId) === -1) {
console.log(`Unhandled button ${buttonId} clicked.`);
}
Expand Down
32 changes: 32 additions & 0 deletions src/world/mob/player/packet/impl/npc-interaction-packet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { incomingPacket } from '../incoming-packet';
import { Player } from '../../player';
import { RsBuffer } from '@server/net/rs-buffer';
import { world } from '@server/game-server';
import { World } from '@server/world/world';
import { npcAction } from '@server/world/mob/player/action/npc-action';

export const npcInteractionPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: RsBuffer): void => {
const npcIndex = packet.readUnsignedShortLE();

if(npcIndex < 0 || npcIndex > World.MAX_NPCS - 1) {
return;
}

const npc = world.npcList[npcIndex];
if(!npc) {
return;
}

const distance = Math.floor(npc.position.distanceBetween(player.position));

if(distance > 16) {
return;
}

if(distance === 1) {
npcAction(player, npc);
} else {
// @TODO wait for the player to finish walking to their target
npcAction(player, npc);
}
};
3 changes: 3 additions & 0 deletions src/world/mob/player/packet/incoming-packet-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { itemOption1Packet } from './impl/item-option-1-packet';
import { commandPacket } from './impl/command-packet';
import { itemSwapPacket } from './impl/item-swap-packet';
import { dialogueInteractionPacket } from '@server/world/mob/player/packet/impl/dialogue-interaction-packet';
import { npcInteractionPacket } from '@server/world/mob/player/packet/impl/npc-interaction-packet';

const packets: { [key: number]: incomingPacket } = {
19: interfaceClickPacket,
Expand All @@ -21,6 +22,8 @@ const packets: { [key: number]: incomingPacket } = {
79: buttonClickPacket,
226: dialogueInteractionPacket,

112: npcInteractionPacket,

28: walkPacket,
213: walkPacket,
247: walkPacket,
Expand Down
7 changes: 7 additions & 0 deletions src/world/mob/player/packet/packet-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ export class PacketSender {
this.send(packet);
}

public setInterfacePlayerHead(interfaceId: number): void {
const packet = new Packet(255);
packet.writeNegativeOffsetShortLE(interfaceId);

this.send(packet);
}

/**
* Clears the player's current map chunk of all ground items and spawned/modified landscape objects.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/world/mob/player/task/updating/npc-update-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class NpcUpdateTask extends Task<void> {
this.player.trackedNpcs.push(newNpc);

// Notify the client of the new npc and their worldIndex
npcUpdatePacket.writeBits(14, newNpc.worldIndex + 1);
npcUpdatePacket.writeBits(14, newNpc.worldIndex);
npcUpdatePacket.writeBits(1, newNpc.updateFlags.updateBlockRequired ? 1 : 0); // Update is required
npcUpdatePacket.writeBits(5, positionOffsetY); // World Position Y axis offset relative to the player
npcUpdatePacket.writeBits(5, positionOffsetX); // World Position X axis offset relative to the player
Expand Down