hook in data sources for adventurers and quests,

programatically generate quest rewards,
change damage math,
This commit is contained in:
2023-03-25 14:42:17 +01:00
parent 9ac13c4cae
commit c71eeda11f
13 changed files with 163 additions and 32 deletions
@@ -3,36 +3,36 @@
"id": "rincewind-diskworld", "id": "rincewind-diskworld",
"name": "Rincewind", "name": "Rincewind",
"portrait": "/img/adventurers/rincewind.png", "portrait": "/img/adventurers/rincewind.png",
"attackPerLevel": 1.7 "attackExponent": 1.09
}, },
{ {
"id": "fran-sword-isekai", "id": "fran-sword-isekai",
"name": "Fran", "name": "Fran",
"portrait": "/img/adventurers/fran.png", "portrait": "/img/adventurers/fran.png",
"attackPerLevel": 2.2 "attackExponent": 1.115
}, },
{ {
"id": "kazuma-konosuba", "id": "kazuma-konosuba",
"name": "Kazuma", "name": "Kazuma",
"portrait": "/img/adventurers/kazuma.png", "portrait": "/img/adventurers/kazuma.png",
"attackPerLevel": 1.8 "attackExponent": 1.1
}, },
{ {
"id": "rein-beast-tamer", "id": "rein-beast-tamer",
"name": "Rein", "name": "Rein",
"portrait": "/img/adventurers/rein.png", "portrait": "/img/adventurers/rein.png",
"attackPerLevel": 1.9 "attackExponent": 1.1
}, },
{ {
"id": "momon-overlord", "id": "momon-overlord",
"name": "Momon", "name": "Momon",
"portrait": "/img/adventurers/momon.png", "portrait": "/img/adventurers/momon.png",
"attackPerLevel": 2.1 "attackExponent": 1.11
}, },
{ {
"id": "goblin-slayer", "id": "goblin-slayer",
"name": "Goblin Slayer", "name": "Goblin Slayer",
"portrait": "/img/adventurers/goblin-slayer.png", "portrait": "/img/adventurers/goblin-slayer.png",
"attackPerLevel": 2 "attackExponent": 1.1
} }
] ]
+19 -10
View File
@@ -24,10 +24,10 @@ import {RouterLink, RouterView} from 'vue-router'</script>
<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 {Quest} from "@/classes/Quest"; import {getQuestWithRewards, Quest} from "@/classes/Quest";
import {Guild} from "@/classes/Guild"; import {Guild} from "@/classes/Guild";
import {getFromString, QuestRank} from "@/classes/QuestRank"; import {getFromString, QuestRank} from "@/classes/QuestRank";
import {GameData, loadGame, saveGame} from "@/GameData"; import {GameData, loadAvailableQuests, loadGame, saveGame} from "@/GameData";
export default defineComponent({ export default defineComponent({
name: "GuildView", name: "GuildView",
@@ -56,8 +56,7 @@ export default defineComponent({
F: null as null|number, F: null as null|number,
} as { [key: string]: null|number }, } as { [key: string]: null|number },
lastRecruitHandled: null as null|number, lastRecruitHandled: null as null|number,
adventurers: { adventurers: {} as { [key: string]: Adventurer },
} as { [key: string]: Adventurer },
quests: { quests: {
F: { F: {
"1": new Quest("1", QuestRank.F, "Frog Frenzy", "Kill 10 demon frogs.", 120, 1, 25), "1": new Quest("1", QuestRank.F, "Frog Frenzy", "Kill 10 demon frogs.", 120, 1, 25),
@@ -118,7 +117,7 @@ export default defineComponent({
} }
for (const adventurerId in missive.adventurers) { for (const adventurerId in missive.adventurers) {
const adventurer = missive.adventurers[adventurerId]; const adventurer = missive.adventurers[adventurerId];
const attack = adventurer.attackPerLevel * adventurer.level; const attack = adventurer.getAttack();
missive.progressPoints = Math.min(missive.progressPoints + attack, missive.maxProgress); missive.progressPoints = Math.min(missive.progressPoints + attack, missive.maxProgress);
} }
} }
@@ -142,7 +141,7 @@ export default defineComponent({
const questsForRank = this.quests[rank] as { [key: string]: Quest }; const questsForRank = this.quests[rank] as { [key: string]: Quest };
const randomId = keys.length * Math.random() << 0; const randomId = keys.length * Math.random() << 0;
const randomIdString = keys[randomId] as string; const randomIdString = keys[randomId] as string;
return questsForRank[randomIdString]; return getQuestWithRewards(questsForRank[randomIdString]);
}, },
createMissive(questToAdd: Quest, rank: QuestRank) { createMissive(questToAdd: Quest, rank: QuestRank) {
const quest = JSON.parse(JSON.stringify(questToAdd)); const quest = JSON.parse(JSON.stringify(questToAdd));
@@ -162,9 +161,18 @@ export default defineComponent({
for (const id in saveData.adventurers) { for (const id in saveData.adventurers) {
const data = saveData.adventurers[id]; const data = saveData.adventurers[id];
const adventurer = new Adventurer(data.id, data.name, data.portrait, data.attackPerLevel, data.level, data.exp); try {
adventurer.busy = data.busy; const adventurer = new Adventurer(
adventurers[data.id] = adventurer; data.id,
data.name,
data.portrait,
data.attackExponent ?? 1.1,
data.level ?? 1,
data.exp ?? 0
);
adventurer.busy = data.busy;
adventurers[data.id] = adventurer;
} catch (e) {}
} }
this.adventurers = adventurers; this.adventurers = adventurers;
@@ -194,8 +202,9 @@ export default defineComponent({
window.location.reload(); window.location.reload();
} }
}, },
mounted() { async mounted() {
this.loadGame(); this.loadGame();
this.quests = await loadAvailableQuests();
setInterval(() => { setInterval(() => {
saveGame(new GameData( saveGame(new GameData(
+67 -4
View File
@@ -1,6 +1,7 @@
import type {Guild} from "@/classes/Guild"; import type {Guild} from "@/classes/Guild";
import type {Adventurer} from "@/classes/Adventurer"; import {Adventurer} from "@/classes/Adventurer";
import type {Quest} from "@/classes/Quest"; import {Quest} from "@/classes/Quest";
import {getFromString, QuestRank} from "@/classes/QuestRank";
export class GameData { export class GameData {
guild: Guild; guild: Guild;
@@ -47,7 +48,69 @@ export function loadGame(): GameData | null {
parsedGame.lastQuestGot, parsedGame.lastQuestGot,
parsedGame.lastRecruitAction parsedGame.lastRecruitAction
); );
} }
export async function loadAvailableQuests(): Promise<{ [key: string]: { [key: string]: Quest } }> {
const quests = {
S: {} as { [key: string]: Quest },
A: {} as { [key: string]: Quest },
B: {} as { [key: string]: Quest },
C: {} as { [key: string]: Quest },
D: {} as { [key: string]: Quest },
E: {} as { [key: string]: Quest },
F: {} as { [key: string]: Quest },
} as { [key: string]: { [key: string]: Quest } };
for (const rank in quests) {
const response = await fetch(`data/quests/Rank${rank}.json`);
if (response.status !== 200) {
console.error("Failed to load quests");
alert("Failed to load quests. Please try refreshing the page.");
return quests;
}
const questData = await response.json();
let id = 0;
for (const quest of questData) {
id++;
quests[rank.toString()][id] = new Quest(
id.toString(),
getFromString(rank as QuestRank),
quest.title,
quest.text,
1,
0,
0
);
}
}
console.log(quests);
return quests;
}
export async function loadAdventurersForHire(currentAdventurerIds: Array<string> = []): Promise<Array<Adventurer>> {
const response = await fetch("data/adventurers.json");
if (response.status !== 200) {
console.error("Failed to load adventurers");
alert("Failed to load adventurers. Please try refreshing the page.");
return [];
}
const adventurerData = await response.json();
const adventurers = [] as Array<Adventurer>;
for (const adventurer of adventurerData) {
if (currentAdventurerIds.includes(adventurer.id)) continue;
adventurers.push(new Adventurer(
adventurer.id,
adventurer.name,
adventurer.portrait,
adventurer.attackExponent,
adventurer.level,
adventurer.exp,
));
}
return adventurers;
}
+11 -3
View File
@@ -4,21 +4,21 @@ export class Adventurer {
portrait: string; portrait: string;
level: number; level: number;
exp: number; exp: number;
attackPerLevel: number; attackExponent: number;
busy: boolean; busy: boolean;
constructor( constructor(
id: string, id: string,
name: string, name: string,
portrait: string, portrait: string,
attackPerLevel: number, attackExponent: number,
level: number = 1, level: number = 1,
exp: number = 0 exp: number = 0
) { ) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.portrait = portrait; this.portrait = portrait;
this.attackPerLevel = attackPerLevel; this.attackExponent = attackExponent;
this.level = level; this.level = level;
this.exp = exp; this.exp = exp;
this.busy = false; this.busy = false;
@@ -44,4 +44,12 @@ export class Adventurer {
return (this.exp / this.getNextLevelExpRequirement()) * 100; return (this.exp / this.getNextLevelExpRequirement()) * 100;
} }
getAttack(): number {
return Math.floor(2 * this.level ^ this.attackExponent);
}
getDPS(): number {
return this.getAttack() * 4;
}
} }
+54 -1
View File
@@ -1,5 +1,5 @@
import type {Adventurer} from "@/classes/Adventurer"; import type {Adventurer} from "@/classes/Adventurer";
import type {QuestRank} from "@/classes/QuestRank"; import {QuestRank} from "@/classes/QuestRank";
export class Quest { export class Quest {
id: string; id: string;
@@ -29,3 +29,56 @@ export class Quest {
} }
} }
/**
* Generate rewards for a quest and return it
* @param quest
*/
export function getQuestWithRewards(quest: Quest) {
let maxProgress = 1;
switch (quest.rank) {
case QuestRank.S:
// at level 30 adventurers have ~2353 dps, this will take 30 seconds on level 30
maxProgress = 70590
break;
case QuestRank.A:
// at level 25 adventurers have ~1122 dps, this will take 15 seconds on level 25
maxProgress = 16800
break;
case QuestRank.B:
// at level 20 adventurers have ~564 dps, this will take 15 seconds on level 20
maxProgress = 8460;
break;
case QuestRank.C:
// at level 15 adventurers have ~256 dps, this will take 15 seconds on level 15
maxProgress = 3840;
break;
case QuestRank.D:
// at level 10 adventurers have ~103 dps, this will take 15 seconds on level 10
maxProgress = 1545;
break;
case QuestRank.E:
// at level 5 adventurers have ~45 dps, this will take 15 seconds on level 5
maxProgress = 675;
break;
case QuestRank.F:
// at level 1 adventurers have ~8 dps, this will take 15 seconds on level 1
maxProgress = 120;
break;
}
let goldReward = Math.floor(maxProgress/6);
let expReward = Math.floor(Math.floor(maxProgress/120) - maxProgress/1000);
// add some randomness to the rewards
goldReward = Math.floor(randomNumberBetween(goldReward * 0.95, goldReward * 1.1));
expReward = Math.max(1, Math.floor(randomNumberBetween(expReward * 0.95, expReward * 1.1)));
return new Quest(quest.id, quest.rank, quest.title, quest.text, maxProgress, expReward, goldReward);
}
function randomNumberBetween(min: number, max: number) {
return Math.random() * (max - min) + min;
}
+6 -8
View File
@@ -44,6 +44,7 @@ import type {PropType} from "vue";
import {defineComponent} from "vue"; import {defineComponent} from "vue";
import AdventurerTile from "@/components/AdventurerTile.vue"; import AdventurerTile from "@/components/AdventurerTile.vue";
import {Adventurer} from "@/classes/Adventurer"; import {Adventurer} from "@/classes/Adventurer";
import { loadAdventurersForHire } from "@/GameData";
export default defineComponent({ export default defineComponent({
name: "RecruitView", name: "RecruitView",
@@ -51,13 +52,7 @@ export default defineComponent({
data: () => { data: () => {
return { return {
currentlyForHire: null as Adventurer|null, currentlyForHire: null as Adventurer|null,
adventurersForHire: [ adventurersForHire: [] as Array<Adventurer>,
new Adventurer("rincewind-diskworld", "Rincewind", "/img/adventurers/rincewind.png", 2),
new Adventurer("fran-sword-isekai", "Fran", "/img/adventurers/fran.png", 3),
new Adventurer("kazuma-konosuba", "Kazuma", "/img/adventurers/kazuma.png", 2),
new Adventurer("rein-beast-tamer", "Rein", "/img/adventurers/rein.png", 2),
new Adventurer("momon-overlord", "Momon", "/img/adventurers/momon.png", 2),
] as Array<Adventurer>,
} }
}, },
props: { props: {
@@ -109,7 +104,10 @@ export default defineComponent({
window.localStorage.removeItem("currentlyForHire"); window.localStorage.removeItem("currentlyForHire");
} }
}, },
mounted() { async mounted() {
this.adventurersForHire = await loadAdventurersForHire();
if (Object.keys(this.adventurers).length <= 0) { if (Object.keys(this.adventurers).length <= 0) {
this.currentlyForHire = this.adventurersForHire[0]; this.currentlyForHire = this.adventurersForHire[0];
window.localStorage.setItem("currentlyForHire", this.adventurersForHire[0].id); window.localStorage.setItem("currentlyForHire", this.adventurersForHire[0].id);