Force the player to clear an oasis before they can pillage it.

This commit is contained in:
Adrian 2024-11-19 17:52:05 +01:00
parent 0caa40bdca
commit 8bed89b6dc
9 changed files with 110 additions and 8 deletions

View File

@ -26,6 +26,12 @@
} }
function clear() {
if (!region) return;
moves.clear(region.id, numberOfUnits);
}
function toggleMissionRepeat() { function toggleMissionRepeat() {
if (!region) return; if (!region) return;
moves.toggleMissionRepeat(region.id); moves.toggleMissionRepeat(region.id);
@ -49,14 +55,34 @@
<button on:click={ close }>X</button> <button on:click={ close }>X</button>
</span> </span>
</header> </header>
{ #if region.state.cleared }
<p>Region cleared</p>
{ :else }
<p>Defensive strength: { region.strength }</p>
{ /if }
{ #if region.state.mission } { #if region.state.mission }
<div> <div>
<p>{ region.state.mission.unitCount } soldiers are on a mission here.</p> <p>{ region.state.mission.unitCount } soldiers are on a mission here.</p>
<p>Remaining: { Math.ceil(region.state.mission.remainingTime / 1000) }</p> <p>Remaining: { Math.ceil(region.state.mission.remainingTime / 1000) }</p>
{ #if region.state.mission.type === 'pillage' }
<label> <label>
<input type="checkbox" checked={ region.state.mission.repeat } on:change={ toggleMissionRepeat }> <input type="checkbox" checked={ region.state.mission.repeat } on:change={ toggleMissionRepeat }>
<span title="Send the same troops again when they come back">Repeat</span> <span title="Send the same troops again when they come back">Repeat</span>
</label> </label>
{ /if }
</div>
{ :else if !region.state.cleared }
<div>
<input
type="range"
name="units"
min="0"
max={ maximumUnits }
bind:value={ numberOfUnits }
/>
{ numberOfUnits }
<button on:click={ setMaxUnits }>↑</button>
<button on:click={ clear } disabled={ numberOfUnits === 0 }>Clear</button>
</div> </div>
{ :else } { :else }
<div> <div>

View File

@ -16,6 +16,7 @@
class:empty={ region.type === WORLDMAP_TYPES.EMPTY } class:empty={ region.type === WORLDMAP_TYPES.EMPTY }
class:bourgade={ region.type === WORLDMAP_TYPES.BOURGADE } class:bourgade={ region.type === WORLDMAP_TYPES.BOURGADE }
class:oasis={ region.type === WORLDMAP_TYPES.OASIS } class:oasis={ region.type === WORLDMAP_TYPES.OASIS }
class:cleared={ region.type === WORLDMAP_TYPES.OASIS && region.state.cleared }
> >
{ #if region.type === WORLDMAP_TYPES.BOURGADE } { #if region.type === WORLDMAP_TYPES.BOURGADE }
<button class="invisible" on:click={ () => gameTab.set('resources') }> <button class="invisible" on:click={ () => gameTab.set('resources') }>
@ -63,6 +64,10 @@
} }
.region.oasis { .region.oasis {
background-color: red;
}
.region.oasis.cleared {
background-color: cyan; background-color: cyan;
} }
</style> </style>

View File

@ -17,6 +17,11 @@ export function resolveMission(V: VillageState, region: RegionType) {
resolvePillageOasis(V, region); resolvePillageOasis(V, region);
} }
break; break;
case 'clear':
if (region.type === WORLDMAP_TYPES.OASIS) {
resolveClearOasis(V, region);
}
break;
default: default:
throw new Error(`Unknown mission type: "${ mission.type }"`); throw new Error(`Unknown mission type: "${ mission.type }"`);
} }
@ -30,6 +35,36 @@ export function resolveMission(V: VillageState, region: RegionType) {
} }
function resolveClearOasis(V: VillageState, region: OasisType) {
const mission = region.state.mission;
if (!mission) {
return;
}
const ratio = mission.unitCount / region.strength;
const lostRatio = 1 / (ratio * ratio);
const lostUnits = Math.min(
Math.round(mission.unitCount * lostRatio),
mission.unitCount
);
mission.unitCount -= lostUnits;
V.units.soldier -= lostUnits;
if (ratio >= 1) {
region.state.cleared = true;
}
const unit = getUnitSource('soldier');
const maxResources = region.distance * region.distance * 100;
V.resources[region.resource] += Math.min(
mission.unitCount * unit.behavior.caryingCapacity,
maxResources
);
}
function resolvePillageOasis(V: VillageState, region: OasisType) { function resolvePillageOasis(V: VillageState, region: OasisType) {
const mission = region.state.mission; const mission = region.state.mission;
if (!mission) { if (!mission) {

28
src/moves/clear.ts Normal file
View File

@ -0,0 +1,28 @@
import { WORLDMAP_TYPES } from "../types";
import { assert, getRemainingUnitCount } from "../utils";
import type { VillageState } from "../village";
export default function clear(
V: VillageState, regionIndex: number, soldiersCount: number
) {
if (soldiersCount > getRemainingUnitCount(V, 'soldier')) {
return false;
}
const region = V.worldmap[regionIndex];
assert(region.type === WORLDMAP_TYPES.OASIS);
if (region.state.mission) {
return false;
}
region.state.mission = {
type: 'clear',
unitType: 'soldier',
unitCount: soldiersCount,
remainingTime: region.distance * 10 * 1000,
};
return true;
}

View File

@ -2,6 +2,7 @@ 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 clear from './clear';
import pillage from './pillage'; import pillage from './pillage';
import recruitUnits from './recruitUnits'; import recruitUnits from './recruitUnits';
import startQuest from './startQuest'; import startQuest from './startQuest';
@ -33,6 +34,7 @@ export function makeMove(move: (...args: any[]) => boolean) {
export default { export default {
build: makeMove(build), build: makeMove(build),
clear: makeMove(clear),
upgradeBuilding: makeMove(upgradeBuilding), upgradeBuilding: makeMove(upgradeBuilding),
pillage: makeMove(pillage), pillage: makeMove(pillage),
recruitUnits: makeMove(recruitUnits), recruitUnits: makeMove(recruitUnits),

View File

@ -3,7 +3,9 @@ import { assert, getRemainingUnitCount } from "../utils";
import type { VillageState } from "../village"; import type { VillageState } from "../village";
export default function pillage(V: VillageState, regionIndex: number, soldiersCount: number, repeat: boolean) { export default function pillage(
V: VillageState, regionIndex: number, soldiersCount: number, repeat: boolean
) {
if (soldiersCount > getRemainingUnitCount(V, 'soldier')) { if (soldiersCount > getRemainingUnitCount(V, 'soldier')) {
return false; return false;
} }
@ -20,7 +22,7 @@ export default function pillage(V: VillageState, regionIndex: number, soldiersCo
type: 'pillage', type: 'pillage',
unitType: 'soldier', unitType: 'soldier',
unitCount: soldiersCount, unitCount: soldiersCount,
remainingTime: 1 * 10 * 1000, remainingTime: region.distance * 10 * 1000,
repeat, repeat,
}; };

View File

@ -80,11 +80,11 @@ export interface UnitType {
export interface MissionType { export interface MissionType {
type: string; type: 'pillage' | 'clear';
unitType: string; unitType: string;
unitCount: number; unitCount: number;
remainingTime: number; remainingTime: number;
repeat: boolean; repeat?: boolean;
} }
@ -110,7 +110,9 @@ export interface EmptyRegionType extends BaseRegionType {
export interface OasisType extends BaseRegionType { export interface OasisType extends BaseRegionType {
type: WORLDMAP_TYPES.OASIS; type: WORLDMAP_TYPES.OASIS;
resource: keyof CostType; resource: keyof CostType;
strength: number;
state: { state: {
cleared: boolean;
mission?: MissionType; mission?: MissionType;
}; };
} }

View File

@ -213,6 +213,6 @@ export function distanceBetweenCells(a: Point, b: Point) {
export function indexToPoint(index: number): Point { export function indexToPoint(index: number): Point {
return { return {
x: index % WORLD_MAP_WIDTH, x: index % WORLD_MAP_WIDTH,
y: index / WORLD_MAP_WIDTH, y: Math.floor(index / WORLD_MAP_WIDTH),
}; };
} }

View File

@ -82,10 +82,9 @@ function getInitialWorldmap(): RegionType[] {
} }
board[centerIndex].type = WORLDMAP_TYPES.BOURGADE; board[centerIndex].type = WORLDMAP_TYPES.BOURGADE;
const adj = shuffle(getAdjacentWorldmapCells(centerIndex));
worldmap.forEach(region => { worldmap.forEach(region => {
const candidates = board.filter(c => const candidates = board.filter(c =>
c.type !== WORLDMAP_TYPES.EMPTY c.type === WORLDMAP_TYPES.EMPTY
&& c.distance >= region.distance[0] && c.distance >= region.distance[0]
&& c.distance < region.distance[1] && c.distance < region.distance[1]
); );
@ -95,7 +94,10 @@ function getInitialWorldmap(): RegionType[] {
id: cell.id, id: cell.id,
resource: region.resource as keyof CostType, resource: region.resource as keyof CostType,
distance: cell.distance, distance: cell.distance,
state: {}, strength: Math.round(cell.distance * cell.distance * 50),
state: {
cleared: false,
},
}; };
}); });