Compare commits

...

43 Commits

Author SHA1 Message Date
YouHaveTrouble d221e8f8f7 better mobile view for changelogs 2025-06-02 23:07:14 +02:00
YouHaveTrouble 28e9df8251 make button not overflow 2025-06-02 23:05:09 +02:00
YouHaveTrouble 2ce333fd63 adjust scaling of instant adventurer recruitment cost 2025-06-02 23:00:57 +02:00
YouHaveTrouble c0b51e8362 drastically improve applying adventurers interface section 2025-06-02 22:16:24 +02:00
YouHaveTrouble 6cc8304018 add a button that instantly finds new recruit 2025-06-02 20:55:03 +02:00
YouHaveTrouble 49baa613bd fix saving recruitment cap upgrade 2025-06-02 20:54:46 +02:00
YouHaveTrouble ae65ff9d51 make sure adventurers for hire don't duplicate on arrival 2025-06-02 20:54:09 +02:00
YouHaveTrouble 4de7ac97d1 bump version 2025-06-02 18:15:55 +02:00
YouHaveTrouble 6e9dc1ada1 ts-ignore the package.json version import 2025-05-31 20:11:39 +02:00
YouHaveTrouble 3368755a31 don't immediately find new recruit after dismissing one with all slots full 2025-05-31 20:10:30 +02:00
YouHaveTrouble a821094513 finish up base recruitment system 2025-05-31 19:53:31 +02:00
YouHaveTrouble 2cb9221da1 allowed more than 1 recruit to show up 2025-05-28 19:52:28 +02:00
YouHaveTrouble 85ed1224c0 update news message 2025-05-28 17:44:56 +02:00
YouHaveTrouble 0d87376270 move quests to a single file 2025-05-12 16:20:03 +02:00
YouHaveTrouble ae89704380 fix a type error 2025-05-11 17:03:28 +02:00
YouHaveTrouble 972b9251c3 bump version 2025-05-11 17:00:18 +02:00
YouHaveTrouble 2f10f940d8 bump version 2025-05-11 16:52:02 +02:00
YouHaveTrouble d962c85629 better quest adventurer choice ux 2025-05-11 16:37:58 +02:00
YouHaveTrouble e71326d89b update busy status dynamically 2025-05-11 14:36:10 +02:00
YouHaveTrouble 20567be96d new experimental quest view 2025-05-08 21:08:03 +02:00
YouHaveTrouble 3c79074c4c create GDD for the next iteration of the game 2024-11-19 20:35:55 +01:00
YouHaveTrouble 571dee6cc9 update news 2024-08-23 23:55:24 +02:00
YouHaveTrouble b49a30bb6e actually bearable mobile view for quests board 2023-09-23 15:29:47 +02:00
YouHaveTrouble 2a38461a47 move icon svgs to components, so they can load along everything else 2023-09-16 15:58:54 +02:00
YouHaveTrouble 52c340be26 bump version 2023-09-16 15:39:23 +02:00
YouHaveTrouble f2c5304643 don't load paths dynamically to allow offline play 2023-09-16 15:26:36 +02:00
YouHaveTrouble b6aefde9da add / to the path 2023-09-16 14:29:29 +02:00
YouHaveTrouble f31a0013a9 Merge pull request #11 from xYundy/fix-quest-button-on-ios
Fix select button color for iOS Safari
2023-09-15 12:04:09 +02:00
Wojciech 'xYundy' Zięciak e4af877358 Add missing empty line at EOF 2023-09-15 12:03:48 +02:00
YouHaveTrouble d162771749 Merge pull request #10 from xYundy/public-testing
Add script which allow to test app on external devices
2023-09-15 12:03:15 +02:00
YouHaveTrouble f53644119a Merge pull request #9 from xYundy/pwa-mobile-icon
Add PWA icons and manifest
2023-09-15 12:02:21 +02:00
Wojciech 'xYundy' Zięciak 34999a27b7 add PWA icons and manifest 2023-09-15 11:59:11 +02:00
Wojciech 'xYundy' Zięciak 6e23f9a7b4 Fix select button color for iOS Safar 2023-09-15 11:55:27 +02:00
Wojciech 'xYundy' Zięciak d852f6fd96 Add script which allow to test app on ext. devices 2023-09-15 11:47:43 +02:00
YouHaveTrouble 0e0c133f6f swap meta image 2023-09-07 19:06:29 +02:00
YouHaveTrouble 8a6b581981 bump version 2023-09-07 18:39:53 +02:00
YouHaveTrouble 5497717605 have enough portraits for now 2023-09-07 18:38:11 +02:00
YouHaveTrouble 85fa53b16e add gradient background for adventurer portraits 2023-09-07 18:23:36 +02:00
YouHaveTrouble 9feab73fd2 small tool to automatically merge data and portraits into one file 2023-09-07 18:23:17 +02:00
YouHaveTrouble ed7c9e66ec add new adventurers and their assets 2023-09-07 18:22:22 +02:00
YouHaveTrouble 82350c6c42 new loading screen 2023-08-28 19:33:44 +02:00
YouHaveTrouble 0b7489b21f add new assets 2023-08-28 19:33:29 +02:00
YouHaveTrouble 21fb2c5f72 update depends 2023-08-20 17:20:29 +02:00
75 changed files with 1782 additions and 1020 deletions
+23
View File
@@ -0,0 +1,23 @@
# Game Design Document
## 1. Introduction
Guild Master is a game simulating being a fantasy guild master. The player will be able to recruit adventurers,
send them on quests, and manage the guild's resources.
## 2. Gameplay
Player will recruit adventurers and assign then to quests. Adventurers will have different statistics and a passive
ability that will make each character unique. Player will have to manage guild's resources to complete more and more
resource intensive quests and assignments.
## 3. Mechanics
Menus. Lots of menus. Possibly with fancy animations.
## 4. Characters
Set amount of available adventurers. Each with their own inventory and passive ability. Items in the inventory will
boost specific statistics of the character. They will be scaled based on level and the xp curve will be exponential.
## 5. Quests
There will always be a minimum set amount of quests available. Adventurers will have to be assigned to a quest that is
within their level range. Quests will come in different difficulties within the level range.
+21
View File
@@ -0,0 +1,21 @@
/**
* This file is used to import the character portraits to base64 from raw assets
*/
const fs = require('fs');
const characterData = require('./rawAssets/data/adventurers.json');
for (const character of characterData) {
try {
const base64 = base64_encode(`./rawAssets/img/portraits/${character.id}.png`);
character.portrait = "data:image/png;base64,"+base64;
} catch (e) {
console.error(`Error: Didn't find portrait for ${character.id}`);
}
}
fs.writeFileSync('./public/data/adventurers.json', JSON.stringify(characterData, null, 2), "utf-8");
function base64_encode(file) {
return fs.readFileSync(file, "base64");
}
+3 -3
View File
@@ -8,18 +8,18 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=EB+Garamond&display=swap" rel="stylesheet">
<link rel="manifest" href="/manifest.json" />
<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:image" content="https://guildmaster.yht.one/img/app-icons/icon.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"/>
<meta property="og:image" content="https://guildmaster.yht.one/img/app-icons/icon.png"/>
</head>
<body>
<div id="app"></div>
+1 -1
View File
@@ -1 +1 @@
Looking for artists for assets for this game! Need backgrounds, icons and character portraits! Join discord for more info!
This is alpha version. Saves can lose data across updates.
+481 -476
View File
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -1,29 +1,31 @@
{
"name": "adventurers-guild",
"version": "0.11.0",
"version": "0.15.0",
"private": true,
"scripts": {
"dev": "vite",
"dev-public": "vite --host",
"build": "run-p type-check build-only && cp -r CNAME dist/CNAME",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit"
"type-check": "vue-tsc --noEmit",
"gen-character-data": "node characterDataGenerator.js"
},
"dependencies": {
"@vueuse/components": "^9.13.0",
"sass": "^1.63.6",
"sass": "^1.66.1",
"vue": "^3.3.4",
"vue-router": "^4.2.2"
"vue-router": "^4.2.4"
},
"devDependencies": {
"@types/node": "^18.16.19",
"@vitejs/plugin-vue": "^4.2.3",
"@types/node": "^18.17.6",
"@vitejs/plugin-vue": "^4.3.1",
"@vue/tsconfig": "^0.4.0",
"eslint": "^8.44.0",
"eslint-plugin-vue": "^9.15.1",
"eslint": "^8.47.0",
"eslint-plugin-vue": "^9.17.0",
"npm-run-all": "^4.1.5",
"typescript": "~5.1.6",
"vite": "4.3.9",
"vite": "4.4.9",
"vue-tsc": "^1.8.3"
}
}
File diff suppressed because one or more lines are too long
+106
View File
@@ -0,0 +1,106 @@
{
"ranks": {
"A": [
{
"title": "Ogre king",
"text": "Ogres have chosen a new king through democratic vote. They all voted for the strongest ogre."
},
{
"title": "Devilish dungeon",
"text": "New dungeon was discovered. It needs to be mapped and explored so lower rank adventurers can enter."
},
{
"title": "Eater of Worlds",
"text": "A giant worm emerged from the ground and appears to be consuming the ground itself."
}
],
"B": [
{
"title": "Undead horde",
"text": "Due to the spillage of necromancy potion at nearby graveyard we now have an undead army on our doorstep."
},
{
"title": "Runaway prisoner",
"text": "During the last prison guard strike a prisoner managed to escape. Bring them back to their cell."
},
{
"title": "The aristocrats",
"text": "Royalty wants an escort for one of their carriages."
}
],
"C": [
{
"title": "Scratchy, the butcher",
"text": "Scratchy turned evil and is terrorizing its victims. Put a stop to it!"
},
{
"title": "Hobgnoblin subjugation",
"text": "Gnoblins evolved and are back for vengeance."
},
{
"title": "Holy",
"text": "Gnoblins summoned their machine god and it started going haywire on everything around it. Destroy it!"
}
],
"D": [
{
"title": "Caravan escort",
"text": "Escort a merchant caravan."
},
{
"title": "Rare ore",
"text": "Obtain laudanium ore for town's blacksmith."
},
{
"title": "Demonic pests!",
"text": "Clear the fields from cabbage imps."
}
],
"E": [
{
"title": "Gnoblin subjugation",
"text": "Kill 3 gnoblins."
},
{
"title": "Phantom menace",
"text": "Exorcise ghosts out of someone's apartment."
},
{
"title": "Scratchy in peril",
"text": "Get Scratchy the cat from the tree safe onto the ground."
}
],
"F": [
{
"title": "Frog Frenzy",
"text": "Kill 10 demon frogs."
},
{
"title": "Rats!",
"text": "Get rid of the rats from someone's basement."
},
{
"title": "Herb gathering",
"text": "Collect medicinal herbs."
},
{
"title": "Big pile of rubble",
"text": "Tavern collapsed. Again. Help clean up the debris."
}
],
"S": [
{
"title": "The Demon King",
"text": "Demon King has awoken and is a threat to whole existence. Heroes needed."
},
{
"title": "Scratchy, Destruction Incarnate",
"text": "Scratchy was reborn as a machine of pure destruction and needs to be stopped."
},
{
"title": "Jiggly Jungle",
"text": "A jungle south began rapidly expanding and experts think arson is our only option."
}
]
}
}
-14
View File
@@ -1,14 +0,0 @@
[
{
"title": "Ogre king",
"text": "Ogres have chosen a new king through democratic vote. They all voted for the strongest ogre."
},
{
"title": "Devilish dungeon",
"text": "New dungeon was discovered. It needs to be mapped and explored so lower rank adventurers can enter."
},
{
"title": "Eater of Worlds",
"text": "A giant worm emerged from the ground and appears to be consuming the ground itself."
}
]
-14
View File
@@ -1,14 +0,0 @@
[
{
"title": "Undead horde",
"text": "Due to the spillage of necromancy potion at nearby graveyard we now have an undead army on our doorstep."
},
{
"title": "Runaway prisoner",
"text": "During the last prison guard strike a prisoner managed to escape. Bring them back to their cell."
},
{
"title": "The aristocrats",
"text": "Royalty wants an escort for one of their carriages."
}
]
-14
View File
@@ -1,14 +0,0 @@
[
{
"title": "Scratchy, the butcher",
"text": "Scratchy turned evil and is terrorizing its victims. Put a stop to it!"
},
{
"title": "Hobgnoblin subjegation",
"text": "Gnoblins evolved and are back for vengeance."
},
{
"title": "Holy",
"text": "Gnoblins summoned their machine god and it started going haywire on everything around it. Destroy it!"
}
]
-14
View File
@@ -1,14 +0,0 @@
[
{
"title": "Caravan escort",
"text": "Escort a merchant caravan."
},
{
"title": "Rare ore",
"text": "Obtain laudanium ore for town's blacksmith."
},
{
"title": "Demonic pests!",
"text": "Clear the fields from cabbage imps."
}
]
-14
View File
@@ -1,14 +0,0 @@
[
{
"title": "Gnoblin subjegation",
"text": "Kill 3 gnoblins."
},
{
"title": "Phantom menace",
"text": "Exorcise ghosts out of someone's apartment."
},
{
"title": "Scratchy in peril",
"text": "Get Scratchy the cat from the tree safe onto the ground."
}
]
-18
View File
@@ -1,18 +0,0 @@
[
{
"title": "Frog Frenzy",
"text": "Kill 10 demon frogs."
},
{
"title": "Rats!",
"text": "Get rid of the rats from someone's basement."
},
{
"title": "Herb gathering",
"text": "Collect medicinal herbs."
},
{
"title": "Big pile of rubble",
"text": "Tavern collapsed. Again. Help clean up the debris."
}
]
-14
View File
@@ -1,14 +0,0 @@
[
{
"title": "The Demon King",
"text": "Demon King has awoken and is a threat to whole existence. Heroes needed."
},
{
"title": "Scratchy, Destruction Incarnate",
"text": "Scratchy was reborn as a machine of pure destruction and needs to be stopped."
},
{
"title": "Jiggly Jungle",
"text": "A jungle south began rapidly expanding and experts think arson is our only option."
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

-1
View File
@@ -1 +0,0 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discord</title><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

-5
View File
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" fill="none"/>
<path d="M12,2A10,10,0,0,0,8.84,21.5c.5.08.66-.23.66-.5V19.31C6.73,19.91,6.14,18,6.14,18A2.69,2.69,0,0,0,5,16.5c-.91-.62.07-.6.07-.6a2.1,2.1,0,0,1,1.53,1,2.15,2.15,0,0,0,2.91.83,2.16,2.16,0,0,1,.63-1.34C8,16.17,5.62,15.31,5.62,11.5a3.87,3.87,0,0,1,1-2.71,3.58,3.58,0,0,1,.1-2.64s.84-.27,2.75,1a9.63,9.63,0,0,1,5,0c1.91-1.29,2.75-1,2.75-1a3.58,3.58,0,0,1,.1,2.64,3.87,3.87,0,0,1,1,2.71c0,3.82-2.34,4.66-4.57,4.91a2.39,2.39,0,0,1,.69,1.85V21c0,.27.16.59.67.5A10,10,0,0,0,12,2Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

+59
View File
@@ -0,0 +1,59 @@
{
"name": "Guild Master - Adventurer's guild management game",
"short_name": "Guild Master",
"theme_color": "#3C2114",
"background_color": "#d9c8b3",
"display": "standalone",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "img/app-icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "img/app-icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "img/app-icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "img/app-icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "img/app-icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "img/app-icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "img/app-icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "img/app-icons/icon.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
+102
View File
@@ -0,0 +1,102 @@
[
{
"id": "aldek",
"name": "Aldek",
"attackExponent": 1.1
},
{
"id": "aria",
"name": "Aria",
"attackExponent": 1.1
},
{
"id": "burnett",
"name": "Burnett",
"attackExponent": 1.1
},
{
"id": "charlotte",
"name": "Charlotte",
"attackExponent": 1.1
},
{
"id": "ella",
"name": "Ella",
"attackExponent": 1.1
},
{
"id": "elyza",
"name": "Elyza",
"attackExponent": 1.1
},
{
"id": "emille",
"name": "Emille",
"attackExponent": 1.1
},
{
"id": "garret",
"name": "Garret",
"attackExponent": 1.1
},
{
"id": "gryza",
"name": "Gryza",
"attackExponent": 1.1
},
{
"id": "lestat",
"name": "Lestat",
"attackExponent": 1.1
},
{
"id": "lydia",
"name": "Lydia",
"attackExponent": 1.1
},
{
"id": "noor",
"name": "Noor",
"attackExponent": 1.1
},
{
"id": "noron",
"name": "Noron",
"attackExponent": 1.1
},
{
"id": "oola",
"name": "Oola",
"attackExponent": 1.1
},
{
"id": "owen",
"name": "Owen",
"attackExponent": 1.1
},
{
"id": "ryslette",
"name": "Ryslette",
"attackExponent": 1.1
},
{
"id": "sally",
"name": "Sally",
"attackExponent": 1.1
},
{
"id": "tovu",
"name": "Tovu",
"attackExponent": 1.1
},
{
"id": "wrydio",
"name": "Wrydio",
"attackExponent": 1.1
},
{
"id": "xarya",
"name": "Xarya",
"attackExponent": 1.1
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

+156 -122
View File
File diff suppressed because one or more lines are too long
+49 -34
View File
@@ -6,20 +6,23 @@ import {getFromString, QuestRank} from "@/classes/QuestRank";
export class GameData {
guild: Guild;
adventurers: { [key: string]: Adventurer };
missives: { [key: string]: { [key: string]: Quest } };
missives: Array<Quest>;
lastQuestGot: { [key: string]: null | number };
lastRecruitAction: null | number;
adventurerForHireId: string | null;
adventurersForHire: {[key: string]: Adventurer};
nextRecruitArrival: Date;
constructor(
data: any,
) {
this.guild = data.guild ?? new Guild(1, 0);
this.adventurers = data.adventurers ?? {} as { [key: string]: Adventurer };
this.missives = data.missives ?? {} as { [key: string]: { [key: string]: Quest } };
this.lastQuestGot = data.lastQuestGot ?? {} as { [key: string]: null | number };
this.lastRecruitAction = data.lastRecruitAction ?? null;
this.adventurerForHireId = data.adventurerForHireId ?? null;
this.adventurers = data.adventurers ?? {};
this.missives = data.missives ?? [] as Array<Quest>;
this.lastQuestGot = data.lastQuestGot ?? {};
this.adventurersForHire = data.adventurersForHire ?? {};
this.nextRecruitArrival = data.nextRecruitArrival ? new Date(data.nextRecruitArrival) : new Date();
if (isNaN(this.nextRecruitArrival.getTime())) {
this.nextRecruitArrival = new Date();
}
}
}
@@ -39,13 +42,20 @@ export function saveGame(
adventurers[adventurerId] = adventurer;
}
const adventurersForHire = {} as { [key: string]: any };
for (const adventurerId in data.adventurersForHire) {
const adventurer: {[key: string]: any} = JSON.parse(JSON.stringify(data.adventurersForHire[adventurerId]));
delete adventurer.portrait;
adventurersForHire[adventurerId] = adventurer;
}
window.localStorage.setItem("savedGame", JSON.stringify({
guild: data.guild,
adventurers: adventurers,
missives: data.missives,
lastQuestGot: data.lastQuestGot,
lastRecruitAction: data.lastRecruitAction,
adventurerForHireId: data.adventurerForHireId,
adventurersForHire: adventurersForHire,
nextRecruitArrival: data.nextRecruitArrival.getTime(),
}));
}
@@ -68,64 +78,69 @@ export async function loadAvailableQuests(): Promise<{ [key: string]: { [key: st
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) {
const questsResponse = await fetch(`data/quests.json`);
if (questsResponse.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),
const questsData = await questsResponse.json();
for (const rank of Object.keys(questsData.ranks)) {
const questRank = getFromString(rank as keyof typeof QuestRank);
if (!questRank) {
console.error(`Invalid quest rank: ${rank}`);
continue;
}
const questRankData = questsData.ranks[questRank];
for (const quest of questRankData) {
const id = quest.id;
quests[questRank][id] = new Quest(
id,
questRank,
quest.title,
quest.text,
1,
0,
0
);
}
}
return quests;
}
export async function loadAdventurersForHire(): Promise<Array<Adventurer>> {
export async function loadAdventurersForHire(): Promise<{[key: string]: 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 [];
return {};
}
const adventurerData = await response.json();
const adventurers: Array<Adventurer> = [];
const adventurers: {[key: string]: Adventurer} = {};
for (const adventurer of adventurerData) {
adventurers.push(new Adventurer(
const loadedAdventurer = new Adventurer(
adventurer.id,
adventurer.name,
adventurer.portrait,
adventurer.attackExponent,
adventurer.level,
adventurer.exp,
));
)
adventurers[loadedAdventurer.id] = loadedAdventurer;
}
return adventurers;
}
export function removeAlreadyHiredAdventurers(
adventurers: Array<Adventurer>,
adventurers: { [key: string]: Adventurer },
adventurersHired: { [key: string]: Adventurer }
): Array<Adventurer> {
const adventurersForHire: Array<Adventurer> = [];
for (const adventurer of adventurers) {
): { [key: string]: Adventurer } {
const adventurersForHire: { [key: string]: Adventurer } = {};
for (const adventurer of Object.values(adventurers)) {
if (adventurersHired[adventurer.id]) continue;
adventurersForHire.push(adventurer);
adventurersForHire[adventurer.id] = adventurer;
}
return adventurersForHire;
}
+7 -2
View File
@@ -1,8 +1,13 @@
* {
box-sizing: border-box;
font-family: 'EB Garamond', serif;
scrollbar-color: #8f7256 rgba(0, 0, 0, 0.1);
}
body {
margin: 0;
padding: 0 0 10rem;
padding: 0 0 2rem;
min-height: calc(100vh - 10rem);
font-family: 'EB Garamond', serif;
overflow-y: scroll;
user-select: none;
background-size: 25rem;
+3 -1
View File
@@ -4,6 +4,7 @@ import {formatGold} from "@/classes/NumberMagic";
import QuestExpUpgrade from "@/classes/guildUpgrades/QuestExpUpgrade";
import QuestGoldUpgrade from "@/classes/guildUpgrades/QuestGoldUpgrade";
import AutoFinishQuestsUpgrade from "@/classes/guildUpgrades/AutoFinishQuestsUpgrade";
import RecruitmentCapacityUpgrade from "@/classes/guildUpgrades/RecruitmentCapacityUpgrade";
const MAX_LEVEL: number = 8;
@@ -16,6 +17,7 @@ export class Guild {
expModifier: QuestExpUpgrade;
goldModifier: QuestGoldUpgrade;
autoFinishQuestsUpgrade: AutoFinishQuestsUpgrade;
recruitmentCapacity: RecruitmentCapacityUpgrade;
constructor(level: number, gold: number, upgrades: {[index:string]: GuildUpgrade} = {}) {
this.gold = gold;
@@ -28,7 +30,7 @@ export class Guild {
this.expModifier = upgrades.expModifier as QuestExpUpgrade ?? new QuestExpUpgrade();
this.goldModifier = upgrades.goldModifier as QuestGoldUpgrade ?? new QuestGoldUpgrade();
this.autoFinishQuestsUpgrade = upgrades.autoFinishQuestsUpgrade as AutoFinishQuestsUpgrade ?? new AutoFinishQuestsUpgrade();
this.recruitmentCapacity = upgrades.recruitmentCapacity as RecruitmentCapacityUpgrade ?? new RecruitmentCapacityUpgrade();
}
upgrade(): void {
+12 -1
View File
@@ -7,12 +7,22 @@ export class Quest {
title: string;
text: string;
adventurers: Array<Adventurer>;
maxAdventurers: number;
progressPoints: number;
maxProgress: number;
expReward: number;
goldReward: number;
constructor(id: string, rank: QuestRank, title: string, text: string, maxProgress: number, expReward: number, goldReward: number) {
constructor(
id: string,
rank: QuestRank,
title: string,
text: string,
maxProgress: number = 1,
expReward: number = 0,
goldReward: number = 0,
maxAdventurers: number = 1
) {
this.id = id;
this.rank = rank;
this.title = title;
@@ -22,6 +32,7 @@ export class Quest {
this.goldReward = goldReward;
this.progressPoints = 0;
this.adventurers = [];
this.maxAdventurers = maxAdventurers;
}
getPercentProgress(): number {
+5 -1
View File
@@ -4,10 +4,14 @@ import type {Adventurer} from "@/classes/Adventurer";
/**
* Get a random adventurer from the pool
* @param adventurerPool
* @param exceptions
* @returns {Adventurer|null} null if the pool is empty
*/
export function getNewAdventurerForHire(adventurerPool: Array<Adventurer>): Adventurer|null {
export function getNewAdventurerForHire(adventurerPool: Array<Adventurer>, exceptions: Array<Adventurer> = []): Adventurer|null {
if (adventurerPool.length <= 0) return null;
const pool = Array.from(adventurerPool);
const exceptionSet = new Set(exceptions);
pool.filter((adventurer) => !exceptionSet.has(adventurer));
const randomId = adventurerPool.length * Math.random() << 0;
return adventurerPool[randomId];
}
@@ -0,0 +1,36 @@
import {GuildUpgrade} from "@/classes/GuildUpgrade";
import type MaxLevellable from "@/classes/MaxLevellable";
export default class AdventurerCapacityUpgrade extends GuildUpgrade implements MaxLevellable {
maxLevel: number;
constructor(level: number = 1) {
super();
this.level = level;
this.nextLevelCost = this.getCostForLevel(this.level);
this.guildLevelRequirement = 3;
this.maxLevel = 3;
}
upgrade(): void {
this.level += 1;
this.nextLevelCost = this.getCostForLevel(this.level);
}
getCostForLevel(level: number): number {
if (level === 1) return 1500;
return Math.floor(1500 * (level * 4));
}
/**
* Returns the number of adventurers the guild can have
*/
getRecruitmentCapacity(): number {
return this.level ;
}
isMaxLevel(): boolean {
return this.level >= this.maxLevel;
}
}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -22,9 +22,9 @@ export default defineComponent({
}),
props: {
adventurers: {
type: Object as PropType<{ [key: string]: Adventurer }>,
type: Object as PropType<Array<Adventurer>>,
default() {
return {} as { [key: string]: Adventurer };
return [] as Array<Adventurer>;
},
},
},
+30 -10
View File
@@ -21,6 +21,8 @@ import { vOnClickOutside } from '@vueuse/components'
<span>+</span>
</article>
<div class="selection" v-if="selection" v-on-click-outside="closeSelect">
<span>Choose adventurer</span>
<div class="list">
<button
class="slot"
v-for="adventurer in allAdventurers"
@@ -37,6 +39,7 @@ import { vOnClickOutside } from '@vueuse/components'
/>
</button>
</div>
</div>
</template>
<script lang="ts">
@@ -61,9 +64,9 @@ export default defineComponent({
},
},
allAdventurers: {
type: Object as PropType<{[key: string]: Adventurer}>,
type: Object as PropType<Array<Adventurer>>,
default() {
return {} as {[key: string]: Adventurer};
return [] as Array<Adventurer>;
},
},
},
@@ -82,22 +85,35 @@ export default defineComponent({
.selection {
position: absolute;
bottom: 0;
left: 50%;
width: max-content;
max-width: 17rem;
transform: translateX(-50%) translateY(104%);
left: 0;
width: 100%;
max-width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-items: flex-start;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.5rem;
background-color: rgba(0,0,0, 0.2);
background-color: rgba(0,0,0, 0.6);
backdrop-filter: blur(4px);
z-index: 2;
cursor: default;
max-height: 12rem;
overflow-y: auto;
height: 100%;
overflow-y: scroll;
scrollbar-gutter: stable;
.list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 1rem;
max-width: 100%;
}
span {
font-size: 1.5rem;
color: #fff;
}
.slot {
width: 5rem;
height: 5rem;
@@ -131,5 +147,9 @@ export default defineComponent({
width: 100%;
height: 100%;
font-size: 4.5rem;
color: #000;
span {
transform: translateY(-0.5rem);
}
}
</style>
+226
View File
@@ -0,0 +1,226 @@
<template>
<section class="recruit panel pinned-paper">
<h1>Applying adventurers
{{ `(${Object.keys(adventurersForHire).length}/${guild.recruitmentCapacity.getRecruitmentCapacity()})` }}</h1>
<div class="adventurers">
<div class="adventurer-tile" v-for="adventurerForHire in currentlyForHire" :key="adventurerForHire.id">
<adventurer-tile
class="hire-tile"
:adventurer="adventurerForHire"
@click="previewAdventurer(adventurerForHire)"
/>
<div class="decision">
<span
title="Hire"
@click="hireAdventurer(adventurerForHire)"
:class="{disabled: !canRecruitMore}"
>
</span>
<span
:title="Object.keys(adventurersForHire).length > 0 ? 'Dismiss' : ''"
:class="{disabled: Object.keys(adventurersForHire).length <= 0}"
@click="dismissAdventurer(adventurerForHire)"
>
</span>
</div>
</div>
<div v-if="Object.keys(adventurersForHire).length == 0">
<span>No one applied as of now. Check back later!</span>
</div>
</div>
<div>
<button
class="button metal find-recruit"
:disabled="recruitSlotsFilled || guild.gold < newRecruitCost"
@click="findNewRecruit()"
>Find a recruit now {{(`(${formatGold(newRecruitCost)}) gold`)}}</button>
</div>
</section>
</template>
<script lang="ts">
import {defineComponent, type PropType} from "vue";
import AdventurerTile from "@/components/AdventurerTile.vue";
import type {Guild} from "@/classes/Guild";
import type {Adventurer} from "@/classes/Adventurer";
import {formatGold} from "@/classes/NumberMagic";
export default defineComponent({
name: "RecruitView",
components: {AdventurerTile},
computed: {
currentlyForHire(): Array<Adventurer> {
return Object.values(this.adventurersForHire);
},
canRecruitMore() {
return Object.keys(this.adventurersForHire).length < this.guild.adventurerCapacity.getAdventurerCapacity();
},
newRecruitCost(): number {
const guildLevel = this.guild.level;
return Math.max(500, 500 * Math.pow(2.2, guildLevel - 1));
},
recruitSlotsFilled(): boolean {
return Object.keys(this.adventurersForHire).length >= this.guild.recruitmentCapacity.getRecruitmentCapacity();
}
},
methods: {
formatGold,
hireAdventurer(adventurer: Adventurer): void {
if (!this.canRecruitMore) return;
this.$emit("hireAdventurer", adventurer);
},
dismissAdventurer(adventurer: Adventurer) {
if (Object.keys(this.adventurersForHire).length <= 0) return;
this.$emit("dismissAdventurer", adventurer);
},
previewAdventurer(adventurer: Adventurer): void {
this.$emit("previewAdventurer", adventurer);
},
findNewRecruit(): void {
if (!this.canRecruitMore) return;
this.$emit("findNewRecruit");
},
},
props: {
guild: {
type: Object as PropType<Guild>,
required: true,
},
adventurersForHire: {
type: Object as PropType<{ [key: string]: Adventurer }>,
required: true,
},
},
emits: ["dismissAdventurer", "hireAdventurer", "previewAdventurer", "findNewRecruit"],
})
</script>
<style scoped lang="scss">
section {
display: flex;
flex-direction: column;
align-items: center;
overflow-x: hidden;
}
.find-recruit {
text-wrap: wrap;
}
.adventurers {
display: flex;
flex-direction: row;
justify-content: start;
align-items: center;
flex-wrap: nowrap;
gap: 1rem;
scroll-snap-type: x mandatory;
overflow-x: scroll;
width: 100%;
min-height: 12rem;
@media (min-width: 800px) {
flex-wrap: wrap;
justify-content: center;
overflow-x: hidden;
scroll-snap-type: none;
width: auto;
}
.adventurer-tile {
display: flex;
position: relative;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 0.25rem;
font-size: 1.1rem;
cursor: pointer;
padding-block: 1rem;
padding-inline: 0.5rem;
min-width: 100%;
scroll-snap-align: center;
&:not(:first-of-type) {
&::before {
position: absolute;
left: 0;
top: 4rem;
content: "⇠";
width: 1rem;
height: 1rem;
line-height: 1;
}
}
&:not(:last-of-type) {
&::after {
position: absolute;
right: 0;
top: 4rem;
content: "⇢";
width: 1rem;
height: 1rem;
line-height: 1;
}
}
@media (min-width: 800px) {
min-width: auto;
scroll-snap-align: none;
&::before {
content: none !important;
}
&::after {
content: none !important;
}
}
.hire-tile {
height: 7rem;
width: 7rem;
}
b {
line-height: 1;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
}
}
.decision {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-size: 2rem;
gap: 1rem;
line-height: 1;
width: 100%;
span {
cursor: pointer;
&:hover {
color: #fff;
}
&.disabled {
color: rgba(0, 0, 0, 0.5);
cursor: default;
}
}
}
</style>
+3
View File
@@ -48,8 +48,11 @@ export default defineComponent({
overflow: clip;
font-size: 5rem;
line-height: 1;
aspect-ratio: 1/1;
color: rgba(0, 0, 0, 0.75);
position: relative;
background: rgb(2,0,36);
background: radial-gradient(circle, rgba(2,0,36,1) 0%, rgb(69, 69, 84) 57%, rgb(85, 112, 117) 100%);
.level {
position: absolute;
+89
View File
@@ -0,0 +1,89 @@
<template>
<div class="missives-wrapper">
<h1 v-if="label !== undefined">{{ label }}</h1>
<section class="missives">
<QuestMissive
v-for="(missive, key) in quests"
:key="key"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
/>
</section>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
import QuestMissive from "@/components/QuestMissive.vue";
export default defineComponent({
name: "QuestGroup",
components: {QuestMissive},
props: {
quests: {
type: Object,
required: true,
},
adventurers: {
type: Object,
required: true,
},
finalizeQuest: {
type: Function,
required: true,
},
label: {
type: String,
default: undefined,
}
},
})
</script>
<style scoped lang="scss">
h1 {
text-align: center;
}
.missives-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.missives {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
gap: 1rem;
padding-block: 0.5rem;
padding-inline: 5rem;
overflow-x: scroll;
scroll-snap-type: x mandatory;
width: 100vw;
max-width: 100%;
}
@media(min-width: 800px) {
.missives-wrapper {
padding-inline: 1rem;
max-width: 100vw;
overflow-x: hidden;
}
.missives {
display: grid;
padding-inline: 0;
max-width: 1200px;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
grid-auto-rows: auto;
gap: 1rem;
overflow-x: visible;
}
}
</style>
+30 -9
View File
@@ -12,13 +12,14 @@
<div class="drink-stain" v-if="drinkStain.exists">
<DrinkStain/>
</div>
<div class="rank">{{missive.rank}}</div>
<h2>{{ missive.title }}</h2>
<p>{{ missive.text }}</p>
<div class="slots">
<button class="slot">
<AdventurerComponent
:adventurer="missive.adventurers[0]"
:all-adventurers="adventurers"
:all-adventurers="notBusyAdventurers"
@hire-adventurer="(id) => {
adventurers[id].busy = true;
missive.adventurers[0] = adventurers[id];
@@ -36,7 +37,7 @@
</div>
<div class="progressWrap">
<span class="progress"></span>
<span class="percentage">{{ progressPercentage }}</span>
<span class="percentage">{{ `${progressPercentage.toFixed(2)}%` }}</span>
</div>
<h3>Rewards</h3>
<div class="rewards">
@@ -58,6 +59,14 @@ import Parchment from "@/components/misc/Parchment.vue";
export default defineComponent({
name: "QuestMissive",
components: {Parchment, WaterStain, DrinkStain, AdventurerComponent},
computed: {
progressPercentageValue(): string {
return `${this.missive.progressPoints / this.missive.maxProgress * 100}%`;
},
notBusyAdventurers(): Adventurer[] {
return Object.values(this.adventurers).filter(adventurer => !adventurer.busy);
},
},
props: {
missive: {
type: Object as PropType<Quest | any>,
@@ -73,7 +82,7 @@ export default defineComponent({
},
data: () => {
return {
progressPercentage: "0%",
progressPercentage: 0,
stain: false,
drinkStain: {
exists: false,
@@ -85,8 +94,7 @@ export default defineComponent({
methods: {
updateProgress() {
if (this.missive === undefined) return;
const progress = (this.missive.progressPoints / this.missive.maxProgress * 100).toFixed(2);
this.progressPercentage = `${progress}%`;
this.progressPercentage = this.missive.progressPoints / this.missive.maxProgress * 100;
},
randomNumber(min: number, max: number) {
return Math.random() * (max - min) + min;
@@ -102,7 +110,7 @@ export default defineComponent({
}
},
watch: {
missive: {
"missive.progressPoints": {
handler() {
this.updateProgress();
},
@@ -114,11 +122,14 @@ export default defineComponent({
<style lang="scss" scoped>
.missive {
width: min(100%, 14rem);
width: 14rem;
min-width: 14rem;
text-align: center;
border: 2px solid #000;
padding: 0.5rem;
position: relative;
scroll-snap-align: center;
margin: 0 auto;
.parchment {
position: absolute;
@@ -159,7 +170,7 @@ export default defineComponent({
left: 0;
height: 100%;
display: block;
width: v-bind(progressPercentage);
width: v-bind(progressPercentageValue);
background-color: rgba(0, 128, 0, 0.65);
transition: width 250ms linear;
}
@@ -176,6 +187,16 @@ export default defineComponent({
}
}
.rank {
position: absolute;
top: -0.5rem;
left: 0.25rem;
font-size: 3rem;
font-weight: bold;
color: #ab0707;
z-index: -1;
}
.rewards {
display: flex;
flex-direction: row;
@@ -214,7 +235,6 @@ export default defineComponent({
background-color: rgba(0, 0, 0, 0.2);
cursor: pointer;
border-radius: 0.2rem;
position: relative;
}
}
@@ -257,4 +277,5 @@ export default defineComponent({
}
}
}
</style>
+21
View File
@@ -13,6 +13,19 @@
Upgrade ({{ formatGold(guild.adventurerCapacity.nextLevelCost) }} gold)
</button>
</div>
<div class="upgrade">
<span>Recruitment capacity (level {{ guild.recruitmentCapacity.level }})</span>
<small>Increases the maximum amount of adventurers that await recruitment</small>
<button
class="button metal"
v-if="guild.recruitmentCapacity.nextLevelCost"
:disabled="guild.gold < guild.recruitmentCapacity.nextLevelCost || guild.recruitmentCapacity.isMaxLevel()"
@click="upgradeRecruitmentCapacity()"
>
<span v-if="!guild.recruitmentCapacity.isMaxLevel()">Upgrade ({{ formatGold(guild.recruitmentCapacity.nextLevelCost) }} gold)</span>
<span v-else>Max level</span>
</button>
</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>
@@ -75,6 +88,14 @@ export default defineComponent({
this.guild.gold -= this.guild.adventurerCapacity.nextLevelCost;
this.guild.adventurerCapacity.upgrade();
},
upgradeRecruitmentCapacity(): void {
if (!this.guild.recruitmentCapacity) return;
if (this.guild.recruitmentCapacity.isMaxLevel()) return;
if (!this.guild.recruitmentCapacity.nextLevelCost) return;
if (this.guild.gold < this.guild.recruitmentCapacity.nextLevelCost) return;
this.guild.gold -= this.guild.recruitmentCapacity.nextLevelCost;
this.guild.recruitmentCapacity.upgrade();
},
upgradeAutoFinishQuests(): void {
if (!this.guild.autoFinishQuestsUpgrade) return;
if (this.guild.autoFinishQuestsUpgrade.isMaxLevel()) return;
+35
View File
@@ -0,0 +1,35 @@
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
name: "DiscordLogo",
props: {
width: {
type: String,
default: "2rem",
},
height: {
type: String,
default: "2rem",
},
},
})
</script>
<template>
<svg
:width="width"
:height="height"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Discord</title>
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/>
</svg>
</template>
<style scoped lang="scss">
svg {
}
</style>
+33
View File
@@ -0,0 +1,33 @@
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
name: "GithubLogo",
props: {
width: {
type: String,
default: "2rem",
},
height: {
type: String,
default: "2rem",
},
},
})
</script>
<template>
<svg
:width="width"
:height="height"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="24" height="24" fill="none"/>
<path d="M12,2A10,10,0,0,0,8.84,21.5c.5.08.66-.23.66-.5V19.31C6.73,19.91,6.14,18,6.14,18A2.69,2.69,0,0,0,5,16.5c-.91-.62.07-.6.07-.6a2.1,2.1,0,0,1,1.53,1,2.15,2.15,0,0,0,2.91.83,2.16,2.16,0,0,1,.63-1.34C8,16.17,5.62,15.31,5.62,11.5a3.87,3.87,0,0,1,1-2.71,3.58,3.58,0,0,1,.1-2.64s.84-.27,2.75,1a9.63,9.63,0,0,1,5,0c1.91-1.29,2.75-1,2.75-1a3.58,3.58,0,0,1,.1,2.64,3.87,3.87,0,0,1,1,2.71c0,3.82-2.34,4.66-4.57,4.91a2.39,2.39,0,0,1,.69,1.85V21c0,.27.16.59.67.5A10,10,0,0,0,12,2Z"/>
</svg>
</template>
<style scoped lang="scss">
</style>
@@ -70,10 +70,13 @@ export default defineComponent({
gap: 1rem;
max-width: 45rem;
min-height: 30rem;
max-height: 30rem;
width: 100%;
overflow-y: auto;
@media(min-width: 800px) {
max-height: 30rem;
}
h1 {
font-size: 3rem;
line-height: 1;
+10 -5
View File
@@ -1,28 +1,33 @@
import {createRouter, createWebHashHistory} from 'vue-router'
import HomeView from '@/views/HomeView.vue';
import QuestView from "@/views/QuestView.vue";
import AdventurerView from "@/views/AdventurerView.vue";
import TechnicalView from "@/views/TechnicalView.vue";
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'guild',
component: () => import('@/views/HomeView.vue')
component: HomeView,
},
{
path: '/quests',
name: 'quests',
component: () => import('@/views/QuestView.vue')
component: QuestView,
},
{
path: '/adventurers',
name: 'adventurers',
component: () => import('@/views/AdventurerView.vue')
component: AdventurerView,
},
{
path: '/technical',
name: 'technical',
component: () => import('@/views/TechnicalView.vue')
}
component: TechnicalView,
},
]
})
+18 -51
View File
@@ -4,39 +4,20 @@
:adventurer="selectedAdventurer"
v-if="selectedAdventurer !== null"
@closeButtonClicked="selectedAdventurer = null"
:show-prestige-button="adventurers[selectedAdventurer?.id] !== undefined"
/>
<AdventurerRecruitment
:guild="guild"
:adventurers-for-hire="adventurersForHire"
@hireAdventurer="$emit('hireAdventurer', $event)"
@dismissAdventurer="$emit('dismissAdventurer', $event)"
@previewAdventurer="selectedAdventurer = $event"
@findNewRecruit="$emit('findNewRecruit')"
/>
<section class="recruit panel pinned-paper">
<h1>Applying adventurers</h1>
<div class="adventurers">
<div v-if="adventurerForHire">
<adventurer-tile class="hire-tile" :adventurer="adventurerForHire"/>
<div class="decision">
<span
title="Hire"
@click="hireAdventurer()"
:class="{disabled: Object.keys(adventurers).length >= guild.adventurerCapacity.getAdventurerCapacity()}"
>
</span>
<span
:title="Object.keys(adventurers).length > 0 ? 'Dismiss' : ''"
:class="{disabled: Object.keys(adventurers).length <= 0}"
@click="dismissAdventurer()"
>
</span>
</div>
</div>
<div v-else>
<span>Noone applied as of now. Check back later!</span>
</div>
</div>
</section>
<section class="collection panel pinned-paper">
<h1>
Recruited adventurers ({{ Object.keys(adventurers).length }} / {{ guild.adventurerCapacity.getAdventurerCapacity() }})
Recruited adventurers ({{ Object.keys(adventurers).length }} /
{{ guild.adventurerCapacity.getAdventurerCapacity() }})
</h1>
<small>Click an adventurer to see details about them</small>
<div class="adventurers">
@@ -61,14 +42,13 @@ import AdventurerTile from "@/components/AdventurerTile.vue";
import type {Adventurer} from "@/classes/Adventurer";
import type {Guild} from "@/classes/Guild";
import AdventurerDetails from "@/components/AdventurerDetails.vue";
import AdventurerRecruitment from "@/components/AdventurerRecruitment.vue";
export default defineComponent({
name: "RecruitView",
components: {AdventurerDetails, AdventurerTile},
name: "AdventurerView",
components: {AdventurerDetails, AdventurerTile, AdventurerRecruitment},
data: () => {
return {
currentlyForHire: null as Adventurer | null,
adventurersForHire: [] as Array<Adventurer>,
selectedAdventurer: null as Adventurer | null,
}
},
@@ -81,27 +61,14 @@ export default defineComponent({
type: Object as PropType<{ [key: string]: Adventurer }>,
required: true,
},
adventurerForHire: {
type: Object as PropType<Adventurer | null>,
adventurersForHire: {
type: Object as PropType<{ [key: string]: Adventurer }>,
default() {
return null;
return {};
}
},
},
methods: {
hireAdventurer(): void {
if (Object.keys(this.adventurers).length >= this.guild.adventurerCapacity.getAdventurerCapacity()) return;
this.$emit("recruitActionTaken", this.adventurerForHire);
},
dismissAdventurer() {
if (Object.keys(this.adventurers).length <= 0) return;
this.$emit("recruitActionTaken", null);
}
},
async mounted() {
},
emits: ["recruitActionTaken"]
emits: ["hireAdventurer", "dismissAdventurer", "findNewRecruit"],
});
</script>
+19 -97
View File
@@ -1,90 +1,20 @@
<template>
<section>
<div class="guild" v-if="guild.level >= 7 && Object.keys(quests.S).length > 0">
<h1>Rank S Quests</h1>
<section class="missives">
<QuestMissive
v-for="(missive, key, index) in quests.S"
:key="key"
<QuestGroup
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
:quests="quests.filter(quest => quest.progressPoints < quest.maxProgress)"
:finalizeQuest="finalizeQuest"
label="Quests"
v-show="quests.filter(quest => quest.progressPoints < quest.maxProgress).length > 0"
/>
</section>
</div>
<div class="guild" v-if="guild.level >= 6 && Object.keys(quests.A).length > 0">
<h1>Rank A Quests</h1>
<section class="missives">
<QuestMissive
v-for="(missive, key, index) in quests.A"
:key="key"
<QuestGroup
:finalize-quest="finalizeQuest"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
:quests="quests.filter(quest => quest.progressPoints >= quest.maxProgress)"
label="Completed Quests"
v-show="quests.filter(quest => quest.progressPoints >= quest.maxProgress).length > 0"
/>
</section>
</div>
<div class="guild" v-if="guild.level >= 5 && Object.keys(quests.B).length > 0">
<h1>Rank B Quests</h1>
<section class="missives">
<QuestMissive
v-for="(missive, key, index) in quests.B"
:key="key"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
/>
</section>
</div>
<div class="guild" v-if="guild.level >= 4 && Object.keys(quests.C).length > 0">
<h1>Rank C Quests</h1>
<section class="missives">
<QuestMissive
v-for="(missive, key, index) in quests.C"
:key="key"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
/>
</section>
</div>
<div class="guild" v-if="guild.level >= 3 && Object.keys(quests.D).length > 0">
<h1>Rank D Quests</h1>
<section class="missives">
<QuestMissive
v-for="(missive, key, index) in quests.D"
:key="key"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest((missive))"
/>
</section>
</div>
<div class="guild" v-if="guild.level >= 2 && Object.keys(quests.E).length > 0">
<h1>Rank E Quests</h1>
<section class="missives">
<QuestMissive
v-for="(missive, key, index) in quests.E"
:key="key"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
/>
</section>
</div>
<div class="guild" v-if="Object.keys(quests.F).length > 0">
<h1>Rank F Quests</h1>
<section class="missives">
<QuestMissive
v-for="(missive, key, index) in quests.F"
:key="key"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
/>
</section>
</div>
</section>
</template>
<script lang="ts">
@@ -94,10 +24,11 @@ import type {Adventurer} from "@/classes/Adventurer";
import type {Quest} from "@/classes/Quest";
import {Guild} from "@/classes/Guild";
import QuestMissive from "@/components/QuestMissive.vue";
import QuestGroup from "@/components/QuestGroup.vue";
export default defineComponent({
name: "QuestView",
components: {QuestMissive, AdventurerComponent},
components: {QuestGroup, QuestMissive, AdventurerComponent},
props: {
guild: {
type: Object as PropType<Guild>,
@@ -108,7 +39,7 @@ export default defineComponent({
required: true,
},
quests: {
type: Object as PropType<{ [key: string]: Quest }>,
type: Object as PropType<Array<Quest>>,
required: true,
},
lastRecruitTime: {
@@ -130,28 +61,19 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
.guild {
section {
display: flex;
flex-direction: column;
justify-content: center;
justify-content: flex-start;
align-items: center;
width: 100%;
padding-bottom: 0.5rem;
h1 {
font-size: 3rem;
text-align: center;
}
.missives {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: calc(100% - 2rem);
justify-content: center;
align-items: stretch;
padding-inline: 1rem;
gap: 1rem;
@media(min-width: 800px) {
section {
flex-direction: column-reverse;
}
}
</style>
+11 -3
View File
@@ -10,11 +10,11 @@
<h1>Socials</h1>
<div class="links">
<a class="link" href="https://discord.gg/j8KK5dGBps">
<img class="icon" src="/img/icons/discord.svg" alt="Discord"/>
<DiscordLogo/>
Discord
</a>
<a class="link" href="https://github.com/YouHaveTrouble/GuildMaster">
<img class="icon" src="/img/icons/github.svg" alt="GitHub"/>
<GithubLogo/>
GitHub
</a>
</div>
@@ -30,10 +30,18 @@ import {defineComponent} from "vue";
import ChangelogComponent from "@/components/technical/ChangelogComponent.vue";
import SaveManagerComponent from "@/components/technical/SaveManagerComponent.vue";
import Nail from "@/components/misc/Nail.vue";
import DiscordLogo from "@/components/misc/DiscordLogo.vue";
import GithubLogo from "@/components/misc/GithubLogo.vue";
export default defineComponent({
name: "TechnicalView",
components: {Nail, SaveManagerComponent, ChangelogComponent},
components: {
DiscordLogo,
GithubLogo,
Nail,
SaveManagerComponent,
ChangelogComponent,
},
});
</script>