Compare commits

...

33 Commits

Author SHA1 Message Date
YouHaveTrouble 1b1695bf1f bump version 2023-07-03 23:52:34 +02:00
YouHaveTrouble f524ce5e2e make a and s rank quests appear more often 2023-07-03 23:51:25 +02:00
YouHaveTrouble d14ef20fb1 rebalance adventurer capacity cost 2023-07-03 23:25:27 +02:00
YouHaveTrouble 83070b632b update depends 2023-07-03 23:25:16 +02:00
YouHaveTrouble 2723801c9c update depends 2023-07-03 22:51:40 +02:00
YouHaveTrouble 5d1ce0a262 make gold update actually work 2023-07-03 22:38:50 +02:00
YouHaveTrouble 74ebac9862 rebalance upgrades 2023-07-03 22:38:38 +02:00
YouHaveTrouble 2a5b162605 bump version 2023-06-26 19:44:57 +02:00
YouHaveTrouble d6e897c289 save data export and import 2023-06-26 19:44:32 +02:00
YouHaveTrouble a2b8b40464 download news once per hour 2023-06-26 18:02:38 +02:00
YouHaveTrouble bdaa3bc029 news 2023-06-26 17:43:12 +02:00
YouHaveTrouble 816c1fb0e7 make adventurer choice closeable by clicking on the slot again 2023-06-26 17:12:46 +02:00
YouHaveTrouble acdd8f6964 prevent phones from turning off display when the game's running 2023-06-26 16:03:39 +02:00
YouHaveTrouble 5fa8f2cf15 tooltip on technical menu 2023-06-26 15:54:14 +02:00
YouHaveTrouble 5a818fb142 prototype tooltips 2023-06-22 14:18:08 +02:00
YouHaveTrouble 6625a5b9de move title styles to global file 2023-06-20 20:06:54 +02:00
YouHaveTrouble cf69b92934 bump version 2023-06-20 19:58:31 +02:00
YouHaveTrouble 005cde4a01 remove debug 2023-06-20 19:57:34 +02:00
YouHaveTrouble 1b94d676e7 minor fixes 2023-06-20 19:56:36 +02:00
YouHaveTrouble 7cda820a99 add metadata 2023-06-20 19:56:28 +02:00
YouHaveTrouble 7281fdab60 dynamically load home view 2023-06-20 19:39:13 +02:00
YouHaveTrouble 3b9441b555 improve loading process 2023-06-20 19:37:27 +02:00
YouHaveTrouble c2abfd6dfd add loading screen to hide loading assets and not yet loaded gamestate 2023-06-20 19:18:50 +02:00
YouHaveTrouble f05c393600 new favicon 2023-06-20 19:18:09 +02:00
YouHaveTrouble 23b6f4b4f8 update packages and fix breaking changes 2023-06-20 18:12:20 +02:00
YouHaveTrouble d5195aa157 bump version 2023-05-12 21:52:06 +02:00
YouHaveTrouble 15072fe2c8 fix up ts errors 2023-05-12 21:52:00 +02:00
YouHaveTrouble ba4a6cb2a2 add quest auto-finish upgrade 2023-05-12 21:47:23 +02:00
YouHaveTrouble 4847d22f7e cancel update and save tasks on main component unload 2023-05-12 19:58:47 +02:00
YouHaveTrouble 2c053cc3eb increased cost of adventurer capacity upgrade 2023-05-12 19:13:48 +02:00
YouHaveTrouble c55e0c8bb6 added quest gold modifier upgrade 2023-05-12 18:41:40 +02:00
YouHaveTrouble 29aafedcfa fix max guild level being displayed on level 7 when upgraded to it 2023-05-12 18:25:45 +02:00
YouHaveTrouble 8070c855a0 drastically reduce price and scaling of exp modifier upgrade 2023-05-12 18:24:59 +02:00
26 changed files with 1195 additions and 708 deletions
+20 -8
View File
@@ -1,18 +1,30 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Guild Master - Adventurer's guild management game</title> <title>Guild Master - Adventurer's guild management game</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=EB+Garamond&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=EB+Garamond&display=swap" rel="stylesheet">
<meta name="description"
content="Guild Master is a browser game where you manage your own adventurer's guild!"/>
<meta property="twitter:title" content="Guild Master - Adventurer's guild management game"/>
<meta property="twitter:image" content="https://guildmaster.yht.one/img/compass_rose.png"/>
<meta property="twitter:description"
content="Guild Master is a browser game where you manage your own adventurer's guild!"/>
<meta property="og:title" content="Guild Master - Adventurer's guild management game"/>
<meta property="og:url" content="https://guildmaster.yht.one/"/>
<meta property="og:description"
content="Guild Master is a browser game where you manage your own adventurer's guild!"/>
<meta property="og:image" content="https://guildmaster.yht.one/img/compass_rose.png"/>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<noscript> <noscript>
This is a javascript game. You need to enable javascript for it to work. This is a javascript game. You need to enable javascript for it to work.
</noscript> </noscript>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
+1
View File
@@ -0,0 +1 @@
Looking for artists for assets for this game! Need backgrounds, icons and character portraits! Join discord for more info!
+566 -600
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -1,6 +1,6 @@
{ {
"name": "adventurers-guild", "name": "adventurers-guild",
"version": "0.6.0", "version": "0.10.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -11,19 +11,19 @@
}, },
"dependencies": { "dependencies": {
"@vueuse/components": "^9.13.0", "@vueuse/components": "^9.13.0",
"sass": "^1.59.3", "sass": "^1.63.6",
"vue": "^3.2.45", "vue": "^3.3.4",
"vue-router": "^4.1.6" "vue-router": "^4.2.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.11.12", "@types/node": "^18.16.19",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.2.3",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.4.0",
"eslint": "^8.36.0", "eslint": "^8.44.0",
"eslint-plugin-vue": "^9.9.0", "eslint-plugin-vue": "^9.15.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"typescript": "~4.7.4", "typescript": "~5.1.6",
"vite": "^4.0.0", "vite": "4.3.9",
"vue-tsc": "^1.0.12" "vue-tsc": "^1.8.3"
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

+183 -35
View File
@@ -1,13 +1,47 @@
<script setup lang="ts"> <script setup lang="ts">
import {RouterLink, RouterView} from 'vue-router'</script> import {RouterLink, RouterView} from 'vue-router'
import {version} from "../package.json"
</script>
<template> <template>
<section class="loading-screen" :class="{disabled: !loading}">
<div class="title panel note-paper">
<h1>Guild Master</h1>
<h3>Adventurer's guild management game</h3>
<small>v{{ version }}</small>
<div class="lds-ring">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<h3>Loading assets...</h3>
</div>
</section>
<header> <header>
<nav> <nav>
<RouterLink :to="{name: 'guild'}">Guild</RouterLink> <RouterLink
<RouterLink :to="{name: 'quests'}">Quests</RouterLink> :to="{name: 'guild'}"
<RouterLink :to="{name: 'adventurers'}">Adventurers</RouterLink> >
<RouterLink :to="{name: 'technical'}"><img class="icon" src="/img/icons/cog.svg" alt="Technical"></RouterLink> Guild
</RouterLink>
<RouterLink
:to="{name: 'quests'}"
>
Quests
</RouterLink>
<RouterLink
:to="{name: 'adventurers'}"
>
Adventurers
</RouterLink>
<RouterLink
data-tooltip="Technical information"
data-tooltip-position="bottom"
:to="{name: 'technical'}"
>
<img class="icon" src="/img/icons/cog.svg" alt="Technical">
</RouterLink>
</nav> </nav>
</header> </header>
<RouterView <RouterView
@@ -15,8 +49,8 @@ import {RouterLink, RouterView} from 'vue-router'</script>
:adventurers="adventurers" :adventurers="adventurers"
:quests="missives" :quests="missives"
:adventurerForHire="adventurerForHire" :adventurerForHire="adventurerForHire"
:news="news"
@finalizeQuest="finalizeQuest($event)" @finalizeQuest="finalizeQuest($event)"
@wipeSave="resetSave()"
@recruitActionTaken="recruitAction($event)" @recruitActionTaken="recruitAction($event)"
/> />
</template> </template>
@@ -39,11 +73,18 @@ import type {GuildUpgrade} from "@/classes/GuildUpgrade";
import AdventurerCapacityUpgrade from "@/classes/guildUpgrades/AdventurerCapacityUpgrade"; import AdventurerCapacityUpgrade from "@/classes/guildUpgrades/AdventurerCapacityUpgrade";
import {getNewAdventurerForHire} from "@/classes/Recruitment"; import {getNewAdventurerForHire} from "@/classes/Recruitment";
import QuestExpUpgrade from "@/classes/guildUpgrades/QuestExpUpgrade"; import QuestExpUpgrade from "@/classes/guildUpgrades/QuestExpUpgrade";
import QuestGoldUpgrade from "@/classes/guildUpgrades/QuestGoldUpgrade";
import AutoFinishQuestsUpgrade from "@/classes/guildUpgrades/AutoFinishQuestsUpgrade";
export default defineComponent({ export default defineComponent({
name: "GuildView", name: "GuildView",
data: () => ({ data: () => ({
loading: true as boolean,
screenWakeLock: null as null | WakeLockSentinel,
guild: new Guild(1, 500), guild: new Guild(1, 500),
gameTickTask: null as null | number,
gameSaveTask: null as null | number,
news: "" as string,
lastQuestGot: { lastQuestGot: {
S: null as null | number, S: null as null | number,
A: null as null | number, A: null as null | number,
@@ -78,7 +119,13 @@ export default defineComponent({
missive.progressPoints = 0; missive.progressPoints = 0;
continue; continue;
} }
if (missive.progressPoints >= missive.maxProgress) continue; if (missive.progressPoints >= missive.maxProgress) {
if (this.guild.autoFinishQuestsUpgrade.getRanksToAutoFinishQuestsIn().includes(rank)) {
this.finalizeQuest(missive);
continue;
}
continue;
}
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.getAttack(); const attack = adventurer.getAttack();
@@ -133,7 +180,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 getQuestWithRewards(questsForRank[randomIdString], this.guild.expModifier.getModifier()); return getQuestWithRewards(questsForRank[randomIdString], this.guild.expModifier.getModifier(), this.guild.goldModifier.getModifier());
}, },
createMissive(questToAdd: Quest, rank: QuestRank) { createMissive(questToAdd: Quest, rank: QuestRank) {
const quest = JSON.parse(JSON.stringify(questToAdd)); const quest = JSON.parse(JSON.stringify(questToAdd));
@@ -154,6 +201,12 @@ export default defineComponent({
if (saveData.guild.expModifier) { if (saveData.guild.expModifier) {
guildUpgrades.expModifier = new QuestExpUpgrade(saveData.guild.expModifier.level); guildUpgrades.expModifier = new QuestExpUpgrade(saveData.guild.expModifier.level);
} }
if (saveData.guild.goldModifier) {
guildUpgrades.goldModifier = new QuestGoldUpgrade(saveData.guild.goldModifier.level);
}
if (saveData.guild.autoFinishQuestsUpgrade) {
guildUpgrades.autoFinishQuestsUpgrade = new AutoFinishQuestsUpgrade(saveData.guild.autoFinishQuestsUpgrade.level);
}
this.guild = new Guild(saveData.guild.level, saveData.guild.gold, guildUpgrades); this.guild = new Guild(saveData.guild.level, saveData.guild.gold, guildUpgrades);
@@ -163,13 +216,13 @@ export default defineComponent({
const data = saveData.adventurers[id]; const data = saveData.adventurers[id];
try { try {
const adventurer = new Adventurer( const adventurer = new Adventurer(
data.id, data.id,
data.name, data.name,
data.portrait, data.portrait,
data.attackExponent ?? 1.1, data.attackExponent ?? 1.1,
data.level ?? 1, data.level ?? 1,
data.exp ?? 0, data.exp ?? 0,
data.prestige ?? 0, data.prestige ?? 0,
); );
adventurer.busy = data.busy; adventurer.busy = data.busy;
adventurers[data.id] = adventurer; adventurers[data.id] = adventurer;
@@ -208,20 +261,48 @@ export default defineComponent({
} }
} }
}, },
resetSave() { async updateNews() {
if (!confirm("You are about to wipe your save file. Are you sure?")) return; const result = await fetch("https://raw.githubusercontent.com/YouHaveTrouble/GuildMaster/master/news.txt").catch(() => {
window.localStorage.removeItem("savedGame"); return null;
window.location.reload(); });
if (result === null) return;
this.news = await result.text();
} }
}, },
async mounted() { async mounted() {
this.quests = await loadAvailableQuests();
this.adventurersDatabase = await loadAdventurersForHire();
this.loadGame();
setInterval(async () => {
if (this.screenWakeLock) return;
try {
this.screenWakeLock = await navigator.wakeLock.request("screen");
console.debug("Screen wake lock acquired");
} catch (e) {}
}, 1000);
console.debug("Loading game data")
const promises = await Promise.all([
loadAvailableQuests(),
loadAdventurersForHire(),
]);
this.updateNews().then(() => {
setInterval(() => {
this.updateNews();
}, 1000 * 60 * 60);
});
this.quests = promises[0] as { [key: string]: { [key: string]: Quest } };
this.adventurersDatabase = promises[1] as Array<Adventurer>;
console.debug("Game data loaded!")
this.loadGame();
this.adventurersDatabase = removeAlreadyHiredAdventurers(this.adventurersDatabase, this.adventurers); this.adventurersDatabase = removeAlreadyHiredAdventurers(this.adventurersDatabase, this.adventurers);
setInterval(() => { // Wait a second to make sure at least most images load in behind the loader
setTimeout(() => {
this.loading = false;
}, 1000);
this.gameSaveTask = Number(setInterval(() => {
saveGame(new GameData({ saveGame(new GameData({
adventurers: this.adventurers, adventurers: this.adventurers,
guild: this.guild, guild: this.guild,
@@ -230,9 +311,10 @@ export default defineComponent({
lastRecruitAction: this.lastRecruitHandled, lastRecruitAction: this.lastRecruitHandled,
adventurerForHireId: this.adventurerForHire?.id ?? null, adventurerForHireId: this.adventurerForHire?.id ?? null,
})); }));
}, 10 * 1000) }, 10 * 1000));
setInterval(() => {
this.gameTickTask = Number(setInterval(() => {
this.updateMissives(); this.updateMissives();
const now = Number(new Date()); const now = Number(new Date());
@@ -299,7 +381,7 @@ export default defineComponent({
} }
if (this.guild.level < 6) return; if (this.guild.level < 6) return;
if (Number(now) - Number(this.lastQuestGot.A) >= 5 * 60 * 1000) { if (Number(now) - Number(this.lastQuestGot.A) >= 3 * 60 * 1000) {
this.lastQuestGot.A = now; this.lastQuestGot.A = now;
const keys = Object.keys(this.missives[QuestRank.A]); const keys = Object.keys(this.missives[QuestRank.A]);
if (keys.length >= 5) return; if (keys.length >= 5) return;
@@ -310,7 +392,7 @@ export default defineComponent({
} }
if (this.guild.level < 6) return; if (this.guild.level < 6) return;
if (Number(now) - Number(this.lastQuestGot.S) >= 30 * 60 * 1000) { if (Number(now) - Number(this.lastQuestGot.S) >= 5 * 60 * 1000) {
this.lastQuestGot.S = now; this.lastQuestGot.S = now;
const keys = Object.keys(this.missives[QuestRank.S]); const keys = Object.keys(this.missives[QuestRank.S]);
if (keys.length >= 5) return; if (keys.length >= 5) return;
@@ -320,8 +402,12 @@ export default defineComponent({
} }
} }
}, 250); }, 250));
},
beforeUnmount() {
if (this.gameSaveTask) clearInterval(this.gameSaveTask);
if (this.gameTickTask) clearInterval(this.gameTickTask);
} }
}) })
</script> </script>
@@ -345,22 +431,24 @@ nav {
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 1rem; gap: 1rem;
padding: 2rem; padding-inline: 2rem;
background-size: 200px; padding-bottom: 2rem;
background-blend-mode: darken; padding-top: 0.5rem;
background-color: rgba(0, 0, 0, 0.65); background-size: 100%;
background-repeat: no-repeat;
background-position: bottom;
background-image: url("/img/background/panels/wall_panel_empty.png");
filter: drop-shadow(0 0 0.5rem rgba(0,0,0, 0.25));
.icon { .icon {
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
fill: white;
filter: invert(1);
transform: translateY(0.35rem); transform: translateY(0.35rem);
} }
a { a {
font-size: 2rem; font-size: 2rem;
color: #fff; color: #000;
text-decoration: none; text-decoration: none;
&.router-link-active { &.router-link-active {
@@ -370,4 +458,64 @@ nav {
} }
.loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 5;
user-select: none;
background-size: 25rem;
background-color: rgba(87, 50, 20, 0.45);
background-image: url("/img/background/wood/cut_wood_background.png");
background-blend-mode: darken;
background-repeat: repeat;
display: flex;
justify-content: center;
align-items: center;
transition: opacity 0.25s linear;
&.disabled {
opacity: 0;
pointer-events: none;
}
.lds-ring {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-ring div {
box-sizing: border-box;
display: block;
position: absolute;
width: 64px;
height: 64px;
margin: 8px;
border: 8px solid;
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #000 transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes lds-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}
</style> </style>
+84
View File
@@ -82,3 +82,87 @@ body {
filter: brightness(1.2); filter: brightness(1.2);
} }
} }
.title {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-block: 2.5rem;
text-align: center;
width: 100%;
max-width: 45rem;
gap: 0.5rem;
h1 {
font-size: 4rem;
line-height: 0.75;
white-space: pre-wrap;
margin: 0;
}
h3 {
margin: 0;
line-height: 0.9;
}
small {
font-size: 0.9rem;
font-weight: bold;
line-height: 0.25;
}
}
[data-tooltip] {
position: relative;
&:after {
pointer-events: none;
transition: opacity 0.25s ease-in-out;
opacity: 0;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: max-content;
min-height: 2rem;
background-color: rgba(0,0,0, 0.5);
color: #fff;
font-size: 1rem;
padding-block: 0.25rem;
padding-inline: 0.5rem;
white-space: break-spaces;
}
&[data-tooltip-position="bottom"]:after {
content: attr(data-tooltip);
bottom: calc(-100% - 0.5rem);
left: 50%;
transform: translateX(-50%);
max-width: 200%;
}
&[data-tooltip-position="top"]:after {
content: attr(data-tooltip);
top: calc(-100% - 0.5rem);
left: 50%;
transform: translateX(-50%);
max-width: 200%;
}
&[data-tooltip-position="left"]:after {
content: attr(data-tooltip);
top: 50%;
right: calc(100% + 0.5rem);
transform: translateY(-50%);
max-width: 20rem;
}
&[data-tooltip-position="right"]:after {
content: attr(data-tooltip);
top: 50%;
left: calc(100% + 0.5rem);
transform: translateY(-50%);
max-width: 20rem;
}
&:hover:after {
opacity: 100%;
}
}
+9 -2
View File
@@ -2,7 +2,10 @@ import type {GuildUpgrade} from "@/classes/GuildUpgrade";
import AdventurerCapacityUpgrade from "@/classes/guildUpgrades/AdventurerCapacityUpgrade"; import AdventurerCapacityUpgrade from "@/classes/guildUpgrades/AdventurerCapacityUpgrade";
import {formatGold} from "@/classes/NumberMagic"; import {formatGold} from "@/classes/NumberMagic";
import QuestExpUpgrade from "@/classes/guildUpgrades/QuestExpUpgrade"; import QuestExpUpgrade from "@/classes/guildUpgrades/QuestExpUpgrade";
import QuestGoldUpgrade from "@/classes/guildUpgrades/QuestGoldUpgrade";
import AutoFinishQuestsUpgrade from "@/classes/guildUpgrades/AutoFinishQuestsUpgrade";
const MAX_LEVEL: number = 8;
export class Guild { export class Guild {
gold: number; gold: number;
@@ -11,6 +14,8 @@ export class Guild {
upgradeCost: number|null = null; upgradeCost: number|null = null;
adventurerCapacity: AdventurerCapacityUpgrade; adventurerCapacity: AdventurerCapacityUpgrade;
expModifier: QuestExpUpgrade; expModifier: QuestExpUpgrade;
goldModifier: QuestGoldUpgrade;
autoFinishQuestsUpgrade: AutoFinishQuestsUpgrade;
constructor(level: number, gold: number, upgrades: {[index:string]: GuildUpgrade} = {}) { constructor(level: number, gold: number, upgrades: {[index:string]: GuildUpgrade} = {}) {
this.gold = gold; this.gold = gold;
@@ -21,6 +26,8 @@ export class Guild {
this.adventurerCapacity = upgrades.adventurerCapacity as AdventurerCapacityUpgrade ?? new AdventurerCapacityUpgrade(); this.adventurerCapacity = upgrades.adventurerCapacity as AdventurerCapacityUpgrade ?? new AdventurerCapacityUpgrade();
this.expModifier = upgrades.expModifier as QuestExpUpgrade ?? new QuestExpUpgrade(); this.expModifier = upgrades.expModifier as QuestExpUpgrade ?? new QuestExpUpgrade();
this.goldModifier = upgrades.goldModifier as QuestGoldUpgrade ?? new QuestGoldUpgrade();
this.autoFinishQuestsUpgrade = upgrades.autoFinishQuestsUpgrade as AutoFinishQuestsUpgrade ?? new AutoFinishQuestsUpgrade();
} }
@@ -30,7 +37,7 @@ export class Guild {
if (this.gold < cost) return; if (this.gold < cost) return;
this.gold -= cost; this.gold -= cost;
this.level += 1; this.level += 1;
if (this.level >= 7) { if (this.level >= MAX_LEVEL) {
this.displayUpgradeCost = "Max level"; this.displayUpgradeCost = "Max level";
this.upgradeCost = null; this.upgradeCost = null;
} else { } else {
@@ -46,7 +53,7 @@ export class Guild {
} }
isMaxLevel(): boolean { isMaxLevel(): boolean {
return this.level >= 8; return this.level >= MAX_LEVEL;
} }
} }
+7
View File
@@ -0,0 +1,7 @@
export default interface MaxLevellable {
maxLevel: number;
isMaxLevel(): boolean;
}
+2 -1
View File
@@ -11,7 +11,8 @@ const damageFormatter = new Intl.NumberFormat('en-US', {
notation: "compact", notation: "compact",
}); });
export function formatGold(number: number): string { export function formatGold(number: number | null): string {
if (number === null) return "";
return goldFormatter.format(number); return goldFormatter.format(number);
} }
+3 -2
View File
@@ -34,8 +34,9 @@ export class Quest {
* Generate rewards for a quest and return it * Generate rewards for a quest and return it
* @param quest * @param quest
* @param expModifier - multiplification modifier for the exp reward * @param expModifier - multiplification modifier for the exp reward
* @param goldModifier - multiplification modifier for the gold reward
*/ */
export function getQuestWithRewards(quest: Quest, expModifier: number = 1) { export function getQuestWithRewards(quest: Quest, expModifier: number = 1, goldModifier: number = 1) {
let maxProgress = 1; let maxProgress = 1;
@@ -70,7 +71,7 @@ export function getQuestWithRewards(quest: Quest, expModifier: number = 1) {
break; break;
} }
let goldReward = Math.floor(maxProgress/6); let goldReward = Math.floor(maxProgress/6 * goldModifier);
let expReward = Math.floor((Math.floor(maxProgress/120) - maxProgress/1000) * expModifier); let expReward = Math.floor((Math.floor(maxProgress/120) - maxProgress/1000) * expModifier);
// add some randomness to the rewards // add some randomness to the rewards
@@ -14,8 +14,8 @@ export default class AdventurerCapacityUpgrade extends GuildUpgrade {
} }
getCostForLevel(level: number): number { getCostForLevel(level: number): number {
const scalingFactor = Math.pow(1.25, level - 1); if (level === 1) return 1500;
return Math.floor(1500 * scalingFactor * Math.pow(level, 1.25)); return Math.floor(1500 * (level * 4));
} }
/** /**
@@ -0,0 +1,70 @@
import {GuildUpgrade} from "@/classes/GuildUpgrade";
import type MaxLevellable from "@/classes/MaxLevellable";
import {QuestRank} from "@/classes/QuestRank";
export default class AutoFinishQuestsUpgrade extends GuildUpgrade implements MaxLevellable {
maxLevel: number;
constructor(level: number = 1) {
super();
this.level = level;
this.nextLevelCost = this.getCostForLevel(this.level);
this.guildLevelRequirement = 7;
this.maxLevel = 8;
}
upgrade(): void {
this.level += 1;
this.nextLevelCost = this.getCostForLevel(this.level);
}
getCostForLevel(level: number): number {
switch (level) {
case 1:
return 25000;
case 2:
return 50000;
case 3:
return 75000;
case 4:
return 150000;
case 5:
return 275000;
case 6:
return 750000;
case 7:
return 1500000;
case 8:
return 2500000;
default:
return 0;
}
}
isMaxLevel(): boolean {
return this.level >= this.maxLevel;
}
getRanksToAutoFinishQuestsIn(): Array<QuestRank> {
switch (this.level) {
case 1:
default:
return [];
case 2:
return [QuestRank.F];
case 3:
return [QuestRank.F, QuestRank.E];
case 4:
return [QuestRank.F, QuestRank.E, QuestRank.D];
case 5:
return [QuestRank.F, QuestRank.E, QuestRank.D, QuestRank.C];
case 6:
return [QuestRank.F, QuestRank.E, QuestRank.D, QuestRank.C, QuestRank.B];
case 7:
return [QuestRank.F, QuestRank.E, QuestRank.D, QuestRank.C, QuestRank.B, QuestRank.A];
case 8:
return [QuestRank.F, QuestRank.E, QuestRank.D, QuestRank.C, QuestRank.B, QuestRank.A, QuestRank.S];
}
}
}
+2 -2
View File
@@ -14,8 +14,8 @@ export default class QuestExpUpgrade extends GuildUpgrade {
} }
getCostForLevel(level: number): number { getCostForLevel(level: number): number {
const scalingFactor = Math.pow(1.15, level - 1); if (level === 1) return 1000000;
return Math.floor(4000000 * scalingFactor * Math.pow(level, 1.1)); return Math.floor(1000000 * (level * 1.05));
} }
getModifier(): number { getModifier(): number {
@@ -0,0 +1,24 @@
import {GuildUpgrade} from "@/classes/GuildUpgrade";
export default class QuestGoldUpgrade extends GuildUpgrade {
constructor(level: number = 1) {
super();
this.level = level;
this.nextLevelCost = this.getCostForLevel(this.level);
this.guildLevelRequirement = 8;
}
upgrade(): void {
this.level += 1;
this.nextLevelCost = this.getCostForLevel(this.level);
}
getCostForLevel(level: number): number {
if (level === 1) return 1000000;
return Math.floor(1000000 * (level * 1.05));
}
getModifier(): number {
return 1 + (this.level * 0.1);
}
}
+3 -1
View File
@@ -70,7 +70,9 @@ export default defineComponent({
}, },
methods: { methods: {
closeSelect() { closeSelect() {
this.selection = false; setTimeout(() => {
this.selection = false;
}, 0);
} }
} }
}) })
+41 -1
View File
@@ -13,8 +13,21 @@
Upgrade ({{ formatGold(guild.adventurerCapacity.nextLevelCost) }} gold) Upgrade ({{ formatGold(guild.adventurerCapacity.nextLevelCost) }} gold)
</button> </button>
</div> </div>
<div class="upgrade" v-if="guild.level >= guild.autoFinishQuestsUpgrade.guildLevelRequirement">
<span>Auto-finish quests (level {{ guild.autoFinishQuestsUpgrade.level - 1 }})</span>
<small>Automatically finish quests when they are completed.</small>
<button
class="button metal"
v-if="guild.autoFinishQuestsUpgrade.nextLevelCost"
:disabled="guild.gold < guild.autoFinishQuestsUpgrade.nextLevelCost || guild.autoFinishQuestsUpgrade.isMaxLevel()"
@click="upgradeAutoFinishQuests()"
>
<span v-if="!guild.autoFinishQuestsUpgrade.isMaxLevel()">Upgrade ({{ formatGold(guild.autoFinishQuestsUpgrade.nextLevelCost) }} gold)</span>
<span v-else>Max level</span>
</button>
</div>
<div class="upgrade" v-if="guild.level >= guild.expModifier.guildLevelRequirement"> <div class="upgrade" v-if="guild.level >= guild.expModifier.guildLevelRequirement">
<span>Quest exp modifier (level {{ guild.expModifier.level }})</span> <span>Quest exp modifier (level {{ guild.expModifier.level - 1 }})</span>
<small>Increases exp from newly offered quests by 10% per level</small> <small>Increases exp from newly offered quests by 10% per level</small>
<button <button
class="button metal" class="button metal"
@@ -25,6 +38,18 @@
Upgrade ({{ formatGold(guild.expModifier.nextLevelCost) }} gold) Upgrade ({{ formatGold(guild.expModifier.nextLevelCost) }} gold)
</button> </button>
</div> </div>
<div class="upgrade" v-if="guild.level >= guild.goldModifier.guildLevelRequirement">
<span>Quest gold modifier (level {{ guild.goldModifier.level - 1 }})</span>
<small>Increases gold from newly offered quests by 10% per level</small>
<button
class="button metal"
v-if="guild.goldModifier.nextLevelCost"
:disabled="guild.gold < guild.goldModifier.nextLevelCost"
@click="upgradeQuestGoldModifier()"
>
Upgrade ({{ formatGold(guild.goldModifier.nextLevelCost) }} gold)
</button>
</div>
</section> </section>
</template> </template>
@@ -53,6 +78,14 @@ export default defineComponent({
this.guild.gold -= this.guild.adventurerCapacity.nextLevelCost; this.guild.gold -= this.guild.adventurerCapacity.nextLevelCost;
this.guild.adventurerCapacity.upgrade(); this.guild.adventurerCapacity.upgrade();
}, },
upgradeAutoFinishQuests(): void {
if (!this.guild.autoFinishQuestsUpgrade) return;
if (this.guild.autoFinishQuestsUpgrade.isMaxLevel()) return;
if (!this.guild.autoFinishQuestsUpgrade.nextLevelCost) return;
if (this.guild.gold < this.guild.autoFinishQuestsUpgrade.nextLevelCost) return;
this.guild.gold -= this.guild.autoFinishQuestsUpgrade.nextLevelCost;
this.guild.autoFinishQuestsUpgrade.upgrade();
},
upgradeQuestExpModifier(): void { upgradeQuestExpModifier(): void {
if (!this.guild.expModifier) return; if (!this.guild.expModifier) return;
if (!this.guild.expModifier.nextLevelCost) return; if (!this.guild.expModifier.nextLevelCost) return;
@@ -60,6 +93,13 @@ export default defineComponent({
this.guild.gold -= this.guild.expModifier.nextLevelCost; this.guild.gold -= this.guild.expModifier.nextLevelCost;
this.guild.expModifier.upgrade(); this.guild.expModifier.upgrade();
}, },
upgradeQuestGoldModifier(): void {
if (!this.guild.goldModifier) return;
if (!this.guild.goldModifier.nextLevelCost) return;
if (this.guild.gold < this.guild.goldModifier.nextLevelCost) return;
this.guild.gold -= this.guild.goldModifier.nextLevelCost;
this.guild.goldModifier.upgrade();
},
} }
}); });
</script> </script>
@@ -53,7 +53,6 @@ export default defineComponent({
}, },
async mounted() { async mounted() {
this.getReleases(1); this.getReleases(1);
} }
}); });
</script> </script>
@@ -0,0 +1,146 @@
<template>
<div class="save-manager panel pinned-paper">
<dialog ref="importDialog" >
<div class="import-dialog">
<textarea
v-model="importSaveText"
placeholder="Paste your save data here"
></textarea>
<div style="display: flex; flex-direction: row; flex-wrap: wrap; gap: 0.5rem;">
<button type="button" class="button metal" @click="importSave()" :disabled="importSaveText === ''">
Import
</button>
<button class="close" @click="closeSaveImport"></button>
</div>
</div>
</dialog>
<div class="nail top-left">
<img src="/img/quests/overlays/nail.png" alt="" draggable="false"/>
</div>
<div class="nail top-right">
<img src="/img/quests/overlays/nail.png" alt="" draggable="false"/>
</div>
<h1>Save file</h1>
<div class="buttons">
<button class="button metal" @click="exportSave()">
Export
</button>
<button class="button metal" @click="openSaveImport()">
Import
</button>
<button class="button metal" @click="wipeSave()">
<span class="red">Wipe</span>
</button>
</div>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
export default defineComponent({
name: "ChangelogComponent",
data: () => ({
importSaveText: "",
}),
methods: {
openSaveImport() {
const dialog = this.$refs.importDialog as HTMLDialogElement;
dialog.showModal();
},
closeSaveImport() {
const dialog = this.$refs.importDialog as HTMLDialogElement;
dialog.close();
},
exportSave() {
const save = window.localStorage.getItem("savedGame");
if (!save) {
alert("No save file found!");
return;
}
navigator.clipboard.writeText(btoa(save));
setTimeout(() => alert("Save data copied to clipboard!"), 100);
},
importSave() {
const saveBase64 = this.importSaveText;
try {
const save = atob(saveBase64);
JSON.parse(save);
window.localStorage.setItem("savedGame", save);
window.location.reload();
} catch (e) {
alert("Invalid save file!");
return;
}
},
wipeSave() {
if (!confirm("You are about to wipe your save file. Are you sure?")) return;
window.localStorage.removeItem("savedGame");
window.location.reload();
},
}
});
</script>
<style scoped lang="scss">
.save-manager {
padding-block: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 1rem;
max-width: 45rem;
width: 100%;
h1 {
margin-block: 0;
font-size: 2rem;
}
dialog {
padding: 0;
}
.buttons {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 0.25rem;
.red {
color: #d52121;
}
}
.import-dialog {
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: url("/img/background/panels/plaster.png");
background-size: 100% 100%;
gap: 1rem;
position: relative;
textarea {
width: 90%;
height: 10rem;
resize: none;
border: 1px solid var(--color-metal);
border-radius: 0.25rem;
padding: 0.5rem;
font-family: monospace;
}
.close {
color: #ab0707;
font-size: 2rem;
border: none;
background: transparent;
&:hover {
color: #d52121;
cursor: pointer;
}
}
}
}
</style>
+1 -1
View File
@@ -2,7 +2,7 @@ import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import './assets/main.scss' import '@/assets/main.scss'
const app = createApp(App) const app = createApp(App)
+4 -5
View File
@@ -1,5 +1,4 @@
import {createRouter, createWebHashHistory} from 'vue-router' import {createRouter, createWebHashHistory} from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL), history: createWebHashHistory(import.meta.env.BASE_URL),
@@ -7,22 +6,22 @@ const router = createRouter({
{ {
path: '/', path: '/',
name: 'guild', name: 'guild',
component: HomeView component: () => import('@/views/HomeView.vue')
}, },
{ {
path: '/quests', path: '/quests',
name: 'quests', name: 'quests',
component: () => import('../views/QuestView.vue') component: () => import('@/views/QuestView.vue')
}, },
{ {
path: '/adventurers', path: '/adventurers',
name: 'adventurers', name: 'adventurers',
component: () => import('../views/AdventurerView.vue') component: () => import('@/views/AdventurerView.vue')
}, },
{ {
path: '/technical', path: '/technical',
name: 'technical', name: 'technical',
component: () => import('../views/TechnicalView.vue') component: () => import('@/views/TechnicalView.vue')
} }
] ]
}) })
+9 -31
View File
@@ -4,6 +4,7 @@
<h1>Guild Master</h1> <h1>Guild Master</h1>
<h3>Adventurer's guild management game</h3> <h3>Adventurer's guild management game</h3>
<small>v{{ version }}</small> <small>v{{ version }}</small>
<p class="news">{{ news }}</p>
</section> </section>
<section class="upgrades panel pinned-paper"> <section class="upgrades panel pinned-paper">
<div class="nail top-left"> <div class="nail top-left">
@@ -29,9 +30,6 @@
<section class="upgrade"> <section class="upgrade">
<UpgradesList :guild="guild"/> <UpgradesList :guild="guild"/>
</section> </section>
<section class="upgrade">
<span class="wipe-save" @click="$emit('wipeSave')">Wipe your save data</span>
</section>
</section> </section>
</main> </main>
@@ -56,6 +54,10 @@ export default defineComponent({
} }
}, },
props: { props: {
news: {
type: String,
default: "",
},
guild: { guild: {
type: Object as PropType<Guild>, type: Object as PropType<Guild>,
default: () => new Guild(1, 0) as Guild, default: () => new Guild(1, 0) as Guild,
@@ -77,37 +79,13 @@ main {
.upgrades { .upgrades {
max-width: 45rem; max-width: 45rem;
width: 100%; width: 100%;
padding-bottom: 1rem;
} }
} }
.title { .news {
display: flex; max-width: 75%;
flex-direction: column; color: #ab0707;
justify-content: center;
align-items: center;
padding-block: 2.5rem;
text-align: center;
width: 100%;
max-width: 45rem;
gap: 0.5rem;
h1 {
font-size: 4rem;
line-height: 0.75;
white-space: pre-wrap;
margin: 0;
}
h3 {
margin: 0;
line-height: 0.9;
}
small {
font-size: 0.9rem;
font-weight: bold;
line-height: 0.25;
}
} }
.coffer { .coffer {
+4 -2
View File
@@ -19,6 +19,7 @@
</a> </a>
</div> </div>
</div> </div>
<SaveManagerComponent/>
<ChangelogComponent/> <ChangelogComponent/>
</section> </section>
@@ -26,11 +27,12 @@
<script lang="ts"> <script lang="ts">
import {defineComponent} from "vue"; import {defineComponent} from "vue";
import ChangelogComponent from "@/components/ChangelogComponent.vue"; import ChangelogComponent from "@/components/technical/ChangelogComponent.vue";
import SaveManagerComponent from "@/components/technical/SaveManagerComponent.vue";
export default defineComponent({ export default defineComponent({
name: "TechnicalView", name: "TechnicalView",
components: {ChangelogComponent}, components: {SaveManagerComponent, ChangelogComponent},
}) })
</script> </script>
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"extends": "@vue/tsconfig/tsconfig.node.json", "extends": "@vue/tsconfig/tsconfig.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"], "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"extends": "@vue/tsconfig/tsconfig.web.json", "extends": "@vue/tsconfig/tsconfig.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",