@rpgjs/server 5.0.0-alpha.23 → 5.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,577 @@
1
+ import { beforeEach, test, expect, afterEach, describe, vi } from 'vitest'
2
+ import { testing, waitForSyncComplete } from '@rpgjs/testing'
3
+ import { defineModule, createModule } from '@rpgjs/common'
4
+ import { RpgPlayer, RpgServer } from '../src'
5
+ import { RpgClient } from '../../client/src'
6
+ import { ItemLog } from '../src/logs'
7
+ import type { ItemObject } from '../src/Player/ItemManager'
8
+
9
+ // Define test items as objects for database
10
+ const TestPotion = {
11
+ id: 'TestPotion',
12
+ name: 'Test Potion',
13
+ description: 'Restores 100 HP',
14
+ price: 200,
15
+ hpValue: 100,
16
+ consumable: true,
17
+ _type: 'item' as const,
18
+ }
19
+
20
+ const TestSword = {
21
+ name: 'Test Sword',
22
+ description: 'A basic sword',
23
+ price: 500,
24
+ atk: 50,
25
+ _type: 'weapon' as const,
26
+ }
27
+
28
+ const TestArmor = {
29
+ name: 'Test Armor',
30
+ description: 'Basic armor',
31
+ price: 300,
32
+ pdef: 30,
33
+ sdef: 20,
34
+ _type: 'armor' as const,
35
+ }
36
+
37
+ const TestNonConsumable = {
38
+ name: 'Test Non-Consumable',
39
+ description: 'Cannot be used',
40
+ price: 100,
41
+ consumable: false,
42
+ _type: 'item' as const,
43
+ }
44
+
45
+ const TestExpensiveItem = {
46
+ name: 'Expensive Item',
47
+ description: 'Very expensive',
48
+ price: 10000,
49
+ _type: 'item' as const,
50
+ }
51
+
52
+ const TestNoPriceItem = {
53
+ name: 'No Price Item',
54
+ description: 'Item without price',
55
+ _type: 'item' as const,
56
+ }
57
+
58
+ // Define server module with items in database
59
+ const serverModule = defineModule<RpgServer>({
60
+ maps: [
61
+ {
62
+ id: 'test-map',
63
+ file: '',
64
+ },
65
+ ],
66
+ database: {
67
+ 'TestPotion': TestPotion,
68
+ 'TestSword': TestSword,
69
+ 'TestArmor': TestArmor,
70
+ 'TestNonConsumable': TestNonConsumable,
71
+ 'TestExpensiveItem': TestExpensiveItem,
72
+ 'TestNoPriceItem': TestNoPriceItem,
73
+ },
74
+ player: {
75
+ async onConnected(player) {
76
+ await player.changeMap('test-map', { x: 100, y: 100 })
77
+ },
78
+ },
79
+ })
80
+
81
+ // Define client module
82
+ const clientModule = defineModule<RpgClient>({
83
+ // Client-side logic
84
+ })
85
+
86
+ let player: RpgPlayer
87
+ let client: any
88
+ let fixture: any
89
+
90
+ beforeEach(async () => {
91
+ const myModule = createModule('TestModule', [
92
+ {
93
+ server: serverModule,
94
+ client: clientModule,
95
+ },
96
+ ])
97
+
98
+ fixture = await testing(myModule)
99
+ client = await fixture.createClient()
100
+ player = await client.waitForMapChange('test-map')
101
+
102
+ })
103
+
104
+ afterEach(async () => {
105
+ fixture.clear()
106
+ })
107
+
108
+ test('should add item ', async () => {
109
+ const item = player.addItem(TestPotion, 5)
110
+ expect(item).toBeDefined()
111
+ expect(item.id()).toBe('TestPotion')
112
+ expect(item.quantity()).toBe(5)
113
+ expect(item.name()).toBe('Test Potion')
114
+ await waitForSyncComplete(player as any, client.client)
115
+ console.log((client.client.sceneMap.currentPlayer() as any).items())
116
+ })
117
+
118
+ /*
119
+ describe('Item Management - Basic Operations', () => {
120
+ test('should add item using string ID', () => {
121
+ const item = player.addItem('TestPotion', 5)
122
+ expect(item).toBeDefined()
123
+ expect(item.id()).toBe('TestPotion')
124
+ expect(item.quantity()).toBe(5)
125
+ expect(item.name()).toBe('Test Potion')
126
+ })
127
+
128
+ test('should add item using object', () => {
129
+ const customItem: ItemObject = {
130
+ id: 'custom-item',
131
+ name: 'Custom Item',
132
+ description: 'A custom item',
133
+ price: 150,
134
+ onAdd(player) {
135
+ // Hook test
136
+ },
137
+ }
138
+ const item = player.addItem(customItem, 3)
139
+ expect(item).toBeDefined()
140
+ expect(item.id()).toBe('custom-item')
141
+ expect(item.quantity()).toBe(3)
142
+ expect(item.name()).toBe('Custom Item')
143
+ })
144
+
145
+ test('should add item without ID (auto-generated)', () => {
146
+ const customItem: ItemObject = {
147
+ name: 'Auto ID Item',
148
+ price: 100,
149
+ }
150
+ const item = player.addItem(customItem, 1)
151
+ expect(item).toBeDefined()
152
+ expect(item.id()).toMatch(/^item-\d+$/)
153
+ expect(item.name()).toBe('Auto ID Item')
154
+ })
155
+
156
+ test('should increment quantity when adding existing item', () => {
157
+ player.addItem('TestPotion', 3)
158
+ const item = player.addItem('TestPotion', 2)
159
+ expect(item.quantity()).toBe(5)
160
+ })
161
+
162
+ test('should throw error when adding item without map', async () => {
163
+ const newFixture = await testing()
164
+ const newClient = await newFixture.createClient()
165
+ const newPlayer = newClient.player
166
+
167
+ expect(() => {
168
+ newPlayer.addItem('TestPotion', 1)
169
+ }).toThrow('Player must be on a map to add items')
170
+
171
+ newFixture.clear()
172
+ })
173
+
174
+ test('should throw error when adding item with invalid string ID', () => {
175
+ expect(() => {
176
+ player.addItem('NonExistentItem', 1)
177
+ }).toThrow('The ID=NonExistentItem data is not found in the database')
178
+ })
179
+
180
+ test('should get item from inventory', () => {
181
+ player.addItem('TestPotion', 5)
182
+ const item = player.getItem('TestPotion')
183
+ expect(item).toBeDefined()
184
+ expect(item.id()).toBe('TestPotion')
185
+ expect(item.quantity()).toBe(5)
186
+ })
187
+
188
+ test('should return undefined when getting non-existent item', () => {
189
+ const item = player.getItem('TestPotion')
190
+ expect(item).toBeUndefined()
191
+ })
192
+
193
+ test('should check if player has item', () => {
194
+ expect(player.hasItem('TestPotion')).toBe(false)
195
+ player.addItem('TestPotion', 1)
196
+ expect(player.hasItem('TestPotion')).toBe(true)
197
+ })
198
+
199
+ test('should remove item from inventory', () => {
200
+ player.addItem('TestPotion', 5)
201
+ const item = player.removeItem('TestPotion', 2)
202
+ expect(item).toBeDefined()
203
+ expect(item?.quantity()).toBe(3)
204
+ })
205
+
206
+ test('should remove item completely when quantity reaches zero', () => {
207
+ player.addItem('TestPotion', 2)
208
+ player.removeItem('TestPotion', 2)
209
+ expect(player.hasItem('TestPotion')).toBe(false)
210
+ const item = player.getItem('TestPotion')
211
+ expect(item).toBeUndefined()
212
+ })
213
+
214
+ test('should throw error when removing non-existent item', () => {
215
+ expect(() => {
216
+ player.removeItem('TestPotion', 1)
217
+ }).toThrow()
218
+ })
219
+ })
220
+
221
+ describe('Item Management - Buy and Sell', () => {
222
+ test('should buy item and reduce gold', () => {
223
+ player.gold = 1000
224
+ const item = player.buyItem('TestPotion', 2)
225
+ expect(item).toBeDefined()
226
+ expect(item.quantity()).toBe(2)
227
+ expect(player.gold).toBe(600) // 1000 - (200 * 2)
228
+ })
229
+
230
+ test('should throw error when buying item without price', () => {
231
+ player.gold = 1000
232
+ expect(() => {
233
+ player.buyItem('TestNoPriceItem', 1)
234
+ }).toThrow()
235
+ })
236
+
237
+ test('should throw error when not enough gold', () => {
238
+ player.gold = 100
239
+ expect(() => {
240
+ player.buyItem('TestExpensiveItem', 1)
241
+ }).toThrow()
242
+ })
243
+
244
+ test('should sell item and increase gold', () => {
245
+ player.gold = 1000
246
+ player.addItem('TestPotion', 3)
247
+ const item = player.sellItem('TestPotion', 2)
248
+ expect(item).toBeDefined()
249
+ expect(player.gold).toBe(1200) // 1000 + (200 / 2 * 2)
250
+ expect(player.getItem('TestPotion')?.quantity()).toBe(1)
251
+ })
252
+
253
+ test('should throw error when selling non-existent item', () => {
254
+ expect(() => {
255
+ player.sellItem('TestPotion', 1)
256
+ }).toThrow()
257
+ })
258
+
259
+ test('should throw error when selling more items than available', () => {
260
+ player.addItem('TestPotion', 2)
261
+ expect(() => {
262
+ player.sellItem('TestPotion', 5)
263
+ }).toThrow()
264
+ })
265
+
266
+ test('should throw error when selling item without price', () => {
267
+ player.addItem('TestNoPriceItem', 1)
268
+ expect(() => {
269
+ player.sellItem('TestNoPriceItem', 1)
270
+ }).toThrow()
271
+ })
272
+ })
273
+
274
+ describe('Item Management - Use Item', () => {
275
+ test('should use consumable item', () => {
276
+ const initialHp = player.hp
277
+ player.addItem('TestPotion', 1)
278
+ const item = player.useItem('TestPotion')
279
+ expect(item).toBeDefined()
280
+ expect(player.hasItem('TestPotion')).toBe(false)
281
+ // Note: applyEffect might not be implemented in test environment
282
+ })
283
+
284
+ test('should throw error when using non-existent item', () => {
285
+ expect(() => {
286
+ player.useItem('TestPotion')
287
+ }).toThrow()
288
+ })
289
+
290
+ test('should throw error when using non-consumable item', () => {
291
+ player.addItem('TestNonConsumable', 1)
292
+ expect(() => {
293
+ player.useItem('TestNonConsumable')
294
+ }).toThrow()
295
+ })
296
+
297
+ test('should handle hitRate chance (success)', () => {
298
+ // Mock Math.random to return a value that passes hitRate
299
+ const originalRandom = Math.random
300
+ Math.random = vi.fn(() => 0.5) // 0.5 < 1.0 (default hitRate)
301
+
302
+ player.addItem('TestPotion', 1)
303
+ const item = player.useItem('TestPotion')
304
+ expect(item).toBeDefined()
305
+ expect(player.hasItem('TestPotion')).toBe(false)
306
+
307
+ Math.random = originalRandom
308
+ })
309
+
310
+ test('should handle hitRate chance (failure)', () => {
311
+ // Mock Math.random to return a value that fails hitRate
312
+ const originalRandom = Math.random
313
+ Math.random = vi.fn(() => 0.99) // 0.99 > 1.0 (default hitRate)
314
+
315
+ player.addItem('TestPotion', 1)
316
+ expect(() => {
317
+ player.useItem('TestPotion')
318
+ }).toThrow()
319
+
320
+ // Item should still be removed even on failure
321
+ expect(player.hasItem('TestPotion')).toBe(false)
322
+
323
+ Math.random = originalRandom
324
+ })
325
+
326
+ test('should use item with custom hitRate', () => {
327
+ const customItem: ItemObject = {
328
+ id: 'chance-item',
329
+ name: 'Chance Item',
330
+ price: 100,
331
+ consumable: true,
332
+ hitRate: 0.5, // 50% chance
333
+ }
334
+ player.addItem(customItem, 1)
335
+
336
+ // Mock Math.random to pass
337
+ const originalRandom = Math.random
338
+ Math.random = vi.fn(() => 0.3) // 0.3 < 0.5
339
+
340
+ const item = player.useItem('chance-item')
341
+ expect(item).toBeDefined()
342
+
343
+ Math.random = originalRandom
344
+ })
345
+ })
346
+
347
+ describe('Item Management - Equipment', () => {
348
+ test('should equip weapon', () => {
349
+ player.addItem('TestSword', 1)
350
+ player.equip('TestSword', true)
351
+ const item = player.getItem('TestSword')
352
+ expect((item as any).equipped).toBe(true)
353
+ expect(player.equipments().some((eq) => eq.id() === 'TestSword')).toBe(true)
354
+ })
355
+
356
+ test('should unequip weapon', () => {
357
+ player.addItem('TestSword', 1)
358
+ player.equip('TestSword', true)
359
+ player.equip('TestSword', false)
360
+ const item = player.getItem('TestSword')
361
+ expect((item as any).equipped).toBe(false)
362
+ expect(player.equipments().some((eq) => eq.id() === 'TestSword')).toBe(false)
363
+ })
364
+
365
+ test('should equip armor', () => {
366
+ player.addItem('TestArmor', 1)
367
+ player.equip('TestArmor', true)
368
+ const item = player.getItem('TestArmor')
369
+ expect((item as any).equipped).toBe(true)
370
+ })
371
+
372
+ test('should throw error when equipping non-existent item', () => {
373
+ expect(() => {
374
+ player.equip('TestSword', true)
375
+ }).toThrow()
376
+ })
377
+
378
+ test('should throw error when equipping regular item (not weapon/armor)', () => {
379
+ player.addItem('TestPotion', 1)
380
+ expect(() => {
381
+ player.equip('TestPotion', true)
382
+ }).toThrow()
383
+ })
384
+
385
+ test('should throw error when equipping already equipped item', () => {
386
+ player.addItem('TestSword', 1)
387
+ player.equip('TestSword', true)
388
+ expect(() => {
389
+ player.equip('TestSword', true)
390
+ }).toThrow()
391
+ })
392
+ })
393
+
394
+ describe('Item Management - Parameters (ATK, PDEF, SDEF)', () => {
395
+ test('should calculate attack from equipped weapon', () => {
396
+ player.addItem('TestSword', 1)
397
+ player.equip('TestSword', true)
398
+ expect(player.atk).toBe(50)
399
+ })
400
+
401
+ test('should calculate physical defense from equipped armor', () => {
402
+ player.addItem('TestArmor', 1)
403
+ player.equip('TestArmor', true)
404
+ expect(player.pdef).toBe(30)
405
+ })
406
+
407
+ test('should calculate skill defense from equipped armor', () => {
408
+ player.addItem('TestArmor', 1)
409
+ player.equip('TestArmor', true)
410
+ expect(player.sdef).toBe(20)
411
+ })
412
+
413
+ test('should return 0 for parameters when no equipment', () => {
414
+ expect(player.atk).toBe(0)
415
+ expect(player.pdef).toBe(0)
416
+ expect(player.sdef).toBe(0)
417
+ })
418
+
419
+ test('should sum parameters from multiple equipped items', () => {
420
+ const sword2: ItemObject & { atk: number } = {
421
+ id: 'sword2',
422
+ name: 'Sword 2',
423
+ price: 600,
424
+ atk: 75,
425
+ _type: 'weapon' as const,
426
+ }
427
+ player.getCurrentMap()?.addInDatabase('sword2', sword2)
428
+
429
+ player.addItem('TestSword', 1)
430
+ player.addItem('sword2', 1)
431
+ player.equip('TestSword', true)
432
+ player.equip('sword2', true)
433
+
434
+ // Only one weapon should be equipped at a time typically, but test the sum
435
+ expect(player.atk).toBeGreaterThanOrEqual(50)
436
+ })
437
+ })
438
+
439
+ describe('Item Management - Hooks', () => {
440
+ test('should call onAdd hook when adding item', () => {
441
+ const onAddSpy = vi.fn()
442
+ const customItem: ItemObject = {
443
+ id: 'hook-item',
444
+ name: 'Hook Item',
445
+ price: 100,
446
+ onAdd: onAddSpy,
447
+ }
448
+
449
+ player.addItem(customItem, 1)
450
+ expect(onAddSpy).toHaveBeenCalledWith(player)
451
+ })
452
+
453
+ test('should call onRemove hook when removing item', () => {
454
+ const onRemoveSpy = vi.fn()
455
+ const customItem: ItemObject = {
456
+ id: 'remove-hook-item',
457
+ name: 'Remove Hook Item',
458
+ price: 100,
459
+ onRemove: onRemoveSpy,
460
+ }
461
+
462
+ player.addItem(customItem, 1)
463
+ player.removeItem('remove-hook-item', 1)
464
+ expect(onRemoveSpy).toHaveBeenCalledWith(player)
465
+ })
466
+
467
+ test('should call onUse hook when using item', () => {
468
+ const onUseSpy = vi.fn()
469
+ const customItem: ItemObject = {
470
+ id: 'use-hook-item',
471
+ name: 'Use Hook Item',
472
+ price: 100,
473
+ consumable: true,
474
+ onUse: onUseSpy,
475
+ }
476
+
477
+ // Mock Math.random to ensure success
478
+ const originalRandom = Math.random
479
+ Math.random = vi.fn(() => 0.5)
480
+
481
+ player.addItem(customItem, 1)
482
+ player.useItem('use-hook-item')
483
+ expect(onUseSpy).toHaveBeenCalledWith(player)
484
+
485
+ Math.random = originalRandom
486
+ })
487
+
488
+ test('should call onUseFailed hook when item usage fails', () => {
489
+ const onUseFailedSpy = vi.fn()
490
+ const customItem: ItemObject = {
491
+ id: 'fail-hook-item',
492
+ name: 'Fail Hook Item',
493
+ price: 100,
494
+ consumable: true,
495
+ hitRate: 0.1, // 10% chance
496
+ onUseFailed: onUseFailedSpy,
497
+ }
498
+
499
+ // Mock Math.random to fail
500
+ const originalRandom = Math.random
501
+ Math.random = vi.fn(() => 0.9) // 0.9 > 0.1
502
+
503
+ player.addItem(customItem, 1)
504
+ try {
505
+ player.useItem('fail-hook-item')
506
+ } catch (e) {
507
+ // Expected to throw
508
+ }
509
+ expect(onUseFailedSpy).toHaveBeenCalledWith(player)
510
+
511
+ Math.random = originalRandom
512
+ })
513
+
514
+ test('should call onEquip hook when equipping item', () => {
515
+ const onEquipSpy = vi.fn()
516
+ const customWeapon: ItemObject & { atk: number } = {
517
+ id: 'equip-hook-weapon',
518
+ name: 'Equip Hook Weapon',
519
+ price: 500,
520
+ atk: 40,
521
+ _type: 'weapon' as const,
522
+ onEquip: onEquipSpy,
523
+ }
524
+
525
+ player.addItem(customWeapon, 1)
526
+ player.equip('equip-hook-weapon', true)
527
+ expect(onEquipSpy).toHaveBeenCalledWith(player, true)
528
+
529
+ player.equip('equip-hook-weapon', false)
530
+ expect(onEquipSpy).toHaveBeenCalledWith(player, false)
531
+ })
532
+ })
533
+
534
+ describe('Item Management - Edge Cases', () => {
535
+ test('should handle adding item with class (if supported)', () => {
536
+ // This test would require an actual Item class
537
+ // For now, we test that object items work
538
+ const itemObj: ItemObject = {
539
+ id: 'class-like-item',
540
+ name: 'Class Like Item',
541
+ price: 100,
542
+ }
543
+ const item = player.addItem(itemObj, 1)
544
+ expect(item).toBeDefined()
545
+ })
546
+
547
+ test('should handle merging existing item data with new object', () => {
548
+ // Add item first
549
+ player.addItem('TestPotion', 1)
550
+
551
+ // Add same item with different properties
552
+ const updatedItem: ItemObject = {
553
+ id: 'TestPotion',
554
+ name: 'Updated Potion',
555
+ price: 250,
556
+ }
557
+ const item = player.addItem(updatedItem, 1)
558
+ expect(item.name()).toBe('Updated Potion')
559
+ expect(item.quantity()).toBe(2)
560
+ })
561
+
562
+ test('should handle multiple items in inventory', () => {
563
+ player.addItem('TestPotion', 3)
564
+ player.addItem('TestSword', 1)
565
+ player.addItem('TestArmor', 2)
566
+
567
+ expect(player.hasItem('TestPotion')).toBe(true)
568
+ expect(player.hasItem('TestSword')).toBe(true)
569
+ expect(player.hasItem('TestArmor')).toBe(true)
570
+
571
+ expect(player.getItem('TestPotion')?.quantity()).toBe(3)
572
+ expect(player.getItem('TestSword')?.quantity()).toBe(1)
573
+ expect(player.getItem('TestArmor')?.quantity()).toBe(2)
574
+ })
575
+ })
576
+
577
+ */
@@ -0,0 +1,38 @@
1
+ import { beforeEach, test, expect } from 'vitest'
2
+ import { testing } from '@rpgjs/testing'
3
+ import { defineModule, createModule } from '@rpgjs/common'
4
+ import { RpgPlayer, RpgServer } from '../src'
5
+ import { RpgClient } from '../../client/src'
6
+
7
+ // Define your server module
8
+ const serverModule = defineModule<RpgServer>({
9
+ player: {
10
+ onConnected(player) {
11
+ player.setVariable('test', 'value')
12
+ }
13
+ }
14
+ })
15
+
16
+ // Define your client module
17
+ const clientModule = defineModule<RpgClient>({
18
+ // Client-side logic
19
+ })
20
+
21
+ let player: RpgPlayer
22
+
23
+ beforeEach(async () => {
24
+ // Create module using createModule
25
+ const myModule = createModule('MyModule', [{
26
+ server: serverModule,
27
+ client: clientModule
28
+ }])
29
+
30
+ // Pass the module created with createModule to testing
31
+ const fixture = await testing(myModule)
32
+ const client = await fixture.createClient()
33
+ player = client.player
34
+ })
35
+
36
+ test('Module hook was called', () => {
37
+ expect(player.getVariable('test')).toBe('value')
38
+ })
@@ -0,0 +1,28 @@
1
+
2
+ import { testing } from '@rpgjs/testing'
3
+ import { beforeEach, describe, expect, test, vi } from 'vitest'
4
+ import { ATK, MAXHP, MAXHP_CURVE, MAXSP, MAXSP_CURVE, RpgPlayer } from '../src'
5
+
6
+ let player: RpgPlayer
7
+
8
+ beforeEach(async () => {
9
+ const fixture = await testing();
10
+ const client = await fixture.createClient()
11
+ player = client.player
12
+ })
13
+
14
+ test('Test HP', () => {
15
+ expect(player.hp).toBe(MAXHP_CURVE.start)
16
+ })
17
+
18
+ test('Test SP', () => {
19
+ expect(player.sp).toBe(MAXSP_CURVE.start)
20
+ })
21
+
22
+ test('Test MaxHP', () => {
23
+ expect(player.param[MAXHP]).toBe(MAXHP_CURVE.start)
24
+ })
25
+
26
+ test('Test MaxSP', () => {
27
+ expect(player.param[MAXSP]).toBe(MAXSP_CURVE.start)
28
+ })