Compare commits

..

3 Commits

10 changed files with 172 additions and 27 deletions

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import moves from "../moves"; import moves from "../moves";
import type { BuildingType } from "../types"; import type { BuildingType } from "../types";
import { canPayBuildingCost } from "../utils"; import { canPayBuildingCost, getPrettyTime } from "../utils";
import village from "../village"; import village from "../village";
export let building: BuildingType; export let building: BuildingType;
@ -23,7 +23,6 @@
<img src="/img/buildings/{ building.type }.png" alt=""> <img src="/img/buildings/{ building.type }.png" alt="">
</div> </div>
<div class="content"> <div class="content">
<!-- <p>{ building.name }</p> -->
<div class="level"> <div class="level">
<button <button
class:can-upgrade={ canUpgrade } class:can-upgrade={ canUpgrade }
@ -34,7 +33,7 @@
{ building.level } { building.level }
{ #if building.state.upgrade.isUpgrading } { #if building.state.upgrade.isUpgrading }
<span> <span>
({ Math.ceil(building.state.upgrade.remainingTime / 1000) }) ({ getPrettyTime(building.state.upgrade.remainingTime) })
</span> </span>
{ /if } { /if }
</button> </button>

View File

@ -56,6 +56,10 @@ export default [
}, },
constructionTimeReductionPerLevel: 0.025, constructionTimeReductionPerLevel: 0.025,
}, },
description: {
short: '',
long: 'Increases the construction time of buildings by 2,5% per level, and unlocks new buildings.'
},
}, },
{ {
type: 'woodcutter', type: 'woodcutter',
@ -84,6 +88,10 @@ export default [
return prod; return prod;
}, },
}, },
description: {
short: '',
long: 'Produces wood.'
},
}, },
{ {
type: 'mine', type: 'mine',
@ -112,6 +120,10 @@ export default [
return prod; return prod;
}, },
}, },
description: {
short: '',
long: 'Produces iron.'
},
}, },
{ {
type: 'pit', type: 'pit',
@ -140,6 +152,10 @@ export default [
return prod; return prod;
}, },
}, },
description: {
short: '',
long: 'Produces stone.'
},
}, },
{ {
type: 'field', type: 'field',
@ -164,6 +180,10 @@ export default [
return prod; return prod;
}, },
}, },
description: {
short: '',
long: 'Produces food.'
},
}, },
{ {
type: 'warehouse', type: 'warehouse',
@ -191,6 +211,10 @@ export default [
}; };
}, },
}, },
description: {
short: 'Stores wood, stone and iron',
long: 'Stores wood, stone and iron.'
},
}, },
{ {
type: 'granary', type: 'granary',
@ -218,6 +242,10 @@ export default [
}; };
}, },
}, },
description: {
short: 'Stores food',
long: 'Stores food.'
},
}, },
{ {
type: 'great-warehouse', type: 'great-warehouse',
@ -245,6 +273,10 @@ export default [
}; };
}, },
}, },
description: {
short: 'Stores great quantities of wood, stone and iron',
long: 'Stores great quantities of wood, stone and iron.'
},
}, },
{ {
type: 'great-granary', type: 'great-granary',
@ -272,12 +304,16 @@ export default [
}; };
}, },
}, },
description: {
short: 'Stores great quantities of food',
long: 'Stores great quantities of food.'
},
}, },
{ {
type: 'university', type: 'university',
name: 'University', name: 'University',
maxLevel: 20, maxLevel: 20,
requiredTownhallLevel: 5, requiredTownhallLevel: 3,
cost: (level: number) => { cost: (level: number) => {
return { return {
wood: getUnitBuildingCost(level, 75), wood: getUnitBuildingCost(level, 75),
@ -296,15 +332,19 @@ export default [
}, },
units: { units: {
type: 'philosopher', type: 'philosopher',
recruitmentTime: (V: VillageState, self: BuildingType) => 2 - 0.06 * self.level, recruitmentTime: (V: VillageState, self: BuildingType) => 3 - 0.06 * self.level,
}, },
}, },
description: {
short: 'To recruit philosophers',
long: 'Recruit philosophers. Recruitment time is lowered by 2% per level.'
},
}, },
{ {
type: 'barracks', type: 'barracks',
name: 'Barracks', name: 'Barracks',
maxLevel: 20, maxLevel: 20,
requiredTownhallLevel: 3, requiredTownhallLevel: 0,
cost: (level: number) => { cost: (level: number) => {
return { return {
wood: getUnitBuildingCost(level, 60), wood: getUnitBuildingCost(level, 60),
@ -323,9 +363,13 @@ export default [
}, },
units: { units: {
type: 'soldier', type: 'soldier',
recruitmentTime: (V: VillageState, self: BuildingType) => 1 - 0.03 * self.level, recruitmentTime: (V: VillageState, self: BuildingType) => 2 - 0.04 * self.level,
}, },
}, },
description: {
short: 'To recruit soldiers',
long: 'Recruit soldiers. Recruitment time is lowered by 2% per level.'
},
}, },
{ {
type: 'palace', type: 'palace',
@ -357,6 +401,10 @@ export default [
}, },
}, },
}, },
description: {
short: 'Attracts heroes',
long: 'Attracts a new hero at levels 5 and 10.'
},
}, },
{ {
type: 'sawmill', type: 'sawmill',
@ -384,6 +432,10 @@ export default [
return prod; return prod;
}, },
}, },
description: {
short: 'Improves wood production',
long: 'Improves wood production by 5% per level.'
},
}, },
{ {
type: 'stonecutter', type: 'stonecutter',
@ -411,6 +463,10 @@ export default [
return prod; return prod;
}, },
}, },
description: {
short: 'Improves stone production',
long: 'Improves stone production by 5% per level.'
},
}, },
{ {
type: 'blacksmith', type: 'blacksmith',
@ -438,6 +494,10 @@ export default [
return prod; return prod;
}, },
}, },
description: {
short: 'Improves iron production',
long: 'Improves iron production by 5% per level.'
},
}, },
{ {
type: 'bakery', type: 'bakery',
@ -465,6 +525,10 @@ export default [
return prod; return prod;
}, },
}, },
description: {
short: 'Improves food production',
long: 'Improves food production by 5% per level.'
},
}, },
{ {
type: 'wonder', type: 'wonder',
@ -496,5 +560,9 @@ export default [
}, },
}, },
}, },
description: {
short: 'Provides a huge culture boost',
long: 'Gives 18,000 culture points upon construction.'
},
}, },
]; ];

View File

@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import { createBuilding } from "../create"; import { createBuilding } from "../create";
import buildings from "../data/buildings"; import buildings from "../data/buildings";
import { getTimeToBuild } from "../logic";
import moves from "../moves"; import moves from "../moves";
import showBuildingCreator from "../stores/showBuildingCreator"; import showBuildingCreator from "../stores/showBuildingCreator";
import { canPayBuildingCost, getTownhall } from "../utils"; import { canPayBuildingCost, getPrettyTime, getTownhall } from "../utils";
import village from "../village"; import village from "../village";
import Cost from "./Cost.svelte"; import Cost from "./Cost.svelte";
@ -38,21 +39,27 @@
<section> <section>
<div class="building-creator"> <div class="building-creator">
<header> <header>
<h1>Build</h1> <h1>Choose a new building</h1>
<span class="close"> <span class="close">
<button on:click={ close }>X</button> <button on:click={ close }>X</button>
</span> </span>
</header> </header>
<div class="buildings"> <div class="buildings">
{ #each constructible as building } { #each constructible as building }
<div> <div class="building">
<p>{ building.name }</p> <div class="content">
<img src="/img/buildings/{building.type}.png" alt="">
<div>
<h3>{ building.name }</h3>
<p>{ building.description.short }</p>
</div>
</div>
<Cost cost={ building.cost(1) } /> <Cost cost={ building.cost(1) } />
<button <button
on:click={ () => build(building.type) } on:click={ () => build(building.type) }
disabled={ !canPayBuildingCost($village, building) } disabled={ !canPayBuildingCost($village, building) }
> >
Build Build ({ getPrettyTime(getTimeToBuild($village, building, 1)) })
</button> </button>
</div> </div>
{ /each } { /each }
@ -75,25 +82,60 @@
.building-creator { .building-creator {
background-color: hsl(0, 0%, 20%); background-color: hsl(0, 0%, 20%);
border: 0.2em solid grey; border: 0.2em solid hsl(0, 0%, 40%);
border-radius: .4em; border-radius: .4em;
box-sizing: border-box;
width: 80%; width: 80%;
height: 60%; height: 60%;
} }
.building-creator header { .building-creator header {
align-items: center;
border-bottom: 2px solid hsl(0, 0%, 40%);
box-sizing: border-box;
display: flex;
height: 15%;
justify-content: center;
position: relative; position: relative;
} }
.building-creator header h1 {
margin: 0;
}
.building-creator header .close { .building-creator header .close {
position: absolute; position: absolute;
right: 1em; right: 1em;
top: 0; top: 1em;
} }
.building-creator .buildings { .building-creator .buildings {
box-sizing: border-box;
display: grid; display: grid;
gap: 1em; gap: 1em;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
height: 85%;
overflow: hidden;
overflow-y: scroll;
padding: 0.5em;
}
.building {
background-color: hsl(0, 0%, 22%);
box-shadow: 0 0 0.2em hsl(0, 0%, 15%);
padding: 0.4em 0.2em;
}
.building .content {
display: flex;
text-align: left;
}
.building .content img {
height: 10em;
}
.building .content h3 {
margin: 0;
} }
</style> </style>

View File

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { getTimeToBuild } from "../logic";
import moves from "../moves"; import moves from "../moves";
import showBuildingPanel from "../stores/showBuildingPanel"; import showBuildingPanel from "../stores/showBuildingPanel";
import { getBuilding, getBuildingUpgradeCost } from "../utils"; import { canPayBuildingCost, getBuilding, getBuildingUpgradeCost, getPrettyTime } from "../utils";
import village from "../village"; import village from "../village";
import BuildingRecruitment from "./BuildingRecruitment.svelte"; import BuildingRecruitment from "./BuildingRecruitment.svelte";
import Cost from "./Cost.svelte"; import Cost from "./Cost.svelte";
@ -33,6 +34,9 @@
<button on:click={ close }>X</button> <button on:click={ close }>X</button>
</span> </span>
</header> </header>
<div class="description">
<p>{ building.description.long }</p>
</div>
<div class="content"> <div class="content">
{ #if building.level === 0 } { #if building.level === 0 }
<p>Building in construction…</p> <p>Building in construction…</p>
@ -44,14 +48,19 @@
{ #if building.state.upgrade.isUpgrading } { #if building.state.upgrade.isUpgrading }
<p> <p>
Upgrading to level { building.level + 1 } Upgrading to level { building.level + 1 }
({ Math.ceil(building.state.upgrade.remainingTime / 1000) }) ({ getPrettyTime(building.state.upgrade.remainingTime) })
</p> </p>
{ :else if building.level === building.maxLevel } { :else if building.level === building.maxLevel }
<p>Max level reached!</p> <p>Max level reached!</p>
{ :else } { :else }
<div> <div>
<Cost cost={ getBuildingUpgradeCost($village, building) } /> <Cost cost={ getBuildingUpgradeCost($village, building) } />
<button on:click={ () => upgrade() }>Upgrade</button> <button
disabled={ !canPayBuildingCost($village, building) }
on:click={ () => upgrade() }
>
Upgrade ({ getPrettyTime(getTimeToBuild($village, building, building.level + 1)) })
</button>
</div> </div>
{ /if } { /if }
</div> </div>

View File

@ -2,6 +2,7 @@
import { Duration } from 'luxon'; import { Duration } from 'luxon';
import type { CostType } from "../types"; import type { CostType } from "../types";
import village from '../village';
export let cost: CostType; export let cost: CostType;
export let duration: number | null = null; export let duration: number | null = null;
@ -11,19 +12,30 @@
</script> </script>
<div class="cost"> <div class="cost">
<div> <div
class:short={ $village.resources.wood < cost.wood }
>
<img src="/img/icons/wood.png" alt="Wood" /> <img src="/img/icons/wood.png" alt="Wood" />
{ cost.wood } { cost.wood }
</div> </div>
<div>
<div
class:short={ $village.resources.stone < cost.stone }
>
<img src="/img/icons/stone.png" alt="Stone" /> <img src="/img/icons/stone.png" alt="Stone" />
{ cost.stone } { cost.stone }
</div> </div>
<div>
<div
class:short={ $village.resources.iron < cost.iron }
>
<img src="/img/icons/iron.png" alt="Iron" /> <img src="/img/icons/iron.png" alt="Iron" />
{ cost.iron } { cost.iron }
</div> </div>
<div>
<div
class:short={ $village.resources.food < cost.food }
>
<img src="/img/icons/food.png" alt="Food" /> <img src="/img/icons/food.png" alt="Food" />
{ cost.food } { cost.food }
</div> </div>
@ -48,4 +60,8 @@
align-items: center; align-items: center;
gap: 0.2em; gap: 0.2em;
} }
.cost .short {
color: red;
}
</style> </style>

View File

@ -1,10 +1,11 @@
<script lang="ts"> <script lang="ts">
import { flip } from "svelte/animate";
import { fly } from "svelte/transition"; import { fly } from "svelte/transition";
import moves from "../moves"; import moves from "../moves";
import { getPrettyTime } from "../utils";
import village from "../village"; import village from "../village";
import Reward from "./Reward.svelte"; import Reward from "./Reward.svelte";
import { flip } from "svelte/animate";
$: availableHeroes = $village.heroes.filter(h => !$village.quests.some(q => q.hero === h.id)); $: availableHeroes = $village.heroes.filter(h => !$village.quests.some(q => q.hero === h.id));
@ -28,7 +29,7 @@
<p> <p>
<img src="/img/icons/time.png" alt="Duration"> <img src="/img/icons/time.png" alt="Duration">
{ #if quest.started } { #if quest.started }
{ Math.ceil((quest.remainingTime || 0) / 1000) } { getPrettyTime(quest.remainingTime || 0) }
{ :else } { :else }
{ quest.duration } { quest.duration }
{ #each availableHeroes as hero } { #each availableHeroes as hero }

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { BuildingType } from "../types"; import type { BuildingType } from "../types";
import { getPrettyTime } from "../utils";
import village from "../village"; import village from "../village";
export let building: BuildingType; export let building: BuildingType;
@ -9,12 +10,11 @@
$: recruitmentTime = building.behavior.units?.recruitmentTime($village, building) || 0; $: recruitmentTime = building.behavior.units?.recruitmentTime($village, building) || 0;
$: currentRecruitTime = building.state.recruitment?.elapsedTime || 0; $: currentRecruitTime = building.state.recruitment?.elapsedTime || 0;
$: timeToNextRecruit = recruitmentTime * 1000 - currentRecruitTime; $: timeToNextRecruit = recruitmentTime * 1000 - currentRecruitTime;
$: prettyDuration = Math.ceil(timeToNextRecruit / 1000);
</script> </script>
{ #if unitsCount > 0 } { #if unitsCount > 0 }
<div> <div>
{ unitsCount } { unitsCount }
({ prettyDuration }) ({ getPrettyTime(timeToNextRecruit) })
</div> </div>
{ /if } { /if }

View File

@ -47,6 +47,10 @@ export interface BuildingSource {
onLevelUp?: Function; onLevelUp?: Function;
}; };
}; };
description: {
short: string;
long: string;
};
} }

View File

@ -216,3 +216,9 @@ export function indexToPoint(index: number): Point {
y: Math.floor(index / WORLD_MAP_WIDTH), y: Math.floor(index / WORLD_MAP_WIDTH),
}; };
} }
export function getPrettyTime(milliseconds: number) {
const timeInSeconds = Math.ceil(milliseconds / 1000.0);
return `${timeInSeconds}s`;
}

View File

@ -137,7 +137,7 @@ function getInitialState() {
// Create the Town hall. // Create the Town hall.
const townhall = createBuilding('townhall'); const townhall = createBuilding('townhall');
// townhall.level = 20; townhall.level = 20;
state.villageTiles[0][0] = townhall.id; state.villageTiles[0][0] = townhall.id;
state.buildings.push(townhall); state.buildings.push(townhall);
@ -160,7 +160,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 = 1; //20; //newBuilding.type === 'field' ? 1 : 10; newBuilding.level = 20; //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);
}); });