Skip to content
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
18 changes: 18 additions & 0 deletions src/net/outgoing-packets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,15 @@ export class OutgoingPackets {
this.queue(packet);
}

public setItemOnWidget(widgetId: number, childId: number, itemId: number, zoom: number): void {
const packet = new Packet(120);
packet.writeUnsignedShortBE(zoom);
packet.writeUnsignedShortLE(itemId);
packet.writeIntLE(widgetId << 16 | childId);

this.queue(packet);
}

public toggleWidgetVisibility(widgetId: number, childId: number, hidden: boolean): void {
const packet = new Packet(115);
packet.writeUnsignedByte(hidden ? 1 : 0);
Expand All @@ -321,6 +330,15 @@ export class OutgoingPackets {
this.queue(packet);
}

public moveWidgetChild(widgetId: number, childId: number, offsetX: number, offsetY: number): void {
const packet = new Packet(3);
packet.writeIntBE(widgetId << 16 | childId);
packet.writeUnsignedOffsetShortLE(offsetY);
packet.writeUnsignedOffsetShortLE(offsetX);

this.queue(packet);
}

public sendTabWidget(tabIndex: number, widgetId: number): void {
const packet = new Packet(140);
packet.writeShortBE(widgetId);
Expand Down
27 changes: 27 additions & 0 deletions src/plugins/commands/item-selection-test-command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ActionType, RunePlugin } from '@server/plugins/plugin';
import { commandAction } from '@server/world/actor/player/action/input-command-action';
import { itemSelectionAction } from '@server/world/actor/player/action/item-selection-action';

const action: commandAction = (details) => {
const { player } = details;

itemSelectionAction(player, 'MAKING', [
{ itemId: 52, itemName: 'Arrow Shafts' },
{ itemId: 50, itemName: 'Shortbow' },
{ itemId: 48, itemName: 'Longbow' },
{ itemId: 9440, itemName: 'Crossbow Stock', offset: -4 } // `offset` and `zoom` are optional params for better item positioning if needed
]).then(choice => {
if(!choice) {
return;
}

player.outgoingPackets.chatboxMessage(`Player selected itemId ${choice.itemId} with amount ${choice.amount}`);
}).catch(); // <- CATCH IS REQUIRED FOR ALL ITEM SELECTION ACTIONS!
// Always catch these, as the promise returned by `itemSelectionAction` will reject if actions have been cancelled!
};

export default new RunePlugin({
type: ActionType.COMMAND,
commands: 'itemselection',
action
});
13 changes: 13 additions & 0 deletions src/plugins/dialogue/item-selection-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ActionType, RunePlugin } from '@server/plugins/plugin';
import { widgetAction } from '@server/world/actor/player/action/widget-action';

/**
* Handles an item selection dialogue choice.
*/
export const action: widgetAction = (details) => {
const { player, childId } = details;
player.dialogueInteractionEvent.next(childId);
player.activeWidget = null;
};

export default new RunePlugin({ type: ActionType.WIDGET_ACTION, widgetIds: [ 303, 304, 305, 306, 307, 309 ], action, cancelActions: false });
7 changes: 7 additions & 0 deletions src/task/task.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { timer } from 'rxjs';
import { World } from '@server/world/world';

export abstract class Task<T> {

public abstract async execute(): Promise<T>;

}

export const schedule = (ticks: number): Promise<number> => {
return timer(ticks * World.TICK_LENGTH).toPromise();
};
11 changes: 8 additions & 3 deletions src/world/actor/player/action/dialogue-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import { skillDetails } from '@server/world/actor/skills';
export const dialogueWidgetIds = {
PLAYER: [ 64, 65, 66, 67 ],
NPC: [ 241, 242, 243, 244 ],
OPTIONS: [ 228, 230, 232, 234 ]
OPTIONS: [ 228, 230, 232, 234 ],
TEXT: [ 210, 211, 212, 213, 214 ]
};

/**
* Min -> max lines for a specific dialogue type.
*/
const lineConstraints = {
PLAYER: [ 1, 4 ],
NPC: [ 1, 4 ],
OPTIONS: [ 2, 5 ],
LEVEL_UP: [ 2, 2 ]
LEVEL_UP: [ 2, 2 ],
TEXT: [ 1, 5 ]
};

export enum DialogueEmote {
Expand Down Expand Up @@ -49,7 +54,7 @@ export enum DialogueEmote {
ANGRY_4 = 617
}

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

export interface DialogueOptions {
type: DialogueType;
Expand Down
126 changes: 126 additions & 0 deletions src/world/actor/player/action/item-selection-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Player } from '@server/world/actor/player/player';

const amounts = [
1, 5, 10, 0
];

const widgets = {
// 303-306 - what would you like to make?
303: {
items: [ 2, 3 ],
text: [ 7, 11 ],
options: [ [ 7, 6, 5, 4 ], [ 11, 10, 9, 8 ] ]
},
304: {
items: [ 2, 3, 4 ],
text: [ 8, 12, 16 ],
options: [ [ 8, 7, 6, 5 ], [ 12, 11, 10, 9 ], [ 16, 15, 14, 13 ] ]
},
305: {
items: [ 2, 3, 4, 5 ],
text: [ 9, 13, 17, 21 ],
options: [ [ 9, 8, 7, 6 ], [ 13, 12, 11, 10 ], [ 17, 16, 15, 14 ], [ 21, 20, 19, 18 ] ]
},
306: {
items: [ 2, 3, 4, 5, 6 ],
text: [ 10, 14, 18, 22, 26 ],
options: [ [ 10, 9, 8, 7 ], [ 14, 13, 12, 11 ], [ 18, 17, 16, 15 ], [ 22, 21, 20, 19 ], [ 26, 25, 24, 23 ] ]
},
307: { // 307 - how many would you like to cook?
items: [ 2 ],
text: [ 6 ],
options: [ [ 6, 5, 4, 3 ] ]
},
309: { // 309 - how many would you like to make?
items: [ 2 ],
text: [ 6 ],
options: [ [ 6, 5, 4, 3 ] ]
}
};

export interface SelectableItem {
itemId: number;
itemName: string;
offset?: number;
zoom?: number;
}

export interface ItemSelection {
itemId: number;
amount: number;
}

// @TODO Make-X
export const itemSelectionAction = (player: Player, type: 'COOKING' | 'MAKING', items: SelectableItem[]): Promise<ItemSelection> => {
let widgetId = 307;

if(type === 'MAKING') {
if(items.length === 1) {
widgetId = 309;
} else {
if(items.length > 5) {
throw `Too many items provided to the item selection action!`;
}

widgetId = (301 + items.length);
}
}

return new Promise((resolve, reject) => {
const childIds = widgets[widgetId].items;
childIds.forEach((childId, index) => {
const itemInfo = items[index];

if(itemInfo.offset === undefined) {
itemInfo.offset = -12;
}

if(itemInfo.zoom === undefined) {
itemInfo.zoom = 180;
}

player.outgoingPackets.setItemOnWidget(widgetId, childId, itemInfo.itemId, itemInfo.zoom);
player.outgoingPackets.moveWidgetChild(widgetId, childId, 0, itemInfo.offset);
player.outgoingPackets.updateWidgetString(widgetId, widgets[widgetId].text[index], '\\n\\n\\n\\n' + itemInfo.itemName);
});

player.activeWidget = {
widgetId,
type: 'CHAT',
closeOnWalk: false
};

const actionsSub = player.actionsCancelled.subscribe(() => {
actionsSub.unsubscribe();
reject();
});

const interactionSub = player.dialogueInteractionEvent.subscribe(childId => {
const options = widgets[widgetId].options;

console.log(childId);

const choiceIndex = options.findIndex(arr => arr.indexOf(childId) !== -1);

if(choiceIndex === -1) {
interactionSub.unsubscribe();
reject();
return;
}

const optionIndex = options[choiceIndex].indexOf(childId);

if(optionIndex === -1) {
interactionSub.unsubscribe();
reject();
return;
}

const itemId = items[choiceIndex].itemId;
const amount = amounts[optionIndex];

interactionSub.unsubscribe();
resolve({ itemId, amount } as ItemSelection);
});
});
};
3 changes: 2 additions & 1 deletion src/world/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { WorldItem } from '@server/world/items/world-item';
import { Item } from '@server/world/items/item';
import { Chunk } from '@server/world/map/chunk';
import { LandscapeObject } from '@runejs/cache-parser';
import { schedule } from '@server/task/task';

export interface QuadtreeKey {
x: number;
Expand Down Expand Up @@ -156,7 +157,7 @@ export class World {
this.addLandscapeObject(newObject, position);

if(respawnTicks !== -1) {
timer(respawnTicks * World.TICK_LENGTH).toPromise().then(() => this.addLandscapeObject(oldObject, position));
schedule(respawnTicks).then(() => this.addLandscapeObject(oldObject, position));
}
}

Expand Down