Compare commits

..

4 Commits

10 changed files with 266 additions and 221 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.
+1 -1
View File
@@ -1 +1 @@
Looking for artists for assets for this game! Need backgrounds and icons! Join discord for more info!
Almost complete rewrite coming soon. Probably. Maybe. Not sure yet.
+82 -104
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -6,7 +6,7 @@ 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;
@@ -16,7 +16,7 @@ export class GameData {
) {
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.missives = data.missives ?? [] as Array<Quest>;
this.lastQuestGot = data.lastQuestGot ?? {} as { [key: string]: null | number };
this.lastRecruitAction = data.lastRecruitAction ?? null;
this.adventurerForHireId = data.adventurerForHireId ?? null;
+5 -1
View File
@@ -1,6 +1,10 @@
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0 0 10rem;
padding: 0 0 2rem;
min-height: calc(100vh - 10rem);
font-family: 'EB Garamond', serif;
overflow-y: scroll;
+13 -2
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,
expReward: number,
goldReward: number,
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 {
@@ -83,4 +94,4 @@ export function getQuestWithRewards(quest: Quest, expModifier: number = 1, goldM
function randomNumberBetween(min: number, max: number) {
return Math.random() * (max - min) + min;
}
}
File diff suppressed because one or more lines are too long
+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: auto;
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;
}
}
</style>
+26 -7
View File
@@ -12,6 +12,7 @@
<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">
@@ -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,11 @@ 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}%`;
},
},
props: {
missive: {
type: Object as PropType<Quest | any>,
@@ -73,7 +79,7 @@ export default defineComponent({
},
data: () => {
return {
progressPercentage: "0%",
progressPercentage: 0,
stain: false,
drinkStain: {
exists: false,
@@ -85,8 +91,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 +107,7 @@ export default defineComponent({
}
},
watch: {
missive: {
"missive.progressPoints": {
handler() {
this.updateProgress();
},
@@ -114,11 +119,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 +167,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 +184,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;
@@ -257,4 +275,5 @@ export default defineComponent({
}
}
}
</style>
+24 -102
View File
@@ -1,89 +1,19 @@
<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"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
/>
</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"
:adventurers="adventurers"
:missive="missive"
@click="finalizeQuest(missive)"
/>
</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>
<QuestGroup
:adventurers="adventurers"
:quests="quests.filter(quest => quest.progressPoints < quest.maxProgress)"
:finalizeQuest="finalizeQuest"
label="Quests"
v-show="quests.filter(quest => quest.progressPoints < quest.maxProgress).length > 0"
/>
<QuestGroup
:finalize-quest="finalizeQuest"
:adventurers="adventurers"
:quests="quests.filter(quest => quest.progressPoints >= quest.maxProgress)"
label="Completed Quests"
v-show="quests.filter(quest => quest.progressPoints >= quest.maxProgress).length > 0"
/>
</section>
</template>
@@ -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>