Skip to content

Commit

Permalink
Storylines (#125)
Browse files Browse the repository at this point in the history
* Improve mission dialog layout

* wip

* Update storybook

* Rework mission system

* Fix asteroid mining order

* Add mission generator

* Simplify Observer type

* Add trade tutorial

* Remove unused files

* Drop .m. part of conversation filename

* Fix mission chains

* Fix copy

* Add pirate removing tutorial

* Use fromPolar util

* Degrade Russo to commander

* Fix case when player ends conversation

* Add rescue missions

* Fix negative trade offer

* Add tutorial end

* Update base save

* Update readme
  • Loading branch information
dominik-zeglen committed Apr 8, 2024
1 parent de1f8e5 commit afab3ac
Show file tree
Hide file tree
Showing 91 changed files with 95,504 additions and 99,047 deletions.
2 changes: 1 addition & 1 deletion .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import custom from "../webpack.config.js";
import custom from "../webpack.config.ts";

module.exports = {
stories: [
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sector Alpha

Welcome to Sector Alpha, where you take on the role of a fleet commander in a universe that's living its own life. Set in the 2600s, shortly after humankind's first encounter with the enigmatic Tau in Tau Ceti system, this game unfolds in a world shrouded in mystery. Humanity knows little about the Tau, and fear is our constant companion.
Welcome to Sector Alpha, where you take on the role of a fleet commander in a universe that's living its own life. Set in the 2500s, shortly after humankind's first encounter with the enigmatic Tau in Tau Ceti system, this game unfolds in a world shrouded in mystery. Humanity knows little about the Tau, and fear is our constant companion.

### Game Features

Expand Down
19 changes: 19 additions & 0 deletions _templates/mission/new/conversation.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
to: core/world/data/missions/<%= name.split(".").join("/") %>.yml
---

Start: npc.greeting

Actors:
npc:
name: NPC
lines:
greeting:
text: Line
next:
- player.greeting

player:
lines:
greeting:
text: Response
58 changes: 58 additions & 0 deletions _templates/mission/new/mission.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
to: core/systems/mission/<%= name.split(".").join("/") %>.ts
---

import Mustache from "mustache";
import type { Mission, MissionCommon } from "@core/components/missions";
import { first } from "@fxts/core";
import type { Sim } from "@core/sim";
import type { MissionHandler } from "../../types";
import conversation from "../../../../world/data/missions/<%= name.split(".").join("/") %>.yml";

Mustache.escape = (text) => text;

interface <%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_"), false) %>Mission extends Mission {
type: "<%= name %>";
}

export const <%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_"), true) %>Mission = (
minerId: number,
common: MissionCommon
): <%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_")) %>Mission => ({
...common,
minerId,
type: "<%= name %>",
});

export const is<%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_")) %>Mission = (
mission: Mission
): mission is <%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_")) %>Mission =>
mission.type === "<%= name %>";

export const <%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_"), true) %>MissionHandler: MissionHandler = {
generate: (_sim) => ({
conversation,
rewards: [],
type: "<%= name %>",
}),
accept: (sim, offer) => {
const player = first(sim.queries.player.getIt())!;

return <%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_"), true) %>Mission(miner.id, {
accepted: sim.getTime(),
});
},
isFailed: (mission, sim) => {
if (!is<%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_")) %>Mission(mission))
throw new Error("Mission is not a <%= name %> mission");
},
isCompleted: (mission: Mission, sim) => {
if (!is<%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_")) %>Mission(mission))
throw new Error("Mission is not a <%= name %> mission");
},
update: (mission: Mission, _sim: Sim) => {
if (!is<%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_")) %>Mission(mission))
throw new Error("Mission is not a <%= name %> mission");
},
formatProgress: (mission: <%= h.inflection.camelize(name.replace(/\./g, "_").replace(/-/g, "_")) %>Mission) => "",
};
53 changes: 53 additions & 0 deletions build/conversation-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* eslint-disable no-invalid-this */

import { load } from "js-yaml";
import type webpack from "webpack";
import fs from "fs";
import path from "path";
import AJV from "ajv";
import schema from "../core/world/data/missions/schema.json";

const validator = new AJV();

// eslint-disable-next-line func-names
const loader: webpack.LoaderDefinition = function (this, content) {
const parsed = load(content, {
json: true,
});
if (!validator.validate(schema, parsed)) {
throw new Error(validator.errorsText());
}

const filename = `${this.resourcePath.split(".yml")[0]}.d.ts`;

let fileContent = "";
try {
fileContent = fs.readFileSync(filename).toString();
} catch (err) {
if (err.code !== "ENOENT") {
throw err;
}
}

const generatedContent = `/* @generated */
/* prettier-ignore */
/* eslint-disable */
declare module "*/${path.relative(
path.join(process.cwd(), "core/world/data"),
this.resourcePath
)}" {
import type { MissionConversation } from "@core/systems/mission/types";
const content: MissionConversation;
export default content;
}
`;

if (fileContent !== generatedContent) {
fs.writeFileSync(filename, generatedContent);
}

return `export default ${JSON.stringify(parsed)}`;
};

export default loader;
6 changes: 6 additions & 0 deletions core/components/journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export interface TradeEntry extends Entry {
* entity)
*/
target: string;
/**
* ID of target object
*
* Entity may not exist anymore, therefore sim.getOrThrow should not be used
*/
targetId: number;
price: number;
action: TradeOfferType;
}
Expand Down
11 changes: 10 additions & 1 deletion core/components/missions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MissionOffer } from "@ui/components/MissionDialog";
import type { MissionConversation } from "@core/systems/mission/types";
import type { BaseComponent } from "./component";

export type Reward = {
Expand All @@ -23,12 +23,21 @@ export interface MissionCommon {
max: number;
label?: string;
};
cancellable: boolean;
}
export type Mission = MissionCommon & {
type: string;
[key: string]: any;
};

export interface MissionOffer {
conversation: MissionConversation;
rewards: Reward[];
type: string;
immediate: boolean;
data?: Record<string, any>;
}

export interface Missions extends BaseComponent<"missions"> {
/**
* Time at which the last mission was declined
Expand Down
2 changes: 1 addition & 1 deletion core/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { Observable } from "./utils/observer";

export const storageHook = new Observable<[string]>("storage");
export const storageHook = new Observable<string>("storage");
34 changes: 17 additions & 17 deletions core/sim/Sim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,31 +31,29 @@ export class Sim extends BaseSim {
@Expose()
entityIdCounter: number = 0;
hooks: {
addComponent: Observable<
[{ entity: Entity; component: keyof CoreComponents }]
>;
removeComponent: Observable<
[
{
entity: Entity;
component: keyof CoreComponents;
}
]
>;
addTag: Observable<[{ entity: Entity; tag: EntityTag }]>;
removeTag: Observable<[{ entity: Entity; tag: EntityTag }]>;
removeEntity: Observable<[Entity]>;
destroy: Observable<[]>;
addComponent: Observable<{
entity: Entity;
component: keyof CoreComponents;
}>;
removeComponent: Observable<{
entity: Entity;
component: keyof CoreComponents;
}>;
addTag: Observable<{ entity: Entity; tag: EntityTag }>;
removeTag: Observable<{ entity: Entity; tag: EntityTag }>;
removeEntity: Observable<Entity>;
destroy: Observable<void>;

phase: Record<
"start" | "init" | "update" | "render" | "cleanup" | "end",
Observable<[number]>
Observable<number>
>;
};

@Expose()
@Type(() => Entity)
entities: Map<number, Entity>;
systems: System[];
queries: Queries;
paths: Record<string, Record<string, Path>>;

Expand Down Expand Up @@ -84,8 +82,9 @@ export class Sim extends BaseSim {
};

this.queries = createQueries(this);
this.systems = systems;

systems.forEach((system) => system.apply(this));
this.systems.forEach((system) => system.apply(this));
}

registerEntity = (entity: Entity) => {
Expand Down Expand Up @@ -237,6 +236,7 @@ export class Sim extends BaseSim {

sim.entities = entityMap;

sim.systems = config.systems;
config.systems.forEach((system) => system.apply(sim));
Object.values(sim.queries).forEach((indexOrNested) => {
if (indexOrNested instanceof Index) {
Expand Down
21 changes: 3 additions & 18 deletions core/sim/baseConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ import { FacilityBuildingSystem } from "@core/systems/facilityBuilding";
import { HitpointsRegeneratingSystem } from "@core/systems/hitpointsRegenerating";
import { InflationStatisticGatheringSystem } from "@core/systems/inflationStatisticGathering";
import { MiningSystem } from "@core/systems/mining";
import { MissionSystem } from "@core/systems/mission";
import { destroyMissionHandler } from "@core/systems/mission/destroy";
import { patrolMissionHandler } from "@core/systems/mission/patrol";
import {
moneyRewardHandler,
relationRewardHandler,
} from "@core/systems/mission/rewards";
import { MovingSystem } from "@core/systems/moving";
import { NavigatingSystem } from "@core/systems/navigating";
import { OrderExecutingSystem } from "@core/systems/orderExecuting/orderExecuting";
Expand Down Expand Up @@ -65,7 +58,8 @@ export const bootstrapSystems = [
new CrewGrowingSystem(),
];

export const createBaseConfig = (): SimConfig => {
export const createBaseConfig = async (): Promise<SimConfig> => {
const { MissionSystem } = await import("@core/systems/mission/mission");
const config: SimConfig = {
systems: [
...bootstrapSystems,
Expand All @@ -78,16 +72,7 @@ export const createBaseConfig = (): SimConfig => {
new TauHarassingSystem(),
new DeadUnregisteringSystem(),
new CollectibleUnregisteringSystem(),
new MissionSystem(
{
patrol: patrolMissionHandler,
destroy: destroyMissionHandler,
},
{
money: moneyRewardHandler,
relation: relationRewardHandler,
}
),
new MissionSystem(),
new PirateSpawningSystem(),
new FogOfWarUpdatingSystem(),
],
Expand Down
34 changes: 25 additions & 9 deletions core/systems/ai/orderPlanning.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import type { Faction } from "@core/archetypes/faction";
import { relationThresholds } from "@core/components/relations";
import { minBy } from "lodash";
import { add, norm, random, subtract } from "mathjs";
import { add, norm, random, subtract, distance } from "mathjs";
import { filter, flatMap, map, pipe, sum, toArray } from "@fxts/core";
import type { TransactionInput } from "@core/components/trade";
import type { Position2D } from "@core/components/position";
import { getRandomPositionInBounds } from "@core/utils/misc";
import { hecsToCartesian } from "@core/components/hecsPosition";
import { asteroidField } from "../../archetypes/asteroidField";
import { commanderRange, facility } from "../../archetypes/facility";
import type { Waypoint } from "../../archetypes/waypoint";
import { createWaypoint } from "../../archetypes/waypoint";
import type { Sector } from "../../archetypes/sector";
import { sector as asSector } from "../../archetypes/sector";
import { sector as asSector, sectorSize } from "../../archetypes/sector";
import type { MineOrder, TradeOrder } from "../../components/orders";
import { mineAction } from "../../components/orders";
import { dumpCargo, getAvailableSpace } from "../../components/storage";
Expand Down Expand Up @@ -214,12 +215,16 @@ function autoMine(
if (getAvailableSpace(entity.cp.storage) !== entity.cp.storage.max) {
autoTrade(entity, sectorDistance);
} else {
const baseSector = asSector(
entity.sim.getOrThrow(
(entity.cp.autoOrder.default as MineOrder).sectorId!
)
);
const currentSector = asSector(
entity.sim.getOrThrow(entity.cp.position.sector)
);
const sectorsInRange = getSectorsInTeleportRange(
asSector(
entity.sim.getOrThrow(
(entity.cp.autoOrder.default as MineOrder).sectorId!
)
),
baseSector,
sectorDistance,
entity.sim
);
Expand All @@ -241,8 +246,19 @@ function autoMine(
toArray
);
const field = minBy(eligibleFields, (e) =>
norm(
subtract(entity.cp.position.coord, e.cp.position.coord) as Position2D
distance(
add(
hecsToCartesian(currentSector.cp.hecsPosition.value, sectorSize),
entity.cp.position.coord
),
add(
hecsToCartesian(
entity.sim.getOrThrow<Sector>(e.cp.position.sector).cp.hecsPosition
.value,
sectorSize
),
e.cp.position.coord
)
)
);

Expand Down
2 changes: 1 addition & 1 deletion core/systems/fogOfWarUpdating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class FogOfWarUpdatingSystem extends System<"exec"> {
entity.cp.render!.visible &&
(entity.tags.has("facility") || entity.tags.has("collectible"))
) {
entity.tags.add("discovered");
entity.addTag("discovered");
}
}
}
Expand Down
Loading

0 comments on commit afab3ac

Please sign in to comment.