diff --git a/public/img/buildings/palace.png b/public/img/buildings/palace.png new file mode 100644 index 0000000..9a70141 Binary files /dev/null and b/public/img/buildings/palace.png differ diff --git a/src/create.ts b/src/create.ts index 2cfe341..5064948 100644 --- a/src/create.ts +++ b/src/create.ts @@ -1,6 +1,8 @@ import buildings from "./data/buildings"; +import { NAMES } from "./data/heroes"; import { Hex } from "./hexgrid"; -import type { BuildingSource, BuildingType } from "./types"; +import type { BuildingSource, BuildingType, HeroType, QuestType, ResourcesType } from "./types"; +import { getEmptyResources, random, shuffle } from "./utils"; let uid = 0; @@ -33,3 +35,37 @@ export function createBuilding(buildingType: string): BuildingType { }, }; } + + +export function createQuest(level: number): QuestType { + const reward = getEmptyResources(); + const adjustedLevel = level * level + 3; + const duration = random(adjustedLevel - 2, adjustedLevel + 2); + Object.keys(reward).forEach(r => { + const resource = r as keyof ResourcesType; + reward[resource] = random( + Math.round((duration * 5 - level * 10) * (1 + level / 3)), + Math.round((duration * 5 + level * 10) * (1 + level / 3)), + ); + + if (resource === 'culture') { + reward[resource] = Math.round(reward[resource] / 20); + } + }); + + return { + id: uid++, + duration, + reward, + level, + started: false, + }; +} + + +export function createHero(): HeroType { + return { + id: uid++, + name: shuffle(NAMES)[0], + }; +} diff --git a/src/data/buildings.ts b/src/data/buildings.ts index 66d1140..090b5c8 100644 --- a/src/data/buildings.ts +++ b/src/data/buildings.ts @@ -1,3 +1,4 @@ +import { createHero } from "../create"; import type { BuildingType } from "../types"; import { getEmptyResources } from "../utils"; import type { VillageState } from "../village"; @@ -31,10 +32,10 @@ export default [ maxLevel: 20, cost: (level: number) => { return { - wood: level * 10, - stone: level * 10, - iron: level * 10, - food: 0, + wood: getResourceBuildingCost(level, 10), + stone: getResourceBuildingCost(level, 15), + iron: getResourceBuildingCost(level, 20), + food: getResourceBuildingCost(level, 5), }; }, timeToBuild: getStandardTimeToBuild, @@ -258,4 +259,34 @@ export default [ }, }, }, + { + type: 'palace', + name: 'Palace', + maxLevel: 20, + unique: true, + cost: (level: number) => { + return { + wood: getStorageBuildingCost(level, 200), + stone: getStorageBuildingCost(level, 240), + iron: getStorageBuildingCost(level, 110), + food: getStorageBuildingCost(level, 70), + }; + }, + timeToBuild: getStandardTimeToBuild, + behavior: { + production: (V: VillageState, self: BuildingType) => { + const prod = getEmptyResources(); + prod.food = self.level * -30; + return prod; + }, + triggers: { + onLevelUp: (V: VillageState, self: BuildingType) => { + if (self.level === 10 || self.level === 20) { + // Create a new hero. + V.heroes.push(createHero()); + } + }, + }, + }, + }, ]; diff --git a/src/data/heroes.ts b/src/data/heroes.ts new file mode 100644 index 0000000..775bdd4 --- /dev/null +++ b/src/data/heroes.ts @@ -0,0 +1,12 @@ +export const NAMES = [ + 'Adosinda', + 'Garsendis', + 'Amalfriede', + 'Seda', + 'Amalgard', + 'Samocenus', + 'Licno', + 'Vercombogious', + 'Elusconos', + 'Excingullus', +]; diff --git a/src/hud/BuildingCreator.svelte b/src/hud/BuildingCreator.svelte index 3041e20..06490fc 100644 --- a/src/hud/BuildingCreator.svelte +++ b/src/hud/BuildingCreator.svelte @@ -4,7 +4,7 @@ import moves from "../moves"; import showBuildingCreator from "../stores/showBuildingCreator"; import { canPayBuildingCost } from "../utils"; - import village from "../village"; + import village, { type VillageState } from "../village"; import Cost from "./Cost.svelte"; function close() { @@ -22,11 +22,14 @@ } } - const constructible = buildings.filter(b => !b.autoBuilt).map(b =>{ - const building = createBuilding(b.type); - building.level = 0; - return building; - }); + $: constructible = buildings + .filter(b => !b.autoBuilt) + .filter(b => !(b.unique && $village.buildings.some(b2 => b2.type === b.type))) + .map(b =>{ + const building = createBuilding(b.type); + building.level = 0; + return building; + }); { #if $showBuildingCreator !== null } diff --git a/src/hud/Quests.svelte b/src/hud/Quests.svelte index 4fcd89d..7408744 100644 --- a/src/hud/Quests.svelte +++ b/src/hud/Quests.svelte @@ -7,10 +7,10 @@ import { flip } from "svelte/animate"; - $: isQuestStarted = $village.quests.some(q => q.started); + $: availableHeroes = $village.heroes.filter(h => !$village.quests.some(q => q.hero === h.id)); - function startQuest(id: number) { - moves.startQuest(id); + function startQuest(id: number, heroId: number) { + moves.startQuest(id, heroId); } @@ -31,12 +31,15 @@ { Math.ceil((quest.remainingTime || 0) / 1000) } { :else } { quest.duration } + { #each availableHeroes as hero } + { :else } + No heroes available + { /each } { /if }
diff --git a/src/hud/Units.svelte b/src/hud/Units.svelte index a9a9b73..878ccd1 100644 --- a/src/hud/Units.svelte +++ b/src/hud/Units.svelte @@ -17,6 +17,9 @@{ hero.name }
+ { /each } { #each currentUnits as unit }{ unit.name }: { getRemainingUnitCount($village, unit.type) } / { unit.count }
{ /each } diff --git a/src/moves/startQuest.ts b/src/moves/startQuest.ts index 7656d79..ef9eb60 100644 --- a/src/moves/startQuest.ts +++ b/src/moves/startQuest.ts @@ -1,12 +1,23 @@ import type { VillageState } from "../village"; -export default function startQuest(V: VillageState, questId: number) { +export default function startQuest(V: VillageState, questId: number, heroId: number) { const quest = V.quests.find(q => q.id === questId); if (!quest) { return false; } + const hero = V.heroes.find(h => h.id === heroId); + if (!hero) { + return false; + } + + // Make sure the hero is not already on a quest. + if (V.quests.some(q => q.hero === heroId)) { + return false; + } + quest.started = true; + quest.hero = heroId; quest.remainingTime = quest.duration * 1000; return true; diff --git a/src/types.ts b/src/types.ts index 5a862b9..a49432f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,6 +24,7 @@ export interface BuildingSource { name: string; type: string; autoBuilt?: boolean; + unique?: boolean; maxLevel: number; cost: (level: number) => CostType; timeToBuild: (level: number) => number; @@ -35,6 +36,9 @@ export interface BuildingSource { recruitmentTime: Function; }; constructionTimeReductionPerLevel?: number; + triggers?: { + onLevelUp?: Function; + }; }; } @@ -101,6 +105,7 @@ export type RegionType = OasisType | BourgadeType; export interface HeroType { id: number; + name: string; } @@ -110,5 +115,6 @@ export interface QuestType { reward: ResourcesType; level: number; started: boolean; + hero?: number; remainingTime?: number; } diff --git a/src/update.ts b/src/update.ts index 6ea9aa0..11298ac 100644 --- a/src/update.ts +++ b/src/update.ts @@ -31,6 +31,7 @@ export default function update(timestamp: number) { if (b.state.upgrade.remainingTime <= 0) { b.level++; b.state.upgrade.isUpgrading = false; + b.behavior.triggers?.onLevelUp?.(V, b); } }); diff --git a/src/village.ts b/src/village.ts index ada6414..2543b1a 100644 --- a/src/village.ts +++ b/src/village.ts @@ -1,11 +1,10 @@ import { writable } from "svelte/store"; -import { createBuilding } from "./create"; +import { createBuilding, createHero, createQuest } from "./create"; import worldmap from "./data/worldmap"; import { getTilesAtDistance, Hex } from "./hexgrid"; -import type { BuildingType, QuestType, RegionType, ResourcesType } from "./types"; +import type { BuildingType, HeroType, QuestType, RegionType, ResourcesType } from "./types"; import { getKeysAsNumbers, shuffle } from "./utils"; -import { createQuest } from "./quests"; type Board = { @@ -24,6 +23,7 @@ export interface VillageState { villageTiles: Board; outsideTiles: Board; worldmap: RegionType[]; + heroes: HeroType[]; quests: QuestType[]; victory: boolean; } @@ -103,6 +103,9 @@ function getInitialState() { villageTiles: getInitialVillageBoard(), outsideTiles: getInitialOutsideBoard(), worldmap: getInitialWorldmap(), + heroes: [ + createHero(), + ], quests: getInitialQuests(), victory: false, }; @@ -131,7 +134,7 @@ function getInitialState() { } const newBuilding = createBuilding(type); newBuilding.tile = new Hex(x, y); - newBuilding.level = 1; //newBuilding.type === 'field' ? 1 : 10; + newBuilding.level = 20; //newBuilding.type === 'field' ? 1 : 10; state.outsideTiles[y][x] = newBuilding.id; state.buildings.push(newBuilding); });