Compare commits

..

14 Commits

10 changed files with 242 additions and 50 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "adventurers-guild", "name": "adventurers-guild",
"version": "0.15.2", "version": "0.15.4",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+39 -17
View File
@@ -66,7 +66,7 @@ import {version} from "@/../package.json";
<script lang="ts"> <script lang="ts">
import {defineComponent} from "vue"; import {defineComponent} from "vue";
import {Adventurer} from "@/classes/Adventurer"; import {Adventurer} from "@/classes/Adventurer";
import {getQuestWithRewards, Quest} from "@/classes/Quest"; import {getQuestWithRewards, Quest} from "@/classes/quests/Quest";
import {Guild} from "@/classes/Guild"; import {Guild} from "@/classes/Guild";
import {getFromString, QuestRank} from "@/classes/QuestRank"; import {getFromString, QuestRank} from "@/classes/QuestRank";
import { import {
@@ -84,6 +84,8 @@ import QuestExpUpgrade from "@/classes/guildUpgrades/QuestExpUpgrade";
import QuestGoldUpgrade from "@/classes/guildUpgrades/QuestGoldUpgrade"; import QuestGoldUpgrade from "@/classes/guildUpgrades/QuestGoldUpgrade";
import AutoFinishQuestsUpgrade from "@/classes/guildUpgrades/AutoFinishQuestsUpgrade"; import AutoFinishQuestsUpgrade from "@/classes/guildUpgrades/AutoFinishQuestsUpgrade";
import RecruitmentCapacityUpgrade from "@/classes/guildUpgrades/RecruitmentCapacityUpgrade"; import RecruitmentCapacityUpgrade from "@/classes/guildUpgrades/RecruitmentCapacityUpgrade";
import QuestPhase from "@/classes/quests/QuestPhase";
import {PhaseType} from "@/classes/quests/QuestPhaseType";
export default defineComponent({ export default defineComponent({
name: "GuildView", name: "GuildView",
@@ -121,23 +123,23 @@ export default defineComponent({
async updateMissives() { async updateMissives() {
for (const missive of this.missives) { for (const missive of this.missives) {
if (missive.adventurers.length < missive.maxAdventurers) { if (missive.adventurers.length < missive.maxAdventurers) {
missive.progressPoints = 0; missive.phases.forEach(phase => {
phase.points = 0;
});
continue; continue;
} }
for (const adventurerId in missive.adventurers) { for (const phase of missive.phases) {
const adventurer = missive.adventurers[adventurerId]; if (phase.completed()) continue;
const attack = adventurer.getAttack(); phase.tick(missive.adventurers);
adventurer.busy = true; break;
missive.progressPoints = Math.min(missive.progressPoints + attack, missive.maxProgress);
} }
if ( if (
missive.progressPoints >= missive.maxProgress missive.isCompleted()
&& this.guild.autoFinishQuestsUpgrade.getRanksToAutoFinishQuestsIn().includes(missive.rank) && this.guild.autoFinishQuestsUpgrade.getRanksToAutoFinishQuestsIn().includes(missive.rank)
) { ) {
this.finalizeQuest(missive); this.finalizeQuest(missive);
} }
} }
}, },
async checkForNewRecruit(currentTimestamp: number, cooldownModifier: number = 1) { async checkForNewRecruit(currentTimestamp: number, cooldownModifier: number = 1) {
const recruitCapacity = this.guild.recruitmentCapacity.getRecruitmentCapacity(); const recruitCapacity = this.guild.recruitmentCapacity.getRecruitmentCapacity();
@@ -148,8 +150,10 @@ export default defineComponent({
if (deltaTime > 0) return; // not yet time for a new recruit if (deltaTime > 0) return; // not yet time for a new recruit
if (Object.keys(this.adventurers).length <= 0) { if (Object.keys(this.adventurers).length <= 0) {
const firstAdventurer = this.adventurersDatabase[0]; const firstAdventurer = this.adventurersDatabase["aldek"];
this.adventurersForHire[firstAdventurer.id] = firstAdventurer; this.adventurersForHire[firstAdventurer.id] = firstAdventurer;
this.setNextRecruitArrival(currentTimestamp, cooldownModifier)
return;
} }
const newAdventurerForHire = getNewAdventurerForHire(Object.values(this.adventurersDatabase), Object.values(this.adventurers)); const newAdventurerForHire = getNewAdventurerForHire(Object.values(this.adventurersDatabase), Object.values(this.adventurers));
@@ -176,7 +180,6 @@ export default defineComponent({
delete this.adventurersForHire[adventurer.id]; delete this.adventurersForHire[adventurer.id];
}, },
finalizeQuest(missive: Quest) { finalizeQuest(missive: Quest) {
missive.progressPoints = 0;
this.guild.gold += missive.goldReward; this.guild.gold += missive.goldReward;
for (const adventurerId in missive.adventurers) { for (const adventurerId in missive.adventurers) {
const adventurer = missive.adventurers[adventurerId]; const adventurer = missive.adventurers[adventurerId];
@@ -198,7 +201,7 @@ export default defineComponent({
return getQuestWithRewards(questsForRank[randomIdString], this.guild.expModifier.getModifier(), this.guild.goldModifier.getModifier()); return getQuestWithRewards(questsForRank[randomIdString], this.guild.expModifier.getModifier(), this.guild.goldModifier.getModifier());
}, },
createMissive(questToAdd: Quest) { createMissive(questToAdd: Quest) {
const quest = JSON.parse(JSON.stringify(questToAdd)); const quest = Quest.deserialize(questToAdd.serialize());
quest.id = Math.random().toString(16).slice(2).toString(); quest.id = Math.random().toString(16).slice(2).toString();
this.missives.push(quest); this.missives.push(quest);
}, },
@@ -255,9 +258,28 @@ export default defineComponent({
if (Array.isArray(saveData.missives)) { if (Array.isArray(saveData.missives)) {
for (const data of saveData.missives) { for (const data of saveData.missives) {
const quest = new Quest(data.id, getFromString(data.rank), data.title, data.text, data.maxProgress, data.expReward, data.goldReward); const phases: Array<QuestPhase> = [];
quest.progressPoints = data.progressPoints; if (Array.isArray(data.phases) && data.phases.length > 0) {
if (data.adventurers.length > 0) { for (const phaseData of data.phases) {
const types: Array<PhaseType> = [];
if (Array.isArray(phaseData.types)) {
for (const type of phaseData.types) {
try {
const phaseType = PhaseType[type as keyof typeof PhaseType];
types.push(phaseType);
} catch (e) {
console.error("Error creating phase type", e);
}
}
}
const phase = new QuestPhase(types, phaseData.maxPoints, phaseData.points);
phases.push(phase);
}
} else {
continue; // skip this missive if it has no phases
}
const quest = new Quest(data.id, getFromString(data.rank), data.title, data.text, phases, data.expReward, data.goldReward);
if (Array.isArray(data?.adventurers)) {
for (const adventurer of data.adventurers) { for (const adventurer of data.adventurers) {
const adventurerId = adventurer.id; const adventurerId = adventurer.id;
if (this.adventurers[adventurerId] == null) continue; if (this.adventurers[adventurerId] == null) continue;
@@ -338,11 +360,11 @@ export default defineComponent({
this.loadGame(); this.loadGame();
this.adventurersDatabase = removeAlreadyHiredAdventurers(this.adventurersDatabase, this.adventurers); this.adventurersDatabase = removeAlreadyHiredAdventurers(this.adventurersDatabase, this.adventurers);
if (Object.keys(this.adventurersForHire).length < this.guild.recruitmentCapacity.getRecruitmentCapacity()) { if (Object.keys(this.adventurers).length > 0 && Object.keys(this.adventurersForHire).length < this.guild.recruitmentCapacity.getRecruitmentCapacity()) {
// check if more time passed than next recruit arrival and simulate next recruit arrivals up to now // check if more time passed than next recruit arrival and simulate next recruit arrivals up to now
const now = new Date(); const now = new Date();
if (this.nextRecruitArrival.getTime() < now.getTime()) { if (this.nextRecruitArrival.getTime() < now.getTime()) {
const slotsLeft = 2 - Object.keys(this.adventurersForHire).length; const slotsLeft = this.guild.recruitmentCapacity.getRecruitmentCapacity() - Object.keys(this.adventurersForHire).length;
for (let i = 0; i < slotsLeft; i++) { for (let i = 0; i < slotsLeft; i++) {
await this.checkForNewRecruit(this.nextRecruitArrival.getTime()); await this.checkForNewRecruit(this.nextRecruitArrival.getTime());
} }
+37 -2
View File
@@ -1,7 +1,9 @@
import {Guild} from "@/classes/Guild"; import {Guild} from "@/classes/Guild";
import {Adventurer} from "@/classes/Adventurer"; import {Adventurer} from "@/classes/Adventurer";
import {Quest} from "@/classes/Quest"; import {Quest} from "@/classes/quests/Quest";
import {getFromString, QuestRank} from "@/classes/QuestRank"; import {getFromString, QuestRank} from "@/classes/QuestRank";
import QuestPhase from "@/classes/quests/QuestPhase";
import {PhaseType} from "@/classes/quests/QuestPhaseType";
export class GameData { export class GameData {
guild: Guild; guild: Guild;
@@ -97,12 +99,37 @@ export async function loadAvailableQuests(): Promise<{ [key: string]: { [key: st
const questRankData = questsData.ranks[questRank]; const questRankData = questsData.ranks[questRank];
for (const quest of questRankData) { for (const quest of questRankData) {
const id = quest.id; const id = hash(JSON.stringify(quest));
const phases = [] as Array<QuestPhase>;
if (Array.isArray(quest?.phases)) {
for (const phase of quest.phases) {
const phaseTypes: PhaseType[] = [];
if (Array.isArray(phase?.types)) {
for (const type of phase.types) {
const phaseType = PhaseType[type as keyof typeof PhaseType];
if (!phaseType) {
console.error(`Invalid phase type: ${type}`);
continue;
}
phaseTypes.push(phaseType);
}
}
phases.push(new QuestPhase(
phaseTypes,
phase.maxPoints,
0,
));
}
}
quests[questRank][id] = new Quest( quests[questRank][id] = new Quest(
id, id,
questRank, questRank,
quest.title, quest.title,
quest.text, quest.text,
phases,
); );
} }
} }
@@ -144,3 +171,11 @@ export function removeAlreadyHiredAdventurers(
} }
return adventurersForHire; return adventurersForHire;
} }
function hash(str: string): string {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = (hash * 33) ^ str.charCodeAt(i);
}
return (hash >>> 0).toString(16);
}
@@ -1,5 +1,6 @@
import type {Adventurer} from "@/classes/Adventurer"; import type {Adventurer} from "@/classes/Adventurer";
import {QuestRank} from "@/classes/QuestRank"; import {QuestRank} from "@/classes/QuestRank";
import QuestPhase from "@/classes/quests/QuestPhase";
export class Quest { export class Quest {
id: string; id: string;
@@ -7,9 +8,8 @@ export class Quest {
title: string; title: string;
text: string; text: string;
adventurers: Array<Adventurer>; adventurers: Array<Adventurer>;
phases: QuestPhase[] = [];
maxAdventurers: number; maxAdventurers: number;
progressPoints: number;
maxProgress: number;
expReward: number; expReward: number;
goldReward: number; goldReward: number;
@@ -18,7 +18,7 @@ export class Quest {
rank: QuestRank, rank: QuestRank,
title: string, title: string,
text: string, text: string,
maxProgress: number = 1, phases: QuestPhase[],
expReward: number = 0, expReward: number = 0,
goldReward: number = 0, goldReward: number = 0,
maxAdventurers: number = 1 maxAdventurers: number = 1
@@ -27,16 +27,75 @@ export class Quest {
this.rank = rank; this.rank = rank;
this.title = title; this.title = title;
this.text = text; this.text = text;
this.maxProgress = maxProgress;
this.expReward = expReward; this.expReward = expReward;
this.goldReward = goldReward; this.goldReward = goldReward;
this.progressPoints = 0;
this.adventurers = []; this.adventurers = [];
this.maxAdventurers = maxAdventurers; this.maxAdventurers = maxAdventurers;
for (const phase of phases) {
this.phases.push(new QuestPhase(Array.from(phase.types), phase.maxPoints, phase.points));
}
} }
getPercentProgress(): number { getPercentProgress(): number {
return Math.round(this.progressPoints / this.maxProgress * 100); let maxProgress = this.getMaxProgress();
let progressPoints = this.getProgress();
if (maxProgress === 0) return 0;
return progressPoints / maxProgress * 100;
}
isCompleted(): boolean {
for (const phase of this.phases) {
if (!phase.completed()) return false;
}
return true;
}
getMaxProgress(): number {
let maxProgress = 0;
for (const phase of this.phases) {
maxProgress += phase.maxPoints;
}
return maxProgress;
}
getProgress(): number {
let progressPoints = 0;
for (const phase of this.phases) {
progressPoints += phase.points;
}
return progressPoints;
}
serialize(): {[key: string]: any} {
return {
id: this.id,
rank: this.rank,
title: this.title,
text: this.text,
phases: this.phases.map(phase => phase.serialize()),
expReward: this.expReward,
goldReward: this.goldReward,
maxAdventurers: this.maxAdventurers,
};
}
static deserialize(data: {[key: string]: any}): Quest {
if (!data || typeof data !== 'object') {
throw new Error("Invalid data for Quest deserialization");
}
const phases = (data.phases || []).map((phaseData: any) => QuestPhase.deserialize(phaseData));
return new Quest(
data.id,
data.rank,
data.title,
data.text,
phases,
data.expReward || 0,
data.goldReward || 0,
data.maxAdventurers || 1
);
} }
} }
@@ -49,47 +108,47 @@ export class Quest {
*/ */
export function getQuestWithRewards(quest: Quest, expModifier: number = 1, goldModifier: number = 1) { export function getQuestWithRewards(quest: Quest, expModifier: number = 1, goldModifier: number = 1) {
let maxProgress = 1; let rewardValue = 1;
switch (quest.rank) { switch (quest.rank) {
case QuestRank.S: case QuestRank.S:
// at level 30 adventurers have ~6513 dps, this will take 30 seconds on level 30 // at level 30 adventurers have ~6513 dps, this will take 30 seconds on level 30
maxProgress = 195390; rewardValue = 195390;
break; break;
case QuestRank.A: case QuestRank.A:
// at level 25 adventurers have ~2051 dps, this will take 15 seconds on level 25 // at level 25 adventurers have ~2051 dps, this will take 15 seconds on level 25
maxProgress = 30770; rewardValue = 30770;
break; break;
case QuestRank.B: case QuestRank.B:
// at level 20 adventurers have ~645 dps, this will take 15 seconds on level 20 // at level 20 adventurers have ~645 dps, this will take 15 seconds on level 20
maxProgress = 9690; rewardValue = 9690;
break; break;
case QuestRank.C: case QuestRank.C:
// at level 15 adventurers have ~203 dps, this will take 15 seconds on level 15 // at level 15 adventurers have ~203 dps, this will take 15 seconds on level 15
maxProgress = 3045; rewardValue = 3045;
break; break;
case QuestRank.D: case QuestRank.D:
// at level 10 adventurers have ~64 dps, this will take 15 seconds on level 10 // at level 10 adventurers have ~64 dps, this will take 15 seconds on level 10
maxProgress = 960; rewardValue = 960;
break; break;
case QuestRank.E: case QuestRank.E:
// at level 5 adventurers have ~20 dps, this will take 15 seconds on level 5 // at level 5 adventurers have ~20 dps, this will take 15 seconds on level 5
maxProgress = 300; rewardValue = 300;
break; break;
case QuestRank.F: case QuestRank.F:
// at level 1 adventurers have ~8 dps, this will take 15 seconds on level 1 // at level 1 adventurers have ~8 dps, this will take 15 seconds on level 1
maxProgress = 120; rewardValue = 120;
break; break;
} }
let goldReward = Math.floor(maxProgress/6 * goldModifier); let goldReward = Math.floor(rewardValue/6 * goldModifier);
let expReward = Math.floor((Math.floor(maxProgress/120) - maxProgress/1000) * expModifier); let expReward = Math.floor((Math.floor(rewardValue/120) - rewardValue/1000) * expModifier);
// add some randomness to the rewards // add some randomness to the rewards
goldReward = Math.floor(randomNumberBetween(goldReward * 0.95, goldReward * 1.1)); goldReward = Math.floor(randomNumberBetween(goldReward * 0.95, goldReward * 1.1));
expReward = Math.max(1, Math.floor(randomNumberBetween(expReward * 0.95, expReward * 1.2))); expReward = Math.max(1, Math.floor(randomNumberBetween(expReward * 0.95, expReward * 1.2)));
return new Quest(quest.id, quest.rank, quest.title, quest.text, maxProgress, expReward, goldReward); return new Quest(quest.id, quest.rank, quest.title, quest.text, quest.phases, expReward, goldReward);
} }
function randomNumberBetween(min: number, max: number) { function randomNumberBetween(min: number, max: number) {
+67
View File
@@ -0,0 +1,67 @@
import type {Adventurer} from "@/classes/Adventurer";
import {PhaseType} from "@/classes/quests/QuestPhaseType";
export default class QuestPhase {
types: Set<PhaseType>;
points: number;
maxPoints: number;
constructor(
types: PhaseType[],
maxPoints: number,
points: number = 0,
) {
this.types = new Set<PhaseType>(types);
this.points = Math.max(0, points);
this.maxPoints = Math.max(1, maxPoints);
}
/**
* Get how many points should be added each tick based on adventurers on a task.
*/
getPointIncrement(adventurers: Array<Adventurer>): number {
// TODO add point multiplier based on adventurer stats
return 0.25;
}
public tick(adventurers: Array<Adventurer> = []) {
if (this.completed()) return;
const pointsToAdd = this.getPointIncrement(adventurers);
this.points = Math.max(Math.min(this.points + pointsToAdd, this.maxPoints), 0);
}
public completed(): boolean {
return this.points >= this.maxPoints;
}
public serialize(): string {
return JSON.stringify({
types: Array.from(this.types),
points: this.points,
maxPoints: this.maxPoints,
});
}
public static deserialize(data: string): QuestPhase {
const parsedData = JSON.parse(data);
if (typeof parsedData?.points !== 'number') {
throw new Error("Invalid data: 'points' must be a number.");
}
if (typeof parsedData?.maxPoints !== 'number') {
throw new Error("Invalid data: 'maxPoints' must be a number.");
}
if (!Array.isArray(parsedData?.types)) {
throw new Error("Invalid data: 'types' must be an array.");
}
const types = parsedData.types.map((type: string) => PhaseType[type as keyof typeof PhaseType]);
return new QuestPhase(
types,
parsedData.maxPoints,
parsedData.points
);
}
}
+9
View File
@@ -0,0 +1,9 @@
export enum PhaseType {
TRAVEL = "travel",
FIGHT = "fight",
GATHER = "gather",
PHYSICAL = "physical",
MENTAL = "mental",
}
+2 -2
View File
@@ -19,7 +19,7 @@
</span> </span>
<span <span
:title="Object.keys(adventurersForHire).length > 0 ? 'Dismiss' : ''" :title="Object.keys(adventurersForHire).length > 0 ? 'Dismiss' : ''"
:class="{disabled: Object.keys(adventurersForHire).length <= 0}" :class="{disabled: Object.keys(adventurers).length <= 0}"
@click="dismissAdventurer(adventurerForHire)" @click="dismissAdventurer(adventurerForHire)"
> >
@@ -73,7 +73,7 @@ export default defineComponent({
this.$emit("hireAdventurer", adventurer); this.$emit("hireAdventurer", adventurer);
}, },
dismissAdventurer(adventurer: Adventurer) { dismissAdventurer(adventurer: Adventurer) {
if (Object.keys(this.adventurersForHire).length <= 0) return; if (Object.keys(this.adventurers).length <= 0) return;
this.$emit("dismissAdventurer", adventurer); this.$emit("dismissAdventurer", adventurer);
}, },
previewAdventurer(adventurer: Adventurer): void { previewAdventurer(adventurer: Adventurer): void {
+4 -4
View File
@@ -37,7 +37,7 @@
</div> </div>
<div class="progressWrap"> <div class="progressWrap">
<span class="progress"></span> <span class="progress"></span>
<span class="percentage">{{ `${progressPercentage.toFixed(2)}%` }}</span> <span class="percentage">{{ `${missive.getPercentProgress().toFixed(2)}%` }}</span>
</div> </div>
<h3>Rewards</h3> <h3>Rewards</h3>
<div class="rewards"> <div class="rewards">
@@ -48,7 +48,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type {Quest} from "@/classes/Quest"; import type {Quest} from "@/classes/quests/Quest";
import AdventurerComponent from "@/components/AdventurerMiniComponent.vue"; import AdventurerComponent from "@/components/AdventurerMiniComponent.vue";
import type {Adventurer} from "@/classes/Adventurer"; import type {Adventurer} from "@/classes/Adventurer";
import {defineComponent, type PropType} from "vue"; import {defineComponent, type PropType} from "vue";
@@ -61,7 +61,7 @@ export default defineComponent({
components: {Parchment, WaterStain, DrinkStain, AdventurerComponent}, components: {Parchment, WaterStain, DrinkStain, AdventurerComponent},
computed: { computed: {
progressPercentageValue(): string { progressPercentageValue(): string {
return `${this.missive.progressPoints / this.missive.maxProgress * 100}%`; return `${this.missive.getPercentProgress()}%`;
}, },
notBusyAdventurers(): Adventurer[] { notBusyAdventurers(): Adventurer[] {
return Object.values(this.adventurers).filter(adventurer => !adventurer.busy); return Object.values(this.adventurers).filter(adventurer => !adventurer.busy);
@@ -94,7 +94,7 @@ export default defineComponent({
methods: { methods: {
updateProgress() { updateProgress() {
if (this.missive === undefined) return; if (this.missive === undefined) return;
this.progressPercentage = this.missive.progressPoints / this.missive.maxProgress * 100; this.progressPercentage = this.missive.getPercentProgress();
}, },
randomNumber(min: number, max: number) { randomNumber(min: number, max: number) {
return Math.random() * (max - min) + min; return Math.random() * (max - min) + min;
@@ -91,7 +91,7 @@ export default defineComponent({
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
width: 100%; width: 100%;
overflow-y: auto; user-select: text;
} }
.changelog-entry { .changelog-entry {
+6 -6
View File
@@ -2,17 +2,17 @@
<section> <section>
<QuestGroup <QuestGroup
:adventurers="adventurers" :adventurers="adventurers"
:quests="quests.filter(quest => quest.progressPoints < quest.maxProgress)" :quests="quests.filter(quest => quest.getProgress() < quest.getMaxProgress())"
:finalizeQuest="finalizeQuest" :finalizeQuest="finalizeQuest"
label="Quests" label="Quests"
v-show="quests.filter(quest => quest.progressPoints < quest.maxProgress).length > 0" v-show="quests.filter(quest => quest.getProgress() < quest.getMaxProgress()).length > 0"
/> />
<QuestGroup <QuestGroup
:finalize-quest="finalizeQuest" :finalize-quest="finalizeQuest"
:adventurers="adventurers" :adventurers="adventurers"
:quests="quests.filter(quest => quest.progressPoints >= quest.maxProgress)" :quests="quests.filter(quest => quest.getProgress() >= quest.getMaxProgress())"
label="Completed Quests" label="Completed Quests"
v-show="quests.filter(quest => quest.progressPoints >= quest.maxProgress).length > 0" v-show="quests.filter(quest => quest.getProgress() >= quest.getMaxProgress()).length > 0"
/> />
</section> </section>
</template> </template>
@@ -21,7 +21,7 @@
import {defineComponent, type PropType} from "vue"; import {defineComponent, type PropType} from "vue";
import AdventurerComponent from "@/components/AdventurerMiniComponent.vue"; import AdventurerComponent from "@/components/AdventurerMiniComponent.vue";
import type {Adventurer} from "@/classes/Adventurer"; import type {Adventurer} from "@/classes/Adventurer";
import type {Quest} from "@/classes/Quest"; import type {Quest} from "@/classes/quests/Quest";
import {Guild} from "@/classes/Guild"; import {Guild} from "@/classes/Guild";
import QuestMissive from "@/components/QuestMissive.vue"; import QuestMissive from "@/components/QuestMissive.vue";
import QuestGroup from "@/components/QuestGroup.vue"; import QuestGroup from "@/components/QuestGroup.vue";
@@ -53,7 +53,7 @@ export default defineComponent({
methods: { methods: {
// This is a workaround for vue not reporting Quest as Quest in v-for // This is a workaround for vue not reporting Quest as Quest in v-for
finalizeQuest(quest: any | Quest): void { finalizeQuest(quest: any | Quest): void {
if (quest.progressPoints < quest.maxProgress) return; if (!quest.isCompleted()) return;
this.$emit('finalizeQuest', quest) this.$emit('finalizeQuest', quest)
}, },
} }