Skip to content

Commit

Permalink
Merge pull request #451 from scottbenton/feat/campaign-types
Browse files Browse the repository at this point in the history
Feat/campaign types
  • Loading branch information
scottbenton authored May 30, 2024
2 parents 9259549 + 289c3a7 commit c8c4601
Show file tree
Hide file tree
Showing 117 changed files with 2,642 additions and 1,788 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
- Added asset clock and counter controls working for assets that use them (ex: Snub Fighter Ability #3, or Marked Ability #2).
- Added scene challenges to Crew Link
- Added a new Hinterlands theme
- Added types to campaigns

### Changes

- Re-added missing roll buttons for assets in the move dialog
- Made tensions clocks available in Iron Fellowship
- You can now enter other species into the NPC species box. They will use the default Ironlander name oracles (Ironsworn)
- Guides can now view their player's bond progress (Starforged)
- Bond progress is now only visible when you have a connection with a character (Starforged)

### Bug Fixes

Expand Down
2 changes: 1 addition & 1 deletion firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ service cloud.firestore {
allow read: if true;
allow create: if request.auth.uid != null;
allow write: if request.auth.uid in resource.data.ownerIds;
allow update: if request.resource.data.diff(resource.data).affectedKeys().hasOnly(["ownerIds"])
allow update: if request.resource.data.diff(resource.data).affectedKeys().hasOnly(["ownerIds", "campaignGuides", "campaignGuideCampaignIdMap"])

function checkIsOwner() {
let world = get(/databases/$(database)/documents/worlds/$(worldId)).data;
Expand Down
377 changes: 377 additions & 0 deletions public/assets/ironsworn/HinterlandsEmptyState.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions public/hinterlands-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 16 additions & 3 deletions src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import {
RouterProvider,
createBrowserRouter,
createRoutesFromElements,
redirect,
} from "react-router-dom";
import { ErrorRoute } from "components/shared/ErrorRoute";
import { CHARACTER_ROUTES, characterPaths } from "pages/Character/routes";
import { CAMPAIGN_ROUTES, campaignPaths } from "pages/Campaign/routes";
import {
CAMPAIGN_ROUTES,
campaignPaths,
constructCampaignSheetPath,
} from "pages/Campaign/routes";
import { WORLD_ROUTES, worldPaths } from "pages/World/routes";
import { HeadProvider } from "providers/HeadProvider";
import { useListenToCharacters } from "stores/character/useListenToCharacters";
Expand All @@ -21,6 +26,7 @@ import { useListenToOracleSettings } from "stores/settings/useListenToOracleSett
import { useListenToAccessibilitySettings } from "stores/accessibilitySettings/useListenToAccessibilitySettings";
import { useListenToHomebrew } from "stores/homebrew/useListenToHomebrew";
import { HOMEBREW_ROUTES, homebrewPaths } from "pages/Homebrew/routes";
import { useSyncCampaignWorldPermissions } from "stores/campaign/useSyncCampaignWorldPermissions";

const router = createBrowserRouter(
createRoutesFromElements(
Expand Down Expand Up @@ -65,11 +71,16 @@ const router = createBrowserRouter(
/>
<Route
path={campaignPaths[CAMPAIGN_ROUTES.SHEET]}
lazy={() => import("pages/Campaign/CampaignSheetPage")}
lazy={() => import("pages/Campaign/CampaignPage")}
/>
<Route
path={campaignPaths[CAMPAIGN_ROUTES.GM_SCREEN]}
lazy={() => import("pages/Campaign/CampaignGMScreenPage")}
loader={({ params }) => {
const campaignId = params.campaignId ?? "";
return redirect(
constructCampaignSheetPath(campaignId, CAMPAIGN_ROUTES.SHEET)
);
}}
/>
<Route
path={campaignPaths[CAMPAIGN_ROUTES.JOIN]}
Expand Down Expand Up @@ -133,5 +144,7 @@ export function Router() {
useListenToOracleSettings();
useListenToHomebrew();

useSyncCampaignWorldPermissions();

return <RouterProvider router={router} />;
}
7 changes: 7 additions & 0 deletions src/api-calls/campaign/_campaign.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { LegacyTrack } from "types/LegacyTrack.type";

export enum CampaignType {
Solo = "solo",
Coop = "co-op",
Guided = "guided",
}

export interface CampaignDocument {
name: string;
users: string[];
Expand All @@ -10,4 +16,5 @@ export interface CampaignDocument {
customTracks?: Record<string, number>;
conditionMeters?: Record<string, number>;
specialTracks?: Record<string, LegacyTrack>;
type?: CampaignType; // Todo - perhaps run a migration to set this so that we can remove the optional
}
11 changes: 8 additions & 3 deletions src/api-calls/campaign/createCampaign.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { addDoc } from "firebase/firestore";
import { CampaignDocument } from "../../api-calls/campaign/_campaign.type";
import {
CampaignDocument,
CampaignType,
} from "../../api-calls/campaign/_campaign.type";
import { getCampaignCollection } from "./_getRef";
import { createApiFunction } from "api-calls/createApiFunction";

export const createCampaign = createApiFunction<
{ uid: string; campaignName: string },
{ uid: string; campaignName: string; campaignType: CampaignType },
string
>((params) => {
const { uid, campaignName } = params;
const { uid, campaignName, campaignType } = params;
return new Promise((resolve, reject) => {
const storedCampaign: CampaignDocument = {
name: campaignName,
users: [uid],
gmIds: campaignType === CampaignType.Coop ? [uid] : [],
characters: [],
type: campaignType,
};

addDoc(getCampaignCollection(), storedCampaign)
Expand Down
10 changes: 7 additions & 3 deletions src/api-calls/world/_getRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import {
doc,
DocumentReference,
} from "firebase/firestore";
import { World } from "types/World.type";
import { WorldDocument } from "./_world.type";
import { WorldDocument, World } from "./_world.type";

export function constructWorldsPath() {
return `/worlds`;
Expand Down Expand Up @@ -46,11 +45,16 @@ export function encodeWorld(world: World): WorldDocument {
}

export function decodeWorld(encodedWorld: WorldDocument): World {
const { worldDescription, ...remainingWorld } = encodedWorld;
const { worldDescription, ownerIds, campaignGuides, ...remainingWorld } =
encodedWorld;

const newOwnerIds = [...ownerIds, ...(campaignGuides ?? [])];

const world: World = {
...remainingWorld,
ownerIds: newOwnerIds,
worldDescription: worldDescription?.toUint8Array(),
campaignGuides,
};

return world;
Expand Down
27 changes: 24 additions & 3 deletions src/api-calls/world/_world.type.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import { Bytes } from "firebase/firestore";
import { World } from "types/World.type";

export type WorldDocument = Omit<World, "worldDescription"> & {
export interface WorldDocument {
name: string;
worldDescription?: Bytes;
};
newTruths?: Record<string, Truth>;
ownerIds: string[];
campaignGuides?: string[];
}

export interface World {
name: string;
newTruths?: Record<string, Truth>;
ownerIds: string[];
worldDescription?: Uint8Array;
campaignGuides?: string[];
}

export interface Truth {
selectedTruthOptionIndex?: number;
selectedSubItemIndex?: number | null;

customTruth?: {
description: string;
questStarter: string;
};
}
2 changes: 1 addition & 1 deletion src/api-calls/world/createWorld.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { addDoc } from "firebase/firestore";
import { World } from "types/World.type";
import { World } from "api-calls/world/_world.type";
import { encodeWorld, getWorldCollection } from "./_getRef";
import { createApiFunction } from "api-calls/createApiFunction";

Expand Down
10 changes: 7 additions & 3 deletions src/api-calls/world/listenToUsersWorlds.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { World } from "types/World.type";
import { World } from "api-calls/world/_world.type";
import { decodeWorld, getWorldCollection } from "./_getRef";
import { onSnapshot, query, where } from "firebase/firestore";
import { onSnapshot, or, query, where } from "firebase/firestore";

export function listenToUsersWorlds(
uid: string,
Expand All @@ -12,8 +12,12 @@ export function listenToUsersWorlds(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onError: (error: any) => void
) {
const filter = or(
where("ownerIds", "array-contains", uid ?? ""),
where("campaignGuides", "array-contains", uid ?? "")
);
return onSnapshot(
query(getWorldCollection(), where("ownerIds", "array-contains", uid ?? "")),
query(getWorldCollection(), filter),
(snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "removed") {
Expand Down
2 changes: 1 addition & 1 deletion src/api-calls/world/listenToWorld.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { onSnapshot } from "firebase/firestore";
import { World } from "types/World.type";
import { World } from "api-calls/world/_world.type";
import { decodeWorld, getWorldDoc } from "./_getRef";

export function listenToWorld(
Expand Down
2 changes: 1 addition & 1 deletion src/api-calls/world/updateWorld.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { updateDoc } from "firebase/firestore";
import { getWorldDoc } from "./_getRef";
import { createApiFunction } from "api-calls/createApiFunction";
import { World } from "types/World.type";
import { World } from "api-calls/world/_world.type";

export const updateWorld = createApiFunction<
{ worldId: string; partialWorld: Partial<World> },
Expand Down
28 changes: 28 additions & 0 deletions src/api-calls/world/updateWorldGuide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { arrayRemove, arrayUnion, updateDoc } from "firebase/firestore";
import { getWorldDoc } from "./_getRef";
import { createApiFunction } from "api-calls/createApiFunction";

export const updateWorldGuide = createApiFunction<
{
worldId: string;
guideId: string;
shouldRemove?: boolean;
},
void
>((params) => {
const { worldId, guideId, shouldRemove } = params;

return new Promise((resolve, reject) => {
updateDoc(getWorldDoc(worldId), {
campaignGuides: !shouldRemove
? arrayUnion(guideId)
: arrayRemove(guideId),
})
.then(() => {
resolve();
})
.catch((e) => {
reject(e);
});
});
}, "Failed to update world guides.");
2 changes: 1 addition & 1 deletion src/api-calls/world/updateWorldTruth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { updateDoc } from "firebase/firestore";
import { getWorldDoc } from "./_getRef";
import { createApiFunction } from "api-calls/createApiFunction";
import { Truth } from "types/World.type";
import { Truth } from "api-calls/world/_world.type";

export const updateWorldTruth = createApiFunction<
{ worldId: string; truthKey: string; truth: Truth },
Expand Down
35 changes: 35 additions & 0 deletions src/assets/SoloCampaignLogo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c8c4601

Please sign in to comment.