Skip to content

Commit e1df341

Browse files
authored
Merge pull request #361 from runejs/feature/item-groups
Feature: Item grouping
2 parents ad8b48a + fde1a32 commit e1df341

File tree

8 files changed

+270
-45
lines changed

8 files changed

+270
-45
lines changed

data/config/items/equipment/standard-metals/adamant-armour.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"defence": 30
1010
}
1111
}
12-
}
12+
},
13+
"groups": ["adamant_metal", "equipment"]
1314
}
1415
},
1516

@@ -31,7 +32,8 @@
3132
"magic": -4,
3233
"ranged": 31
3334
}
34-
}
35+
},
36+
"groups": ["legs"]
3537
},
3638

3739
"rs:adamant_plateskirt": {
@@ -52,6 +54,7 @@
5254
"magic": -4,
5355
"ranged": 31
5456
}
55-
}
57+
},
58+
"groups": ["legs"]
5659
}
5760
}

data/config/items/equipment/standard-metals/black-armour.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"defence": 10
1010
}
1111
}
12-
}
12+
},
13+
"groups": ["black_metal", "equipment"]
1314
}
1415
},
1516

@@ -31,7 +32,8 @@
3132
"magic": -4,
3233
"ranged": 20
3334
}
34-
}
35+
},
36+
"groups": ["legs"]
3537
},
3638

3739
"rs:black_plateskirt": {
@@ -52,6 +54,7 @@
5254
"magic": -4,
5355
"ranged": 20
5456
}
55-
}
57+
},
58+
"groups": ["legs"]
5659
}
5760
}

data/config/items/equipment/standard-metals/bronze-armour.json

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
{
2+
"presets": {
3+
"rs:bronze_armour_base": {
4+
"tradable": true,
5+
"equippable": true,
6+
"equipment_data": {
7+
"requirements": {
8+
"skills": {
9+
"defence": 1
10+
}
11+
}
12+
},
13+
"groups": ["bronze_metal", "equipment"]
14+
}
15+
},
216
"rs:bronze_platelegs": {
17+
"extends": "rs:bronze_armour_base",
318
"game_id": 1075,
419
"examine": "These look pretty heavy.",
520
"weight": 9.071,
@@ -18,11 +33,13 @@
1833
"magic": -4,
1934
"ranged": 7
2035
}
21-
}
36+
},
37+
"groups": ["legs"]
2238
},
2339

2440
"rs:bronze_plateskirt": {
2541
"game_id": 1087,
42+
"extends": "rs:bronze_armour_base",
2643
"examine": "Designer leg protection.",
2744
"weight": 8.164,
2845
"tradable": true,
@@ -40,6 +57,7 @@
4057
"magic": -4,
4158
"ranged": 7
4259
}
43-
}
60+
},
61+
"groups": ["legs"]
4462
}
4563
}

data/config/items/equipment/standard-metals/bronze-weapons.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
{
2+
"presets": {
3+
"rs:bronze_weapon_base": {
4+
"tradable": true,
5+
"equippable": true,
6+
"equipment_data": {
7+
"requirements": {
8+
"skills": {
9+
"defence": 1
10+
}
11+
}
12+
},
13+
"groups": ["bronze_metal", "equipment", "weapon"]
14+
}
15+
},
216
"rs:bronze_dagger": {
317
"game_id": 1205,
18+
"extends": "rs:bronze_weapon_base",
419
"tradable": true,
520
"weight": 0.453,
621
"equippable": true,
@@ -28,6 +43,7 @@
2843
"weapon_info": {
2944
"style": "dagger"
3045
}
31-
}
46+
},
47+
"groups": ["main_hand", "dagger"]
3248
}
3349
}

src/engine/config/config-handler.ts

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
ItemDetails,
66
ItemPresetConfiguration,
77
loadItemConfigurations,
8-
translateItemConfig
98
} from '@engine/config/item-config';
109
import { filestore } from '@server/game/game-server';
1110
import {
@@ -24,6 +23,7 @@ import { questMap } from '@engine/plugins';
2423

2524

2625
export let itemMap: { [key: string]: ItemDetails };
26+
export let itemGroupMap: Record<string, Record<string, boolean>>;
2727
export let itemIdMap: { [key: number]: string };
2828
export let objectMap: { [key: number]: ObjectConfig };
2929
export let itemPresetMap: ItemPresetConfiguration;
@@ -46,8 +46,9 @@ export async function loadCoreConfigurations(): Promise<void> {
4646
export async function loadGameConfigurations(): Promise<void> {
4747
logger.info(`Loading server configurations...`);
4848

49-
const { items, itemIds, itemPresets } = await loadItemConfigurations('data/config/items/');
49+
const { items, itemIds, itemPresets, itemGroups } = await loadItemConfigurations('data/config/items/');
5050
itemMap = items;
51+
itemGroupMap = itemGroups;
5152
itemIdMap = itemIds;
5253
itemPresetMap = itemPresets;
5354

@@ -70,6 +71,47 @@ export async function loadGameConfigurations(): Promise<void> {
7071
}
7172

7273

74+
/**
75+
* find all items in all select groups
76+
* @param groupKeys array of string of which to find items connected with
77+
* @return itemsKeys array of itemkeys in all select groups
78+
*/
79+
export const findItemTagsInGroups = (groupKeys: string[]): string[] => {
80+
return Object.keys(groupKeys.reduce<Record<string, boolean>>((all, groupKey)=> {
81+
const items = itemGroupMap[groupKey] || {};
82+
return { ...all, ...items };
83+
}, {}));
84+
}
85+
86+
87+
/**
88+
* find all items which are shared by all the groups, and discard items not in all groups
89+
* @param groupKeys groups keys which to find items shared by
90+
* @return itemKeys of items shared by all groups
91+
*/
92+
export const findItemTagsInGroupFilter = (groupKeys: string[]): string[] => {
93+
if(!groupKeys || groupKeys.length === 0) {
94+
return [];
95+
}
96+
let collection: Record<string, boolean> | undefined = undefined;
97+
groupKeys.forEach((groupKey) => {
98+
if(!collection) {
99+
collection = { ...(itemGroupMap[groupKey] || {}) };
100+
return;
101+
}
102+
const current = itemGroupMap[groupKey] || {};
103+
104+
Object.keys(collection).forEach((existingItemKey) => {
105+
if(!(existingItemKey in current)) {
106+
delete collection[existingItemKey];
107+
}
108+
});
109+
});
110+
111+
return Object.keys(collection);
112+
}
113+
114+
73115
export const findItem = (itemKey: number | string): ItemDetails | null => {
74116
if(!itemKey) {
75117
return null;
@@ -96,20 +138,6 @@ export const findItem = (itemKey: number | string): ItemDetails | null => {
96138
if(item?.gameId) {
97139
gameId = item.gameId;
98140
}
99-
100-
if(item?.extends) {
101-
let extensions = item.extends;
102-
if(typeof extensions === 'string') {
103-
extensions = [ extensions ];
104-
}
105-
106-
extensions.forEach(extKey => {
107-
const extensionItem = itemPresetMap[extKey];
108-
if(extensionItem) {
109-
item = _.merge(item, translateItemConfig(undefined, extensionItem));
110-
}
111-
});
112-
}
113141
}
114142

115143
if(gameId) {

src/engine/config/item-config.ts

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { loadConfigurationFiles } from '@runejs/common/fs';
22
import { SkillName } from '@engine/world/actor/skills';
3-
import _ from 'lodash';
43
import { logger } from '@runejs/common';
4+
import { deepMerge } from '@engine/util/objects';
55

66

77
export type WeaponStyle = 'axe' | 'hammer' | 'bow' | 'claws' | 'crossbow' | 'longsword'
@@ -125,6 +125,7 @@ export interface ItemConfiguration {
125125
equippable?: boolean;
126126
consumable?: boolean;
127127
destroy?: string | boolean;
128+
groups?: string[];
128129
equipment_data?: {
129130
equipment_slot: EquipmentSlot;
130131
equipment_type?: EquipmentType;
@@ -155,6 +156,7 @@ export class ItemDetails {
155156
consumable?: boolean;
156157
stackable: boolean = false;
157158
value: number = 0;
159+
groups: string[] = [];
158160
members: boolean = false;
159161
groundOptions: string[] = [];
160162
inventoryOptions: string[] = [];
@@ -194,6 +196,7 @@ export function translateItemConfig(key: string, config: ItemConfiguration): any
194196
equippable: config.equippable,
195197
weight: config.weight,
196198
destroy: config.destroy || undefined,
199+
groups: config.groups || [],
197200
consumable: config.consumable,
198201
equipmentData: config.equipment_data ? {
199202
equipmentType: config.equipment_data?.equipment_type || undefined,
@@ -209,43 +212,88 @@ export function translateItemConfig(key: string, config: ItemConfiguration): any
209212
}
210213

211214
export async function loadItemConfigurations(path: string): Promise<{ items: { [key: string]: ItemDetails };
212-
itemIds: { [key: number]: string }; itemPresets: ItemPresetConfiguration; }> {
215+
itemIds: { [key: number]: string }; itemPresets: ItemPresetConfiguration; itemGroups: Record<string, Record<string, boolean>>; }> {
213216
const itemIds: { [key: number]: string } = {};
214217
const items: { [key: string]: ItemDetails } = {};
218+
const itemGroups : Record<string, Record<string, boolean>> = {} // Record where key is group id, and value is an array of all itemstags in group
215219
let itemPresets: ItemPresetConfiguration = {};
216220

217221
const files = await loadConfigurationFiles(path);
222+
const itemConfigurations: Record<string, ItemConfiguration> = {};
218223

219224
files.forEach(itemConfigs => {
220225
const itemKeys = Object.keys(itemConfigs);
221226
itemKeys.forEach(key => {
222227
if(key === 'presets') {
223228
itemPresets = { ...itemPresets, ...itemConfigs[key] };
224229
} else {
230+
itemConfigurations[key] = itemConfigs[key] as ItemConfiguration;
231+
}
232+
});
233+
});
234+
Object.entries(itemConfigurations).forEach(([key, itemConfig]) => {
235+
if(!isNaN(itemConfig.game_id)) {
236+
itemIds[itemConfig.game_id] = key;
237+
let item = { ...translateItemConfig(key, itemConfig) }
238+
if(item?.extends) {
239+
let extensions = item.extends;
240+
if(typeof extensions === 'string') {
241+
extensions = [ extensions ];
242+
}
225243

226-
const itemConfig: ItemConfiguration = itemConfigs[key] as ItemConfiguration;
227-
if(!isNaN(itemConfig.game_id)) {
228-
itemIds[itemConfig.game_id] = key;
229-
items[key] = { ...translateItemConfig(key, itemConfig) };
244+
extensions.forEach(extKey => {
245+
const extensionItem = itemPresets[extKey];
246+
if(extensionItem) {
247+
const preset = translateItemConfig(undefined, extensionItem);
248+
item = deepMerge(item, preset);
249+
}
250+
});
251+
}
252+
items[key] = item;
253+
item.groups.forEach((group) => {
254+
if(!itemGroups[group]) {
255+
itemGroups[group] = {};
230256
}
257+
itemGroups[group][key] = true;
258+
})
259+
}
231260

232-
if(itemConfig.variations) {
233-
for(const subItem of itemConfig.variations) {
234-
const subKey = subItem.suffix ? key + ':' + subItem.suffix : key;
235-
const baseItem = JSON.parse(JSON.stringify({ ...translateItemConfig(key, itemConfig) }));
236-
const subBaseItem = JSON.parse(JSON.stringify({ ...translateItemConfig(subKey, subItem) }));
237-
itemIds[subItem.game_id] = subKey;
238-
239-
if(!items[subKey]) {
240-
items[subKey] = _.merge(baseItem, subBaseItem);
241-
} else {
242-
logger.warn(`Duplicate item key ${subKey} found - the item was not loaded.`);
261+
if(itemConfig.variations) {
262+
for(const subItem of itemConfig.variations) {
263+
const subKey = subItem.suffix ? key + ':' + subItem.suffix : key;
264+
const baseItem = JSON.parse(JSON.stringify({ ...translateItemConfig(key, itemConfig) }));
265+
const subBaseItem = JSON.parse(JSON.stringify({ ...translateItemConfig(subKey, subItem) }));
266+
itemIds[subItem.game_id] = subKey;
267+
268+
if(!items[subKey]) {
269+
let item = deepMerge(baseItem, subBaseItem);
270+
if(item?.extends) {
271+
let extensions = item.extends;
272+
if(typeof extensions === 'string') {
273+
extensions = [ extensions ];
243274
}
275+
276+
extensions.forEach(extKey => {
277+
const extensionItem = itemPresets[extKey];
278+
if(extensionItem) {
279+
const preset = translateItemConfig(undefined, extensionItem);
280+
item = deepMerge(item, preset);
281+
}
282+
});
244283
}
284+
items[subKey] = item;
285+
items[subKey].groups.forEach((group) => {
286+
if(!itemGroups[group]) {
287+
itemGroups[group] = {};
288+
}
289+
itemGroups[group][subKey] = true;
290+
})
291+
} else {
292+
logger.warn(`Duplicate item key ${subKey} found - the item was not loaded.`);
245293
}
246294
}
247-
});
248-
});
295+
}
296+
})
249297

250-
return { items, itemIds, itemPresets };
298+
return { items, itemIds, itemPresets, itemGroups };
251299
}

0 commit comments

Comments
 (0)