Add quests that give resources as reward.
This commit is contained in:
parent
1f312c072b
commit
10bd9121a2
@ -37,9 +37,6 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,6 @@
|
|||||||
.outside-map {
|
.outside-map {
|
||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-top: 0.8em;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
.village-map {
|
.village-map {
|
||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-top: 0.8em;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
<style>
|
<style>
|
||||||
.worldmap {
|
.worldmap {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.region {
|
.region {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import buildings from "./data/buildings";
|
import buildings from "./data/buildings";
|
||||||
import { Hex } from "./hexgrid";
|
import { Hex } from "./hexgrid";
|
||||||
import type { BuildingType, BuildingSource } from "./types";
|
import type { BuildingSource, BuildingType } from "./types";
|
||||||
|
|
||||||
|
|
||||||
let uid = 0;
|
let uid = 0;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
import Resources from "./Resources.svelte";
|
import Resources from "./Resources.svelte";
|
||||||
import Units from "./Units.svelte";
|
import Units from "./Units.svelte";
|
||||||
import Victory from "./Victory.svelte";
|
import Victory from "./Victory.svelte";
|
||||||
|
import Quests from "./Quests.svelte";
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@ -36,10 +37,13 @@
|
|||||||
|
|
||||||
<section class="hud">
|
<section class="hud">
|
||||||
<header>
|
<header>
|
||||||
<Units />
|
|
||||||
<Resources />
|
|
||||||
<Navigation { setTab } />
|
<Navigation { setTab } />
|
||||||
|
<Resources />
|
||||||
</header>
|
</header>
|
||||||
|
<div class="side">
|
||||||
|
<Units />
|
||||||
|
<Quests />
|
||||||
|
</div>
|
||||||
<div class="board">
|
<div class="board">
|
||||||
{ #if $gameTab === 'village' }
|
{ #if $gameTab === 'village' }
|
||||||
<Village />
|
<Village />
|
||||||
@ -57,9 +61,19 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
.hud {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 20vw 1fr 20vw;
|
||||||
|
grid-template-rows: 20vh 1fr;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hud header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-area: 1 / span 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
|
50
src/hud/Quests.svelte
Normal file
50
src/hud/Quests.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
|
||||||
|
import moves from "../moves";
|
||||||
|
import village from "../village";
|
||||||
|
import Reward from "./Reward.svelte";
|
||||||
|
import { flip } from "svelte/animate";
|
||||||
|
|
||||||
|
|
||||||
|
$: isQuestStarted = $village.quests.some(q => q.started);
|
||||||
|
|
||||||
|
function startQuest(id: number) {
|
||||||
|
moves.startQuest(id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
{ #each $village.quests as quest (quest.id) }
|
||||||
|
<div
|
||||||
|
class="quest"
|
||||||
|
in:fly={{ duration: 200, delay: 100, x: -500 }}
|
||||||
|
out:fly={{ duration: 200, x: 500 }}
|
||||||
|
animate:flip={{ duration: 100 }}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<Reward reward={ quest.reward } />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<img src="/img/icons/time.png" alt="Duration">
|
||||||
|
{ #if quest.started }
|
||||||
|
{ Math.ceil((quest.remainingTime || 0) / 1000) }
|
||||||
|
{ :else }
|
||||||
|
{ quest.duration }
|
||||||
|
<button
|
||||||
|
on:click={ () => startQuest(quest.id) }
|
||||||
|
disabled={ isQuestStarted }
|
||||||
|
>
|
||||||
|
Start quest
|
||||||
|
</button>
|
||||||
|
{ /if }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{ /each }
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.quest {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
@ -38,8 +38,8 @@
|
|||||||
<style>
|
<style>
|
||||||
.resources {
|
.resources {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: center;
|
||||||
gap: 1em;
|
gap: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resources div {
|
.resources div {
|
||||||
|
53
src/hud/Reward.svelte
Normal file
53
src/hud/Reward.svelte
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ResourcesType } from "../types";
|
||||||
|
|
||||||
|
export let reward: ResourcesType;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="reward">
|
||||||
|
{ #if reward.wood > 0 }
|
||||||
|
<div>
|
||||||
|
<img src="/img/icons/wood.png" alt="Wood" />
|
||||||
|
{ reward.wood }
|
||||||
|
</div>
|
||||||
|
{ /if }
|
||||||
|
{ #if reward.stone > 0 }
|
||||||
|
<div>
|
||||||
|
<img src="/img/icons/stone.png" alt="Stone" />
|
||||||
|
{ reward.stone }
|
||||||
|
</div>
|
||||||
|
{ /if }
|
||||||
|
{ #if reward.iron > 0 }
|
||||||
|
<div>
|
||||||
|
<img src="/img/icons/iron.png" alt="Iron" />
|
||||||
|
{ reward.iron }
|
||||||
|
</div>
|
||||||
|
{ /if }
|
||||||
|
{ #if reward.food > 0 }
|
||||||
|
<div>
|
||||||
|
<img src="/img/icons/food.png" alt="Food" />
|
||||||
|
{ reward.food }
|
||||||
|
</div>
|
||||||
|
{ /if }
|
||||||
|
{ #if reward.culture > 0 }
|
||||||
|
<div>
|
||||||
|
<img src="/img/icons/culture.png" alt="Culture" />
|
||||||
|
{ reward.culture }
|
||||||
|
</div>
|
||||||
|
{ /if }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.reward {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reward div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.2em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,9 +2,10 @@ import { produce } from 'immer';
|
|||||||
|
|
||||||
import village, { type VillageState } from '../village';
|
import village, { type VillageState } from '../village';
|
||||||
import build from './build';
|
import build from './build';
|
||||||
import upgradeBuilding from './upgradeBuilding';
|
|
||||||
import recruitUnits from './recruitUnits';
|
|
||||||
import pillage from './pillage';
|
import pillage from './pillage';
|
||||||
|
import recruitUnits from './recruitUnits';
|
||||||
|
import startQuest from './startQuest';
|
||||||
|
import upgradeBuilding from './upgradeBuilding';
|
||||||
|
|
||||||
|
|
||||||
// Encapsulates a move function into a store update, where the data is made
|
// Encapsulates a move function into a store update, where the data is made
|
||||||
@ -34,4 +35,5 @@ export default {
|
|||||||
upgradeBuilding: makeMove(upgradeBuilding),
|
upgradeBuilding: makeMove(upgradeBuilding),
|
||||||
pillage: makeMove(pillage),
|
pillage: makeMove(pillage),
|
||||||
recruitUnits: makeMove(recruitUnits),
|
recruitUnits: makeMove(recruitUnits),
|
||||||
|
startQuest: makeMove(startQuest),
|
||||||
};
|
};
|
||||||
|
13
src/moves/startQuest.ts
Normal file
13
src/moves/startQuest.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { VillageState } from "../village";
|
||||||
|
|
||||||
|
export default function startQuest(V: VillageState, questId: number) {
|
||||||
|
const quest = V.quests.find(q => q.id === questId);
|
||||||
|
if (!quest) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
quest.started = true;
|
||||||
|
quest.remainingTime = quest.duration * 1000;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
34
src/quests.ts
Normal file
34
src/quests.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import type { QuestType, ResourcesType } from "./types";
|
||||||
|
import { getEmptyResources, random } from "./utils";
|
||||||
|
|
||||||
|
|
||||||
|
let uid = 0;
|
||||||
|
|
||||||
|
|
||||||
|
export function createQuest(level: number): QuestType {
|
||||||
|
const reward = getEmptyResources();
|
||||||
|
const adjustedLevel = level * 2 + 5;
|
||||||
|
const duration = random(adjustedLevel - 2, adjustedLevel + 2);
|
||||||
|
Object.keys(reward).forEach(r => {
|
||||||
|
const resource = r as keyof ResourcesType;
|
||||||
|
reward[resource] = random(
|
||||||
|
duration * 5 - level * 10,
|
||||||
|
duration * 5 + level * 10,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resource === 'food') {
|
||||||
|
reward[resource] = Math.round(reward[resource] / 3);
|
||||||
|
}
|
||||||
|
else if (resource === 'culture') {
|
||||||
|
reward[resource] = Math.round(reward[resource] / 20);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: uid++,
|
||||||
|
duration,
|
||||||
|
reward,
|
||||||
|
level,
|
||||||
|
started: false,
|
||||||
|
};
|
||||||
|
}
|
15
src/types.ts
15
src/types.ts
@ -95,3 +95,18 @@ export interface BourgadeType extends BaseRegionType {
|
|||||||
|
|
||||||
|
|
||||||
export type RegionType = OasisType | BourgadeType;
|
export type RegionType = OasisType | BourgadeType;
|
||||||
|
|
||||||
|
|
||||||
|
export interface HeroType {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface QuestType {
|
||||||
|
id: number;
|
||||||
|
duration: number;
|
||||||
|
reward: ResourcesType;
|
||||||
|
level: number;
|
||||||
|
started: boolean;
|
||||||
|
remainingTime?: number;
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { resolveMission } from './missions';
|
|||||||
import type { ProductionType } from './types';
|
import type { ProductionType } from './types';
|
||||||
import { getProduction, getStorage, shuffle } from './utils';
|
import { getProduction, getStorage, shuffle } from './utils';
|
||||||
import village, { type VillageState } from "./village";
|
import village, { type VillageState } from "./village";
|
||||||
|
import { createQuest } from './quests';
|
||||||
|
|
||||||
|
|
||||||
let lastFrame: number;
|
let lastFrame: number;
|
||||||
@ -46,6 +47,26 @@ export default function update(timestamp: number) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Advance quests.
|
||||||
|
V.quests.filter(q => q.started).forEach(quest => {
|
||||||
|
if (!quest.remainingTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
quest.remainingTime -= delta;
|
||||||
|
if (quest.remainingTime <= 0) {
|
||||||
|
V.resources.wood += quest.reward.wood;
|
||||||
|
V.resources.stone += quest.reward.stone;
|
||||||
|
V.resources.iron += quest.reward.iron;
|
||||||
|
V.resources.food += quest.reward.food;
|
||||||
|
V.resources.culture += quest.reward.culture;
|
||||||
|
|
||||||
|
// Replace the finished quest with a new one.
|
||||||
|
V.quests = V.quests.filter(q => q.id !== quest.id);
|
||||||
|
V.quests.push(createQuest(quest.level + 1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Make all buildings and units produce and consume.
|
// Make all buildings and units produce and consume.
|
||||||
const productionPerMinute = getProduction(V);
|
const productionPerMinute = getProduction(V);
|
||||||
const storage = getStorage(V);
|
const storage = getStorage(V);
|
||||||
|
15
src/utils.ts
15
src/utils.ts
@ -130,6 +130,21 @@ export function shuffle<T>(array: Array<T>): Array<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a random integer in the range [ min, max ] (min and max can be returned).
|
||||||
|
* @param min integer
|
||||||
|
* @param max integer
|
||||||
|
* @returns integer
|
||||||
|
*/
|
||||||
|
export function random(min: number, max?: number) {
|
||||||
|
if (max == null) {
|
||||||
|
max = min;
|
||||||
|
min = 0;
|
||||||
|
}
|
||||||
|
return min + Math.floor(Math.random() * (max - min + 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getBuildingUpgradeCost(_V: VillageState, building: BuildingType): CostType {
|
export function getBuildingUpgradeCost(_V: VillageState, building: BuildingType): CostType {
|
||||||
const level = building.level + 1;
|
const level = building.level + 1;
|
||||||
return building.cost(level);
|
return building.cost(level);
|
||||||
|
@ -3,8 +3,9 @@ import { writable } from "svelte/store";
|
|||||||
import { createBuilding } from "./create";
|
import { createBuilding } from "./create";
|
||||||
import worldmap from "./data/worldmap";
|
import worldmap from "./data/worldmap";
|
||||||
import { getTilesAtDistance, Hex } from "./hexgrid";
|
import { getTilesAtDistance, Hex } from "./hexgrid";
|
||||||
import type { BuildingType, RegionType, ResourcesType } from "./types";
|
import type { BuildingType, QuestType, RegionType, ResourcesType } from "./types";
|
||||||
import { getKeysAsNumbers, shuffle } from "./utils";
|
import { getKeysAsNumbers, shuffle } from "./utils";
|
||||||
|
import { createQuest } from "./quests";
|
||||||
|
|
||||||
|
|
||||||
type Board = {
|
type Board = {
|
||||||
@ -23,6 +24,7 @@ export interface VillageState {
|
|||||||
villageTiles: Board;
|
villageTiles: Board;
|
||||||
outsideTiles: Board;
|
outsideTiles: Board;
|
||||||
worldmap: RegionType[];
|
worldmap: RegionType[];
|
||||||
|
quests: QuestType[];
|
||||||
victory: boolean;
|
victory: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +80,15 @@ function getInitialWorldmap(): RegionType[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getInitialQuests(): QuestType[] {
|
||||||
|
return [
|
||||||
|
createQuest(1),
|
||||||
|
createQuest(1),
|
||||||
|
createQuest(1),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function getInitialState() {
|
function getInitialState() {
|
||||||
const state: VillageState = {
|
const state: VillageState = {
|
||||||
buildings: [],
|
buildings: [],
|
||||||
@ -92,6 +103,7 @@ function getInitialState() {
|
|||||||
villageTiles: getInitialVillageBoard(),
|
villageTiles: getInitialVillageBoard(),
|
||||||
outsideTiles: getInitialOutsideBoard(),
|
outsideTiles: getInitialOutsideBoard(),
|
||||||
worldmap: getInitialWorldmap(),
|
worldmap: getInitialWorldmap(),
|
||||||
|
quests: getInitialQuests(),
|
||||||
victory: false,
|
victory: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user