mirror of
https://github.com/thepaperpilot/Planar-Pioneers.git
synced 2024-11-21 16:13:54 +00:00
Implement creating planes
This commit is contained in:
parent
5dcba9fe3a
commit
1e3f065427
4 changed files with 493 additions and 9 deletions
41
src/data/planes.tsx
Normal file
41
src/data/planes.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { jsx } from "features/feature";
|
||||
import MainDisplayVue from "features/resources/MainDisplay.vue";
|
||||
import { createResource } from "features/resources/resource";
|
||||
import { BaseLayer, createLayer } from "game/layers";
|
||||
import { persistent } from "game/persistence";
|
||||
import { DecimalSource } from "util/bignum";
|
||||
import { Resources } from "./projEntry";
|
||||
import { getColor, getName, sfc32 } from "./utils";
|
||||
|
||||
export function createPlane(id: string, tier: Resources, seed: number) {
|
||||
return createLayer(id, function (this: BaseLayer) {
|
||||
const random = sfc32(0, seed >> 0, seed >> 32, 1);
|
||||
for (let i = 0; i < 12; i++) random();
|
||||
|
||||
const name = getName(random);
|
||||
const color = getColor([0.64, 0.75, 0.55], random);
|
||||
const background = getColor([0.18, 0.2, 0.25], random);
|
||||
const resource = createResource<DecimalSource>(0, getName(random));
|
||||
|
||||
return {
|
||||
tier: persistent(tier),
|
||||
seed: persistent(seed),
|
||||
name,
|
||||
color,
|
||||
resource,
|
||||
background,
|
||||
style: {
|
||||
background,
|
||||
"--background": background
|
||||
},
|
||||
display: jsx(() => (
|
||||
<>
|
||||
<h1>{name}</h1>
|
||||
<MainDisplayVue resource={resource} color={color} />
|
||||
</>
|
||||
))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export type GenericPlane = ReturnType<typeof createPlane>;
|
|
@ -14,16 +14,16 @@ import { jsx } from "features/feature";
|
|||
import { createResource } from "features/resources/resource";
|
||||
import { createTabFamily } from "features/tabs/tabFamily";
|
||||
import Formula, { calculateCost } from "game/formulas/formulas";
|
||||
import type { BaseLayer, GenericLayer } from "game/layers";
|
||||
import { createLayer } from "game/layers";
|
||||
import { GenericFormula, InvertibleIntegralFormula } from "game/formulas/types";
|
||||
import { BaseLayer, GenericLayer, addLayer, createLayer, layers } from "game/layers";
|
||||
import {
|
||||
Modifier,
|
||||
createAdditiveModifier,
|
||||
createMultiplicativeModifier,
|
||||
createSequentialModifier
|
||||
} from "game/modifiers";
|
||||
import { State, persistent } from "game/persistence";
|
||||
import type { Player } from "game/player";
|
||||
import { State } from "game/persistence";
|
||||
import type { LayerData, Player } from "game/player";
|
||||
import player from "game/player";
|
||||
import settings from "game/settings";
|
||||
import Decimal, { DecimalSource } from "lib/break_eternity";
|
||||
|
@ -34,7 +34,7 @@ import { ComputedRef, computed, nextTick, reactive, ref, watch } from "vue";
|
|||
import { useToast } from "vue-toastification";
|
||||
import { Section, createCollapsibleModifierSections, createFormulaPreview } from "./common";
|
||||
import "./main.css";
|
||||
import { GenericFormula, InvertibleIntegralFormula } from "game/formulas/types";
|
||||
import { GenericPlane, createPlane } from "./planes";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
|
@ -64,6 +64,16 @@ export interface EmpowererState {
|
|||
powered: boolean;
|
||||
}
|
||||
|
||||
export interface PortalGeneratorState {
|
||||
tier: Resources | undefined;
|
||||
influences: string[];
|
||||
}
|
||||
|
||||
export interface PortalState {
|
||||
id: string;
|
||||
powered: boolean;
|
||||
}
|
||||
|
||||
const mineLootTable = {
|
||||
dirt: 120,
|
||||
sand: 60,
|
||||
|
@ -132,7 +142,8 @@ const tools = {
|
|||
iron: {
|
||||
cost: 1e10,
|
||||
name: "Portal Generator",
|
||||
type: "portalGenerator"
|
||||
type: "portalGenerator",
|
||||
state: { tier: null, influences: [] }
|
||||
},
|
||||
silver: {
|
||||
cost: 1e12,
|
||||
|
@ -227,7 +238,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
}, {} as Record<Resources, BoardNode>),
|
||||
sand: board.types.dowsing.nodes.value[0],
|
||||
wood: board.types.quarry.nodes.value[0],
|
||||
coal: board.types.empowerer.nodes.value[0]
|
||||
coal: board.types.empowerer.nodes.value[0],
|
||||
iron: board.types.portalGenerator.nodes.value[0]
|
||||
}));
|
||||
|
||||
const resourceLevels = computed(() =>
|
||||
|
@ -854,6 +866,156 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
running: isPowered(node)
|
||||
}),
|
||||
draggable: true
|
||||
},
|
||||
portalGenerator: {
|
||||
shape: Shape.Diamond,
|
||||
size: 50,
|
||||
title: "⛩️",
|
||||
label: node => {
|
||||
if (node === board.selectedNode.value) {
|
||||
return {
|
||||
text:
|
||||
(node.state as unknown as PortalGeneratorState).tier == null
|
||||
? "Portal Spawner - Drag a resource to me!"
|
||||
: `Spawning ${
|
||||
(node.state as unknown as PortalGeneratorState).tier
|
||||
}-tier portal`
|
||||
};
|
||||
}
|
||||
if ((board as GenericBoard).draggingNode.value?.type === "resource") {
|
||||
const resource = (
|
||||
(board as GenericBoard).draggingNode.value
|
||||
?.state as unknown as ResourceState
|
||||
).type;
|
||||
const text =
|
||||
(node.state as unknown as PortalGeneratorState).tier === resource
|
||||
? "Disconnect"
|
||||
: `${camelToTitle(resource)}-tier Portal`;
|
||||
return {
|
||||
text,
|
||||
color: "var(--accent2)"
|
||||
};
|
||||
}
|
||||
// TODO handle influences
|
||||
return null;
|
||||
},
|
||||
actionDistance: Math.PI / 4,
|
||||
actions: [
|
||||
{
|
||||
id: "deselect",
|
||||
icon: "close",
|
||||
tooltip: { text: "Disconnect all" },
|
||||
onClick(node: BoardNode) {
|
||||
node.state = {
|
||||
...(node.state as object),
|
||||
tier: undefined,
|
||||
influences: []
|
||||
};
|
||||
board.selectedAction.value = null;
|
||||
board.selectedNode.value = null;
|
||||
},
|
||||
visibility: (node: BoardNode) => {
|
||||
const { tier, influences } =
|
||||
node.state as unknown as PortalGeneratorState;
|
||||
return tier != null || influences.length > 0;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "makePortal",
|
||||
icon: "done",
|
||||
tooltip: node => ({
|
||||
text: `Spawn ${
|
||||
(node.state as unknown as PortalGeneratorState).tier
|
||||
}-tier portal`
|
||||
}),
|
||||
onClick(node) {
|
||||
let id = 0;
|
||||
while (`portal-${id}` in layers) {
|
||||
id++;
|
||||
}
|
||||
addLayer(
|
||||
createPlane(
|
||||
`portal-${id}`,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
(node.state as unknown as PortalGeneratorState).tier!,
|
||||
Math.floor(Math.random() * 4294967296)
|
||||
),
|
||||
player
|
||||
);
|
||||
const newNode = {
|
||||
id: getUniqueNodeID(board as GenericBoard),
|
||||
position: { ...node.position },
|
||||
type: "portal",
|
||||
state: { id: `portal-${id}`, powered: false }
|
||||
};
|
||||
board.placeInAvailableSpace(newNode);
|
||||
board.nodes.value.push(newNode);
|
||||
board.selectedAction.value = null;
|
||||
board.selectedNode.value = null;
|
||||
node.state = { tier: undefined, influences: [] };
|
||||
},
|
||||
visibility: node =>
|
||||
(node.state as unknown as PortalGeneratorState).tier != null
|
||||
}
|
||||
],
|
||||
canAccept(node, otherNode) {
|
||||
return otherNode.type === "resource" || otherNode.type === "influence";
|
||||
},
|
||||
onDrop(node, otherNode) {
|
||||
if (otherNode.type === "resource") {
|
||||
const droppedType = (otherNode.state as unknown as ResourceState).type;
|
||||
const currentType = (node.state as unknown as PortalGeneratorState).tier;
|
||||
node.state = {
|
||||
...(node.state as object),
|
||||
tier: droppedType === currentType ? undefined : droppedType
|
||||
};
|
||||
} else if (otherNode.type === "influence") {
|
||||
const droppedInfluence = otherNode.state as string;
|
||||
const currentInfluences = (node.state as unknown as PortalGeneratorState)
|
||||
.influences;
|
||||
if (currentInfluences.includes(droppedInfluence)) {
|
||||
node.state = {
|
||||
...(node.state as object),
|
||||
influences: currentInfluences.filter(i => i !== droppedInfluence)
|
||||
};
|
||||
} else {
|
||||
node.state = {
|
||||
...(node.state as object),
|
||||
influences: [...currentInfluences, droppedInfluence]
|
||||
};
|
||||
}
|
||||
}
|
||||
board.selectedNode.value = node;
|
||||
},
|
||||
draggable: true
|
||||
},
|
||||
portal: {
|
||||
shape: Shape.Diamond,
|
||||
size: 50,
|
||||
title: "🌀",
|
||||
label: node =>
|
||||
node === board.selectedNode.value
|
||||
? {
|
||||
text: `Portal to ${
|
||||
(
|
||||
layers[
|
||||
(node.state as unknown as PortalState).id
|
||||
] as GenericPlane
|
||||
).name
|
||||
}`,
|
||||
color: (
|
||||
layers[(node.state as unknown as PortalState).id] as GenericPlane
|
||||
).color
|
||||
}
|
||||
: null,
|
||||
actionDistance: Math.PI / 4,
|
||||
actions: [togglePoweredAction],
|
||||
classes: node => ({
|
||||
running: isPowered(node)
|
||||
}),
|
||||
outlineColor: node =>
|
||||
(layers[(node.state as unknown as PortalState).id] as GenericPlane).background,
|
||||
draggable: true
|
||||
}
|
||||
},
|
||||
style: {
|
||||
|
@ -929,6 +1091,23 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
});
|
||||
});
|
||||
}
|
||||
if (portalGenerator.value != null) {
|
||||
if ((portalGenerator.value.state as unknown as PortalGeneratorState).tier != null) {
|
||||
links.push({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
startNode: portalGenerator.value!,
|
||||
endNode:
|
||||
resourceNodes.value[
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
(portalGenerator.value.state as unknown as PortalGeneratorState)
|
||||
.tier!
|
||||
],
|
||||
stroke: "var(--foreground)",
|
||||
strokeWidth: 4
|
||||
});
|
||||
}
|
||||
// TODO link to influences
|
||||
}
|
||||
return links;
|
||||
}
|
||||
}));
|
||||
|
@ -944,6 +1123,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
const dowsing: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.sand);
|
||||
const quarry: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.wood);
|
||||
const empowerer: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.coal);
|
||||
const portalGenerator: ComputedRef<BoardNode | undefined> = computed(
|
||||
() => toolNodes.value.iron
|
||||
);
|
||||
|
||||
function grantResource(type: Resources, amount: DecimalSource) {
|
||||
let node = resourceNodes.value[type];
|
||||
|
@ -1304,6 +1486,17 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
energyProductionChange
|
||||
);
|
||||
|
||||
const activePortals = computed(() => board.types.portal.nodes.value.filter(n => isPowered(n)));
|
||||
|
||||
watch(activePortals, activePortals => {
|
||||
nextTick(() => {
|
||||
player.tabs = [
|
||||
"main",
|
||||
...activePortals.map(node => (node.state as unknown as PortalState).id)
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
name: "World",
|
||||
board,
|
||||
|
@ -1368,7 +1561,22 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
export const getInitialLayers = (
|
||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||
player: Partial<Player>
|
||||
): Array<GenericLayer> => [main];
|
||||
): Array<GenericLayer> => {
|
||||
const layers: GenericLayer[] = [main];
|
||||
let id = 0;
|
||||
while (`portal-${id}` in (player.layers ?? {})) {
|
||||
const layer = player.layers?.[`portal-${id}`] as LayerData<GenericPlane>;
|
||||
layers.push(
|
||||
createPlane(
|
||||
`portal-${id}`,
|
||||
layer.tier ?? "dirt",
|
||||
layer.seed ?? Math.floor(Math.random() * 4294967296)
|
||||
)
|
||||
);
|
||||
id++;
|
||||
}
|
||||
return layers;
|
||||
};
|
||||
|
||||
/**
|
||||
* A computed ref whose value is true whenever the game is over.
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"versionNumber": "0.0",
|
||||
"versionTitle": "Initial Commit",
|
||||
|
||||
"allowGoBack": true,
|
||||
"allowGoBack": false,
|
||||
"defaultShowSmall": false,
|
||||
"defaultDecimalsShown": 2,
|
||||
"useHeader": true,
|
||||
|
|
235
src/data/utils.tsx
Normal file
235
src/data/utils.tsx
Normal file
|
@ -0,0 +1,235 @@
|
|||
import { camelToTitle } from "util/common";
|
||||
|
||||
// Simple Fast Counter is a part of PractRand suite by Chris Doty-Humphrey.
|
||||
export function sfc32(a: number, b: number, c: number, d: number) {
|
||||
return function () {
|
||||
a >>>= 0;
|
||||
b >>>= 0;
|
||||
c >>>= 0;
|
||||
d >>>= 0;
|
||||
let t = (a + b) | 0;
|
||||
a = b ^ (b >>> 9);
|
||||
b = (c + (c << 3)) | 0;
|
||||
c = (c << 21) | (c >>> 11);
|
||||
d = (d + 1) | 0;
|
||||
t = (t + d) | 0;
|
||||
c = (c + t) | 0;
|
||||
return (t >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
// Modified version of this lib to use seed: https://github.com/hbi99/namegen
|
||||
const morphemes = {
|
||||
1: [
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
"m",
|
||||
"n",
|
||||
"p",
|
||||
"q",
|
||||
"r",
|
||||
"s",
|
||||
"t",
|
||||
"v",
|
||||
"w",
|
||||
"x",
|
||||
"y",
|
||||
"z"
|
||||
],
|
||||
2: ["a", "e", "o", "u"],
|
||||
3: [
|
||||
"br",
|
||||
"cr",
|
||||
"dr",
|
||||
"fr",
|
||||
"gr",
|
||||
"pr",
|
||||
"str",
|
||||
"tr",
|
||||
"bl",
|
||||
"cl",
|
||||
"fl",
|
||||
"gl",
|
||||
"pl",
|
||||
"sl",
|
||||
"sc",
|
||||
"sk",
|
||||
"sm",
|
||||
"sn",
|
||||
"sp",
|
||||
"st",
|
||||
"sw",
|
||||
"ch",
|
||||
"sh",
|
||||
"th",
|
||||
"wh"
|
||||
],
|
||||
4: [
|
||||
"ae",
|
||||
"ai",
|
||||
"ao",
|
||||
"au",
|
||||
"a",
|
||||
"ay",
|
||||
"ea",
|
||||
"ei",
|
||||
"eo",
|
||||
"eu",
|
||||
"e",
|
||||
"ey",
|
||||
"ua",
|
||||
"ue",
|
||||
"ui",
|
||||
"uo",
|
||||
"u",
|
||||
"uy",
|
||||
"ia",
|
||||
"ie",
|
||||
"iu",
|
||||
"io",
|
||||
"iy",
|
||||
"oa",
|
||||
"oe",
|
||||
"ou",
|
||||
"oi",
|
||||
"o",
|
||||
"oy"
|
||||
],
|
||||
5: [
|
||||
"turn",
|
||||
"ter",
|
||||
"nus",
|
||||
"rus",
|
||||
"tania",
|
||||
"hiri",
|
||||
"hines",
|
||||
"gawa",
|
||||
"nides",
|
||||
"carro",
|
||||
"rilia",
|
||||
"stea",
|
||||
"lia",
|
||||
"lea",
|
||||
"ria",
|
||||
"nov",
|
||||
"phus",
|
||||
"mia",
|
||||
"nerth",
|
||||
"wei",
|
||||
"ruta",
|
||||
"tov",
|
||||
"zuno",
|
||||
"vis",
|
||||
"lara",
|
||||
"nia",
|
||||
"liv",
|
||||
"tera",
|
||||
"gantu",
|
||||
"yama",
|
||||
"tune",
|
||||
"ter",
|
||||
"nus",
|
||||
"cury",
|
||||
"bos",
|
||||
"pra",
|
||||
"thea",
|
||||
"nope",
|
||||
"tis",
|
||||
"clite"
|
||||
],
|
||||
6: [
|
||||
"una",
|
||||
"ion",
|
||||
"iea",
|
||||
"iri",
|
||||
"illes",
|
||||
"ides",
|
||||
"agua",
|
||||
"olla",
|
||||
"inda",
|
||||
"eshan",
|
||||
"oria",
|
||||
"ilia",
|
||||
"erth",
|
||||
"arth",
|
||||
"orth",
|
||||
"oth",
|
||||
"illon",
|
||||
"ichi",
|
||||
"ov",
|
||||
"arvis",
|
||||
"ara",
|
||||
"ars",
|
||||
"yke",
|
||||
"yria",
|
||||
"onoe",
|
||||
"ippe",
|
||||
"osie",
|
||||
"one",
|
||||
"ore",
|
||||
"ade",
|
||||
"adus",
|
||||
"urn",
|
||||
"ypso",
|
||||
"ora",
|
||||
"iuq",
|
||||
"orix",
|
||||
"apus",
|
||||
"ion",
|
||||
"eon",
|
||||
"eron",
|
||||
"ao",
|
||||
"omia"
|
||||
]
|
||||
};
|
||||
const templates = [
|
||||
[1, 2, 5],
|
||||
[2, 3, 6],
|
||||
[3, 4, 5],
|
||||
[4, 3, 6],
|
||||
[3, 4, 2, 5],
|
||||
[2, 1, 3, 6],
|
||||
[3, 4, 2, 5],
|
||||
[4, 3, 1, 6],
|
||||
[3, 4, 1, 4, 5],
|
||||
[4, 1, 4, 3, 6]
|
||||
] as const;
|
||||
export function getName(random: () => number) {
|
||||
const template = templates[Math.floor(random() * templates.length)];
|
||||
let name = "";
|
||||
for (let i = 0; i < template.length; i++) {
|
||||
const morphemeSet = morphemes[template[i]];
|
||||
name += morphemeSet[Math.floor(random() * morphemeSet.length)];
|
||||
}
|
||||
return camelToTitle(name);
|
||||
}
|
||||
|
||||
export function getColor(base: [number, number, number], random: () => number) {
|
||||
const [h, s, v] = rgb2hsv(...base);
|
||||
const [r, g, b] = hsv2rgb(Math.floor(random() * 360), s, v);
|
||||
return `rgb(${r * 255}, ${g * 255}, ${b * 255})`;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/54070620/4376101
|
||||
// input: r,g,b in [0,1], out: h in [0,360) and s,v in [0,1]
|
||||
function rgb2hsv(r: number, g: number, b: number) {
|
||||
const v = Math.max(r, g, b),
|
||||
c = v - Math.min(r, g, b);
|
||||
const h = c && (v == r ? (g - b) / c : v == g ? 2 + (b - r) / c : 4 + (r - g) / c);
|
||||
return [60 * (h < 0 ? h + 6 : h), v && c / v, v];
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/54024653/4376101
|
||||
// input: h in [0,360] and s,v in [0,1] - output: r,g,b in [0,1]
|
||||
function hsv2rgb(h: number, s: number, v: number) {
|
||||
const f = (n: number, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
|
||||
return [f(5), f(3), f(1)];
|
||||
}
|
Loading…
Reference in a new issue