@hytopia.com/examples 1.0.12 → 1.0.13

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.
Files changed (67) hide show
  1. package/frontiers-rpg-game/assets/icons/items/leather-boots.png +0 -0
  2. package/frontiers-rpg-game/assets/icons/items/leather-bracers.png +0 -0
  3. package/frontiers-rpg-game/assets/icons/items/leather-helmet.png +0 -0
  4. package/frontiers-rpg-game/assets/icons/items/leather-leggings.png +0 -0
  5. package/frontiers-rpg-game/assets/icons/items/leather-vest.png +0 -0
  6. package/frontiers-rpg-game/assets/icons/items/spiked-club.png +0 -0
  7. package/frontiers-rpg-game/assets/icons/skills/crafting.png +0 -0
  8. package/frontiers-rpg-game/assets/models/weapons/.optimized/mace/baseColor.png +0 -0
  9. package/frontiers-rpg-game/assets/models/weapons/.optimized/mace/mace-named-nodes.bin +0 -0
  10. package/frontiers-rpg-game/assets/models/weapons/.optimized/mace/mace-named-nodes.gltf +653 -0
  11. package/frontiers-rpg-game/assets/models/weapons/.optimized/mace/mace.bin +0 -0
  12. package/frontiers-rpg-game/assets/models/weapons/.optimized/mace/mace.gltf +135 -0
  13. package/frontiers-rpg-game/assets/models/weapons/.optimized/mace/mace.gltf.md5 +1 -0
  14. package/frontiers-rpg-game/assets/models/weapons/.optimized/spiked-club/baseColor.png +0 -0
  15. package/frontiers-rpg-game/assets/models/weapons/.optimized/spiked-club/spiked-club-named-nodes.bin +0 -0
  16. package/frontiers-rpg-game/assets/models/weapons/.optimized/spiked-club/spiked-club-named-nodes.gltf +840 -0
  17. package/frontiers-rpg-game/assets/models/weapons/.optimized/spiked-club/spiked-club.bin +0 -0
  18. package/frontiers-rpg-game/assets/models/weapons/.optimized/spiked-club/spiked-club.gltf +141 -0
  19. package/frontiers-rpg-game/assets/models/weapons/.optimized/spiked-club/spiked-club.gltf.md5 +1 -0
  20. package/frontiers-rpg-game/assets/models/weapons/mace.gltf +1 -0
  21. package/frontiers-rpg-game/assets/ui/build.js +2 -0
  22. package/frontiers-rpg-game/assets/ui/index.html +1328 -64
  23. package/frontiers-rpg-game/assets/ui/menus/crafting.html +976 -0
  24. package/frontiers-rpg-game/assets/ui/menus/quests.html +70 -2
  25. package/frontiers-rpg-game/assets/ui/shared/item-stats.html +224 -0
  26. package/frontiers-rpg-game/assets/ui/shared/item-tooltips.html +72 -81
  27. package/frontiers-rpg-game/dev/persistence/player-player-1.json +121 -12
  28. package/frontiers-rpg-game/src/GamePlayer.ts +53 -0
  29. package/frontiers-rpg-game/src/GamePlayerEntity.ts +9 -2
  30. package/frontiers-rpg-game/src/config.ts +7 -0
  31. package/frontiers-rpg-game/src/entities/BaseCraftingEntity.ts +115 -0
  32. package/frontiers-rpg-game/src/entities/enemies/RatkinBruteEntity.ts +2 -0
  33. package/frontiers-rpg-game/src/entities/enemies/RatkinRangerEntity.ts +2 -0
  34. package/frontiers-rpg-game/src/entities/enemies/RatkinSpellcasterEntity.ts +2 -0
  35. package/frontiers-rpg-game/src/entities/enemies/RatkinWarriorEntity.ts +2 -0
  36. package/frontiers-rpg-game/src/entities/enemies/TaintedRatkinBruteEntity.ts +2 -0
  37. package/frontiers-rpg-game/src/entities/enemies/TaintedRatkinRangerEntity.ts +2 -1
  38. package/frontiers-rpg-game/src/entities/enemies/TaintedRatkinSpellcasterEntity.ts +2 -0
  39. package/frontiers-rpg-game/src/entities/enemies/TaintedRatkinWarriorEntity.ts +2 -0
  40. package/frontiers-rpg-game/src/entities/forageables/DecayingPileEntity.ts +2 -2
  41. package/frontiers-rpg-game/src/entities/forageables/RottenLogEntity.ts +2 -2
  42. package/frontiers-rpg-game/src/items/ItemClasses.ts +14 -2
  43. package/frontiers-rpg-game/src/items/materials/RawHideItem.ts +10 -0
  44. package/frontiers-rpg-game/src/items/weapons/DullSwordItem.ts +5 -4
  45. package/frontiers-rpg-game/src/items/weapons/IronDaggerItem.ts +1 -1
  46. package/frontiers-rpg-game/src/items/weapons/IronLongSwordItem.ts +5 -4
  47. package/frontiers-rpg-game/src/items/weapons/SpikedClubItem.ts +26 -0
  48. package/frontiers-rpg-game/src/items/weapons/TrainingSwordItem.ts +5 -4
  49. package/frontiers-rpg-game/src/items/wearables/LeatherBootsItem.ts +14 -0
  50. package/frontiers-rpg-game/src/items/wearables/LeatherBracersItem.ts +14 -0
  51. package/frontiers-rpg-game/src/items/wearables/LeatherHelmetItem.ts +14 -0
  52. package/frontiers-rpg-game/src/items/wearables/LeatherLeggingsItem.ts +14 -0
  53. package/frontiers-rpg-game/src/items/wearables/LeatherVestItem.ts +14 -0
  54. package/frontiers-rpg-game/src/quests/BaseQuest.ts +1 -2
  55. package/frontiers-rpg-game/src/quests/QuestClasses.ts +2 -0
  56. package/frontiers-rpg-game/src/quests/side/FungalForagingQuest.ts +1 -23
  57. package/frontiers-rpg-game/src/quests/side/HammersAndCraftingQuest.ts +139 -0
  58. package/frontiers-rpg-game/src/regions/stalkhaven/StalkhavenRegion.ts +2 -0
  59. package/frontiers-rpg-game/src/regions/stalkhaven/npcs/BlacksmithArdenEntity.ts +114 -0
  60. package/frontiers-rpg-game/src/systems/QuestLog.ts +2 -5
  61. package/package.json +1 -1
  62. package/frontiers-rpg-game/dev/persistence/player-player-2.json +0 -31
  63. package/frontiers-rpg-game/dev/persistence/player-player-3.json +0 -25
  64. package/frontiers-rpg-game/dev/persistence/player-player-4.json +0 -31
  65. package/frontiers-rpg-game/src/items/materials/MonsterHideItem.ts +0 -10
  66. /package/frontiers-rpg-game/assets/icons/items/{monster-hide.png → raw-hide.png} +0 -0
  67. /package/frontiers-rpg-game/assets/models/weapons/{club.gltf → spiked-club.gltf} +0 -0
@@ -15,6 +15,7 @@ import Levels from './systems/Levels';
15
15
  import QuestLog from './systems/QuestLog';
16
16
  import Storage from './systems/Storage';
17
17
  import Wearables from './systems/Wearables';
18
+ import type BaseCraftingEntity from './entities/BaseCraftingEntity';
18
19
  import type BaseEntity from './entities/BaseEntity';
19
20
  import type BaseMerchantEntity from './entities/BaseMerchantEntity';
20
21
  import type BaseItem from './items/BaseItem';
@@ -60,6 +61,7 @@ export default class GamePlayer {
60
61
  public readonly storage: Storage;
61
62
  public readonly wearables: Wearables
62
63
 
64
+ private _currentCraftingEntity: BaseCraftingEntity | undefined;
63
65
  private _currentDialogueEntity: BaseEntity | undefined;
64
66
  private _currentMerchantEntity: BaseMerchantEntity | undefined;
65
67
  private _currentEntity: GamePlayerEntity | undefined;
@@ -124,6 +126,10 @@ export default class GamePlayer {
124
126
  }
125
127
 
126
128
  // Getters
129
+ public get currentCraftingEntity(): BaseCraftingEntity | undefined {
130
+ return this._currentCraftingEntity;
131
+ }
132
+
127
133
  public get currentDialogueEntity(): BaseEntity | undefined {
128
134
  return this._currentDialogueEntity;
129
135
  }
@@ -188,6 +194,10 @@ export default class GamePlayer {
188
194
  });
189
195
  }
190
196
 
197
+ public addHeldItem(itemClass: typeof BaseItem, quantity: number = 1): boolean {
198
+ return this.hotbar.addItem(itemClass.create({ quantity })) || this.backpack.addItem(itemClass.create({ quantity }));
199
+ }
200
+
191
201
  // Game state methods
192
202
  public adjustHealth(amount: number): void {
193
203
  const willDie = this._health > 0 && this._health + amount <= 0;
@@ -315,6 +325,15 @@ export default class GamePlayer {
315
325
  return this._skillExperience.get(skillId) ?? 0;
316
326
  }
317
327
 
328
+ public hasHeldItem(itemClass: typeof BaseItem, quantity: number = 1): boolean {
329
+ const totalBackpackItems = this.backpack.getItemQuantityByClass(itemClass);
330
+ const totalHotbarItems = this.hotbar.getItemQuantityByClass(itemClass);
331
+
332
+ return totalBackpackItems + totalHotbarItems >= quantity;
333
+ }
334
+
335
+
336
+
318
337
  public joinRegion(region: GameRegion, facingAngle: number, spawnPoint: Vector3Like): void {
319
338
  this.setCurrentRegion(region);
320
339
  this.setCurrentRegionSpawnFacingAngle(facingAngle);
@@ -351,6 +370,28 @@ export default class GamePlayer {
351
370
  });
352
371
  }
353
372
 
373
+ public removeHeldItem(itemClass: typeof BaseItem, quantity: number = 1): boolean {
374
+ if (!this.hasHeldItem(itemClass, quantity)) return false;
375
+
376
+ // Remove from backpack first
377
+ for (const item of this.backpack.getItemsByClass(itemClass)) {
378
+ if (quantity <= 0) break;
379
+ const toRemove = Math.min(item.quantity, quantity);
380
+ this.adjustInventoryItemQuantityByReference(this.backpack, item, -toRemove);
381
+ quantity -= toRemove;
382
+ }
383
+
384
+ // Remove from hotbar last
385
+ for (const item of this.hotbar.getItemsByClass(itemClass)) {
386
+ if (quantity <= 0) break;
387
+ const toRemove = Math.min(item.quantity, quantity);
388
+ this.adjustInventoryItemQuantityByReference(this.hotbar, item, -toRemove);
389
+ quantity -= toRemove;
390
+ }
391
+
392
+ return true;
393
+ }
394
+
354
395
  public respawn(): void {
355
396
  if (!this._isDead || !this._currentEntity) return;
356
397
 
@@ -370,6 +411,10 @@ export default class GamePlayer {
370
411
  }
371
412
  }
372
413
 
414
+ public setCurrentCraftingEntity(entity: BaseCraftingEntity): void {
415
+ this._currentCraftingEntity = entity;
416
+ }
417
+
373
418
  public setCurrentDialogueEntity(entity: BaseEntity): void {
374
419
  this._currentDialogueEntity = entity;
375
420
  }
@@ -497,6 +542,14 @@ export default class GamePlayer {
497
542
  }
498
543
  }
499
544
 
545
+ if (data.type === 'craftItem') {
546
+ const { recipeIndex } = data;
547
+
548
+ if (this._currentCraftingEntity && this._currentEntity) {
549
+ this._currentCraftingEntity.craftItem(this._currentEntity, recipeIndex);
550
+ }
551
+ }
552
+
500
553
  if (data.type === 'dropItem') {
501
554
  const fromType = data.fromType;
502
555
  const fromIndex = parseInt(data.fromIndex);
@@ -16,6 +16,9 @@ import CustomCollisionGroup from './physics/CustomCollisionGroup';
16
16
  import GameClock from './GameClock';
17
17
  import GamePlayer from './GamePlayer';
18
18
  import Levels from './systems/Levels';
19
+ import type BaseCraftingEntity from './entities/BaseCraftingEntity';
20
+ import type BaseEntity from './entities/BaseEntity';
21
+ import type BaseMerchantEntity from './entities/BaseMerchantEntity';
19
22
  import type GameRegion from './GameRegion';
20
23
  import type IDamageable from './interfaces/IDamageable';
21
24
  import type { NotificationType } from './GamePlayer';
@@ -150,11 +153,15 @@ export default class GamePlayerEntity extends DefaultPlayerEntity implements IDa
150
153
  this._gamePlayer.joinRegion(region, facingAngle, spawnPoint);
151
154
  }
152
155
 
153
- public setCurrentDialogueEntity(entity: any): void {
156
+ public setCurrentCraftingEntity(entity: BaseCraftingEntity): void {
157
+ this._gamePlayer.setCurrentCraftingEntity(entity);
158
+ }
159
+
160
+ public setCurrentDialogueEntity(entity: BaseEntity): void {
154
161
  this._gamePlayer.setCurrentDialogueEntity(entity);
155
162
  }
156
163
 
157
- public setCurrentMerchantEntity(entity: any): void {
164
+ public setCurrentMerchantEntity(entity: BaseMerchantEntity): void {
158
165
  this._gamePlayer.setCurrentMerchantEntity(entity);
159
166
  }
160
167
 
@@ -13,6 +13,7 @@ export enum SkillId {
13
13
  AGILITY = 'agility',
14
14
  BARTERING = 'bartering',
15
15
  COMBAT = 'combat',
16
+ CRAFTING = 'crafting',
16
17
  EXPLORATION = 'exploration',
17
18
  FARMING = 'farming',
18
19
  FORAGING = 'foraging',
@@ -37,6 +38,12 @@ export const skills: Skill[] = [
37
38
  description: 'Purchase and sell items with merchants to gain XP.<br/><br/>Increases merchant discount and unlocks new merchant items.',
38
39
  iconAssetUri: 'icons/skills/bartering.png'
39
40
  },
41
+ {
42
+ id: SkillId.CRAFTING,
43
+ name: 'Crafting',
44
+ description: 'Craft items from to gain XP.<br/><br/>Unlocks new craftable items and perks.',
45
+ iconAssetUri: 'icons/skills/crafting.png'
46
+ },
40
47
  {
41
48
  id: SkillId.COMBAT,
42
49
  name: 'Combat',
@@ -0,0 +1,115 @@
1
+ import BaseEntity, { BaseEntityOptions } from './BaseEntity';
2
+ import { ItemUIDataHelper } from '../items/ItemUIDataHelper';
3
+ import { SkillId } from '../config';
4
+ import type { ItemClass } from '../items/BaseItem';
5
+ import type { BaseEntityDialogueOption } from './BaseEntity';
6
+ import type GamePlayerEntity from '../GamePlayerEntity';
7
+
8
+ export enum BaseCraftingEntityPlayerEvent {
9
+ CRAFT_ITEM = 'BaseCraftingEntity.CRAFT_ITEM',
10
+ }
11
+
12
+ export type BaseCraftingEntityPlayerEventPayloads = {
13
+ [BaseCraftingEntityPlayerEvent.CRAFT_ITEM]: { crafter: BaseCraftingEntity, craftingRecipeIndex: number }
14
+ }
15
+
16
+ export type CraftingRecipe = {
17
+ craftedItemClass: ItemClass;
18
+ requirements: {
19
+ itemClass: ItemClass;
20
+ quantity: number;
21
+ }[];
22
+ }
23
+
24
+ export type BaseCraftingEntityOptions = {
25
+ additionalDialogueOptions?: BaseEntityDialogueOption[];
26
+ craftingRecipes: CraftingRecipe[];
27
+ dialogueAvatarImageUri: string;
28
+ dialogueTitle: string;
29
+ } & BaseEntityOptions;
30
+
31
+ export default class BaseCraftingEntity extends BaseEntity {
32
+ public readonly craftingRecipes: CraftingRecipe[];
33
+
34
+ public constructor(options: BaseCraftingEntityOptions) {
35
+ super({
36
+ ...options,
37
+ dialogue: {
38
+ avatarImageUri: options.dialogueAvatarImageUri,
39
+ title: options.dialogueTitle,
40
+ dialogue: {
41
+ text: 'Good to see you! What can I help you with?',
42
+ options: [
43
+ {
44
+ text: `Craft items.`,
45
+ onSelect: (interactor: GamePlayerEntity) => this.openCraftMenu(interactor),
46
+ dismiss: true,
47
+ },
48
+ ...(options.additionalDialogueOptions ?? []),
49
+ {
50
+ text: `Nevermind, thanks!`,
51
+ dismiss: true,
52
+ pureExit: true,
53
+ }
54
+ ]
55
+ }
56
+ },
57
+ });
58
+
59
+ this.craftingRecipes = options.craftingRecipes;
60
+ }
61
+
62
+
63
+ public craftItem(interactor: GamePlayerEntity, craftingRecipeIndex: number): void {
64
+ const craftingRecipe = this.craftingRecipes[craftingRecipeIndex];
65
+
66
+ if (!craftingRecipe) {
67
+ return;
68
+ }
69
+
70
+ // Check if the player has the required items to craft the item
71
+ let hasRequirements = true;
72
+
73
+ for (const requirement of craftingRecipe.requirements) {
74
+ if (!interactor.gamePlayer.hasHeldItem(requirement.itemClass, requirement.quantity)) {
75
+ hasRequirements = false;
76
+ break;
77
+ }
78
+ }
79
+
80
+ if (!hasRequirements) {
81
+ return interactor.showNotification(`You don't have the required items to craft this item.`, 'error');
82
+ }
83
+
84
+ // Remove the required items from the player's inventory and give the crafted item
85
+ for (const requirement of craftingRecipe.requirements) {
86
+ interactor.gamePlayer.removeHeldItem(requirement.itemClass, requirement.quantity);
87
+ }
88
+
89
+ interactor.gamePlayer.addHeldItem(craftingRecipe.craftedItemClass);
90
+ this._awardCraftingSkillExperience(interactor, craftingRecipe.craftedItemClass);
91
+ interactor.showNotification(`You crafted a ${craftingRecipe.craftedItemClass.name}.`, 'success');
92
+
93
+ interactor.gamePlayer.eventRouter.emit(BaseCraftingEntityPlayerEvent.CRAFT_ITEM, { crafter: this, craftingRecipeIndex });
94
+ }
95
+
96
+ public openCraftMenu(interactor: GamePlayerEntity): void {
97
+ interactor.setCurrentCraftingEntity(this);
98
+ interactor.player.ui.sendData({
99
+ type: 'toggleCrafting',
100
+ crafterName: this.name,
101
+ crafterTitle: this.dialogueRoot?.title,
102
+ crafterAvatarUri: this.dialogueRoot?.avatarImageUri,
103
+ craftingRecipes: this.craftingRecipes.map((recipe, index) => ({
104
+ craftedItem: ItemUIDataHelper.getUIData(recipe.craftedItemClass),
105
+ requirements: recipe.requirements.map(requirement => ItemUIDataHelper.getUIData(requirement.itemClass, { quantity: requirement.quantity })),
106
+ position: index,
107
+ })),
108
+ })
109
+ }
110
+
111
+ private _awardCraftingSkillExperience(interactor: GamePlayerEntity, craftedItemClass: ItemClass) {
112
+ const craftingSkillExperience = Math.max(10, Math.floor(craftedItemClass.sellPrice / 2));
113
+ interactor.gamePlayer.adjustSkillExperience(SkillId.CRAFTING, craftingSkillExperience);
114
+ }
115
+ }
@@ -8,6 +8,7 @@ import RatkinBonesItem from "../../items/materials/RatkinBonesItem";
8
8
  import RatkinEyesItem from "../../items/materials/RatkinEyesItem.ts";
9
9
  import RatkinTailItem from "../../items/materials/RatkinTailItem";
10
10
  import RatkinToothItem from "../../items/materials/RatkinToothItem.ts";
11
+ import RawHideItem from "../../items/materials/RawHideItem.ts";
11
12
 
12
13
  export type RatkinBruteEntityOptions = {
13
14
 
@@ -59,6 +60,7 @@ export default class RatkinBruteEntity extends BaseCombatEntity {
59
60
  { itemClass: RatkinToothItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
60
61
  { itemClass: RatkinTailItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
61
62
  { itemClass: GoldItem, minQuantity: 6, maxQuantity: 15, weight: 2 },
63
+ { itemClass: RawHideItem, minQuantity: 1, maxQuantity: 2, weight: 1 },
62
64
  { itemClass: CommonMushroomItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
63
65
  { itemClass: CommonSeedsItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
64
66
  ],
@@ -10,6 +10,7 @@ import RatkinBonesItem from "../../items/materials/RatkinBonesItem";
10
10
  import RatkinEyesItem from "../../items/materials/RatkinEyesItem.ts";
11
11
  import RatkinTailItem from "../../items/materials/RatkinTailItem";
12
12
  import RatkinToothItem from "../../items/materials/RatkinToothItem.ts";
13
+ import RawHideItem from "../../items/materials/RawHideItem.ts";
13
14
 
14
15
  export type RatkinRangerEntityOptions = {
15
16
 
@@ -51,6 +52,7 @@ export default class RatkinRangerEntity extends BaseCombatEntity {
51
52
  { itemClass: RatkinToothItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
52
53
  { itemClass: RatkinTailItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
53
54
  { itemClass: GoldItem, minQuantity: 6, maxQuantity: 15, weight: 2 },
55
+ { itemClass: RawHideItem, minQuantity: 1, maxQuantity: 2, weight: 1 },
54
56
  { itemClass: CommonMushroomItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
55
57
  { itemClass: CommonSeedsItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
56
58
  ],
@@ -10,6 +10,7 @@ import RatkinBonesItem from "../../items/materials/RatkinBonesItem";
10
10
  import RatkinEyesItem from "../../items/materials/RatkinEyesItem.ts";
11
11
  import RatkinTailItem from "../../items/materials/RatkinTailItem";
12
12
  import RatkinToothItem from "../../items/materials/RatkinToothItem.ts";
13
+ import RawHideItem from "../../items/materials/RawHideItem.ts";
13
14
 
14
15
  export type RatkinSpellcasterEntityOptions = {
15
16
 
@@ -41,6 +42,7 @@ export default class RatkinSpellcasterEntity extends BaseCombatEntity {
41
42
  { itemClass: RatkinToothItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
42
43
  { itemClass: RatkinTailItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
43
44
  { itemClass: GoldItem, minQuantity: 6, maxQuantity: 15, weight: 2 },
45
+ { itemClass: RawHideItem, minQuantity: 1, maxQuantity: 2, weight: 1 },
44
46
  { itemClass: CommonMushroomItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
45
47
  { itemClass: CommonSeedsItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
46
48
  ],
@@ -8,6 +8,7 @@ import RatkinBonesItem from "../../items/materials/RatkinBonesItem";
8
8
  import RatkinEyesItem from "../../items/materials/RatkinEyesItem.ts";
9
9
  import RatkinTailItem from "../../items/materials/RatkinTailItem";
10
10
  import RatkinToothItem from "../../items/materials/RatkinToothItem.ts";
11
+ import RawHideItem from "../../items/materials/RawHideItem.ts";
11
12
 
12
13
  export type RatkinWarriorEntityOptions = {
13
14
 
@@ -49,6 +50,7 @@ export default class RatkinWarriorEntity extends BaseCombatEntity {
49
50
  { itemClass: RatkinToothItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
50
51
  { itemClass: RatkinTailItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
51
52
  { itemClass: GoldItem, minQuantity: 6, maxQuantity: 15, weight: 2 },
53
+ { itemClass: RawHideItem, minQuantity: 1, maxQuantity: 2, weight: 1 },
52
54
  { itemClass: CommonMushroomItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
53
55
  { itemClass: CommonSeedsItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
54
56
  ],
@@ -8,6 +8,7 @@ import RatkinBonesItem from "../../items/materials/RatkinBonesItem";
8
8
  import RatkinEyesItem from "../../items/materials/RatkinEyesItem.ts";
9
9
  import RatkinTailItem from "../../items/materials/RatkinTailItem";
10
10
  import RatkinToothItem from "../../items/materials/RatkinToothItem.ts";
11
+ import RawHideItem from "../../items/materials/RawHideItem.ts";
11
12
 
12
13
  export type TaintedRatkinBruteEntityOptions = {
13
14
 
@@ -59,6 +60,7 @@ export default class TaintedRatkinBruteEntity extends BaseCombatEntity {
59
60
  { itemClass: RatkinToothItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
60
61
  { itemClass: RatkinTailItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
61
62
  { itemClass: GoldItem, minQuantity: 11, maxQuantity: 20, weight: 2 },
63
+ { itemClass: RawHideItem, minQuantity: 2, maxQuantity: 3, weight: 1 },
62
64
  { itemClass: CommonMushroomItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
63
65
  { itemClass: CommonSeedsItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
64
66
  ],
@@ -10,7 +10,7 @@ import RatkinBonesItem from "../../items/materials/RatkinBonesItem";
10
10
  import RatkinEyesItem from "../../items/materials/RatkinEyesItem.ts";
11
11
  import RatkinTailItem from "../../items/materials/RatkinTailItem";
12
12
  import RatkinToothItem from "../../items/materials/RatkinToothItem.ts";
13
-
13
+ import RawHideItem from "../../items/materials/RawHideItem.ts";
14
14
 
15
15
  export type TaintedRatkinRangerEntityOptions = {
16
16
 
@@ -52,6 +52,7 @@ export default class TaintedRatkinRangerEntity extends BaseCombatEntity {
52
52
  { itemClass: RatkinToothItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
53
53
  { itemClass: RatkinTailItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
54
54
  { itemClass: GoldItem, minQuantity: 9, maxQuantity: 17, weight: 2 },
55
+ { itemClass: RawHideItem, minQuantity: 2, maxQuantity: 3, weight: 1 },
55
56
  { itemClass: CommonMushroomItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
56
57
  { itemClass: CommonSeedsItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
57
58
  ],
@@ -10,6 +10,7 @@ import RatkinBonesItem from "../../items/materials/RatkinBonesItem";
10
10
  import RatkinEyesItem from "../../items/materials/RatkinEyesItem.ts";
11
11
  import RatkinTailItem from "../../items/materials/RatkinTailItem";
12
12
  import RatkinToothItem from "../../items/materials/RatkinToothItem.ts";
13
+ import RawHideItem from "../../items/materials/RawHideItem.ts";
13
14
 
14
15
  export type TaintedRatkinSpellcasterEntityOptions = {
15
16
 
@@ -41,6 +42,7 @@ export default class TaintedRatkinSpellcasterEntity extends BaseCombatEntity {
41
42
  { itemClass: RatkinToothItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
42
43
  { itemClass: RatkinTailItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
43
44
  { itemClass: GoldItem, minQuantity: 9, maxQuantity: 17, weight: 2 },
45
+ { itemClass: RawHideItem, minQuantity: 2, maxQuantity: 3, weight: 1 },
44
46
  { itemClass: CommonMushroomItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
45
47
  { itemClass: CommonSeedsItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
46
48
  ],
@@ -8,6 +8,7 @@ import RatkinBonesItem from "../../items/materials/RatkinBonesItem";
8
8
  import RatkinEyesItem from "../../items/materials/RatkinEyesItem.ts";
9
9
  import RatkinTailItem from "../../items/materials/RatkinTailItem";
10
10
  import RatkinToothItem from "../../items/materials/RatkinToothItem.ts";
11
+ import RawHideItem from "../../items/materials/RawHideItem.ts";
11
12
 
12
13
  export type TaintedRatkinWarriorEntityOptions = {
13
14
 
@@ -49,6 +50,7 @@ export default class TaintedRatkinWarriorEntity extends BaseCombatEntity {
49
50
  { itemClass: RatkinToothItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
50
51
  { itemClass: RatkinTailItem, minQuantity: 1, maxQuantity: 3, weight: 4 },
51
52
  { itemClass: GoldItem, minQuantity: 9, maxQuantity: 17, weight: 2 },
53
+ { itemClass: RawHideItem, minQuantity: 2, maxQuantity: 3, weight: 1 },
52
54
  { itemClass: CommonMushroomItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
53
55
  { itemClass: CommonSeedsItem, minQuantity: 1, maxQuantity: 3, weight: 1 },
54
56
  ],
@@ -5,7 +5,7 @@ import CommonMushroomItem from '../../items/consumables/CommonMushroomItem';
5
5
  import CommonSeedsItem from '../../items/seeds/CommonSeedsItem';
6
6
  import GoldItem from '../../items/general/GoldItem';
7
7
  import MinorHealingPotionItem from '../../items/consumables/MinorHealingPotionItem';
8
- import MonsterHideItem from '../../items/materials/MonsterHideItem';
8
+ import RawHideItem from '../../items/materials/RawHideItem';
9
9
  import StonebellyFungusMushroomItem from '../../items/consumables/StonebellyFungusMushroomItem';
10
10
  import UnusualSeedsItem from '../../items/seeds/UnusualSeedsItem';
11
11
 
@@ -22,7 +22,7 @@ export default class DecayingPileEntity extends BaseForageableEntity {
22
22
  { itemClass: CommonSeedsItem, weight: 35 },
23
23
  { itemClass: GoldItem, weight: 15, minQuantity: 9, maxQuantity: 20 },
24
24
  { itemClass: MinorHealingPotionItem, weight: 25 },
25
- { itemClass: MonsterHideItem, weight: 12 },
25
+ { itemClass: RawHideItem, weight: 12 },
26
26
  { itemClass: StonebellyFungusMushroomItem, weight: 5, minQuantity: 1, maxQuantity: 3 },
27
27
  { itemClass: UnusualSeedsItem, weight: 3 },
28
28
  ],
@@ -6,7 +6,7 @@ import CommonSeedsItem from '../../items/seeds/CommonSeedsItem';
6
6
  import EmbercapMushroomItem from '../../items/consumables/EmbercapMushroomItem';
7
7
  import GoldItem from '../../items/general/GoldItem';
8
8
  import MinorHealingPotionItem from '../../items/consumables/MinorHealingPotionItem';
9
- import MonsterHideItem from '../../items/materials/MonsterHideItem';
9
+ import RawHideItem from '../../items/materials/RawHideItem';
10
10
  import UnusualSeedsItem from '../../items/seeds/UnusualSeedsItem';
11
11
 
12
12
  export type RottenLogEntityOptions = {
@@ -22,7 +22,7 @@ export default class RottenLogEntity extends BaseForageableEntity {
22
22
  { itemClass: CommonSeedsItem, weight: 25 },
23
23
  { itemClass: MinorHealingPotionItem, weight: 25 },
24
24
  { itemClass: GoldItem, weight: 15, minQuantity: 6, maxQuantity: 17 },
25
- { itemClass: MonsterHideItem, weight: 10 },
25
+ { itemClass: RawHideItem, weight: 10 },
26
26
  { itemClass: EmbercapMushroomItem, weight: 5, minQuantity: 1, maxQuantity: 3 },
27
27
  { itemClass: UnusualSeedsItem, weight: 1 },
28
28
  ],
@@ -13,7 +13,7 @@ import GoldItem from './general/GoldItem';
13
13
 
14
14
  // Materials
15
15
  import BlightedRootItem from './materials/BlightedRootItem';
16
- import MonsterHideItem from './materials/MonsterHideItem';
16
+ import RawHideItem from './materials/RawHideItem';
17
17
  import RatkinBonesItem from './materials/RatkinBonesItem';
18
18
  import RatkinEyesItem from './materials/RatkinEyesItem';
19
19
  import RatkinTailItem from './materials/RatkinTailItem';
@@ -28,6 +28,7 @@ import UnusualSeedsItem from './seeds/UnusualSeedsItem';
28
28
  import DullSwordItem from './weapons/DullSwordItem';
29
29
  import IronDaggerItem from './weapons/IronDaggerItem';
30
30
  import IronLongSwordItem from './weapons/IronLongSwordItem';
31
+ import SpikedClubItem from './weapons/SpikedClubItem';
31
32
  import TrainingSwordItem from './weapons/TrainingSwordItem';
32
33
 
33
34
  // Wearables
@@ -36,6 +37,11 @@ import AdventurerGlovesItem from './wearables/AdventurerGlovesItem';
36
37
  import AdventurerHoodItem from './wearables/AdventurerHoodItem';
37
38
  import AdventurerLeggingsItem from './wearables/AdventurerLeggingsItem';
38
39
  import AdventurerTunicItem from './wearables/AdventurerTunicItem';
40
+ import LeatherBootsItem from './wearables/LeatherBootsItem';
41
+ import LeatherBracersItem from './wearables/LeatherBracersItem';
42
+ import LeatherHelmetItem from './wearables/LeatherHelmetItem';
43
+ import LeatherLeggingsItem from './wearables/LeatherLeggingsItem';
44
+ import LeatherVestItem from './wearables/LeatherVestItem';
39
45
 
40
46
  export default [
41
47
  // Consumables
@@ -53,7 +59,7 @@ export default [
53
59
 
54
60
  // Materials
55
61
  BlightedRootItem,
56
- MonsterHideItem,
62
+ RawHideItem,
57
63
  RatkinBonesItem,
58
64
  RatkinEyesItem,
59
65
  RatkinTailItem,
@@ -68,6 +74,7 @@ export default [
68
74
  DullSwordItem,
69
75
  IronDaggerItem,
70
76
  IronLongSwordItem,
77
+ SpikedClubItem,
71
78
  TrainingSwordItem,
72
79
 
73
80
  // Wearables
@@ -76,4 +83,9 @@ export default [
76
83
  AdventurerHoodItem,
77
84
  AdventurerLeggingsItem,
78
85
  AdventurerTunicItem,
86
+ LeatherBootsItem,
87
+ LeatherBracersItem,
88
+ LeatherHelmetItem,
89
+ LeatherLeggingsItem,
90
+ LeatherVestItem,
79
91
  ];
@@ -0,0 +1,10 @@
1
+ import BaseItem from '../BaseItem';
2
+
3
+ export default class RawHideItem extends BaseItem {
4
+ static readonly id = 'raw_hide';
5
+ static readonly name = 'Raw Hide';
6
+ static readonly iconImageUri = 'icons/items/raw-hide.png';
7
+ static readonly description = 'Thick hide from a Frontier creature. Tough and weathered.';
8
+ static readonly stackable = true;
9
+ static readonly sellPrice = 15;
10
+ }
@@ -25,7 +25,7 @@ export default class DullSwordItem extends BaseWeaponItem {
25
25
  damageDelayMs: 200,
26
26
  damageVariance: 0.2,
27
27
  knockbackForce: 7,
28
- reach: 3,
28
+ reach: 1.5,
29
29
  };
30
30
  static readonly description = 'A dull metal sword. Better than nothing.';
31
31
  static readonly heldModelUri = 'models/items/sword-stone.gltf';
@@ -52,13 +52,14 @@ export default class DullSwordItem extends BaseWeaponItem {
52
52
  );
53
53
 
54
54
  for (const target of targets) {
55
+ const targetDirection = target.directionFromRotation;
55
56
  this.dealDamage(
56
57
  target,
57
58
  this.calculateDamageWithVariance(attack.damage, attack.damageVariance),
58
59
  {
59
- x: -target.directionFromRotation.x,
60
- y: target.directionFromRotation.y,
61
- z: -target.directionFromRotation.z,
60
+ x: -targetDirection.x,
61
+ y: targetDirection.y,
62
+ z: -targetDirection.z,
62
63
  },
63
64
  attack.knockbackForce
64
65
  );
@@ -13,7 +13,7 @@ export default class IronDaggerItem extends BaseWeaponItem {
13
13
  damageDelayMs: 50,
14
14
  damageVariance: 0.4,
15
15
  knockbackForce: 3,
16
- reach: 1.5,
16
+ reach: 1.25,
17
17
  };
18
18
 
19
19
  static readonly description = `A sharp iron dagger. It doesn't have much reach, but it's good for quick strikes.`;
@@ -25,7 +25,7 @@ export default class IronLongSwordItem extends BaseWeaponItem {
25
25
  damageDelayMs: 200,
26
26
  damageVariance: 0.2,
27
27
  knockbackForce: 7,
28
- reach: 3.5,
28
+ reach: 2,
29
29
  };
30
30
  static readonly description = `A long iron sword. It's heavy and slow, but hits harder than a normal sword.`;
31
31
  static readonly heldModelUri = 'models/weapons/iron-long-sword.gltf';
@@ -52,13 +52,14 @@ export default class IronLongSwordItem extends BaseWeaponItem {
52
52
  );
53
53
 
54
54
  for (const target of targets) {
55
+ const targetDirection = target.directionFromRotation;
55
56
  this.dealDamage(
56
57
  target,
57
58
  this.calculateDamageWithVariance(attack.damage, attack.damageVariance),
58
59
  {
59
- x: -target.directionFromRotation.x,
60
- y: target.directionFromRotation.y,
61
- z: -target.directionFromRotation.z,
60
+ x: -targetDirection.x,
61
+ y: targetDirection.y,
62
+ z: -targetDirection.z,
62
63
  },
63
64
  attack.knockbackForce
64
65
  );
@@ -0,0 +1,26 @@
1
+ import { Quaternion } from 'hytopia';
2
+ import BaseWeaponItem, { BaseWeaponItemAttack } from '../BaseWeaponItem';
3
+
4
+ export default class SpikedClubItem extends BaseWeaponItem {
5
+ // Required static properties
6
+ static readonly id = 'spiked_club';
7
+ static readonly name = 'Spiked Club';
8
+ static readonly iconImageUri = 'icons/items/spiked-club.png';
9
+ static readonly attack: BaseWeaponItemAttack = {
10
+ animations: ['sword-attack-upper'],
11
+ cooldownMs: 1500,
12
+ damage: 34,
13
+ damageDelayMs: 200,
14
+ damageVariance: 0.2,
15
+ knockbackForce: 8,
16
+ reach: 1.75,
17
+ };
18
+
19
+ static readonly description = `A spiked club. It's bulky and slow to swing but packs a punch.`;
20
+ static readonly heldModelUri = 'models/weapons/spiked-club.gltf';
21
+ static readonly heldModelScale = 0.6;
22
+ static readonly defaultRelativeRotationAsChild = Quaternion.fromEuler(-90, 0, 90);
23
+ static readonly defaultRelativePositionAsChild = { x: 0.3, y: -0.2, z: -0.17 };
24
+ static readonly buyPrice = 750;
25
+ static readonly sellPrice = 75;
26
+ }
@@ -25,7 +25,7 @@ export default class TrainingSwordItem extends BaseWeaponItem {
25
25
  damageDelayMs: 200,
26
26
  damageVariance: 0.2,
27
27
  knockbackForce: 7,
28
- reach: 3,
28
+ reach: 1.5,
29
29
  };
30
30
  static readonly description = 'A training sword made from wood. Useless for combat.';
31
31
  static readonly heldModelUri = 'models/items/sword-wooden.gltf';
@@ -52,13 +52,14 @@ export default class TrainingSwordItem extends BaseWeaponItem {
52
52
  );
53
53
 
54
54
  for (const target of targets) {
55
+ const targetDirection = target.directionFromRotation;
55
56
  this.dealDamage(
56
57
  target,
57
58
  this.calculateDamageWithVariance(attack.damage, attack.damageVariance),
58
59
  {
59
- x: -target.directionFromRotation.x,
60
- y: target.directionFromRotation.y,
61
- z: -target.directionFromRotation.z,
60
+ x: -targetDirection.x,
61
+ y: targetDirection.y,
62
+ z: -targetDirection.z,
62
63
  },
63
64
  attack.knockbackForce
64
65
  );
@@ -0,0 +1,14 @@
1
+ import BaseWearableItem, { WearableSlot } from '../BaseWearableItem';
2
+
3
+ export default class LeatherBootsItem extends BaseWearableItem {
4
+ static readonly id = 'leather_boots';
5
+ static readonly name = 'Leather Boots';
6
+ static readonly iconImageUri = 'icons/items/leather-boots.png';
7
+ static readonly description = `A sturdy pair of boots made from thick leather.`;
8
+ static readonly buyPrice = 350;
9
+ static readonly sellPrice = 35;
10
+
11
+ static readonly damageReduction = 2;
12
+
13
+ static readonly slot: WearableSlot = 'boots';
14
+ }