Compare commits
2 Commits
3ca2cd7f86
...
b68f180663
| Author | SHA1 | Date | |
|---|---|---|---|
| b68f180663 | |||
| 0bbe65f8a4 |
103
.agent/workflows/reference_first_rule.md
Normal file
103
.agent/workflows/reference_first_rule.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
description: MANDATORY - Always check references before generating any assets
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🎨 REFERENCE-FIRST GENERATION RULE
|
||||||
|
|
||||||
|
## **ZAKON: Če obstaja referenca, jo MORAŠ pogledati PRED generacijo!**
|
||||||
|
|
||||||
|
### **Workflow za VSAKO generacijo asseta:**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. ✅ Preveri: Ali obstaja master_reference.* v /references/?
|
||||||
|
|
||||||
|
2. ✅ ČE DA → OBVEZNO view_file() na referenco PRED generate_image()
|
||||||
|
|
||||||
|
3. ✅ Uporabi vse vizualne detajle iz reference slike:
|
||||||
|
- Barve (točne RGB vrednosti)
|
||||||
|
- Oblačila (textures, accessories)
|
||||||
|
- Frizura (style, barve, dodatki)
|
||||||
|
- Piercings, tattoos, scars
|
||||||
|
- Proporce (chibi 1:1 ali realistične)
|
||||||
|
- Outline thickness
|
||||||
|
|
||||||
|
4. ✅ Generate prompt MORA ustrezati referenci 100%
|
||||||
|
|
||||||
|
5. ❌ ČE NE → Šele potem generiraj prosto (brez reference)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **Reference Folder Structure:**
|
||||||
|
|
||||||
|
```
|
||||||
|
/references/
|
||||||
|
├── main_characters/
|
||||||
|
│ ├── kai/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ ├── ana/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ └── gronk/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
├── npcs/
|
||||||
|
│ ├── ivan_kovac/master_reference.jpg ← OBVEZNO GLEJ
|
||||||
|
│ ├── tehnik/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ ├── sivilja/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ ├── pek/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ ├── kustos/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ ├── mayor/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ ├── arborist/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ ├── miro_pravnik/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ └── teacher/master_reference.png ← OBVEZNO Glej
|
||||||
|
├── companions/
|
||||||
|
│ ├── susi/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
│ └── zombie_scout/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
├── enemies/
|
||||||
|
│ ├── zombies/ ← OBVEZNO GLEJ (3 variante)
|
||||||
|
│ └── nomad_raiders/ ← OBVEZNO GLEJ (4 types)
|
||||||
|
└── species/
|
||||||
|
├── trolls/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
├── fairies/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
└── elves/master_reference.png ← OBVEZNO GLEJ
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **TOTAL: 24 Master References**
|
||||||
|
|
||||||
|
**Vsaka od teh 24 slik MORA biti pogledana z `view_file()` PREDEN generiram katerokoli variacijo (8-direction sprites, portraits, animations)!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **Primer Correct Workflow:**
|
||||||
|
|
||||||
|
```
|
||||||
|
USER: Generiraj Kai 8-direction sprites
|
||||||
|
|
||||||
|
AGENT:
|
||||||
|
1. ✅ view_file(/references/main_characters/kai/master_reference.png)
|
||||||
|
2. ✅ Študiram: pink+green dreads, nose ring, gauges, leather jacket
|
||||||
|
3. ✅ generate_image() z TOČNIMI detalji iz reference
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **Primer WRONG Workflow:**
|
||||||
|
|
||||||
|
```
|
||||||
|
USER: Generiraj Kai 8-direction sprites
|
||||||
|
|
||||||
|
AGENT:
|
||||||
|
❌ generate_image() takoj brez pregleda reference
|
||||||
|
❌ NAPAKA: Izgubljeni detalji, nekonzistenten stil!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **Zakaj je to KRITIČNO:**
|
||||||
|
|
||||||
|
- **Vizualna konsistenca** = 100% ujemanje med vsemi asseti
|
||||||
|
- **Style 32 Dark-Chibi Noir** = moraš videti reference za outline thickness, proporce
|
||||||
|
- **Unique details** = piercings, tattoos, color combos so SPECIFIČNI za vsakega lika
|
||||||
|
- **Asset pipeline** = references so "source of truth" za VSE bodoče assete
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**PRAVILO: NO REFERENCE = NO GENERATION (razen če reference dejansko NE obstaja)**
|
||||||
@@ -1,197 +1,593 @@
|
|||||||
class QuestSystem {
|
/**
|
||||||
|
* QUEST SYSTEM v2.0 - Mrtva Dolina
|
||||||
|
* Complete quest management with ADHD-friendly dialogue and VFX integration
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Main story arc (Zamegljeni Spomini, Anina Sled)
|
||||||
|
* - Town economy quests (NPCs)
|
||||||
|
* - Defense quests (Wall building, Museum)
|
||||||
|
* - Side quests & companion recruitment
|
||||||
|
* - Progress tracking & rewards
|
||||||
|
* - Dialogue system integration
|
||||||
|
* - VFX triggers
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class QuestSystem {
|
||||||
constructor(scene) {
|
constructor(scene) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
|
this.quests = new Map();
|
||||||
// Quest Definitions
|
this.activeQuests = [];
|
||||||
this.questDB = {
|
|
||||||
'q1_start': {
|
|
||||||
id: 'q1_start',
|
|
||||||
title: 'Survival Basics',
|
|
||||||
description: 'Collect Wood and Stone to build your first defense.',
|
|
||||||
objectives: [
|
|
||||||
{ type: 'collect', item: 'wood', amount: 5, current: 0, done: false },
|
|
||||||
{ type: 'collect', item: 'stone', amount: 3, current: 0, done: false }
|
|
||||||
],
|
|
||||||
reward: { gold: 10, xp: 50 },
|
|
||||||
nextQuest: 'q2_farm',
|
|
||||||
giver: 'villager'
|
|
||||||
},
|
|
||||||
'q2_farm': {
|
|
||||||
id: 'q2_farm',
|
|
||||||
title: 'The Farmer',
|
|
||||||
description: 'Plant some seeds to grow food. You will need it.',
|
|
||||||
objectives: [
|
|
||||||
{ type: 'action', action: 'plant', amount: 3, current: 0, done: false }
|
|
||||||
],
|
|
||||||
reward: { gold: 20, item: 'wood', amount: 10 },
|
|
||||||
nextQuest: 'q3_defense',
|
|
||||||
giver: 'villager'
|
|
||||||
},
|
|
||||||
'q3_defense': {
|
|
||||||
id: 'q3_defense',
|
|
||||||
title: 'Fortification',
|
|
||||||
description: 'Build a Fence to keep zombies out.',
|
|
||||||
objectives: [
|
|
||||||
{ type: 'action', action: 'build_fence', amount: 2, current: 0, done: false }
|
|
||||||
],
|
|
||||||
reward: { gold: 50, item: 'sword', amount: 1 },
|
|
||||||
nextQuest: 'q4_slayer',
|
|
||||||
giver: 'merchant'
|
|
||||||
},
|
|
||||||
'q4_slayer': {
|
|
||||||
id: 'q4_slayer',
|
|
||||||
title: 'Zombie Slayer',
|
|
||||||
description: 'Kill 3 Zombies using your new sword.',
|
|
||||||
objectives: [
|
|
||||||
{ type: 'kill', target: 'zombie', amount: 3, current: 0, done: false }
|
|
||||||
],
|
|
||||||
reward: { gold: 100, item: 'gold', amount: 50 },
|
|
||||||
nextQuest: null,
|
|
||||||
giver: 'villager'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.activeQuest = null;
|
|
||||||
this.completedQuests = [];
|
this.completedQuests = [];
|
||||||
|
this.questLog = [];
|
||||||
|
|
||||||
|
this.initializeQuests();
|
||||||
}
|
}
|
||||||
|
|
||||||
getAvailableQuest(npcType) {
|
initializeQuests() {
|
||||||
const chain = ['q1_start', 'q2_farm', 'q3_defense', 'q4_slayer'];
|
// MAIN STORY QUEST 1: Zamegljeni Spomini
|
||||||
let targetId = null;
|
this.registerQuest({
|
||||||
for (const id of chain) {
|
id: 'zamegljeni_spomini',
|
||||||
if (!this.completedQuests.includes(id)) {
|
title: 'Zamegljeni Spomini',
|
||||||
targetId = id;
|
type: 'main_story',
|
||||||
break;
|
priority: 5,
|
||||||
|
description: 'Kai se zbudi brez spominov. Najdi fotografijo.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'find_photo', text: 'Najdi družinsko fotografijo', type: 'item', item: 'family_photo', required: 1, current: 0 },
|
||||||
|
{ id: 'unlock_capital', text: 'Odkleni Capital City', type: 'flag', flag: 'capital_unlocked', complete: false }
|
||||||
|
],
|
||||||
|
rewards: { xp: 500, items: ['family_photo'], unlocks: ['capital_city'] },
|
||||||
|
vfx: { start: 'amnesia_blur', complete: 'flashback' },
|
||||||
|
dialogue: {
|
||||||
|
start: ["Kje sem? Kaj se mi dogaja?", "Ta fotka... Kdo je ta punca?"],
|
||||||
|
complete: ["Ana! Pomnim se! To je moja sestra!"]
|
||||||
|
},
|
||||||
|
npc: null,
|
||||||
|
location: 'base_farm'
|
||||||
|
});
|
||||||
|
|
||||||
|
// MAIN STORY QUEST 2: Anina Sled
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'anина_sled',
|
||||||
|
title: 'Anina Sled',
|
||||||
|
type: 'main_story',
|
||||||
|
priority: 5,
|
||||||
|
description: 'Zberi 50 namigov o tem, kje je Ana.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'collect_clues', text: 'Zberi Aniне namige (0/50)', type: 'collection', item: 'ana_clue', required: 50, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { xp: 5000, items: ['final_location'], unlocks: ['ana_encounter'] },
|
||||||
|
vfx: { onClue: 'amnesia_blur', complete: 'revelation' },
|
||||||
|
dialogue: {
|
||||||
|
onClue: ["Kai, veš da te imam rada?", "Ne skrbi zame..."],
|
||||||
|
complete: ["NAŠEL SEM JO! Ana, prihajam!"]
|
||||||
|
},
|
||||||
|
prerequisites: ['zamegljeni_spomini'],
|
||||||
|
npc: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// TOWN QUEST: Šivilja
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'siviljina_prosnja',
|
||||||
|
title: 'Šiviljina Prošnja',
|
||||||
|
type: 'town_economy',
|
||||||
|
priority: 4,
|
||||||
|
description: 'Šivilja rabi 20x Platno.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'cloth', text: 'Zberi platno (0/20)', type: 'item', item: 'cloth', required: 20, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { xp: 200, items: ['enchanted_jacket'], gold: 500 },
|
||||||
|
dialogue: {
|
||||||
|
start: ["Hej! Rabim 20 platna. Lahko mi pomagaš?"],
|
||||||
|
progress: ["Še {remaining}!"],
|
||||||
|
complete: ["SUPER! Na, vzemi ta jopič!"]
|
||||||
|
},
|
||||||
|
npc: 'sivilja'
|
||||||
|
});
|
||||||
|
|
||||||
|
// TOWN QUEST: Pek
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'pekov_recept',
|
||||||
|
title: 'Pekov Recept',
|
||||||
|
type: 'town_economy',
|
||||||
|
priority: 4,
|
||||||
|
description: 'Pek rabi 10x Pšenico za kruh.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'wheat', text: 'Zberi pšenico (0/10)', type: 'item', item: 'wheat', required: 10, current: 0 },
|
||||||
|
{ id: 'restore', text: 'Obnovi pekarno', type: 'building', building: 'bakery', complete: false }
|
||||||
|
],
|
||||||
|
rewards: { xp: 300, items: ['daily_bread'], unlocks: ['bakery_shop'] },
|
||||||
|
dialogue: {
|
||||||
|
start: ["Živjo! Rabim 10 pšenice, pa še pekarna je zrušena!"],
|
||||||
|
complete: ["ZAKON! Zdej lahko spet pečem! Kruh na dan ZASTONJ!"]
|
||||||
|
},
|
||||||
|
npc: 'pek'
|
||||||
|
});
|
||||||
|
|
||||||
|
// TOWN QUEST: Tehnik
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'tehnik_generator',
|
||||||
|
title: 'Tehnikova Naprava',
|
||||||
|
type: 'town_economy',
|
||||||
|
priority: 4,
|
||||||
|
description: 'Tehnik rabi 5x Električne Komponente.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'components', text: 'Zberi komponente (0/5)', type: 'item', item: 'electric_component', required: 5, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { xp: 250, items: ['auto_tiller'], unlocks: ['tech_services'] },
|
||||||
|
dialogue: {
|
||||||
|
start: ["Yo! Rabim 5 električnih komponent za generator."],
|
||||||
|
complete: ["NICE! Generator dela! Auto-Tiller upgrade za te!"]
|
||||||
|
},
|
||||||
|
npc: 'tehnik'
|
||||||
|
});
|
||||||
|
|
||||||
|
// DEFENSE QUEST: Walls
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'obzidje',
|
||||||
|
title: 'Obzidje Mrtve Doline',
|
||||||
|
type: 'defense',
|
||||||
|
priority: 5,
|
||||||
|
description: 'Zgradi 20 segmentov zidov.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'walls', text: 'Zgradi zunanje zidove (0/20)', type: 'building', building: 'wall', required: 20, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { xp: 400, items: ['wall_blueprint_2'], unlocks: ['raid_event'] },
|
||||||
|
vfx: { complete: 'raid_warning' },
|
||||||
|
dialogue: {
|
||||||
|
start: ["Zombiji prihajajo! Hitr zgradi zidove!"],
|
||||||
|
complete: ["DONE! Zidovi so zgoraj! Priprav se na raiderje!"]
|
||||||
|
},
|
||||||
|
npc: 'mayor',
|
||||||
|
triggers: ['day_7']
|
||||||
|
});
|
||||||
|
|
||||||
|
// COLLECTION QUEST: Museum
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'muzej_hrosci',
|
||||||
|
title: 'Muzejski Mejnik',
|
||||||
|
type: 'collection',
|
||||||
|
priority: 3,
|
||||||
|
description: 'Kustos rabi 24 različnih hroščev.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'bugs', text: 'Doniraj hrošče (0/24)', type: 'collection', collection: 'museum_bugs', required: 24, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { xp: 600, items: ['museum_key'], unlocks: ['museum_stage2'] },
|
||||||
|
dialogue: {
|
||||||
|
start: ["Potrebujem 24 različnih hroščev za muzejsko zbirko."],
|
||||||
|
progress: ["Odlično! Še {remaining}!"],
|
||||||
|
complete: ["FANTASTIČNO! Razširim muzej!"]
|
||||||
|
},
|
||||||
|
npc: 'kustos'
|
||||||
|
});
|
||||||
|
|
||||||
|
// SIDE QUEST: Arborist
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'arborist_sadike',
|
||||||
|
title: 'Arboristova Pomoč',
|
||||||
|
type: 'side',
|
||||||
|
priority: 2,
|
||||||
|
description: 'Arborist rabi 50x sadike dreves.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'saplings', text: 'Zberi sadike (0/50)', type: 'item', item: 'tree_sapling', required: 50, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { xp: 350, items: ['tree_planter'], unlocks: ['forest_restore'] },
|
||||||
|
dialogue: {
|
||||||
|
start: ["Rad bi obnovil gozd. Mi lahko pomagaš?"],
|
||||||
|
complete: ["SUPER! Drevo-sadersko orodje je zate!"]
|
||||||
|
},
|
||||||
|
npc: 'arborist'
|
||||||
|
});
|
||||||
|
|
||||||
|
// COMPANION QUEST: Zombie Scout
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'zombie_scout_join',
|
||||||
|
title: 'Zombi Skavt - Rekrutacija',
|
||||||
|
type: 'companion',
|
||||||
|
priority: 5,
|
||||||
|
description: 'Pridobi zaupanje inteligentnega zombija.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'feed', text: 'Nahrani zombija (0/3 mesa)', type: 'item_give', item: 'meat', required: 3, current: 0 },
|
||||||
|
{ id: 'trust', text: 'Pridobi zaupanje', type: 'interaction', complete: false }
|
||||||
|
],
|
||||||
|
rewards: { xp: 1000, unlocks: ['zombie_scout_companion'], companions: ['zombie_scout'] },
|
||||||
|
vfx: { complete: 'companion_join' },
|
||||||
|
dialogue: {
|
||||||
|
start: ["*zombie zvoki* ...Meso?"],
|
||||||
|
progress: ["*munch* ...Ti dobr človek."],
|
||||||
|
complete: ["*zavija* Jaz s teb! Jaz pomagat! [JOINED]"]
|
||||||
|
},
|
||||||
|
npc: 'zombie_scout',
|
||||||
|
location: 'dead_forest'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Legacy basic quest chain for backwards compatibility
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'q1_start',
|
||||||
|
title: 'Survival Basics',
|
||||||
|
type: 'tutorial',
|
||||||
|
priority: 5,
|
||||||
|
description: 'Collect Wood and Stone.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'wood', text: 'Collect Wood (0/5)', type: 'item', item: 'wood', required: 5, current: 0 },
|
||||||
|
{ id: 'stone', text: 'Collect Stone (0/3)', type: 'item', item: 'stone', required: 3, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { gold: 10, xp: 50 },
|
||||||
|
nextQuest: 'q2_farm',
|
||||||
|
npc: 'villager'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'q2_farm',
|
||||||
|
title: 'The Farmer',
|
||||||
|
type: 'tutorial',
|
||||||
|
priority: 5,
|
||||||
|
description: 'Plant some seeds.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'plant', text: 'Plant seeds (0/3)', type: 'action', action: 'plant', required: 3, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { gold: 20, items: ['wood:10'] },
|
||||||
|
prerequisites: ['q1_start'],
|
||||||
|
nextQuest: 'q3_defense',
|
||||||
|
npc: 'villager'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'q3_defense',
|
||||||
|
title: 'Fortification',
|
||||||
|
type: 'tutorial',
|
||||||
|
priority: 4,
|
||||||
|
description: 'Build a Fence.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'fence', text: 'Build Fence (0/2)', type: 'action', action: 'build_fence', required: 2, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { gold: 50, items: ['sword:1'] },
|
||||||
|
prerequisites: ['q2_farm'],
|
||||||
|
nextQuest: 'q4_slayer',
|
||||||
|
npc: 'merchant'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerQuest({
|
||||||
|
id: 'q4_slayer',
|
||||||
|
title: 'Zombie Slayer',
|
||||||
|
type: 'tutorial',
|
||||||
|
priority: 4,
|
||||||
|
description: 'Kill 3 Zombies.',
|
||||||
|
objectives: [
|
||||||
|
{ id: 'kill', text: 'Kill Zombies (0/3)', type: 'kill', target: 'zombie', required: 3, current: 0 }
|
||||||
|
],
|
||||||
|
rewards: { gold: 100, items: ['gold:50'] },
|
||||||
|
prerequisites: ['q3_defense'],
|
||||||
|
npc: 'villager'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerQuest(questData) {
|
||||||
|
// Set defaults
|
||||||
|
questData.isActive = false;
|
||||||
|
questData.isComplete = false;
|
||||||
|
questData.startTime = null;
|
||||||
|
|
||||||
|
this.quests.set(questData.id, questData);
|
||||||
|
}
|
||||||
|
|
||||||
|
startQuest(questId) {
|
||||||
|
const quest = this.quests.get(questId);
|
||||||
|
if (!quest) {
|
||||||
|
console.error(`Quest ${questId} not found!`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check prerequisites
|
||||||
|
if (quest.prerequisites) {
|
||||||
|
for (const prereq of quest.prerequisites) {
|
||||||
|
if (!this.isQuestComplete(prereq)) {
|
||||||
|
console.log(`Cannot start ${questId}: Missing ${prereq}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetId) return null;
|
// Prevent duplicate active
|
||||||
if (this.activeQuest && this.activeQuest.id === targetId) return null;
|
if (this.activeQuests.includes(questId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const q = this.questDB[targetId];
|
quest.isActive = true;
|
||||||
if (q.giver === npcType) return q;
|
quest.startTime = Date.now();
|
||||||
|
this.activeQuests.push(questId);
|
||||||
|
|
||||||
return null;
|
this.questLog.push({
|
||||||
|
questId,
|
||||||
|
startTime: quest.startTime,
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show start dialogue
|
||||||
|
if (quest.dialogue && quest.dialogue.start) {
|
||||||
|
this.showDialogue(quest.dialogue.start, quest.npc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger VFX
|
||||||
|
if (quest.vfx && quest.vfx.start) {
|
||||||
|
this.scene.events.emit('vfx:trigger', quest.vfx.start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show notification
|
||||||
|
this.scene.events.emit('show-floating-text', {
|
||||||
|
x: this.scene.player.x,
|
||||||
|
y: this.scene.player.y - 50,
|
||||||
|
text: 'Quest Accepted!',
|
||||||
|
color: '#FFFF00'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.scene.events.emit('quest:started', questId);
|
||||||
|
this.updateUI();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
startQuest(id) {
|
updateObjective(questId, objectiveId, progress) {
|
||||||
if (this.completedQuests.includes(id)) return;
|
const quest = this.quests.get(questId);
|
||||||
|
if (!quest || !quest.isActive) return;
|
||||||
|
|
||||||
const template = this.questDB[id];
|
const objective = quest.objectives.find(obj => obj.id === objectiveId);
|
||||||
if (!template) return;
|
if (!objective) return;
|
||||||
|
|
||||||
this.activeQuest = JSON.parse(JSON.stringify(template));
|
// Update progress
|
||||||
console.log(`📜 Quest Started: ${this.activeQuest.title}`);
|
if (objective.type === 'item' || objective.type === 'collection') {
|
||||||
|
objective.current = Math.min(progress, objective.required);
|
||||||
|
objective.complete = objective.current >= objective.required;
|
||||||
|
} else if (objective.type === 'action' || objective.type === 'kill') {
|
||||||
|
objective.current = Math.min(progress, objective.required);
|
||||||
|
objective.complete = objective.current >= objective.required;
|
||||||
|
} else if (objective.type === 'flag' || objective.type === 'interaction' || objective.type === 'building') {
|
||||||
|
objective.complete = progress === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check completion
|
||||||
|
const allComplete = quest.objectives.every(obj => obj.complete);
|
||||||
|
if (allComplete) {
|
||||||
|
this.completeQuest(questId);
|
||||||
|
} else {
|
||||||
|
// Progress dialogue
|
||||||
|
if (quest.dialogue && quest.dialogue.progress) {
|
||||||
|
const remaining = objective.required - objective.current;
|
||||||
|
const msg = quest.dialogue.progress[0].replace('{remaining}', remaining);
|
||||||
|
this.showDialogue([msg], quest.npc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scene.events.emit('quest:updated', questId, objectiveId, progress);
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
trackAction(actionType, amount = 1) {
|
||||||
|
// Track actions for active quests' objectives
|
||||||
|
for (const questId of this.activeQuests) {
|
||||||
|
const quest = this.quests.get(questId);
|
||||||
|
if (!quest) continue;
|
||||||
|
|
||||||
|
for (const obj of quest.objectives) {
|
||||||
|
if (obj.complete) continue;
|
||||||
|
|
||||||
|
if (obj.type === 'action' && obj.action === actionType) {
|
||||||
|
obj.current = Math.min((obj.current || 0) + amount, obj.required);
|
||||||
|
if (obj.current >= obj.required) {
|
||||||
|
obj.complete = true;
|
||||||
|
this.scene.events.emit('show-floating-text', {
|
||||||
|
x: this.scene.player.x,
|
||||||
|
y: this.scene.player.y,
|
||||||
|
text: 'Objective Complete!',
|
||||||
|
color: '#00FF00'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (obj.type === 'kill' && obj.target === actionType) {
|
||||||
|
obj.current = Math.min((obj.current || 0) + amount, obj.required);
|
||||||
|
if (obj.current >= obj.required) {
|
||||||
|
obj.complete = true;
|
||||||
|
this.scene.events.emit('show-floating-text', {
|
||||||
|
x: this.scene.player.x,
|
||||||
|
y: this.scene.player.y,
|
||||||
|
text: 'Objective Complete!',
|
||||||
|
color: '#00FF00'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-complete check
|
||||||
|
const allDone = quest.objectives.every(o => o.complete);
|
||||||
|
if (allDone) {
|
||||||
|
this.completeQuest(questId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.updateUI();
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
completeQuest(questId) {
|
||||||
|
const quest = this.quests.get(questId);
|
||||||
|
if (!quest) return;
|
||||||
|
|
||||||
|
quest.isActive = false;
|
||||||
|
quest.isComplete = true;
|
||||||
|
this.activeQuests = this.activeQuests.filter(id => id !== questId);
|
||||||
|
this.completedQuests.push(questId);
|
||||||
|
|
||||||
|
// Grant rewards
|
||||||
|
if (quest.rewards) {
|
||||||
|
if (quest.rewards.xp && this.scene.player && this.scene.player.gainExperience) {
|
||||||
|
this.scene.player.gainExperience(quest.rewards.xp);
|
||||||
|
}
|
||||||
|
if (quest.rewards.gold && this.scene.inventorySystem) {
|
||||||
|
this.scene.inventorySystem.addGold(quest.rewards.gold);
|
||||||
|
}
|
||||||
|
if (quest.rewards.items) {
|
||||||
|
quest.rewards.items.forEach(item => {
|
||||||
|
const [itemName, amt] = item.includes(':') ? item.split(':') : [item, 1];
|
||||||
|
if (this.scene.inventorySystem) {
|
||||||
|
this.scene.inventorySystem.addItem(itemName, parseInt(amt));
|
||||||
|
} else if (this.scene.player && this.scene.player.inventory) {
|
||||||
|
this.scene.player.inventory.addItem(itemName, parseInt(amt));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (quest.rewards.unlocks) {
|
||||||
|
quest.rewards.unlocks.forEach(unlock => {
|
||||||
|
if (this.scene.gameState && this.scene.gameState.unlock) {
|
||||||
|
this.scene.gameState.unlock(unlock);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (quest.rewards.companions) {
|
||||||
|
quest.rewards.companions.forEach(companion => {
|
||||||
|
if (this.scene.player && this.scene.player.addCompanion) {
|
||||||
|
this.scene.player.addCompanion(companion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completion dialogue
|
||||||
|
if (quest.dialogue && quest.dialogue.complete) {
|
||||||
|
this.showDialogue(quest.dialogue.complete, quest.npc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completion VFX
|
||||||
|
if (quest.vfx && (quest.vfx.complete || quest.vfx.onComplete)) {
|
||||||
|
this.scene.events.emit('vfx:trigger', quest.vfx.complete || quest.vfx.onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
// Notification
|
// Notification
|
||||||
this.scene.events.emit('show-floating-text', {
|
this.scene.events.emit('show-floating-text', {
|
||||||
x: this.scene.player.x,
|
x: this.scene.player.x,
|
||||||
y: this.scene.player.y - 50,
|
y: this.scene.player.y - 50,
|
||||||
text: "Quest Accepted!",
|
text: 'Quest Complete!',
|
||||||
color: '#FFFF00'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
update(delta) {
|
|
||||||
if (!this.activeQuest) return;
|
|
||||||
|
|
||||||
let changed = false;
|
|
||||||
let allDone = true;
|
|
||||||
|
|
||||||
if (this.scene.inventorySystem) {
|
|
||||||
const inv = this.scene.inventorySystem;
|
|
||||||
|
|
||||||
for (const obj of this.activeQuest.objectives) {
|
|
||||||
if (obj.done) continue;
|
|
||||||
|
|
||||||
if (obj.type === 'collect') {
|
|
||||||
const count = inv.getItemCount(obj.item);
|
|
||||||
if (count !== obj.current) {
|
|
||||||
obj.current = count;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (obj.current >= obj.amount) {
|
|
||||||
obj.done = true;
|
|
||||||
this.scene.events.emit('show-floating-text', { x: this.scene.player.x, y: this.scene.player.y, text: "Objective Complete!" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const obj of this.activeQuest.objectives) {
|
|
||||||
if (!obj.done) allDone = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) this.updateUI();
|
|
||||||
|
|
||||||
if (allDone) {
|
|
||||||
this.completeQuest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trackAction(actionType, amount = 1) {
|
|
||||||
if (!this.activeQuest) return;
|
|
||||||
|
|
||||||
let changed = false;
|
|
||||||
for (const obj of this.activeQuest.objectives) {
|
|
||||||
if (obj.done) continue;
|
|
||||||
|
|
||||||
if (obj.type === 'action' && obj.action === actionType) {
|
|
||||||
obj.current += amount;
|
|
||||||
changed = true;
|
|
||||||
if (obj.current >= obj.amount) {
|
|
||||||
obj.done = true;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (obj.type === 'kill' && obj.target === actionType) {
|
|
||||||
obj.current += amount;
|
|
||||||
changed = true;
|
|
||||||
if (obj.current >= obj.amount) {
|
|
||||||
obj.done = true;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changed) this.updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
completeQuest() {
|
|
||||||
console.log(`🏆 Quest Complete: ${this.activeQuest.title}`);
|
|
||||||
|
|
||||||
if (this.activeQuest.reward) {
|
|
||||||
const r = this.activeQuest.reward;
|
|
||||||
if (r.gold && this.scene.inventorySystem) {
|
|
||||||
this.scene.inventorySystem.addGold(r.gold);
|
|
||||||
}
|
|
||||||
if (r.item && this.scene.inventorySystem) {
|
|
||||||
this.scene.inventorySystem.addItem(r.item, r.amount || 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scene.events.emit('show-floating-text', {
|
|
||||||
x: this.scene.player.x,
|
|
||||||
y: this.scene.player.y - 50,
|
|
||||||
text: "Quest Complete!",
|
|
||||||
color: '#00FF00'
|
color: '#00FF00'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.completedQuests.push(this.activeQuest.id);
|
console.log(`🏆 Quest Complete: ${quest.title}`);
|
||||||
const next = this.activeQuest.nextQuest;
|
this.scene.events.emit('quest:completed', questId);
|
||||||
this.activeQuest = null;
|
|
||||||
this.updateUI();
|
|
||||||
|
|
||||||
if (next) {
|
// Auto-start next quest if defined
|
||||||
console.log('Next quest available at NPC.');
|
if (quest.nextQuest) {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(`Next quest available: ${quest.nextQuest}`);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialogue(lines, npcId) {
|
||||||
|
// Emit dialogue event
|
||||||
|
this.scene.events.emit('dialogue:show', {
|
||||||
|
npc: npcId,
|
||||||
|
lines: lines
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveQuests() {
|
||||||
|
return this.activeQuests.map(id => this.quests.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvailableQuest(npcType) {
|
||||||
|
// Find first uncompleted, non-active quest for this NPC
|
||||||
|
for (const [id, quest] of this.quests) {
|
||||||
|
if (quest.isComplete || quest.isActive) continue;
|
||||||
|
if (quest.npc !== npcType) continue;
|
||||||
|
|
||||||
|
// Check prerequisites
|
||||||
|
if (quest.prerequisites) {
|
||||||
|
const prereqsMet = quest.prerequisites.every(p => this.isQuestComplete(p));
|
||||||
|
if (!prereqsMet) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return quest;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getQuestProgress(questId) {
|
||||||
|
const quest = this.quests.get(questId);
|
||||||
|
if (!quest) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: questId,
|
||||||
|
title: quest.title,
|
||||||
|
objectives: quest.objectives.map(obj => ({
|
||||||
|
text: obj.text,
|
||||||
|
current: obj.current || 0,
|
||||||
|
required: obj.required || 1,
|
||||||
|
complete: obj.complete || false
|
||||||
|
})),
|
||||||
|
isActive: quest.isActive,
|
||||||
|
isComplete: quest.isComplete
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isQuestActive(questId) {
|
||||||
|
const quest = this.quests.get(questId);
|
||||||
|
return quest && quest.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
isQuestComplete(questId) {
|
||||||
|
const quest = this.quests.get(questId);
|
||||||
|
return quest && quest.isComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(delta) {
|
||||||
|
// Auto-update objectives based on inventory
|
||||||
|
if (!this.scene.inventorySystem) return;
|
||||||
|
|
||||||
|
for (const questId of this.activeQuests) {
|
||||||
|
const quest = this.quests.get(questId);
|
||||||
|
if (!quest) continue;
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
for (const obj of quest.objectives) {
|
||||||
|
if (obj.complete) continue;
|
||||||
|
|
||||||
|
if (obj.type === 'item') {
|
||||||
|
const count = this.scene.inventorySystem.getItemCount(obj.item);
|
||||||
|
if (count !== obj.current) {
|
||||||
|
obj.current = count;
|
||||||
|
changed = true;
|
||||||
|
if (obj.current >= obj.required) {
|
||||||
|
obj.complete = true;
|
||||||
|
this.scene.events.emit('show-floating-text', {
|
||||||
|
x: this.scene.player.x,
|
||||||
|
y: this.scene.player.y,
|
||||||
|
text: 'Objective Complete!',
|
||||||
|
color: '#00FF00'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
const allDone = quest.objectives.every(o => o.complete);
|
||||||
|
if (allDone) {
|
||||||
|
this.completeQuest(questId);
|
||||||
|
} else {
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUI() {
|
updateUI() {
|
||||||
const ui = this.scene.scene.get('UIScene');
|
const ui = this.scene.scene.get('UIScene');
|
||||||
if (ui && ui.updateQuestTracker) {
|
if (ui && ui.updateQuestTracker) {
|
||||||
ui.updateQuestTracker(this.activeQuest);
|
const active = this.activeQuests.length > 0 ? this.quests.get(this.activeQuests[0]) : null;
|
||||||
|
ui.updateQuestTracker(active);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.quests.clear();
|
||||||
|
this.activeQuests = [];
|
||||||
|
this.completedQuests = [];
|
||||||
|
this.questLog = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user