Implement reading content packs

This commit is contained in:
thepaperpilot 2022-10-14 07:44:30 -05:00
parent a356c3f676
commit 49e10cf858
9 changed files with 1849 additions and 155 deletions

View file

@ -39,6 +39,7 @@ import { ref, toRefs, watch } from "vue";
import Text from "components/fields/Text.vue"; import Text from "components/fields/Text.vue";
import { Direction } from "util/common"; import { Direction } from "util/common";
import Tooltip from "features/tooltips/Tooltip.vue"; import Tooltip from "features/tooltips/Tooltip.vue";
import { ClientRoomData } from "alkatest-common/types";
const _props = defineProps<{ const _props = defineProps<{
isPrivate: boolean; isPrivate: boolean;

View file

@ -60,6 +60,7 @@
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import type { ClientRoomData } from "alkatest-common/types";
import Text from "components/fields/Text.vue"; import Text from "components/fields/Text.vue";
import Toggle from "components/fields/Toggle.vue"; import Toggle from "components/fields/Toggle.vue";
import Modal from "components/Modal.vue"; import Modal from "components/Modal.vue";

File diff suppressed because it is too large Load diff

View file

View file

@ -0,0 +1,282 @@
import type {
ReferenceBlock,
DictionaryBlock,
EntryBlock,
ArrayBlock,
StringBlock,
NumberBlock,
BooleanBlock,
ActionBlock,
PositionBlock,
SizeBlock,
Inventory,
ItemStackBlock,
NodeAction,
TypeBlock,
MethodTypeBlock,
StateBlock
} from "alkatest-common/types";
export function validateReferenceBlock(block: ReferenceBlock) {
if (typeof block !== "object" || block == null) {
return false;
}
switch (block._type) {
case "method":
return "object" in block && "method" in block;
case "property":
return "object" in block && "property" in block;
case "getObject":
return "id" in block;
case "ternary":
return "condition" in block && "true" in block && "false" in block;
default:
return false;
}
}
export function validateDictionaryBlock<T>(block: DictionaryBlock<T>): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
if (block._type == null) {
return (Object.values(block) as T[]).every(value => value != null);
}
if (typeof block._type === "string") {
switch (block._type) {
case "createDictionary":
return "entries" in block;
default:
return validateReferenceBlock(block as ReferenceBlock);
}
}
return false;
}
export function validateEntryBlock<T>(block: EntryBlock<T>): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
if (block._type !== "entry") {
return false;
}
return "key" in block && "value" in block;
}
export function validateArrayBlock<T>(block: ArrayBlock<T>): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
if ("_type" in block) {
switch (block._type) {
case "filter":
return "array" in block && "condition" in block;
case "map":
return "array" in block && "value" in block;
case "keys":
return "dictionary" in block;
case "values":
return "dictionary" in block;
default:
return validateReferenceBlock(block);
}
}
return block.every(value => value != null);
}
export function validateStringBlock(block: StringBlock): boolean {
if (typeof block === "string") {
return true;
}
if (typeof block !== "object" || block == null) {
return false;
}
switch (block._type) {
case "concat":
return "operands" in block;
default:
return validateReferenceBlock(block);
}
}
export function validateNumberBlock(block: NumberBlock): boolean {
if (typeof block === "number") {
return true;
}
if (typeof block !== "object" || block == null) {
return false;
}
switch (block._type) {
case "addition":
return "operands" in block;
case "subtraction":
return "operands" in block;
case "random":
return "min" in block && "max" in block;
case "randomInt":
return "min" in block && "max" in block;
default:
return validateReferenceBlock(block);
}
}
export function validateBooleanBlock(block: BooleanBlock): boolean {
if (typeof block === "boolean") {
return true;
}
if (typeof block !== "object" || block == null) {
return false;
}
switch (block._type) {
case "equals":
return "operands" in block;
case "notEquals":
return "operands" in block;
case "greaterThan":
return "operands" in block;
case "greaterThanOrEqual":
return "operands" in block;
case "lessThan":
return "operands" in block;
case "lessThanOrEqual":
return "operands" in block;
case "objectExists":
return "operands" in block;
case "propertyExists":
return "operands" in block && "property" in block;
default:
return validateReferenceBlock(block);
}
}
export function validateActionBlock(block: ActionBlock): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
switch (block._type) {
case "branch":
return "condition" in block;
case "forEach":
return "array" in block && "forEach" in block;
case "repeat":
return "iterations" in block && "run" in block;
case "wait":
return "duration" in block;
case "addItemsToInventory":
return "node" in block && "items" in block;
case "setData":
return "object" in block && "key" in block && "value" in block;
case "addNode":
return "nodeType" in block && "pos" in block;
case "removeNode":
return "node" in block;
case "event":
return "event" in block;
case "error":
return "message" in block;
case "@return":
return true;
case "@break":
return true;
default:
return validateReferenceBlock(block);
}
}
export function validatePositionBlock(block: PositionBlock): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
if ("type" in block) {
return validateReferenceBlock(block);
}
return "x" in block && "y" in block;
}
export function validateSizeBlock(block: SizeBlock): boolean {
if (typeof block === "number") {
return true;
}
if (typeof block !== "object" || block == null) {
return false;
}
if ("type" in block) {
return validateNumberBlock(block) || validateReferenceBlock(block as ReferenceBlock);
}
return "width" in block && "height" in block;
}
export function validateInventoryBlock(block: Inventory): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
return "slots" in block;
}
export function validateItemStackBlock(block: ItemStackBlock): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
if ("item" in block && "quantity" in block) {
return true;
}
return validateReferenceBlock(block);
}
export function validateNodeActionBlock(block: NodeAction): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
return "display" in block && "duration" in block && "run" in block;
}
export function validateTypeBlock(block: TypeBlock): boolean {
if (typeof block === "string") {
return true;
}
if (typeof block !== "object" || block == null) {
return false;
}
switch (block._type) {
case "dictionary":
return "keyType" in block && "valueType" in block;
case "array":
return "elementType" in block;
case "object":
return "properties" in block;
case "number":
return true;
case "boolean":
return true;
case "string":
return true;
case "id":
return "of" in block;
case "itemStack":
return true;
case "action":
return true;
default:
return false;
}
}
export function validateMethodTypeBlock(block: MethodTypeBlock): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
return "run" in block;
}
export function validatePropertyBlock(block: TypeBlock & { value: StateBlock }): boolean {
if (typeof block !== "object" || block == null) {
return false;
}
if (!("value" in block)) {
return false;
}
if (!validateTypeBlock(block)) {
return false;
}
return true;
}

View file

@ -1,13 +1,17 @@
{ {
"startingNodes": [ "display": "Core",
{ "eventListeners": {
"type": "core-tree", "newGame": [
"pos": { {
"x": 0, "_type": "addNode",
"y": 0 "nodeType": "core-tree",
"pos": {
"x": 0,
"y": 0
}
} }
} ]
], },
"nodes": { "nodes": {
"core-tree": { "core-tree": {
"display": "🌳", "display": "🌳",
@ -15,9 +19,12 @@
"draggable": false, "draggable": false,
"data": { "data": {
"drops": { "drops": {
"type": "randomInt", "_type": "number",
"min": 4, "default": {
"max": 8 "_type": "randomInt",
"min": 4,
"max": 8
}
} }
}, },
"inventory": { "inventory": {
@ -31,10 +38,10 @@
"duration": 1000, "duration": 1000,
"run": [ "run": [
{ {
"type": "addItemsToInventory", "_type": "addItemsToInventory",
"node": "@instance", "node": "@instance",
"items": { "items": {
"type": "method", "_type": "method",
"object": "#core-treeLootTable", "object": "#core-treeLootTable",
"method": "roll", "method": "roll",
"params": { "params": {
@ -44,14 +51,14 @@
"overflow": "destroy" "overflow": "destroy"
}, },
{ {
"type": "setData", "_type": "setData",
"object": "@instance", "object": "@instance",
"key": "drops", "key": "drops",
"value": { "value": {
"type": "subtraction", "_type": "subtraction",
"operands": [ "operands": [
{ {
"type": "property", "_type": "property",
"object": "@instance", "object": "@instance",
"property": "drops" "property": "drops"
}, },
@ -60,12 +67,12 @@
} }
}, },
{ {
"type": "branch", "_type": "branch",
"condition": { "condition": {
"type": "equals", "_type": "equals",
"operands": [ "operands": [
{ {
"type": "property", "_type": "property",
"object": "@instance", "object": "@instance",
"property": "drops" "property": "drops"
}, },
@ -74,7 +81,7 @@
}, },
"true": [ "true": [
{ {
"type": "removeNode", "_type": "removeNode",
"node": "@instance" "node": "@instance"
} }
] ]
@ -91,11 +98,11 @@
}, },
"inventory": { "inventory": {
"slots": { "slots": {
"type": "property", "_type": "property",
"object": { "object": {
"type": "getObject", "_type": "getObject",
"id": { "id": {
"type": "property", "_type": "property",
"object": "@instance", "object": "@instance",
"property": "tier" "property": "tier"
} }
@ -106,9 +113,9 @@
"canPlayerInsert": true "canPlayerInsert": true
}, },
"actions": { "actions": {
"type": "property", "_type": "property",
"object": { "object": {
"type": "property", "_type": "property",
"object": "@instance", "object": "@instance",
"property": "tier" "property": "tier"
}, },
@ -121,29 +128,29 @@
"draggable": false, "draggable": false,
"place": [ "place": [
{ {
"type": "wait", "_type": "wait",
"node": "@instance", "node": "@instance",
"duration": { "duration": {
"type": "randomInt", "_type": "randomInt",
"min": 10000, "min": 10000,
"max": 100000 "max": 100000
} }
}, },
{ {
"type": "removeNode", "_type": "removeNode",
"node": "@instance" "node": "@instance"
}, },
{ {
"type": "addNode", "_type": "addNode",
"nodeType": "core-tree", "nodeType": "core-tree",
"pos": { "pos": {
"x": { "x": {
"type": "property", "_type": "property",
"object": "@instance", "object": "@instance",
"property": "x" "property": "x"
}, },
"y": { "y": {
"type": "property", "_type": "property",
"object": "@instance", "object": "@instance",
"property": "y" "property": "y"
} }
@ -165,133 +172,144 @@
"core-weightedLootTable": { "core-weightedLootTable": {
"data": { "data": {
"items": { "items": {
"type": "dictionary", "_type": "dictionary",
"keyType": { "keyType": {
"type": "string" "_type": "string"
}, },
"valueType": { "valueType": {
"type": "object", "_type": "object",
"properties": { "properties": {
"weight": { "weight": {
"type": "number", "_type": "number",
"default": 1 "default": 1
}, },
"item": { "item": {
"type": "id", "_type": "id",
"of": "item" "of": "item"
}, },
"quantity": { "quantity": {
"type": "number", "_type": "number",
"default": 1 "default": 1
} }
} }
} }
}, },
"currentRollValue": { "currentRollValue": {
"type": "number", "_type": "number",
"internal": true "internal": true,
"default": 0
} }
}, },
"methods": { "methods": {
"roll": { "roll": {
"params": { "params": {
"rolls": { "rolls": {
"type": "number", "_type": "number",
"default": 1 "default": 1
} }
}, },
"returns": { "returns": {
"type": "item" "_type": "itemStack"
}, },
"run": [ "run": [
{ {
"type": "setData", "_type": "repeat",
"object": "@instance", "iterations": {
"key": "currentRollValue", "_type": "property",
"value": { "object": "@params",
"type": "random", "property": "rolls"
"min": 0,
"max": {
"type": "addition",
"operands": {
"type": "map",
"array": {
"type": "values",
"dictionary": {
"type": "property",
"object": "@instance",
"property": "items"
}
},
"output": {
"type": "property",
"object": "@element",
"property": "weight"
}
}
}
}
},
{
"type": "forEach",
"array": {
"type": "values",
"dictionary": {
"type": "property",
"object": "@instance",
"property": "items"
}
}, },
"forEach": [ "run": [
{ {
"type": "setData", "_type": "setData",
"object": "@instance", "object": "@instance",
"key": "currentRollValue", "key": "currentRollValue",
"value": { "value": {
"type": "subtraction", "_type": "random",
"operands": [ "min": 0,
{ "max": {
"type": "property", "_type": "addition",
"object": "@instance", "operands": {
"property": "currentRollValue" "_type": "map",
}, "array": {
{ "_type": "values",
"type": "property", "dictionary": {
"object": "@element", "_type": "property",
"property": "weight" "object": "@instance",
"property": "items"
}
},
"value": {
"_type": "property",
"object": "@element",
"property": "weight"
}
} }
] }
} }
}, },
{ {
"type": "branch", "_type": "forEach",
"condition": { "array": {
"type": "lessThanOrEqual", "_type": "values",
"operands": [ "dictionary": {
{ "_type": "property",
"type": "property", "object": "@instance",
"object": "@instance", "property": "items"
"property": "currentRollValue" }
},
0
]
}, },
"true": [ "forEach": [
{ {
"type": "@return", "_type": "setData",
"value": "@element" "object": "@instance",
"key": "currentRollValue",
"value": {
"_type": "subtraction",
"operands": [
{
"_type": "property",
"object": "@instance",
"property": "currentRollValue"
},
{
"_type": "property",
"object": "@element",
"property": "weight"
}
]
}
},
{
"_type": "branch",
"condition": {
"_type": "lessThanOrEqual",
"operands": [
{
"_type": "property",
"object": "@instance",
"property": "currentRollValue"
},
0
]
},
"true": [
{
"_type": "@return",
"value": "@element"
}
]
} }
] ]
},
{
"_type": "error",
"message": "Failed to roll loot table"
},
{
"_type": "@return",
"value": "null"
} }
] ]
},
{
"type": "error",
"message": "Failed to roll loot table"
},
{
"type": "@return",
"value": "null"
} }
] ]
} }
@ -300,25 +318,25 @@
"core-chestTier": { "core-chestTier": {
"data": { "data": {
"upgradesFrom": { "upgradesFrom": {
"type": "dictionary", "_type": "dictionary",
"keyType": { "keyType": {
"type": "id", "_type": "id",
"of": "core-chestTier" "of": "core-chestTier"
}, },
"valueType": { "valueType": {
"type": "object", "_type": "object",
"properties": { "properties": {
"cost": { "cost": {
"type": "array", "_type": "array",
"elementType": { "elementType": {
"type": "object", "_type": "object",
"properties": { "properties": {
"item": { "item": {
"type": "id", "_type": "id",
"of": "item" "of": "item"
}, },
"quantity": { "quantity": {
"type": "number" "_type": "number"
} }
} }
} }
@ -327,81 +345,81 @@
} }
}, },
"slots": { "slots": {
"type": "number" "_type": "number"
} }
}, },
"properties": { "properties": {
"actions": { "actions": {
"type": "dictionary", "_type": "dictionary",
"keyType": { "keyType": {
"type": "id", "_type": "id",
"of": "core-chestTier" "of": "core-chestTier"
}, },
"valueType": { "valueType": {
"type": "action" "_type": "action"
}, },
"value": { "value": {
"type": "createDictionary", "_type": "createDictionary",
"entries": { "entries": {
"type": "map", "_type": "map",
"array": { "array": {
"type": "filter", "_type": "filter",
"array": { "array": {
"type": "values", "_type": "values",
"dictionary": { "dictionary": {
"type": "getAllOfType", "_type": "getAllOfType",
"of": "core-chestTier" "of": "core-chestTier"
} }
}, },
"condition": { "condition": {
"type": "contains", "_type": "contains",
"array": { "array": {
"type": "keys", "_type": "keys",
"dictionary": { "dictionary": {
"type": "property", "_type": "property",
"object": "@element", "object": "@element",
"property": "upgradesFrom" "property": "upgradesFrom"
} }
}, },
"value": { "value": {
"type": "property", "_type": "property",
"object": "@instance", "object": "@instance",
"property": "id" "property": "id"
} }
} }
}, },
"output": { "value": {
"type": "entry", "_type": "entry",
"key": { "key": {
"type": "property", "_type": "property",
"object": "@element", "object": "@element",
"property": "id" "property": "id"
}, },
"value": { "value": {
"type": "action", "_type": "action",
"icon": "⇪", "display": "⇪",
"tooltip": { "tooltip": {
"type": "concat", "_type": "concat",
"operands": [ "operands": [
"Upgrade to ", "Upgrade to ",
{ {
"type": "property", "_type": "property",
"object": "@element", "object": "@element",
"property": "display" "property": "display"
} }
] ]
}, },
"cost": { "cost": {
"type": "property", "_type": "property",
"object": { "object": {
"type": "property", "_type": "property",
"object": { "object": {
"type": "property", "_type": "property",
"object": "@element", "object": "@element",
"property": "upgradesFrom" "property": "upgradesFrom"
}, },
"property": { "property": {
"type": "property", "_type": "property",
"object": "@instance", "object": "@instance",
"property": "id" "property": "id"
} }
@ -410,11 +428,11 @@
}, },
"run": [ "run": [
{ {
"type": "setData", "_type": "setData",
"object": "@instance", "object": "@instance",
"key": "tier", "key": "tier",
"value": { "value": {
"type": "property", "_type": "property",
"object": "@element", "object": "@element",
"property": "id" "property": "id"
} }

View file

@ -1,27 +1,59 @@
import type {
ActionBlock,
ArrayBlock,
ContentPack,
NodeType,
ItemType,
TypeType
} from "alkatest-common/types";
import { createBoard, Shape } from "features/boards/board"; import { createBoard, Shape } from "features/boards/board";
import { jsx } from "features/feature"; import { jsx } from "features/feature";
import type { BaseLayer, GenericLayer } from "game/layers"; import type { BaseLayer, GenericLayer } from "game/layers";
import { createLayer } from "game/layers"; import { createLayer } from "game/layers";
import { persistent } from "game/persistence"; import { persistent, State } from "game/persistence";
import type { PlayerData } from "game/player"; import type { PlayerData } from "game/player";
import { render } from "util/vue"; import { render } from "util/vue";
import { computed, ref } from "vue"; import { computed, ref, watch } from "vue";
import Chat from "./Chat.vue"; import Chat from "./Chat.vue";
import { processContentPacks } from "./contentPackLoader";
import core from "./contentPacks/core.json";
import { emit } from "./socket"; import { emit } from "./socket";
const knownContentPacks: Record<string, ContentPack> = {
core
};
/** /**
* @hidden * @hidden
*/ */
export const main = createLayer("main", function (this: BaseLayer) { export const main = createLayer("main", function (this: BaseLayer) {
const contentPacks = persistent<(ContentPack | string)[]>(["core"]); const contentPacks = persistent<(ContentPack | string)[]>(["core"]);
const itemTypes = ref<Record<string, ItemType>>({});
const nodeTypes = ref<Record<string, NodeType>>({});
const customTypes = ref<Record<string, TypeType>>({});
const customObjects = ref<Record<string, Record<string, Record<string, State>>>>({});
const events = ref<Record<string, ArrayBlock<ActionBlock>[]>>({});
watch(
contentPacks,
contentPacks => {
const { items, nodes, types, objects, eventListeners } = processContentPacks(
contentPacks.map(pack =>
typeof pack === "string" ? knownContentPacks[pack] : pack
)
);
console.log(items, nodes, types, objects, eventListeners);
itemTypes.value = items;
nodeTypes.value = nodes;
customTypes.value = types;
customObjects.value = objects;
events.value = eventListeners;
},
{ immediate: true }
);
const board = createBoard(() => ({ const board = createBoard(() => ({
startNodes: () => [ startNodes: () => [],
{
type: "placeholder",
position: { x: 0, y: 0 }
}
],
types: { types: {
placeholder: { placeholder: {
shape: Shape.Diamond, shape: Shape.Diamond,
@ -41,7 +73,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
position.value = pos; position.value = pos;
emit("set cursor position", pos); emit("set cursor position", pos);
} }
}, 50) }, 50);
return { return {
name: "Main", name: "Main",

View file

@ -11,6 +11,13 @@ import { useToast } from "vue-toastification";
import { ProxyState } from "util/proxies"; import { ProxyState } from "util/proxies";
import satisfies from "semver/functions/satisfies"; import satisfies from "semver/functions/satisfies";
import projInfo from "data/projInfo.json"; import projInfo from "data/projInfo.json";
import {
ClientRoomData,
ClientToServerEvents,
GameState,
ServerToClientEvents
} from "alkatest-common/types";
import { main } from "./projEntry";
export const connected = ref<boolean>(false); export const connected = ref<boolean>(false);
export const room = ref<string | null>(null); export const room = ref<string | null>(null);
@ -159,6 +166,9 @@ function setupSocket(socket: Socket<ServerToClientEvents, ClientToServerEvents>)
cursorPositions.value[id] = pos; cursorPositions.value[id] = pos;
} }
}); });
socket.on("set content packs", contentPacks => {
main.contentPacks.value = contentPacks;
});
} }
function randomName(): string { function randomName(): string {

View file

@ -1,4 +1,5 @@
import { isArray } from "@vue/shared"; import { isArray } from "@vue/shared";
import type { ContentPack } from "alkatest-common/types";
import { globalBus } from "game/events"; import { globalBus } from "game/events";
import type { GenericLayer } from "game/layers"; import type { GenericLayer } from "game/layers";
import { addingLayers, persistentRefs } from "game/layers"; import { addingLayers, persistentRefs } from "game/layers";
@ -39,6 +40,8 @@ export type State =
| number | number
| boolean | boolean
| DecimalSource | DecimalSource
// TODO make it accept objects that only allow State types within them
| ContentPack
| { [key: string]: State } | { [key: string]: State }
| { [key: number]: State }; | { [key: number]: State };