From 2807b05930e9b944d17cbc5c04ee05a54095f85d Mon Sep 17 00:00:00 2001 From: Greegko Date: Tue, 8 Nov 2022 11:32:06 +0100 Subject: [PATCH] add global activities --- .../tests/activities/village/build.test.ts | 18 ++++++ .../commands/village/build-blacksmith.test.ts | 6 +- .../commands/village/build-house.test.ts | 6 +- .../commands/village/build-portals.test.ts | 6 +- .../village/build-rune-workshop.test.ts | 6 +- .../village/build-training-field.test.ts | 4 +- .../src/modules/activity/activity-manager.ts | 20 ++++++- .../src/modules/village/activities/build.ts | 43 ++++++++++++++ .../src/modules/village/activities/index.ts | 1 + .../village/interfaces/village-activity.ts | 1 + .../village/village-command-handler.ts | 56 +++++++++---------- .../src/modules/village/village-module.ts | 4 +- .../src/components/dashboard/dashboard.tsx | 7 ++- packages/web/src/game/store/game/selectors.ts | 3 + 14 files changed, 132 insertions(+), 49 deletions(-) create mode 100644 packages/core-test/tests/activities/village/build.test.ts create mode 100644 packages/core/src/modules/village/activities/build.ts diff --git a/packages/core-test/tests/activities/village/build.test.ts b/packages/core-test/tests/activities/village/build.test.ts new file mode 100644 index 0000000..1a1b0fb --- /dev/null +++ b/packages/core-test/tests/activities/village/build.test.ts @@ -0,0 +1,18 @@ +import { VillageActivity, VillageCommand } from "@rpg-village/core"; + +import { test } from "../../utils"; + +test("should start a build activity when gold is enough", { + initState: { village: { stash: { resource: { gold: 100 } }, trainingField: 0 } }, + commands: [VillageCommand.BuildTrainingField], + expectedState: (state, t) => t.withRandomId(state.activities, { name: VillageActivity.Build, startArgs: { targetBuilding: 'trainingField' } }), +}); + +test("should block the second command for building", { + initState: { village: { stash: { resource: { gold: 1000 } }, trainingField: 0 } }, + commands: [VillageCommand.BuildTrainingField, VillageCommand.BuildTrainingField], + expectedState: [ + (state, t) => t.withRandomId(state.activities, { name: VillageActivity.Build, startArgs: { targetBuilding: 'trainingField' } }), + (state, t) => t.deepEqual(state.village, { stash: { resource: { gold: 900 } }, trainingField: 0 }) + ] +}); diff --git a/packages/core-test/tests/commands/village/build-blacksmith.test.ts b/packages/core-test/tests/commands/village/build-blacksmith.test.ts index 08e97a2..17b2074 100644 --- a/packages/core-test/tests/commands/village/build-blacksmith.test.ts +++ b/packages/core-test/tests/commands/village/build-blacksmith.test.ts @@ -1,4 +1,4 @@ -import { VillageCommand } from "@rpg-village/core"; +import { VillageActivity, VillageCommand } from "@rpg-village/core"; import { test } from "../../utils"; @@ -6,10 +6,10 @@ const initState = { village: { stash: { resource: { gold: 100 } }, blacksmith: 0 }, }; -test("should build a blacksmith", { +test("should start building activity with blacksmith", { initState, commands: [VillageCommand.BuildBlacksmith], - expectedState: { village: { blacksmith: 1 } }, + expectedState: (state, t) => t.withRandomId(state.activities, { name: VillageActivity.Build, startArgs: { targetBuilding: 'blacksmith' } }), }); test("should reduce the village resouce by the blacksmith cost", { diff --git a/packages/core-test/tests/commands/village/build-house.test.ts b/packages/core-test/tests/commands/village/build-house.test.ts index 4b488ec..aa5dea0 100644 --- a/packages/core-test/tests/commands/village/build-house.test.ts +++ b/packages/core-test/tests/commands/village/build-house.test.ts @@ -1,4 +1,4 @@ -import { VillageCommand } from "@rpg-village/core"; +import { VillageActivity, VillageCommand } from "@rpg-village/core"; import { test } from "../../utils"; @@ -6,10 +6,10 @@ const initState = { village: { stash: { resource: { gold: 20 } }, houses: 0 }, }; -test("should build a house", { +test("should start a building acitivity for houses", { initState, commands: [VillageCommand.BuildHouse], - expectedState: { village: { houses: 1 } }, + expectedState: (state, t) => t.withRandomId(state.activities, { name: VillageActivity.Build, startArgs: { targetBuilding: 'houses' } }), }); test("should reduce the village resouce by the house cost", { diff --git a/packages/core-test/tests/commands/village/build-portals.test.ts b/packages/core-test/tests/commands/village/build-portals.test.ts index 61b9c89..7a074d1 100644 --- a/packages/core-test/tests/commands/village/build-portals.test.ts +++ b/packages/core-test/tests/commands/village/build-portals.test.ts @@ -1,4 +1,4 @@ -import { VillageCommand } from "@rpg-village/core"; +import { VillageActivity, VillageCommand } from "@rpg-village/core"; import { test } from "../../utils"; @@ -6,10 +6,10 @@ const initState = { village: { stash: { resource: { gold: 100 } }, portals: 0 }, }; -test("should build a portals", { +test("should start a building activity with portals", { initState, commands: [VillageCommand.BuildPortalSummonerStone], - expectedState: { village: { portals: 1 } }, + expectedState: (state, t) => t.withRandomId(state.activities, { name: VillageActivity.Build, startArgs: { targetBuilding: 'portals' } }), }); test("should reduce the village resouce by the portals cost", { diff --git a/packages/core-test/tests/commands/village/build-rune-workshop.test.ts b/packages/core-test/tests/commands/village/build-rune-workshop.test.ts index 941cefa..8ef48e6 100644 --- a/packages/core-test/tests/commands/village/build-rune-workshop.test.ts +++ b/packages/core-test/tests/commands/village/build-rune-workshop.test.ts @@ -1,4 +1,4 @@ -import { VillageCommand } from "@rpg-village/core"; +import { VillageActivity, VillageCommand } from "@rpg-village/core"; import { test } from "../../utils"; @@ -6,10 +6,10 @@ const initState = { village: { stash: { resource: { gold: 100 } }, runeWorkshop: 0 }, }; -test("should build a runeWorkshop", { +test("should start a build activity runeWorkshop", { initState, commands: [VillageCommand.BuildRuneWorkshop], - expectedState: { village: { runeWorkshop: 1 } }, + expectedState: (state, t) => t.withRandomId(state.activities, { name: VillageActivity.Build, startArgs: { targetBuilding: 'runeWorkshop' } }), }); test("should reduce the village resouce by the runeWorkshop cost", { diff --git a/packages/core-test/tests/commands/village/build-training-field.test.ts b/packages/core-test/tests/commands/village/build-training-field.test.ts index 8783274..70df2e7 100644 --- a/packages/core-test/tests/commands/village/build-training-field.test.ts +++ b/packages/core-test/tests/commands/village/build-training-field.test.ts @@ -1,4 +1,4 @@ -import { VillageCommand } from "@rpg-village/core"; +import { VillageActivity, VillageCommand } from "@rpg-village/core"; import { test } from "../../utils"; @@ -9,7 +9,7 @@ const initState = { test("should build a trainingField", { initState, commands: [VillageCommand.BuildTrainingField], - expectedState: { village: { trainingField: 1 } }, + expectedState: (state, t) => t.withRandomId(state.activities, { name: VillageActivity.Build, startArgs: { targetBuilding: 'trainingField' } }), }); test("should reduce the village resouce by the trainingField cost", { diff --git a/packages/core/src/modules/activity/activity-manager.ts b/packages/core/src/modules/activity/activity-manager.ts index bd14d76..e618aba 100644 --- a/packages/core/src/modules/activity/activity-manager.ts +++ b/packages/core/src/modules/activity/activity-manager.ts @@ -16,14 +16,28 @@ export class ActivityManager { private partyStore: PartyStore, ) {} - startPartyActivity(activtyName: string, startingArgs: PartyActivityStartArgs) { + startActivity(activityName: string, startingArgs: object) { + const activityHandler = this.getActivityHandler(activityName); + if (activityHandler.isRunnable(startingArgs)) { + const activityState = activityHandler.start(startingArgs); + + this.activityStore.add({ + name: activityName, + state: activityState, + type: ActivityType.Global, + startArgs: startingArgs, + }); + } + } + + startPartyActivity(activityName: string, startingArgs: PartyActivityStartArgs) { if (this.partyStore.get(startingArgs.partyId).activityId) return; - const activityHandler = this.getActivityHandler(activtyName); + const activityHandler = this.getActivityHandler(activityName); if (activityHandler.isRunnable(startingArgs)) { const activityState = activityHandler.start(startingArgs); const activity = this.activityStore.add({ - name: activtyName, + name: activityName, state: activityState, type: ActivityType.Party, startArgs: startingArgs, diff --git a/packages/core/src/modules/village/activities/build.ts b/packages/core/src/modules/village/activities/build.ts new file mode 100644 index 0000000..e1ada66 --- /dev/null +++ b/packages/core/src/modules/village/activities/build.ts @@ -0,0 +1,43 @@ +import { injectable } from "inversify"; +import { dec, evolve, inc } from "ramda"; + +import { Activity, IActivityHandler } from "@modules/activity"; + +import { VillageStore } from "../village-store"; + +interface BuildState { + progress: number; +} + +export type VillageBuildings = "houses" | "blacksmith" | "portals" | "trainingField" | "runeWorkshop"; + +interface BuildStartArgs { + targetBuilding: VillageBuildings; +} + +@injectable() +export class BuildActivity implements IActivityHandler> { + constructor(private villageStore: VillageStore) {} + + start(): BuildState { + return { + progress: 100 + }; + } + + isRunnable(): boolean { + return true; + } + + execute({ state }: Activity): BuildState { + return evolve({ progress: dec }, state); + } + + isDone({ state: { progress } }: Activity): boolean { + return progress === 0; + } + + resolve({ startArgs: { targetBuilding } }: Activity) { + this.villageStore.update(targetBuilding, inc); + } +} \ No newline at end of file diff --git a/packages/core/src/modules/village/activities/index.ts b/packages/core/src/modules/village/activities/index.ts index 1477859..e1344a2 100644 --- a/packages/core/src/modules/village/activities/index.ts +++ b/packages/core/src/modules/village/activities/index.ts @@ -1 +1,2 @@ export * from "./village-heal"; +export * from './build'; diff --git a/packages/core/src/modules/village/interfaces/village-activity.ts b/packages/core/src/modules/village/interfaces/village-activity.ts index ab6438a..880812b 100644 --- a/packages/core/src/modules/village/interfaces/village-activity.ts +++ b/packages/core/src/modules/village/interfaces/village-activity.ts @@ -1,3 +1,4 @@ export enum VillageActivity { Heal = "village/heal", + Build = "village/build" } diff --git a/packages/core/src/modules/village/village-command-handler.ts b/packages/core/src/modules/village/village-command-handler.ts index 30c9f51..6bdbe68 100644 --- a/packages/core/src/modules/village/village-command-handler.ts +++ b/packages/core/src/modules/village/village-command-handler.ts @@ -1,18 +1,20 @@ import { injectable } from "inversify"; -import { append, head, inc } from "ramda"; +import { append, find, head, values, whereEq } from "ramda"; import { commandHandler } from "@core/command"; -import { ActivityManager } from "@modules/activity"; +import { ActivityManager, ActivityStore } from "@modules/activity"; import { GameCommand, GeneralGameStore } from "@modules/game"; import { MapLocationType, MapService } from "@modules/map"; import { PartyOwner, PartyService } from "@modules/party"; import { UnitStore, isAlive } from "@modules/unit"; +import { Resource } from "@models/resource"; import { VillageActivity, VillageCommand, VillageCommandHealPartyArgs } from "./interfaces"; import { heroFactory, newBuildingCost, newHeroCost } from "./lib"; import { VillageStashService } from "./village-stash-service"; import { VillageStore } from "./village-store"; +import { VillageBuildings } from "./activities"; @injectable() export class VillageCommandHandler { @@ -22,58 +24,44 @@ export class VillageCommandHandler { private partyService: PartyService, private unitStore: UnitStore, private activityManager: ActivityManager, + private activityStore: ActivityStore, private mapService: MapService, - private generalGameStore: GeneralGameStore, + private generalGameStore: GeneralGameStore ) {} @commandHandler(VillageCommand.BuildHouse) buildHouse(): void { - const goldCost = newBuildingCost(1 + this.villageStore.getState().houses); + const gold = newBuildingCost(1 + this.villageStore.getState().houses); - if (this.villageStash.hasEnoughResource({ gold: goldCost })) { - this.villageStore.update("houses", inc); - this.villageStash.removeResource({ gold: goldCost }); - } + this.buildBuilding('houses', { gold }) } @commandHandler(VillageCommand.BuildBlacksmith) buildBlacksmith(): void { - const goldCost = 100; + const gold = 100; - if (this.villageStash.hasEnoughResource({ gold: goldCost })) { - this.villageStore.update("blacksmith", inc); - this.villageStash.removeResource({ gold: goldCost }); - } + this.buildBuilding('blacksmith', { gold }) } @commandHandler(VillageCommand.BuildRuneWorkshop) buildRuneWorkshop(): void { - const goldCost = 100; + const gold = 100; - if (this.villageStash.hasEnoughResource({ gold: goldCost })) { - this.villageStore.update("runeWorkshop", inc); - this.villageStash.removeResource({ gold: goldCost }); - } + this.buildBuilding('runeWorkshop', { gold }) } @commandHandler(VillageCommand.BuildTrainingField) buildTrainingField(): void { - const goldCost = 100; + const gold = 100; - if (this.villageStash.hasEnoughResource({ gold: goldCost })) { - this.villageStore.update("trainingField", inc); - this.villageStash.removeResource({ gold: goldCost }); - } + this.buildBuilding('trainingField', { gold }) } @commandHandler(VillageCommand.BuildPortalSummonerStone) buildPortals(): void { - const goldCost = 100; + const gold = 100; - if (this.villageStash.hasEnoughResource({ gold: goldCost })) { - this.villageStore.update("portals", inc); - this.villageStash.removeResource({ gold: goldCost }); - } + this.buildBuilding('portals', { gold }) } @commandHandler(VillageCommand.HireHero) @@ -91,7 +79,7 @@ export class VillageCommandHandler { locationId: this.villageStore.getState().locationId, unitIds: [heroId], owner: PartyOwner.Player, - stash: { resource: { gold: 0 }, items: [] }, + stash: { resource: { gold: 0, soul: 0 }, items: [] }, }); } } @@ -109,4 +97,14 @@ export class VillageCommandHandler { this.villageStore.set("stash", { items: [], resource: { gold: 0, soul: 0 } }); this.villageStore.set("locationId", head(map.mapLocationIds)!); } + + private buildBuilding(targetBuilding: VillageBuildings, cost: Resource) { + const activities = values(this.activityStore.getState()); + + if(find(whereEq({ name: VillageActivity.Build, startArgs: { targetBuilding } }), activities) !== undefined) return; + if (!this.villageStash.hasEnoughResource(cost)) return + + this.activityManager.startActivity(VillageActivity.Build, { targetBuilding }); + this.villageStash.removeResource(cost); + } } diff --git a/packages/core/src/modules/village/village-module.ts b/packages/core/src/modules/village/village-module.ts index e8323ec..52e21ba 100644 --- a/packages/core/src/modules/village/village-module.ts +++ b/packages/core/src/modules/village/village-module.ts @@ -1,6 +1,6 @@ import { Module } from "@core/module"; -import { VillageHealActivity } from "./activities"; +import { BuildActivity, VillageHealActivity } from "./activities"; import { VillageActivity, VillageConfig } from "./interfaces"; import { VillageCommandHandler } from "./village-command-handler"; import { VillageEventHandler } from "./village-event-handler"; @@ -8,7 +8,7 @@ import { VillageStashService } from "./village-stash-service"; import { VillageStore } from "./village-store"; export const villageModule: Module = { - activities: [{ name: VillageActivity.Heal, activity: VillageHealActivity }], + activities: [{ name: VillageActivity.Heal, activity: VillageHealActivity }, { name: VillageActivity.Build, activity: BuildActivity }], stores: [ { scope: "village", diff --git a/packages/web/src/components/dashboard/dashboard.tsx b/packages/web/src/components/dashboard/dashboard.tsx index 7442f8e..ff24a63 100644 --- a/packages/web/src/components/dashboard/dashboard.tsx +++ b/packages/web/src/components/dashboard/dashboard.tsx @@ -4,7 +4,7 @@ import { useDispatch } from "react-redux"; import { BlacksmithCommand, ItemType, RuneWorkshopCommand, VillageCommand } from "@rpg-village/core"; import { useGameExecuteCommand } from "@web/react-hooks"; -import { playerPartiesSelector, useGameStateSelector, villageSelector } from "@web/store/game"; +import { playerPartiesSelector, useGameStateSelector, villageActivitiesSelector, villageSelector } from "@web/store/game"; import { changePage, pageSelector, useGameUISelector } from "@web/store/ui"; import { GamePageType } from "@web/store/ui/interface"; @@ -57,10 +57,15 @@ const Village = () => { const executeCommand = useGameExecuteCommand(); const dispatch = useDispatch(); const village = useGameStateSelector(villageSelector); + const villageActivities = useGameStateSelector(villageActivitiesSelector); return (
Village:
+
+ Activities: + {villageActivities.map(x =>
{x.name} - {(x.startArgs as any).targetBuilding}
)} +
House Level: {village.houses}
Blacksmith Level: {village.blacksmith}
Rune Workshop: {village.runeWorkshop}
diff --git a/packages/web/src/game/store/game/selectors.ts b/packages/web/src/game/store/game/selectors.ts index 698a986..18fa4fc 100644 --- a/packages/web/src/game/store/game/selectors.ts +++ b/packages/web/src/game/store/game/selectors.ts @@ -91,6 +91,9 @@ export const activityByIdSelector = createSelector( selectorProperty(), (activities, activityID) => activities[activityID], ); + +export const villageActivitiesSelector = createSelector(activitiesSelector, (activities) => filter(activity => activity.type === ActivityType.Global, values(activities))); + export const partyActivitiesSelector = createSelector(activitiesSelector, activities => filter((activity: Activity) => activity.type === ActivityType.Party, activities), );