Skip to content

Commit

Permalink
add global activities
Browse files Browse the repository at this point in the history
  • Loading branch information
Greegko committed Nov 8, 2022
1 parent 682ecdf commit 2807b05
Show file tree
Hide file tree
Showing 14 changed files with 132 additions and 49 deletions.
18 changes: 18 additions & 0 deletions packages/core-test/tests/activities/village/build.test.ts
Original file line number Diff line number Diff line change
@@ -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 })
]
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { VillageCommand } from "@rpg-village/core";
import { VillageActivity, VillageCommand } from "@rpg-village/core";

import { test } from "../../utils";

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", {
Expand Down
6 changes: 3 additions & 3 deletions packages/core-test/tests/commands/village/build-house.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { VillageCommand } from "@rpg-village/core";
import { VillageActivity, VillageCommand } from "@rpg-village/core";

import { test } from "../../utils";

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", {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { VillageCommand } from "@rpg-village/core";
import { VillageActivity, VillageCommand } from "@rpg-village/core";

import { test } from "../../utils";

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", {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { VillageCommand } from "@rpg-village/core";
import { VillageActivity, VillageCommand } from "@rpg-village/core";

import { test } from "../../utils";

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", {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { VillageCommand } from "@rpg-village/core";
import { VillageActivity, VillageCommand } from "@rpg-village/core";

import { test } from "../../utils";

Expand All @@ -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", {
Expand Down
20 changes: 17 additions & 3 deletions packages/core/src/modules/activity/activity-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
43 changes: 43 additions & 0 deletions packages/core/src/modules/village/activities/build.ts
Original file line number Diff line number Diff line change
@@ -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<Activity<BuildState, BuildStartArgs>> {
constructor(private villageStore: VillageStore) {}

start(): BuildState {
return {
progress: 100
};
}

isRunnable(): boolean {
return true;
}

execute({ state }: Activity<BuildState, BuildStartArgs>): BuildState {
return evolve({ progress: dec }, state);
}

isDone({ state: { progress } }: Activity<BuildState, BuildStartArgs>): boolean {
return progress === 0;
}

resolve({ startArgs: { targetBuilding } }: Activity<BuildState, BuildStartArgs>) {
this.villageStore.update(targetBuilding, inc);
}
}
1 change: 1 addition & 0 deletions packages/core/src/modules/village/activities/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./village-heal";
export * from './build';
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum VillageActivity {
Heal = "village/heal",
Build = "village/build"
}
56 changes: 27 additions & 29 deletions packages/core/src/modules/village/village-command-handler.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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)
Expand All @@ -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: [] },
});
}
}
Expand All @@ -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);
}
}
4 changes: 2 additions & 2 deletions packages/core/src/modules/village/village-module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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";
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",
Expand Down
7 changes: 6 additions & 1 deletion packages/web/src/components/dashboard/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -57,10 +57,15 @@ const Village = () => {
const executeCommand = useGameExecuteCommand();
const dispatch = useDispatch();
const village = useGameStateSelector(villageSelector);
const villageActivities = useGameStateSelector(villageActivitiesSelector);

return (
<div className="village">
<div>Village:</div>
<div>
Activities:
{villageActivities.map(x => <div>{x.name} - {(x.startArgs as any).targetBuilding}</div>)}
</div>
<div>House Level: {village.houses}</div>
<div>Blacksmith Level: {village.blacksmith}</div>
<div>Rune Workshop: {village.runeWorkshop}</div>
Expand Down
3 changes: 3 additions & 0 deletions packages/web/src/game/store/game/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ export const activityByIdSelector = createSelector(
selectorProperty<ActivityID>(),
(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),
);
Expand Down

0 comments on commit 2807b05

Please sign in to comment.