Implement creating planes

This commit is contained in:
thepaperpilot 2023-04-25 23:50:54 -05:00
parent 5dcba9fe3a
commit 1e3f065427
4 changed files with 493 additions and 9 deletions

41
src/data/planes.tsx Normal file
View 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>;

View file

@ -14,16 +14,16 @@ import { jsx } from "features/feature";
import { createResource } from "features/resources/resource"; import { createResource } from "features/resources/resource";
import { createTabFamily } from "features/tabs/tabFamily"; import { createTabFamily } from "features/tabs/tabFamily";
import Formula, { calculateCost } from "game/formulas/formulas"; import Formula, { calculateCost } from "game/formulas/formulas";
import type { BaseLayer, GenericLayer } from "game/layers"; import { GenericFormula, InvertibleIntegralFormula } from "game/formulas/types";
import { createLayer } from "game/layers"; import { BaseLayer, GenericLayer, addLayer, createLayer, layers } from "game/layers";
import { import {
Modifier, Modifier,
createAdditiveModifier, createAdditiveModifier,
createMultiplicativeModifier, createMultiplicativeModifier,
createSequentialModifier createSequentialModifier
} from "game/modifiers"; } from "game/modifiers";
import { State, persistent } from "game/persistence"; import { State } from "game/persistence";
import type { Player } from "game/player"; import type { LayerData, Player } from "game/player";
import player from "game/player"; import player from "game/player";
import settings from "game/settings"; import settings from "game/settings";
import Decimal, { DecimalSource } from "lib/break_eternity"; 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 { useToast } from "vue-toastification";
import { Section, createCollapsibleModifierSections, createFormulaPreview } from "./common"; import { Section, createCollapsibleModifierSections, createFormulaPreview } from "./common";
import "./main.css"; import "./main.css";
import { GenericFormula, InvertibleIntegralFormula } from "game/formulas/types"; import { GenericPlane, createPlane } from "./planes";
const toast = useToast(); const toast = useToast();
@ -64,6 +64,16 @@ export interface EmpowererState {
powered: boolean; powered: boolean;
} }
export interface PortalGeneratorState {
tier: Resources | undefined;
influences: string[];
}
export interface PortalState {
id: string;
powered: boolean;
}
const mineLootTable = { const mineLootTable = {
dirt: 120, dirt: 120,
sand: 60, sand: 60,
@ -132,7 +142,8 @@ const tools = {
iron: { iron: {
cost: 1e10, cost: 1e10,
name: "Portal Generator", name: "Portal Generator",
type: "portalGenerator" type: "portalGenerator",
state: { tier: null, influences: [] }
}, },
silver: { silver: {
cost: 1e12, cost: 1e12,
@ -227,7 +238,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
}, {} as Record<Resources, BoardNode>), }, {} as Record<Resources, BoardNode>),
sand: board.types.dowsing.nodes.value[0], sand: board.types.dowsing.nodes.value[0],
wood: board.types.quarry.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(() => const resourceLevels = computed(() =>
@ -854,6 +866,156 @@ export const main = createLayer("main", function (this: BaseLayer) {
running: isPowered(node) running: isPowered(node)
}), }),
draggable: true 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: { 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; return links;
} }
})); }));
@ -944,6 +1123,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
const dowsing: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.sand); const dowsing: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.sand);
const quarry: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.wood); const quarry: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.wood);
const empowerer: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.coal); const empowerer: ComputedRef<BoardNode | undefined> = computed(() => toolNodes.value.coal);
const portalGenerator: ComputedRef<BoardNode | undefined> = computed(
() => toolNodes.value.iron
);
function grantResource(type: Resources, amount: DecimalSource) { function grantResource(type: Resources, amount: DecimalSource) {
let node = resourceNodes.value[type]; let node = resourceNodes.value[type];
@ -1304,6 +1486,17 @@ export const main = createLayer("main", function (this: BaseLayer) {
energyProductionChange 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 { return {
name: "World", name: "World",
board, board,
@ -1368,7 +1561,22 @@ export const main = createLayer("main", function (this: BaseLayer) {
export const getInitialLayers = ( export const getInitialLayers = (
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
player: Partial<Player> 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. * A computed ref whose value is true whenever the game is over.

View file

@ -11,7 +11,7 @@
"versionNumber": "0.0", "versionNumber": "0.0",
"versionTitle": "Initial Commit", "versionTitle": "Initial Commit",
"allowGoBack": true, "allowGoBack": false,
"defaultShowSmall": false, "defaultShowSmall": false,
"defaultDecimalsShown": 2, "defaultDecimalsShown": 2,
"useHeader": true, "useHeader": true,

235
src/data/utils.tsx Normal file
View 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)];
}