Compare commits

..

No commits in common. "a21585280adf67fc3fa5f08ceb6c001643d4cb4d" and "ff7458757a65d5996fd7150b4e8befb84ed6735c" have entirely different histories.

25 changed files with 49 additions and 212 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

View File

@ -18,57 +18,28 @@
} }
</script> </script>
<div class="building"> <div>
<div class="illustration"> <p>{ building.name }</p>
<img src="/img/buildings/{ building.type }.png" alt=""> <p>
</div> <button
<div class="content"> class="level"
<!-- <p>{ building.name }</p> --> class:can-upgrade={ canUpgrade }
<div class="level"> class:is-upgrading={ isUpgrading }
<button class:max-level={ maxLevelReached }
class:can-upgrade={ canUpgrade } on:click|stopPropagation={ upgradeBuilding }
class:is-upgrading={ isUpgrading } >
class:max-level={ maxLevelReached } { building.level }
on:click|stopPropagation={ upgradeBuilding } { #if building.state.upgrade.isUpgrading }
> <span>
{ building.level } ({ Math.ceil(building.state.upgrade.remainingTime / 1000) })
{ #if building.state.upgrade.isUpgrading } </span>
<span> { /if }
({ Math.ceil(building.state.upgrade.remainingTime / 1000) }) </button>
</span> </p>
{ /if }
</button>
</div>
</div>
</div> </div>
<style> <style>
.building {
position: relative;
height: 100%;
}
.building .illustration,
.building .illustration img {
width: 100%;
}
.building .content {
left: 0;
height: 100%;
position: absolute;
top: 0;
width: 100%;
}
.level { .level {
bottom: -0.4em;
left: 50%;
position: absolute;
translate: -50%;
}
.level button {
aspect-ratio: 1; aspect-ratio: 1;
background-color: hsl(208, 100%, 97%); background-color: hsl(208, 100%, 97%);
border: 0.4em solid hsl(0, 0%, 45%); border: 0.4em solid hsl(0, 0%, 45%);
@ -77,26 +48,21 @@
box-shadow: 0 0 0.2em black; box-shadow: 0 0 0.2em black;
color: hsl(0, 0%, 10%); color: hsl(0, 0%, 10%);
font-size: 1.2em; font-size: 1.2em;
transition: all 100ms ease-in-out;
} }
.level button span { .level span {
font-size: 0.9em; font-size: 0.9em;
} }
.level button.can-upgrade { .level.can-upgrade {
border-color: hsl(90, 99%, 36%); border-color: hsl(90, 99%, 36%);
} }
.level button.is-upgrading { .level.is-upgrading {
border-color: hsl(56, 99%, 43%); border-color: hsl(56, 99%, 43%);
} }
.level button.max-level { .level.max-level {
border-color: hsl(209, 70%, 52%); border-color: hsl(209, 70%, 52%);
} }
.level button:hover {
scale: 1.1;
}
</style> </style>

View File

@ -16,10 +16,8 @@
<style> <style>
.hexagon { .hexagon {
aspect-ratio: 1; aspect-ratio: 1;
border: 0.2em solid; background-color: hsl(220, 75%, 75%);
border-color: hsl(220, 75%, 75%);
border-radius: 100%; border-radius: 100%;
box-sizing: border-box;
cursor: pointer; cursor: pointer;
font-size: 1.2vmin; font-size: 1.2vmin;
height: 12em; height: 12em;
@ -29,11 +27,11 @@
} }
.hexagon:hover { .hexagon:hover {
border-color: hsl(60, 75%, 75%); background-color: hsl(60, 75%, 75%);
} }
.hexagon:active { .hexagon:active {
border-color: hsl(60, 75%, 50%); background-color: hsl(60, 75%, 50%);
} }
.hexagon .content { .hexagon .content {

View File

@ -1,8 +1,6 @@
import buildings from "./data/buildings"; import buildings from "./data/buildings";
import { NAMES } from "./data/heroes";
import { Hex } from "./hexgrid"; import { Hex } from "./hexgrid";
import type { BuildingSource, BuildingType, HeroType, QuestType, ResourcesType } from "./types"; import type { BuildingSource, BuildingType } from "./types";
import { getEmptyResources, random, shuffle } from "./utils";
let uid = 0; let uid = 0;
@ -35,37 +33,3 @@ 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],
};
}

View File

@ -1,4 +1,3 @@
import { createHero } from "../create";
import type { BuildingType } from "../types"; import type { BuildingType } from "../types";
import { getEmptyResources } from "../utils"; import { getEmptyResources } from "../utils";
import type { VillageState } from "../village"; import type { VillageState } from "../village";
@ -32,10 +31,10 @@ export default [
maxLevel: 20, maxLevel: 20,
cost: (level: number) => { cost: (level: number) => {
return { return {
wood: getResourceBuildingCost(level, 10), wood: level * 10,
stone: getResourceBuildingCost(level, 15), stone: level * 10,
iron: getResourceBuildingCost(level, 20), iron: level * 10,
food: getResourceBuildingCost(level, 5), food: 0,
}; };
}, },
timeToBuild: getStandardTimeToBuild, timeToBuild: getStandardTimeToBuild,
@ -48,7 +47,6 @@ export default [
'food': 100, 'food': 100,
} }
}, },
constructionTimeReductionPerLevel: 0.025,
}, },
}, },
{ {
@ -259,34 +257,4 @@ 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());
}
},
},
},
},
]; ];

View File

@ -1,12 +0,0 @@
export const NAMES = [
'Adosinda',
'Garsendis',
'Amalfriede',
'Seda',
'Amalgard',
'Samocenus',
'Licno',
'Vercombogious',
'Elusconos',
'Excingullus',
];

View File

@ -4,7 +4,7 @@
import moves from "../moves"; import moves from "../moves";
import showBuildingCreator from "../stores/showBuildingCreator"; import showBuildingCreator from "../stores/showBuildingCreator";
import { canPayBuildingCost } from "../utils"; import { canPayBuildingCost } from "../utils";
import village, { type VillageState } from "../village"; import village from "../village";
import Cost from "./Cost.svelte"; import Cost from "./Cost.svelte";
function close() { function close() {
@ -22,14 +22,11 @@
} }
} }
$: constructible = buildings const constructible = buildings.filter(b => !b.autoBuilt).map(b =>{
.filter(b => !b.autoBuilt) const building = createBuilding(b.type);
.filter(b => !(b.unique && $village.buildings.some(b2 => b2.type === b.type))) building.level = 0;
.map(b =>{ return building;
const building = createBuilding(b.type); });
building.level = 0;
return building;
});
</script> </script>
{ #if $showBuildingCreator !== null } { #if $showBuildingCreator !== null }

View File

@ -7,10 +7,10 @@
import { flip } from "svelte/animate"; import { flip } from "svelte/animate";
$: availableHeroes = $village.heroes.filter(h => !$village.quests.some(q => q.hero === h.id)); $: isQuestStarted = $village.quests.some(q => q.started);
function startQuest(id: number, heroId: number) { function startQuest(id: number) {
moves.startQuest(id, heroId); moves.startQuest(id);
} }
</script> </script>
@ -31,15 +31,12 @@
{ Math.ceil((quest.remainingTime || 0) / 1000) } { Math.ceil((quest.remainingTime || 0) / 1000) }
{ :else } { :else }
{ quest.duration } { quest.duration }
{ #each availableHeroes as hero }
<button <button
on:click={ () => startQuest(quest.id, hero.id) } on:click={ () => startQuest(quest.id) }
disabled={ isQuestStarted }
> >
Send { hero.name } Start quest
</button> </button>
{ :else }
<span>No heroes available</span>
{ /each }
{ /if } { /if }
</p> </p>
</div> </div>

View File

@ -17,9 +17,6 @@
</script> </script>
<section> <section>
{ #each $village.heroes as hero }
<p>{ hero.name }</p>
{ /each }
{ #each currentUnits as unit } { #each currentUnits as unit }
<p>{ unit.name }: { getRemainingUnitCount($village, unit.type) } / { unit.count }</p> <p>{ unit.name }: { getRemainingUnitCount($village, unit.type) } / { unit.count }</p>
{ /each } { /each }

View File

@ -1,14 +0,0 @@
import type { BuildingSource } from "./types";
import type { VillageState } from "./village";
export function getTimeToBuild(V: VillageState, building: BuildingSource, level: number): number {
const townhall = V.buildings.find(b => b.type === 'townhall');
if (!townhall) {
throw new Error("Unable to find the Town hall");
}
const reduction = townhall.level * (townhall.behavior.constructionTimeReductionPerLevel || 0);
const baseBuildingTime = building.timeToBuild(level) * 1000;
return baseBuildingTime - (baseBuildingTime * reduction);
}

View File

@ -1,6 +1,5 @@
import { createBuilding, getBuildingSource } from "../create"; import { createBuilding, getBuildingSource } from "../create";
import type { Hex } from "../hexgrid"; import type { Hex } from "../hexgrid";
import { getTimeToBuild } from "../logic";
import { DEFAULT_TILE, type VillageState } from "../village"; import { DEFAULT_TILE, type VillageState } from "../village";
@ -30,7 +29,7 @@ export default function build(V: VillageState, buildingType: string, tile: Hex)
newBuilding.tile = tile; newBuilding.tile = tile;
newBuilding.level = 0; newBuilding.level = 0;
newBuilding.state.upgrade.isUpgrading = true; newBuilding.state.upgrade.isUpgrading = true;
newBuilding.state.upgrade.remainingTime = getTimeToBuild(V, building, 1); newBuilding.state.upgrade.remainingTime = 1000 * newBuilding.timeToBuild(newBuilding.level + 1);
V.buildings.push(newBuilding); V.buildings.push(newBuilding);
V.villageTiles[tile.y][tile.x] = newBuilding.id; V.villageTiles[tile.y][tile.x] = newBuilding.id;

View File

@ -1,23 +1,12 @@
import type { VillageState } from "../village"; import type { VillageState } from "../village";
export default function startQuest(V: VillageState, questId: number, heroId: number) { export default function startQuest(V: VillageState, questId: number) {
const quest = V.quests.find(q => q.id === questId); const quest = V.quests.find(q => q.id === questId);
if (!quest) { if (!quest) {
return false; 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.started = true;
quest.hero = heroId;
quest.remainingTime = quest.duration * 1000; quest.remainingTime = quest.duration * 1000;
return true; return true;

View File

@ -1,4 +1,3 @@
import { getTimeToBuild } from "../logic";
import { canPayBuildingCost, getBuildingUpgradeCost } from "../utils"; import { canPayBuildingCost, getBuildingUpgradeCost } from "../utils";
import type { VillageState } from "../village"; import type { VillageState } from "../village";
@ -29,7 +28,7 @@ export default function upgradeBuilding(V: VillageState, buildingId: number) {
V.resources.food -= cost.food; V.resources.food -= cost.food;
building.state.upgrade.isUpgrading = true; building.state.upgrade.isUpgrading = true;
building.state.upgrade.remainingTime = getTimeToBuild(V, building, building.level + 1); building.state.upgrade.remainingTime = 1000 * building.timeToBuild(building.level + 1);
return true; return true;
} }

View File

@ -24,7 +24,6 @@ export interface BuildingSource {
name: string; name: string;
type: string; type: string;
autoBuilt?: boolean; autoBuilt?: boolean;
unique?: boolean;
maxLevel: number; maxLevel: number;
cost: (level: number) => CostType; cost: (level: number) => CostType;
timeToBuild: (level: number) => number; timeToBuild: (level: number) => number;
@ -35,10 +34,6 @@ export interface BuildingSource {
type: string; type: string;
recruitmentTime: Function; recruitmentTime: Function;
}; };
constructionTimeReductionPerLevel?: number;
triggers?: {
onLevelUp?: Function;
};
}; };
} }
@ -105,7 +100,6 @@ export type RegionType = OasisType | BourgadeType;
export interface HeroType { export interface HeroType {
id: number; id: number;
name: string;
} }
@ -115,6 +109,5 @@ export interface QuestType {
reward: ResourcesType; reward: ResourcesType;
level: number; level: number;
started: boolean; started: boolean;
hero?: number;
remainingTime?: number; remainingTime?: number;
} }

View File

@ -31,7 +31,6 @@ export default function update(timestamp: number) {
if (b.state.upgrade.remainingTime <= 0) { if (b.state.upgrade.remainingTime <= 0) {
b.level++; b.level++;
b.state.upgrade.isUpgrading = false; b.state.upgrade.isUpgrading = false;
b.behavior.triggers?.onLevelUp?.(V, b);
} }
}); });

View File

@ -1,10 +1,11 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { createBuilding, createHero, createQuest } 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, HeroType, QuestType, 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,7 +24,6 @@ export interface VillageState {
villageTiles: Board; villageTiles: Board;
outsideTiles: Board; outsideTiles: Board;
worldmap: RegionType[]; worldmap: RegionType[];
heroes: HeroType[];
quests: QuestType[]; quests: QuestType[];
victory: boolean; victory: boolean;
} }
@ -103,9 +103,6 @@ function getInitialState() {
villageTiles: getInitialVillageBoard(), villageTiles: getInitialVillageBoard(),
outsideTiles: getInitialOutsideBoard(), outsideTiles: getInitialOutsideBoard(),
worldmap: getInitialWorldmap(), worldmap: getInitialWorldmap(),
heroes: [
createHero(),
],
quests: getInitialQuests(), quests: getInitialQuests(),
victory: false, victory: false,
}; };
@ -134,7 +131,7 @@ function getInitialState() {
} }
const newBuilding = createBuilding(type); const newBuilding = createBuilding(type);
newBuilding.tile = new Hex(x, y); newBuilding.tile = new Hex(x, y);
newBuilding.level = 20; //newBuilding.type === 'field' ? 1 : 10; newBuilding.level = 1; //newBuilding.type === 'field' ? 1 : 10;
state.outsideTiles[y][x] = newBuilding.id; state.outsideTiles[y][x] = newBuilding.id;
state.buildings.push(newBuilding); state.buildings.push(newBuilding);
}); });