@musashishao/agent-kit 1.9.0 → 1.9.1
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.
- package/.agent/agents/ai-asset-factory.md +700 -0
- package/.agent/agents/ai-audio-factory.md +503 -0
- package/.agent/agents/game-developer.md +4 -4
- package/.agent/agents/orchestrator.md +113 -3
- package/.agent/agents/project-planner.md +67 -0
- package/.agent/agents/unity-mobile-master.md +949 -0
- package/.agent/mcp/config/registry.json +65 -51
- package/.agent/mcp/servers/notebooklm/README.md +114 -0
- package/.agent/mcp/servers/notebooklm/package.json +35 -0
- package/.agent/mcp/servers/notebooklm/src/auth/chrome.ts +225 -0
- package/.agent/mcp/servers/notebooklm/src/auth/index.ts +1 -0
- package/.agent/mcp/servers/notebooklm/src/index.ts +516 -0
- package/.agent/mcp/servers/notebooklm/src/services/index.ts +3 -0
- package/.agent/mcp/servers/notebooklm/src/services/library.ts +217 -0
- package/.agent/mcp/servers/notebooklm/src/services/notebooklm.ts +380 -0
- package/.agent/mcp/servers/notebooklm/tsconfig.json +15 -0
- package/.agent/mcp-gateway/README.md +169 -20
- package/.agent/mcp-gateway/package.json +22 -7
- package/.agent/mcp-gateway/src/auth/index.ts +55 -0
- package/.agent/mcp-gateway/src/auth/middleware.ts +242 -0
- package/.agent/mcp-gateway/src/auth/oauth.ts +462 -0
- package/.agent/mcp-gateway/src/auth/scopes.ts +227 -0
- package/.agent/mcp-gateway/src/index.ts +252 -105
- package/.agent/mcp-gateway/src/observability/index.ts +5 -0
- package/.agent/mcp-gateway/src/observability/otel.ts +405 -0
- package/.agent/mcp-gateway/src/transports/index.ts +5 -0
- package/.agent/mcp-gateway/src/transports/streamableHttp.ts +235 -0
- package/.agent/rules/CODEX.md +89 -0
- package/.agent/rules/CODE_RULES.md +73 -0
- package/.agent/rules/GEMINI.md +25 -0
- package/.agent/rules/MEMORY_STATE.md +110 -0
- package/.agent/rules/REFERENCE.md +33 -141
- package/.agent/rules/REF_SKILLS.md +116 -0
- package/.agent/rules/REF_WORKFLOWS.md +81 -0
- package/.agent/scripts/ak_cli.py +106 -5
- package/.agent/scripts/memory_manager.py +48 -9
- package/.agent/skills/anti-hallucination/SKILL.md +295 -0
- package/.agent/skills/anti-hallucination/scripts/check_hallucination.py +299 -0
- package/.agent/skills/bifurcation-analysis/SKILL.md +56 -0
- package/.agent/skills/brainstorming/SKILL.md +80 -6
- package/.agent/skills/decision-memory/SKILL.md +317 -0
- package/.agent/skills/emergence-detector/SKILL.md +230 -0
- package/.agent/skills/emergence-detector/scripts/check_emergence.py +265 -0
- package/.agent/skills/explained-qa/SKILL.md +142 -0
- package/.agent/skills/explained-qa/game-terminology.md +214 -0
- package/.agent/skills/game-development/ai-dialogue-engine/SKILL.md +442 -0
- package/.agent/skills/game-development/ai-graphics-generator/SKILL.md +463 -0
- package/.agent/skills/game-development/ai-playtest-framework/SKILL.md +570 -0
- package/.agent/skills/game-development/camera-systems/SKILL.md +607 -0
- package/.agent/skills/game-development/card-battle-engine/SKILL.md +618 -0
- package/.agent/skills/game-development/character-controller-3d/SKILL.md +908 -0
- package/.agent/skills/game-development/cloud-save-sync/SKILL.md +527 -0
- package/.agent/skills/game-development/combat-system/SKILL.md +748 -0
- package/.agent/skills/game-development/compliance-rating/SKILL.md +277 -0
- package/.agent/skills/game-development/crossplatform-build/SKILL.md +386 -0
- package/.agent/skills/game-development/cultivation-progression/SKILL.md +520 -0
- package/.agent/skills/game-development/data-driven-balance/SKILL.md +535 -0
- package/.agent/skills/game-development/game-analytics-integrator/SKILL.md +410 -0
- package/.agent/skills/game-development/game-audio-advanced/SKILL.md +646 -0
- package/.agent/skills/game-development/game-economy-designer/SKILL.md +375 -0
- package/.agent/skills/game-development/game-marketing/SKILL.md +85 -0
- package/.agent/skills/game-development/game-state-manager/SKILL.md +883 -0
- package/.agent/skills/game-development/hybrid-game-spec/SKILL.md +220 -0
- package/.agent/skills/game-development/inventory-quest/SKILL.md +747 -0
- package/.agent/skills/game-development/liveops/SKILL.md +308 -0
- package/.agent/skills/game-development/localization/SKILL.md +286 -0
- package/.agent/skills/game-development/mobile-input-patterns/SKILL.md +343 -0
- package/.agent/skills/game-development/monetization-strategy/SKILL.md +94 -0
- package/.agent/skills/game-development/multiplayer-master/SKILL.md +727 -0
- package/.agent/skills/game-development/narrative-branching/SKILL.md +593 -0
- package/.agent/skills/game-development/procedural-level-ai/SKILL.md +367 -0
- package/.agent/skills/game-development/prototyping-rapid/SKILL.md +205 -0
- package/.agent/skills/game-development/spec-ecosystem/SKILL.md +155 -0
- package/.agent/skills/game-development/spec-ecosystem/decision-log-format.md +129 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/PLAN-template.md +178 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/SPEC-template.md +110 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/TASKS-template.md +156 -0
- package/.agent/skills/game-development/survival-systems/SKILL.md +493 -0
- package/.agent/skills/game-development/testing-qa/SKILL.md +270 -0
- package/.agent/skills/game-development/unity-mobile-optimization/SKILL.md +271 -0
- package/.agent/skills/intent-capture/SKILL.md +65 -0
- package/.agent/skills/mcp-composition/SKILL.md +362 -0
- package/.agent/skills/mcp-observability/SKILL.md +323 -0
- package/.agent/skills/mcp-security/SKILL.md +314 -0
- package/.agent/skills/trust-spectrum/SKILL.md +291 -0
- package/.agent/skills/vibe-coding-guard/SKILL.md +328 -0
- package/.agent/templates/AGENTS.game.md +63 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.en.md +100 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.vi.md +100 -0
- package/.agent/workflows/ai-agent.md +2 -0
- package/.agent/workflows/autofix.md +1 -0
- package/.agent/workflows/brainstorm.md +1 -0
- package/.agent/workflows/context.md +1 -0
- package/.agent/workflows/create.md +39 -8
- package/.agent/workflows/dashboard.md +1 -0
- package/.agent/workflows/debug.md +14 -0
- package/.agent/workflows/deploy.md +14 -0
- package/.agent/workflows/enhance.md +44 -0
- package/.agent/workflows/gamekit-init.md +177 -0
- package/.agent/workflows/gamekit-launch.md +338 -0
- package/.agent/workflows/gamekit-plan.md +204 -0
- package/.agent/workflows/gamekit-qa.md +153 -0
- package/.agent/workflows/gamekit-spec.md +243 -0
- package/.agent/workflows/gamekit-tasks.md +208 -0
- package/.agent/workflows/marketing.md +2 -0
- package/.agent/workflows/next.md +1 -0
- package/.agent/workflows/orchestrate.md +12 -0
- package/.agent/workflows/pentest.md +2 -0
- package/.agent/workflows/plan.md +42 -0
- package/.agent/workflows/preview.md +1 -0
- package/.agent/workflows/quality.md +1 -0
- package/.agent/workflows/saas.md +2 -0
- package/.agent/workflows/spec.md +42 -0
- package/.agent/workflows/status.md +1 -0
- package/.agent/workflows/test.md +14 -0
- package/.agent/workflows/ui-ux-pro-max.md +1 -0
- package/bin/cli.js +411 -111
- package/package.json +1 -2
- package/.agent/agents/game-asset-curator.md +0 -317
- package/.agent/agents/game-narrative-designer.md +0 -310
- package/.agent/agents/game-qa-agent.md +0 -441
- package/.agent/workflows/game-prototype.md +0 -154
- package/docs/AI_DATA_INFRASTRUCTURE.md +0 -288
- package/docs/CHANGELOG_AI_INFRA.md +0 -141
- package/docs/MIGRATION_GUIDE_V1.9.md +0 -55
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: inventory-quest
|
|
3
|
+
description: RPG systems. Inventory management, equipment, quest systems, crafting, loot tables, and dialogue integration.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, WebSearch
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Inventory & Quest Systems Skill
|
|
8
|
+
|
|
9
|
+
> Build complete RPG progression systems.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- Implementing inventory systems
|
|
16
|
+
- Building equipment/gear systems
|
|
17
|
+
- Creating quest trackers
|
|
18
|
+
- Designing crafting systems
|
|
19
|
+
- Setting up loot tables
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Inventory System
|
|
24
|
+
|
|
25
|
+
### Item Definition
|
|
26
|
+
|
|
27
|
+
```csharp
|
|
28
|
+
[CreateAssetMenu(fileName = "New Item", menuName = "RPG/Item")]
|
|
29
|
+
public class ItemData : ScriptableObject
|
|
30
|
+
{
|
|
31
|
+
[Header("Basic Info")]
|
|
32
|
+
public string Id;
|
|
33
|
+
public string DisplayName;
|
|
34
|
+
[TextArea] public string Description;
|
|
35
|
+
public Sprite Icon;
|
|
36
|
+
public ItemRarity Rarity;
|
|
37
|
+
public ItemType Type;
|
|
38
|
+
|
|
39
|
+
[Header("Stacking")]
|
|
40
|
+
public bool IsStackable = true;
|
|
41
|
+
public int MaxStackSize = 99;
|
|
42
|
+
|
|
43
|
+
[Header("Value")]
|
|
44
|
+
public int BuyPrice;
|
|
45
|
+
public int SellPrice;
|
|
46
|
+
|
|
47
|
+
[Header("Usage")]
|
|
48
|
+
public bool IsUsable;
|
|
49
|
+
public bool IsEquippable;
|
|
50
|
+
public bool IsDroppable = true;
|
|
51
|
+
|
|
52
|
+
public virtual void Use(Character character) { }
|
|
53
|
+
public virtual void Equip(Character character) { }
|
|
54
|
+
public virtual void Unequip(Character character) { }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public enum ItemRarity
|
|
58
|
+
{
|
|
59
|
+
Common,
|
|
60
|
+
Uncommon,
|
|
61
|
+
Rare,
|
|
62
|
+
Epic,
|
|
63
|
+
Legendary
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public enum ItemType
|
|
67
|
+
{
|
|
68
|
+
Consumable,
|
|
69
|
+
Equipment,
|
|
70
|
+
Material,
|
|
71
|
+
Quest,
|
|
72
|
+
Currency,
|
|
73
|
+
Key
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Specific item types
|
|
77
|
+
[CreateAssetMenu(fileName = "New Weapon", menuName = "RPG/Items/Weapon")]
|
|
78
|
+
public class WeaponData : ItemData
|
|
79
|
+
{
|
|
80
|
+
public int BaseDamage;
|
|
81
|
+
public float AttackSpeed;
|
|
82
|
+
public DamageType DamageType;
|
|
83
|
+
public WeaponType WeaponType;
|
|
84
|
+
|
|
85
|
+
public override void Equip(Character character)
|
|
86
|
+
{
|
|
87
|
+
character.Stats.AddModifier(StatType.Attack, BaseDamage);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public override void Unequip(Character character)
|
|
91
|
+
{
|
|
92
|
+
character.Stats.RemoveModifier(StatType.Attack, BaseDamage);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[CreateAssetMenu(fileName = "New Consumable", menuName = "RPG/Items/Consumable")]
|
|
97
|
+
public class ConsumableData : ItemData
|
|
98
|
+
{
|
|
99
|
+
public ConsumableType ConsumableType;
|
|
100
|
+
public float Value;
|
|
101
|
+
public float Duration;
|
|
102
|
+
public StatusEffectType EffectType;
|
|
103
|
+
|
|
104
|
+
public override void Use(Character character)
|
|
105
|
+
{
|
|
106
|
+
switch (ConsumableType)
|
|
107
|
+
{
|
|
108
|
+
case ConsumableType.HealthPotion:
|
|
109
|
+
character.Health.Heal(Value);
|
|
110
|
+
break;
|
|
111
|
+
case ConsumableType.ManaPotion:
|
|
112
|
+
character.Mana.Restore(Value);
|
|
113
|
+
break;
|
|
114
|
+
case ConsumableType.Buff:
|
|
115
|
+
character.StatusEffects.ApplyEffect(EffectType, Duration, Value);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Inventory Manager
|
|
123
|
+
|
|
124
|
+
```csharp
|
|
125
|
+
public class Inventory : MonoBehaviour
|
|
126
|
+
{
|
|
127
|
+
[SerializeField] private int maxSlots = 40;
|
|
128
|
+
|
|
129
|
+
private List<InventorySlot> _slots = new();
|
|
130
|
+
|
|
131
|
+
public event Action<InventorySlot> OnSlotChanged;
|
|
132
|
+
public event Action OnInventoryChanged;
|
|
133
|
+
|
|
134
|
+
public IReadOnlyList<InventorySlot> Slots => _slots;
|
|
135
|
+
public int MaxSlots => maxSlots;
|
|
136
|
+
public int UsedSlots => _slots.Count(s => !s.IsEmpty);
|
|
137
|
+
|
|
138
|
+
void Awake()
|
|
139
|
+
{
|
|
140
|
+
// Initialize empty slots
|
|
141
|
+
for (int i = 0; i < maxSlots; i++)
|
|
142
|
+
{
|
|
143
|
+
_slots.Add(new InventorySlot(i));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public bool AddItem(ItemData item, int quantity = 1)
|
|
148
|
+
{
|
|
149
|
+
if (item == null || quantity <= 0) return false;
|
|
150
|
+
|
|
151
|
+
int remaining = quantity;
|
|
152
|
+
|
|
153
|
+
// Try to stack with existing items first
|
|
154
|
+
if (item.IsStackable)
|
|
155
|
+
{
|
|
156
|
+
foreach (var slot in _slots.Where(s => s.Item == item && s.Quantity < item.MaxStackSize))
|
|
157
|
+
{
|
|
158
|
+
int canAdd = Mathf.Min(remaining, item.MaxStackSize - slot.Quantity);
|
|
159
|
+
slot.Quantity += canAdd;
|
|
160
|
+
remaining -= canAdd;
|
|
161
|
+
|
|
162
|
+
OnSlotChanged?.Invoke(slot);
|
|
163
|
+
|
|
164
|
+
if (remaining <= 0) break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Add to empty slots
|
|
169
|
+
while (remaining > 0)
|
|
170
|
+
{
|
|
171
|
+
var emptySlot = _slots.FirstOrDefault(s => s.IsEmpty);
|
|
172
|
+
if (emptySlot == null)
|
|
173
|
+
{
|
|
174
|
+
Debug.Log("Inventory full!");
|
|
175
|
+
return remaining < quantity; // Partial success
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
int toAdd = item.IsStackable ? Mathf.Min(remaining, item.MaxStackSize) : 1;
|
|
179
|
+
emptySlot.SetItem(item, toAdd);
|
|
180
|
+
remaining -= toAdd;
|
|
181
|
+
|
|
182
|
+
OnSlotChanged?.Invoke(emptySlot);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
OnInventoryChanged?.Invoke();
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public bool RemoveItem(ItemData item, int quantity = 1)
|
|
190
|
+
{
|
|
191
|
+
int remaining = quantity;
|
|
192
|
+
|
|
193
|
+
foreach (var slot in _slots.Where(s => s.Item == item).OrderBy(s => s.Quantity))
|
|
194
|
+
{
|
|
195
|
+
int toRemove = Mathf.Min(remaining, slot.Quantity);
|
|
196
|
+
slot.Quantity -= toRemove;
|
|
197
|
+
remaining -= toRemove;
|
|
198
|
+
|
|
199
|
+
if (slot.Quantity <= 0)
|
|
200
|
+
{
|
|
201
|
+
slot.Clear();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
OnSlotChanged?.Invoke(slot);
|
|
205
|
+
|
|
206
|
+
if (remaining <= 0) break;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
OnInventoryChanged?.Invoke();
|
|
210
|
+
return remaining <= 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public int GetItemCount(ItemData item)
|
|
214
|
+
{
|
|
215
|
+
return _slots.Where(s => s.Item == item).Sum(s => s.Quantity);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public bool HasItem(ItemData item, int quantity = 1)
|
|
219
|
+
{
|
|
220
|
+
return GetItemCount(item) >= quantity;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public void SwapSlots(int indexA, int indexB)
|
|
224
|
+
{
|
|
225
|
+
if (indexA < 0 || indexA >= maxSlots || indexB < 0 || indexB >= maxSlots) return;
|
|
226
|
+
|
|
227
|
+
var slotA = _slots[indexA];
|
|
228
|
+
var slotB = _slots[indexB];
|
|
229
|
+
|
|
230
|
+
// Can we stack?
|
|
231
|
+
if (slotA.Item == slotB.Item && slotA.Item != null && slotA.Item.IsStackable)
|
|
232
|
+
{
|
|
233
|
+
int total = slotA.Quantity + slotB.Quantity;
|
|
234
|
+
if (total <= slotA.Item.MaxStackSize)
|
|
235
|
+
{
|
|
236
|
+
slotB.Quantity = total;
|
|
237
|
+
slotA.Clear();
|
|
238
|
+
}
|
|
239
|
+
else
|
|
240
|
+
{
|
|
241
|
+
slotB.Quantity = slotB.Item.MaxStackSize;
|
|
242
|
+
slotA.Quantity = total - slotB.Item.MaxStackSize;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else
|
|
246
|
+
{
|
|
247
|
+
// Swap
|
|
248
|
+
var tempItem = slotA.Item;
|
|
249
|
+
var tempQty = slotA.Quantity;
|
|
250
|
+
|
|
251
|
+
slotA.SetItem(slotB.Item, slotB.Quantity);
|
|
252
|
+
slotB.SetItem(tempItem, tempQty);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
OnSlotChanged?.Invoke(slotA);
|
|
256
|
+
OnSlotChanged?.Invoke(slotB);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public List<string> GetItemIds()
|
|
260
|
+
{
|
|
261
|
+
return _slots
|
|
262
|
+
.Where(s => !s.IsEmpty)
|
|
263
|
+
.SelectMany(s => Enumerable.Repeat(s.Item.Id, s.Quantity))
|
|
264
|
+
.ToList();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
public void LoadItems(List<string> itemIds)
|
|
268
|
+
{
|
|
269
|
+
// Clear first
|
|
270
|
+
foreach (var slot in _slots) slot.Clear();
|
|
271
|
+
|
|
272
|
+
foreach (var id in itemIds)
|
|
273
|
+
{
|
|
274
|
+
var item = ItemDatabase.GetItem(id);
|
|
275
|
+
if (item != null)
|
|
276
|
+
{
|
|
277
|
+
AddItem(item, 1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
[System.Serializable]
|
|
284
|
+
public class InventorySlot
|
|
285
|
+
{
|
|
286
|
+
public int Index { get; private set; }
|
|
287
|
+
public ItemData Item { get; private set; }
|
|
288
|
+
public int Quantity { get; set; }
|
|
289
|
+
|
|
290
|
+
public bool IsEmpty => Item == null || Quantity <= 0;
|
|
291
|
+
|
|
292
|
+
public InventorySlot(int index)
|
|
293
|
+
{
|
|
294
|
+
Index = index;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public void SetItem(ItemData item, int quantity)
|
|
298
|
+
{
|
|
299
|
+
Item = item;
|
|
300
|
+
Quantity = quantity;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
public void Clear()
|
|
304
|
+
{
|
|
305
|
+
Item = null;
|
|
306
|
+
Quantity = 0;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Equipment System
|
|
314
|
+
|
|
315
|
+
```csharp
|
|
316
|
+
public class EquipmentManager : MonoBehaviour
|
|
317
|
+
{
|
|
318
|
+
[SerializeField] private EquipmentSlotType[] slotTypes;
|
|
319
|
+
|
|
320
|
+
private Dictionary<EquipmentSlotType, ItemData> _equipped = new();
|
|
321
|
+
|
|
322
|
+
public event Action<EquipmentSlotType, ItemData> OnEquipmentChanged;
|
|
323
|
+
|
|
324
|
+
private Character _character;
|
|
325
|
+
private Inventory _inventory;
|
|
326
|
+
|
|
327
|
+
void Awake()
|
|
328
|
+
{
|
|
329
|
+
_character = GetComponent<Character>();
|
|
330
|
+
_inventory = GetComponent<Inventory>();
|
|
331
|
+
|
|
332
|
+
// Initialize slots
|
|
333
|
+
foreach (var type in slotTypes)
|
|
334
|
+
{
|
|
335
|
+
_equipped[type] = null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public bool Equip(ItemData item, EquipmentSlotType slot)
|
|
340
|
+
{
|
|
341
|
+
if (item == null || !item.IsEquippable) return false;
|
|
342
|
+
|
|
343
|
+
// Unequip current
|
|
344
|
+
if (_equipped[slot] != null)
|
|
345
|
+
{
|
|
346
|
+
Unequip(slot);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Remove from inventory
|
|
350
|
+
_inventory.RemoveItem(item, 1);
|
|
351
|
+
|
|
352
|
+
// Equip
|
|
353
|
+
_equipped[slot] = item;
|
|
354
|
+
item.Equip(_character);
|
|
355
|
+
|
|
356
|
+
OnEquipmentChanged?.Invoke(slot, item);
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public void Unequip(EquipmentSlotType slot)
|
|
361
|
+
{
|
|
362
|
+
var item = _equipped[slot];
|
|
363
|
+
if (item == null) return;
|
|
364
|
+
|
|
365
|
+
item.Unequip(_character);
|
|
366
|
+
_inventory.AddItem(item, 1);
|
|
367
|
+
_equipped[slot] = null;
|
|
368
|
+
|
|
369
|
+
OnEquipmentChanged?.Invoke(slot, null);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
public ItemData GetEquipped(EquipmentSlotType slot)
|
|
373
|
+
{
|
|
374
|
+
return _equipped.GetValueOrDefault(slot);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
public enum EquipmentSlotType
|
|
379
|
+
{
|
|
380
|
+
Head,
|
|
381
|
+
Chest,
|
|
382
|
+
Legs,
|
|
383
|
+
Feet,
|
|
384
|
+
Hands,
|
|
385
|
+
MainHand,
|
|
386
|
+
OffHand,
|
|
387
|
+
Ring1,
|
|
388
|
+
Ring2,
|
|
389
|
+
Necklace
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Quest System
|
|
396
|
+
|
|
397
|
+
### Quest Data
|
|
398
|
+
|
|
399
|
+
```csharp
|
|
400
|
+
[CreateAssetMenu(fileName = "New Quest", menuName = "RPG/Quest")]
|
|
401
|
+
public class QuestData : ScriptableObject
|
|
402
|
+
{
|
|
403
|
+
[Header("Info")]
|
|
404
|
+
public string Id;
|
|
405
|
+
public string Title;
|
|
406
|
+
[TextArea] public string Description;
|
|
407
|
+
public QuestType Type;
|
|
408
|
+
|
|
409
|
+
[Header("Requirements")]
|
|
410
|
+
public int RequiredLevel;
|
|
411
|
+
public QuestData[] Prerequisites;
|
|
412
|
+
|
|
413
|
+
[Header("Objectives")]
|
|
414
|
+
public QuestObjective[] Objectives;
|
|
415
|
+
|
|
416
|
+
[Header("Rewards")]
|
|
417
|
+
public int ExperienceReward;
|
|
418
|
+
public int CurrencyReward;
|
|
419
|
+
public ItemReward[] ItemRewards;
|
|
420
|
+
|
|
421
|
+
[Header("Dialogue")]
|
|
422
|
+
public string StartDialogueId;
|
|
423
|
+
public string CompleteDialogueId;
|
|
424
|
+
public string ProgressDialogueId;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
[System.Serializable]
|
|
428
|
+
public class QuestObjective
|
|
429
|
+
{
|
|
430
|
+
public string Description;
|
|
431
|
+
public ObjectiveType Type;
|
|
432
|
+
public string TargetId; // Enemy type, item type, location, etc.
|
|
433
|
+
public int RequiredCount;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
[System.Serializable]
|
|
437
|
+
public class ItemReward
|
|
438
|
+
{
|
|
439
|
+
public ItemData Item;
|
|
440
|
+
public int Quantity;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
public enum QuestType
|
|
444
|
+
{
|
|
445
|
+
MainStory,
|
|
446
|
+
Side,
|
|
447
|
+
Daily,
|
|
448
|
+
Repeatable
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
public enum ObjectiveType
|
|
452
|
+
{
|
|
453
|
+
Kill,
|
|
454
|
+
Collect,
|
|
455
|
+
Talk,
|
|
456
|
+
Reach,
|
|
457
|
+
Escort,
|
|
458
|
+
Defend
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Quest Manager
|
|
463
|
+
|
|
464
|
+
```csharp
|
|
465
|
+
public class QuestManager : MonoBehaviour
|
|
466
|
+
{
|
|
467
|
+
public static QuestManager Instance { get; private set; }
|
|
468
|
+
|
|
469
|
+
private Dictionary<string, QuestProgress> _activeQuests = new();
|
|
470
|
+
private HashSet<string> _completedQuests = new();
|
|
471
|
+
|
|
472
|
+
public event Action<QuestData> OnQuestStarted;
|
|
473
|
+
public event Action<QuestData, QuestObjective> OnObjectiveProgress;
|
|
474
|
+
public event Action<QuestData> OnQuestCompleted;
|
|
475
|
+
|
|
476
|
+
public IReadOnlyDictionary<string, QuestProgress> ActiveQuests => _activeQuests;
|
|
477
|
+
|
|
478
|
+
void Awake()
|
|
479
|
+
{
|
|
480
|
+
Instance = this;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
public bool CanStartQuest(QuestData quest)
|
|
484
|
+
{
|
|
485
|
+
// Already active or completed
|
|
486
|
+
if (_activeQuests.ContainsKey(quest.Id)) return false;
|
|
487
|
+
if (_completedQuests.Contains(quest.Id) && quest.Type != QuestType.Repeatable) return false;
|
|
488
|
+
|
|
489
|
+
// Level requirement
|
|
490
|
+
if (PlayerManager.Instance.Level < quest.RequiredLevel) return false;
|
|
491
|
+
|
|
492
|
+
// Prerequisites
|
|
493
|
+
foreach (var prereq in quest.Prerequisites)
|
|
494
|
+
{
|
|
495
|
+
if (!_completedQuests.Contains(prereq.Id)) return false;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
public void StartQuest(QuestData quest)
|
|
502
|
+
{
|
|
503
|
+
if (!CanStartQuest(quest)) return;
|
|
504
|
+
|
|
505
|
+
var progress = new QuestProgress(quest);
|
|
506
|
+
_activeQuests[quest.Id] = progress;
|
|
507
|
+
|
|
508
|
+
OnQuestStarted?.Invoke(quest);
|
|
509
|
+
Debug.Log($"Quest started: {quest.Title}");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
public void UpdateObjective(ObjectiveType type, string targetId, int count = 1)
|
|
513
|
+
{
|
|
514
|
+
foreach (var kvp in _activeQuests)
|
|
515
|
+
{
|
|
516
|
+
var progress = kvp.Value;
|
|
517
|
+
|
|
518
|
+
for (int i = 0; i < progress.Quest.Objectives.Length; i++)
|
|
519
|
+
{
|
|
520
|
+
var objective = progress.Quest.Objectives[i];
|
|
521
|
+
|
|
522
|
+
if (objective.Type == type && objective.TargetId == targetId)
|
|
523
|
+
{
|
|
524
|
+
progress.ObjectiveProgress[i] += count;
|
|
525
|
+
|
|
526
|
+
OnObjectiveProgress?.Invoke(progress.Quest, objective);
|
|
527
|
+
|
|
528
|
+
// Check completion
|
|
529
|
+
if (IsQuestComplete(progress))
|
|
530
|
+
{
|
|
531
|
+
CompleteQuest(progress.Quest);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
private bool IsQuestComplete(QuestProgress progress)
|
|
539
|
+
{
|
|
540
|
+
for (int i = 0; i < progress.Quest.Objectives.Length; i++)
|
|
541
|
+
{
|
|
542
|
+
if (progress.ObjectiveProgress[i] < progress.Quest.Objectives[i].RequiredCount)
|
|
543
|
+
{
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private void CompleteQuest(QuestData quest)
|
|
551
|
+
{
|
|
552
|
+
if (!_activeQuests.ContainsKey(quest.Id)) return;
|
|
553
|
+
|
|
554
|
+
_activeQuests.Remove(quest.Id);
|
|
555
|
+
_completedQuests.Add(quest.Id);
|
|
556
|
+
|
|
557
|
+
// Give rewards
|
|
558
|
+
PlayerManager.Instance.AddExperience(quest.ExperienceReward);
|
|
559
|
+
PlayerManager.Instance.AddCurrency(quest.CurrencyReward);
|
|
560
|
+
|
|
561
|
+
foreach (var reward in quest.ItemRewards)
|
|
562
|
+
{
|
|
563
|
+
PlayerManager.Instance.Inventory.AddItem(reward.Item, reward.Quantity);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
OnQuestCompleted?.Invoke(quest);
|
|
567
|
+
Debug.Log($"Quest completed: {quest.Title}");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
public List<string> GetCompletedQuestIds() => _completedQuests.ToList();
|
|
571
|
+
|
|
572
|
+
public void RestoreCompletedQuests(List<string> ids)
|
|
573
|
+
{
|
|
574
|
+
_completedQuests = new HashSet<string>(ids);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
public class QuestProgress
|
|
579
|
+
{
|
|
580
|
+
public QuestData Quest { get; private set; }
|
|
581
|
+
public int[] ObjectiveProgress { get; private set; }
|
|
582
|
+
|
|
583
|
+
public QuestProgress(QuestData quest)
|
|
584
|
+
{
|
|
585
|
+
Quest = quest;
|
|
586
|
+
ObjectiveProgress = new int[quest.Objectives.Length];
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## Loot Tables
|
|
594
|
+
|
|
595
|
+
```csharp
|
|
596
|
+
[CreateAssetMenu(fileName = "New Loot Table", menuName = "RPG/Loot Table")]
|
|
597
|
+
public class LootTable : ScriptableObject
|
|
598
|
+
{
|
|
599
|
+
[System.Serializable]
|
|
600
|
+
public class LootEntry
|
|
601
|
+
{
|
|
602
|
+
public ItemData Item;
|
|
603
|
+
[Range(0, 100)] public float DropChance;
|
|
604
|
+
public int MinQuantity = 1;
|
|
605
|
+
public int MaxQuantity = 1;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
public LootEntry[] Entries;
|
|
609
|
+
public int GuaranteedDrops = 1;
|
|
610
|
+
public int MaxDrops = 5;
|
|
611
|
+
|
|
612
|
+
public List<(ItemData item, int quantity)> Roll()
|
|
613
|
+
{
|
|
614
|
+
var drops = new List<(ItemData, int)>();
|
|
615
|
+
var sortedEntries = Entries.OrderByDescending(e => e.DropChance).ToList();
|
|
616
|
+
|
|
617
|
+
// Guaranteed drops (highest chance items)
|
|
618
|
+
for (int i = 0; i < GuaranteedDrops && i < sortedEntries.Count; i++)
|
|
619
|
+
{
|
|
620
|
+
var entry = sortedEntries[i];
|
|
621
|
+
int qty = Random.Range(entry.MinQuantity, entry.MaxQuantity + 1);
|
|
622
|
+
drops.Add((entry.Item, qty));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Chance drops
|
|
626
|
+
foreach (var entry in Entries)
|
|
627
|
+
{
|
|
628
|
+
if (drops.Count >= MaxDrops) break;
|
|
629
|
+
if (drops.Any(d => d.item == entry.Item)) continue;
|
|
630
|
+
|
|
631
|
+
if (Random.Range(0f, 100f) <= entry.DropChance)
|
|
632
|
+
{
|
|
633
|
+
int qty = Random.Range(entry.MinQuantity, entry.MaxQuantity + 1);
|
|
634
|
+
drops.Add((entry.Item, qty));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return drops;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Usage
|
|
643
|
+
public class Enemy : MonoBehaviour
|
|
644
|
+
{
|
|
645
|
+
[SerializeField] private LootTable lootTable;
|
|
646
|
+
|
|
647
|
+
void OnDeath()
|
|
648
|
+
{
|
|
649
|
+
var drops = lootTable.Roll();
|
|
650
|
+
|
|
651
|
+
foreach (var (item, quantity) in drops)
|
|
652
|
+
{
|
|
653
|
+
SpawnLootDrop(item, quantity);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
private void SpawnLootDrop(ItemData item, int quantity)
|
|
658
|
+
{
|
|
659
|
+
// Spawn pickup in world or add directly
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Crafting System
|
|
667
|
+
|
|
668
|
+
```csharp
|
|
669
|
+
[CreateAssetMenu(fileName = "New Recipe", menuName = "RPG/Recipe")]
|
|
670
|
+
public class CraftingRecipe : ScriptableObject
|
|
671
|
+
{
|
|
672
|
+
[System.Serializable]
|
|
673
|
+
public class Ingredient
|
|
674
|
+
{
|
|
675
|
+
public ItemData Item;
|
|
676
|
+
public int Quantity;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
public string RecipeId;
|
|
680
|
+
public string RecipeName;
|
|
681
|
+
public Ingredient[] Ingredients;
|
|
682
|
+
public ItemData Result;
|
|
683
|
+
public int ResultQuantity = 1;
|
|
684
|
+
public float CraftTime = 1f;
|
|
685
|
+
public int RequiredLevel;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
public class CraftingManager : MonoBehaviour
|
|
689
|
+
{
|
|
690
|
+
[SerializeField] private CraftingRecipe[] knownRecipes;
|
|
691
|
+
|
|
692
|
+
private Inventory _inventory;
|
|
693
|
+
|
|
694
|
+
void Awake()
|
|
695
|
+
{
|
|
696
|
+
_inventory = GetComponent<Inventory>();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
public bool CanCraft(CraftingRecipe recipe)
|
|
700
|
+
{
|
|
701
|
+
foreach (var ingredient in recipe.Ingredients)
|
|
702
|
+
{
|
|
703
|
+
if (!_inventory.HasItem(ingredient.Item, ingredient.Quantity))
|
|
704
|
+
{
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
public bool Craft(CraftingRecipe recipe)
|
|
712
|
+
{
|
|
713
|
+
if (!CanCraft(recipe)) return false;
|
|
714
|
+
|
|
715
|
+
// Consume ingredients
|
|
716
|
+
foreach (var ingredient in recipe.Ingredients)
|
|
717
|
+
{
|
|
718
|
+
_inventory.RemoveItem(ingredient.Item, ingredient.Quantity);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Add result
|
|
722
|
+
_inventory.AddItem(recipe.Result, recipe.ResultQuantity);
|
|
723
|
+
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
public List<CraftingRecipe> GetAvailableRecipes()
|
|
728
|
+
{
|
|
729
|
+
return knownRecipes.Where(CanCraft).ToList();
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
## Anti-Patterns
|
|
737
|
+
|
|
738
|
+
| ❌ Don't | ✅ Do |
|
|
739
|
+
|----------|-------|
|
|
740
|
+
| Store items as strings | Use ScriptableObjects |
|
|
741
|
+
| Hard-coded quest logic | Data-driven quests |
|
|
742
|
+
| No item stacking | Support stacking |
|
|
743
|
+
| Instant crafting | Show progress feedback |
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
> **Remember:** Players love collecting and progressing. Make it satisfying.
|