Skip to content

Commit a66b8eb

Browse files
committed
refactor: enhance item management and shop GUI functionality
- Updated ShopGui to use a more structured ShopItemInput type for item handling, improving type safety. - Refactored ClassManager and ItemManager to support new equip logic, allowing for automatic item addition when equipping. - Added tests for auto-equipping items and preventing duplicates in inventory. - Cleaned up commented-out code in server event handling for better readability and maintainability.
1 parent fcfe18b commit a66b8eb

File tree

5 files changed

+79
-50
lines changed

5 files changed

+79
-50
lines changed

packages/server/src/Gui/ShopGui.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { Gui } from './Gui'
33
import { RpgPlayer } from '../Player/Player'
44

55
export type ShopSellList = Record<string, number> | Array<{ id: string; multiplier: number }>
6+
export type ShopItemInput = string | { id?: string; [key: string]: any }
67

78
export interface ShopGuiOptions {
8-
items: any[]
9+
items: ShopItemInput[]
910
sell?: ShopSellList
1011
sellMultiplier?: number
1112
message?: string
@@ -16,7 +17,7 @@ export interface ShopGuiOptions {
1617
}
1718

1819
export class ShopGui extends Gui {
19-
private itemsInput: any[] = []
20+
private itemsInput: ShopItemInput[] = []
2021
private sellMultipliers: Record<string, number> = {}
2122
private baseSellMultiplier = 0.5
2223
private messageInput?: string
@@ -65,7 +66,7 @@ export class ShopGui extends Gui {
6566
return undefined
6667
}
6768

68-
const buildItemData = (item, overrides: { price?: number; quantity?: number } = {}) => {
69+
const buildItemData = (item: ShopItemInput, overrides: { price?: number; quantity?: number } = {}) => {
6970
const rawId = typeof item === 'string'
7071
? item
7172
: (typeof item?.id === 'function' ? item.id() : (item?.id ?? item?.name))

packages/server/src/Player/ClassManager.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ type ActorClass = any;
66
interface PlayerWithMixins extends RpgCommonPlayer {
77
databaseById(id: string): any;
88
addParameter(name: string, { start, end }: { start: number, end: number }): void;
9-
addItem(item: any): void;
10-
equip(item: any, equip: boolean): void;
9+
addItem(item: any): any;
10+
equip(itemId: string, equip?: boolean | 'auto'): void;
1111
}
1212

1313
/**
@@ -108,8 +108,11 @@ export function WithClassManager<TBase extends PlayerCtor>(Base: TBase) {
108108
(this as any).addParameter(param, actor.parameters[param]);
109109
}
110110
for (let item of actor.startingEquipment) {
111-
(this as any).addItem(item);
112-
(this as any).equip(item, true);
111+
const inventory = (this as any).addItem(item);
112+
const itemId = inventory?.id?.();
113+
if (itemId) {
114+
(this as any).equip(itemId, true);
115+
}
113116
}
114117
if (actor.class) this.setClass(actor.class);
115118
(this as any)["execMethod"]("onSet", [this], actor);

packages/server/src/Player/ItemManager.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -581,15 +581,19 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
581581
}
582582

583583
equip(
584-
itemClass: ItemClass | string,
585-
equip: boolean = true
584+
itemId: string,
585+
equip: boolean | 'auto' = true
586586
): void {
587-
const itemId = isString(itemClass) ? itemClass : (itemClass as any).name;
588-
const inventory: Item = this.getItem(itemClass);
587+
const autoAdd = equip === 'auto';
588+
const equipState = equip === 'auto' ? true : equip;
589+
const data = (this as any).databaseById(itemId);
590+
let inventory: Item = this.getItem(itemId);
591+
if (!inventory && autoAdd) {
592+
inventory = this.addItem(itemId, 1);
593+
}
589594
if (!inventory) {
590595
throw ItemLog.notInInventory(itemId);
591596
}
592-
const data = (this as any).databaseById(itemId);
593597
if (data._type == "item") {
594598
throw ItemLog.invalidToEquiped(itemId);
595599
}
@@ -607,19 +611,19 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
607611

608612
const item = inventory;
609613

610-
if ((item as any).equipped && equip) {
614+
if ((item as any).equipped && equipState) {
611615
throw ItemLog.isAlreadyEquiped(itemId);
612616
}
613-
(item as any).equipped = equip;
614-
if (!equip) {
617+
(item as any).equipped = equipState;
618+
if (!equipState) {
615619
const index = this.equipments().findIndex((it) => it.id() == item.id());
616620
this.equipments().splice(index, 1);
617621
} else {
618622
this.equipments().push(item);
619623
}
620624
// Call onEquip hook - use stored instance if available
621625
const hookTarget = (item as any)._itemInstance || item;
622-
this["execMethod"]("onEquip", [this, equip], hookTarget);
626+
this["execMethod"]("onEquip", [this, equipState], hookTarget);
623627
}
624628
} as unknown as TBase;
625629
}
@@ -852,12 +856,13 @@ export interface IItemManager {
852856
/**
853857
* Equips a weapon or armor on a player
854858
*
855-
* Think first to add the item in the inventory with the `addItem()` method before equipping the item.
859+
* Think first to add the item in the inventory with the `addItem()` method before equipping the item,
860+
* or pass `"auto"` to add the item if it is missing and equip it.
856861
*
857862
* The `onEquip()` method is called on the ItemClass when the item is equipped or unequipped.
858863
*
859-
* @param itemClass - Item class or string identifier. If string, it's the item ID
860-
* @param equip - Equip the item if `true`, unequip if `false` (default: `true`)
864+
* @param itemId - Item identifier to resolve from the database
865+
* @param equip - Equip the item if `true`, unequip if `false`, or `"auto"` to add then equip (default: `true`)
861866
* @throws {Object} ItemLog.notInInventory - If the item is not in the inventory
862867
* - `id`: `ITEM_NOT_INVENTORY`
863868
* - `msg`: Error message
@@ -870,19 +875,17 @@ export interface IItemManager {
870875
*
871876
* @example
872877
* ```ts
873-
* import Sword from 'your-database/sword'
874-
*
875878
* try {
876-
* player.addItem(Sword)
877-
* player.equip(Sword)
879+
* player.addItem('sword')
880+
* player.equip('sword')
878881
* // Later, unequip it
879-
* player.equip(Sword, false)
882+
* player.equip('sword', false)
880883
* } catch (err) {
881884
* console.log(err)
882885
* }
883886
* ```
884887
*/
885-
equip(itemClass: ItemClass | string, equip?: boolean): void;
888+
equip(itemId: string, equip?: boolean | 'auto'): void;
886889

887890
/**
888891
* Get the player's attack (sum of items equipped)

packages/server/tests/item.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,24 @@ describe("Item Management - Equipment", () => {
385385
expect((item as any).equipped).toBe(true);
386386
});
387387

388+
test("should auto add and equip item", () => {
389+
player.equip("TestSword", "auto");
390+
const item = player.getItem("TestSword");
391+
expect(item).toBeDefined();
392+
expect(item?.quantity()).toBe(1);
393+
expect((item as any).equipped).toBe(true);
394+
expect(player.equipments().some((eq) => eq.id() === "TestSword")).toBe(
395+
true
396+
);
397+
});
398+
399+
test("should not add duplicate when auto equipping existing item", () => {
400+
player.addItem("TestSword", 2);
401+
player.equip("TestSword", "auto");
402+
const item = player.getItem("TestSword");
403+
expect(item?.quantity()).toBe(2);
404+
});
405+
388406
test("should throw error when equipping non-existent item", () => {
389407
expect(() => {
390408
player.equip("TestSword", true);
@@ -588,4 +606,4 @@ describe("Item Management - Edge Cases", () => {
588606
expect(player.getItem("TestSword")?.quantity()).toBe(1);
589607
expect(player.getItem("TestArmor")?.quantity()).toBe(2);
590608
});
591-
});
609+
});

sample/src/server.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -151,30 +151,30 @@ export function Event() {
151151
this.equip(EnemyClaw.id);
152152

153153
// Initialize AI behavior
154-
// this.battleAi = new BattleAi(this, {
155-
// enemyType: EnemyType.Defensive,
156-
// visionRange: 150,
157-
// attackRange: 50,
158-
// attackCooldown: 900,
159-
// dodgeChance: 0.35,
160-
// dodgeCooldown: 2000,
161-
// fleeThreshold: 0.2,
162-
// attackPatterns: [
163-
// AttackPattern.Melee,
164-
// AttackPattern.Combo,
165-
// AttackPattern.DashAttack,
166-
// AttackPattern.Charged
167-
// ],
168-
// moveToCooldown: 450,
169-
// retreatCooldown: 700,
170-
// behavior: {
171-
// baseScore: 55,
172-
// updateInterval: 450,
173-
// minStateDuration: 700,
174-
// assaultThreshold: 70,
175-
// retreatThreshold: 30
176-
// }
177-
// });
154+
this.battleAi = new BattleAi(this, {
155+
enemyType: EnemyType.Defensive,
156+
visionRange: 150,
157+
attackRange: 50,
158+
attackCooldown: 900,
159+
dodgeChance: 0.35,
160+
dodgeCooldown: 2000,
161+
fleeThreshold: 0.2,
162+
attackPatterns: [
163+
AttackPattern.Melee,
164+
AttackPattern.Combo,
165+
AttackPattern.DashAttack,
166+
AttackPattern.Charged
167+
],
168+
moveToCooldown: 450,
169+
retreatCooldown: 700,
170+
behavior: {
171+
baseScore: 55,
172+
updateInterval: 450,
173+
minStateDuration: 700,
174+
assaultThreshold: 70,
175+
retreatThreshold: 30
176+
}
177+
});
178178
},
179179
onPlayerTouch(player: RpgPlayer) {
180180
console.log("touch");
@@ -241,6 +241,7 @@ export default createServer({
241241
'basic-helmet': BasicHelmet,
242242
'fire-armor': FireArmor,
243243
'fire-skill': fireSkill,
244+
'basic-potion': BasicPotion,
244245
}
245246
},
246247
player: {
@@ -270,6 +271,9 @@ export default createServer({
270271
y: 150,
271272
});
272273
},
274+
onConnected: (player: RpgPlayer) => {
275+
player.addItem(BasicPotion);
276+
},
273277
onLoad: (player: RpgPlayer, data: any) => {
274278
console.log("load", player.items());
275279
},

0 commit comments

Comments
 (0)