├── packs ├── BP │ ├── texts │ │ ├── languages.json │ │ └── en_US.lang │ ├── pack_icon.png │ ├── scripts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── utils │ │ │ ├── item.ts │ │ │ ├── async_generator.ts │ │ │ ├── log.ts │ │ │ ├── destroyable.ts │ │ │ ├── dynamic_property.ts │ │ │ └── direction.ts │ │ ├── network_links │ │ │ ├── network_link_entity.ts │ │ │ ├── network_link_component.ts │ │ │ └── network_link_internal.ts │ │ ├── conduit.ts │ │ ├── ipc_wrapper.ts │ │ ├── custom_components.ts │ │ ├── debug_commands.ts │ │ ├── data_ipc.ts │ │ ├── item_machine_registry.ts │ │ ├── storage_type_registry.ts │ │ ├── item_machine_ipc.ts │ │ ├── network_ipc.ts │ │ ├── ipc_listeners.ts │ │ ├── debug_mode.ts │ │ └── machine.ts │ ├── items │ │ └── ui_items │ │ │ ├── ui_empty_slot.json │ │ │ ├── ui_disabled_storage_bar_segment.json │ │ │ └── ui_error.json │ └── entities │ │ └── network_link.json ├── RP │ ├── texts │ │ ├── languages.json │ │ └── en_US.lang │ ├── ui │ │ ├── _ui_defs.json │ │ └── fluffyalien │ │ │ └── energisticscore │ │ │ └── common.json │ ├── pack_icon.png │ └── textures │ │ ├── fluffyalien │ │ └── energisticscore │ │ │ └── ui │ │ │ ├── empty_slot.png │ │ │ └── disabled_storage_bar_segment.png │ │ └── item_texture.json └── data │ ├── ui_composite │ ├── storage_bar_segments │ │ ├── blue_on.png │ │ ├── pink_on.png │ │ ├── red_off.png │ │ ├── red_on.png │ │ ├── black_off.png │ │ ├── black_on.png │ │ ├── blue_off.png │ │ ├── green_off.png │ │ ├── green_on.png │ │ ├── orange_on.png │ │ ├── pink_off.png │ │ ├── purple_on.png │ │ ├── white_off.png │ │ ├── white_on.png │ │ ├── yellow_on.png │ │ ├── orange_off.png │ │ ├── purple_off.png │ │ └── yellow_off.png │ └── progress_indicators │ │ ├── arrow_full.png │ │ ├── flame_full.png │ │ ├── arrow_empty.png │ │ └── flame_empty.png │ └── simple_manifest.json ├── .prettierrc ├── public_api ├── src │ ├── constants.ts │ ├── common_registry_types.ts │ ├── misc.ts │ ├── misc_internal.ts │ ├── registration_allowed.ts │ ├── item_machine_internal.ts │ ├── ipc_listener_type.ts │ ├── item_machine_registry_internal.ts │ ├── ipc_wrapper.ts │ ├── index.ts │ ├── network_links │ │ └── ipc_events.ts │ ├── log.ts │ ├── init.ts │ ├── network_internal.ts │ ├── machine_data_internal.ts │ ├── storage_type_registry_types.ts │ ├── machine_utils.ts │ ├── network_utils.ts │ ├── bec_ipc_listener.ts │ ├── item_machine_registry_types.ts │ ├── machine_ui_elements.ts │ ├── machine_registry_internal.ts │ ├── serialize_utils.ts │ ├── standard_storage_types.ts │ ├── item_machine.ts │ ├── storage_type_registry.ts │ ├── machine_item_stack.ts │ ├── machine_data.ts │ └── item_machine_registry.ts ├── tsconfig.json ├── README.md ├── LICENSE ├── package.json └── package-lock.json ├── .gitignore ├── scripts ├── filters │ ├── common.ts │ ├── simple_manifest.ts │ └── gen_ui_bars.ts ├── tsconfig.json ├── package.json └── package-lock.json ├── .prettierignore ├── keyart ├── logo.png ├── logo_nobg.png └── thumbnail.png ├── docs ├── assets │ └── vatonage.png ├── api │ ├── .nojekyll │ ├── assets │ │ ├── hierarchy.js │ │ ├── navigation.js │ │ └── highlight.css │ ├── variables │ │ └── API.VERSION.html │ ├── types │ │ ├── API.MachineEventName.html │ │ ├── API.MachineHandlerName.html │ │ └── API.ItemMachineEventName.html │ └── documents │ │ └── Guides.Network_Links.html ├── guides │ ├── network-links.md │ ├── storage-type-containers.md │ ├── conduits.md │ ├── persistent-entities.md │ ├── debug.md │ ├── pistons.md │ ├── index.md │ ├── versioning.md │ ├── item-machines.md │ ├── machine-priority.md │ ├── storage-types.md │ ├── machine-ui.md │ └── machine-io.md ├── featured_definitions.js ├── featured.js └── style.css ├── typedoc.json ├── .vscode ├── settings.json └── launch.json ├── tsconfig.json ├── .github ├── workflows │ ├── check.yaml │ └── deploy_docs.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── README.md ├── LICENSE ├── package.json ├── eslint.config.js ├── config.json ├── CONTRIBUTING.md └── CODING_GUIDELINES.md /packs/BP/texts/languages.json: -------------------------------------------------------------------------------- 1 | ["en_US"] 2 | -------------------------------------------------------------------------------- /packs/RP/texts/languages.json: -------------------------------------------------------------------------------- 1 | ["en_US"] 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "crlf" 3 | } 4 | -------------------------------------------------------------------------------- /public_api/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const VERSION = "0.8.0"; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.regolith 2 | /build 3 | /public_api/dist 4 | node_modules -------------------------------------------------------------------------------- /scripts/filters/common.ts: -------------------------------------------------------------------------------- 1 | export const TMP_DIR = ".regolith/tmp"; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.regolith 2 | /build 3 | /public_api/dist 4 | /docs/api 5 | node_modules -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["."] 4 | } 5 | -------------------------------------------------------------------------------- /packs/RP/ui/_ui_defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_defs": ["ui/fluffyalien/energisticscore/common.json"] 3 | } 4 | -------------------------------------------------------------------------------- /keyart/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/keyart/logo.png -------------------------------------------------------------------------------- /packs/BP/texts/en_US.lang: -------------------------------------------------------------------------------- 1 | pack.name=Bedrock Energistics Core 2 | pack.description=APIs for creating tech add-ons. -------------------------------------------------------------------------------- /keyart/logo_nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/keyart/logo_nobg.png -------------------------------------------------------------------------------- /keyart/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/keyart/thumbnail.png -------------------------------------------------------------------------------- /packs/BP/pack_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/BP/pack_icon.png -------------------------------------------------------------------------------- /packs/RP/pack_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/RP/pack_icon.png -------------------------------------------------------------------------------- /docs/assets/vatonage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/docs/assets/vatonage.png -------------------------------------------------------------------------------- /docs/api/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/guides/network-links.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Network Links 3 | --- 4 | 5 | # Network Links 6 | 7 | TBD. See the reference documentation for information on network links. 8 | -------------------------------------------------------------------------------- /packs/RP/texts/en_US.lang: -------------------------------------------------------------------------------- 1 | pack.name=Bedrock Energistics Core 2 | pack.description=APIs for creating tech add-ons. 3 | 4 | item.fluffyalien_energisticscore:ui_empty_slot=Empty -------------------------------------------------------------------------------- /docs/guides/storage-type-containers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Storage Type Containers 3 | --- 4 | 5 | # Storage Type Containers 6 | 7 | TBD. This feature is still being actively developed. 8 | -------------------------------------------------------------------------------- /packs/BP/scripts/constants.ts: -------------------------------------------------------------------------------- 1 | import { version as VERSION } from "@/packs/data/simple_manifest.json"; 2 | export { VERSION }; 3 | 4 | export const VERSION_STR = VERSION.join("."); 5 | -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/blue_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/blue_on.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/pink_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/pink_on.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/red_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/red_off.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/red_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/red_on.png -------------------------------------------------------------------------------- /packs/data/ui_composite/progress_indicators/arrow_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/progress_indicators/arrow_full.png -------------------------------------------------------------------------------- /packs/data/ui_composite/progress_indicators/flame_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/progress_indicators/flame_full.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/black_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/black_off.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/black_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/black_on.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/blue_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/blue_off.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/green_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/green_off.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/green_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/green_on.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/orange_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/orange_on.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/pink_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/pink_off.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/purple_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/purple_on.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/white_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/white_off.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/white_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/white_on.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/yellow_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/yellow_on.png -------------------------------------------------------------------------------- /public_api/src/common_registry_types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @beta 3 | */ 4 | export type BaseIpcCallback = ( 5 | this: null, 6 | arg: TArg, 7 | ) => TReturn | Promise; 8 | -------------------------------------------------------------------------------- /packs/data/ui_composite/progress_indicators/arrow_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/progress_indicators/arrow_empty.png -------------------------------------------------------------------------------- /packs/data/ui_composite/progress_indicators/flame_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/progress_indicators/flame_empty.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/orange_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/orange_off.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/purple_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/purple_off.png -------------------------------------------------------------------------------- /packs/data/ui_composite/storage_bar_segments/yellow_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/data/ui_composite/storage_bar_segments/yellow_off.png -------------------------------------------------------------------------------- /packs/RP/textures/fluffyalien/energisticscore/ui/empty_slot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/RP/textures/fluffyalien/energisticscore/ui/empty_slot.png -------------------------------------------------------------------------------- /scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "devDependencies": { 5 | "@types/node": "^20.12.12" 6 | }, 7 | "dependencies": { 8 | "imagescript": "^1.3.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packs/RP/textures/fluffyalien/energisticscore/ui/disabled_storage_bar_segment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fluffyalien1422/bedrock-energistics-core/HEAD/packs/RP/textures/fluffyalien/energisticscore/ui/disabled_storage_bar_segment.png -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bedrock Energistics Core", 3 | "entryPoints": ["public_api/src"], 4 | "projectDocuments": ["docs/guides/index.md"], 5 | "out": "docs/api", 6 | "tsconfig": "public_api/tsconfig.json", 7 | "excludeInternal": true 8 | } 9 | -------------------------------------------------------------------------------- /docs/guides/conduits.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Conduits 3 | --- 4 | 5 | # Conduits 6 | 7 | To create a conduit, use the `fluffyalien_energisticscore:conduit` block custom component and tag. Use I/O tags to define the type of conduit it will be. See [Machine I/O](machine-io.md) for more info on I/O tags. 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/Thumbs.db": true, 9 | "**/.regolith": true, 10 | "**/node_modules": true, 11 | "**/package-lock.json": true, 12 | "public_api/dist": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /public_api/src/misc.ts: -------------------------------------------------------------------------------- 1 | import { ItemTypes } from "@minecraft/server"; 2 | 3 | /** 4 | * Tests whether Bedrock Energistics Core is in the world or not. 5 | * @beta 6 | */ 7 | export function isBedrockEnergisticsCoreInWorld(): boolean { 8 | return !!ItemTypes.get( 9 | "fluffyalien_energisticscore:ui_disabled_storage_bar_segment", 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /public_api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src"], 3 | "compilerOptions": { 4 | "forceConsistentCasingInFileNames": true, 5 | "strict": true, 6 | "module": "nodenext", 7 | "moduleResolution": "nodenext", 8 | "target": "es2022", 9 | "declaration": true, 10 | "outDir": "./dist", 11 | "skipLibCheck": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./packs/BP/scripts"], 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./*"] 6 | }, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "target": "es2022", 10 | "module": "es2022", 11 | "moduleResolution": "bundler", 12 | "noEmit": true, 13 | "skipLibCheck": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packs/BP/scripts/index.ts: -------------------------------------------------------------------------------- 1 | // tell the api that it is embedded in BEC 2 | import { __INIT_BEC__ } from "@/public_api/src/init"; 3 | import { VERSION_STR } from "./constants"; 4 | __INIT_BEC__(VERSION_STR); 5 | // 6 | 7 | // imports 8 | import "./network_links/network_link_entity"; 9 | import "./block_destroyed"; 10 | import "./custom_components"; 11 | import "./debug_commands"; 12 | import "./ipc_listeners"; 13 | import "./ui"; 14 | -------------------------------------------------------------------------------- /public_api/src/misc_internal.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "@minecraft/server"; 2 | import { 3 | VECTOR3_EAST, 4 | VECTOR3_WEST, 5 | VECTOR3_UP, 6 | VECTOR3_DOWN, 7 | } from "@minecraft/math"; 8 | 9 | /** 10 | * @internal 11 | */ 12 | export const DIRECTION_VECTORS: Vector3[] = [ 13 | { x: 0, y: 0, z: -1 }, 14 | VECTOR3_EAST, 15 | { x: 0, y: 0, z: 1 }, 16 | VECTOR3_WEST, 17 | VECTOR3_UP, 18 | VECTOR3_DOWN, 19 | ]; 20 | -------------------------------------------------------------------------------- /public_api/README.md: -------------------------------------------------------------------------------- 1 | # Bedrock Energistics Core API 2 | 3 | Public API for Bedrock Energistics Core. 4 | 5 | ## Links 6 | 7 | - [Homepage](https://fluffyalien1422.github.io/bedrock-energistics-core) 8 | - [GitHub](https://github.com/Fluffyalien1422/bedrock-energistics-core) 9 | - [Guides](https://fluffyalien1422.github.io/bedrock-energistics-core/api/documents/Guides.html) 10 | - [API Reference](https://fluffyalien1422.github.io/bedrock-energistics-core/api/modules/API.html) 11 | -------------------------------------------------------------------------------- /packs/RP/textures/item_texture.json: -------------------------------------------------------------------------------- 1 | { 2 | "texture_data": { 3 | "fluffyalien_energisticscore:ui_disabled_storage_bar_segment": { 4 | "textures": "textures/fluffyalien/energisticscore/ui/disabled_storage_bar_segment" 5 | }, 6 | "fluffyalien_energisticscore:ui_empty_slot": { 7 | "textures": "textures/fluffyalien/energisticscore/ui/empty_slot" 8 | }, 9 | "fluffyalien_energisticscore:ui_error": { 10 | "textures": "textures/blocks/barrier" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/api/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyl1E1vgkAQBuD/MufVyoZF4GY/knpoNVpPjYctjLpxXczuaNMY/nuDtZYIbcCeSCDvzJN3gQPYLCMH8avXE4yLPvMFnzOwuNCYkMqMg/gAXk8UFyM3CDEMCTdPMlkpg3dS6zeZrAd2CQzWyqQQcxEw2FkNMShDaBcyQXczGA+79cHuijYaGCRaOgcxkEs7xaTOOV08XCmdWjSF1IvmOQPPi+pNIzOlzMolTpFauy7CDWw5Ay76JcpV1fy/Fh74jAcR4/2Q8ZAzHvGiJh74VdvI3O6IMjO26BymD3s0Dav6e0DDuoKojvRT/HFcG8xFtCGjH1YZE0wU7vFRmlSjbdVJJdqQEfIqY7ZNJeFMXeOoZhtCojLkGek9s+spSXIt3o+6WLP1viivP53oy8cW7yXJBpsvEm0/H1+I+VFR/tNNcKkcocW0NP1s8Xj4bTluOUFqQ79yvm7kef4J6Vnb6Q==" -------------------------------------------------------------------------------- /packs/BP/scripts/utils/item.ts: -------------------------------------------------------------------------------- 1 | import { ItemStack } from "@minecraft/server"; 2 | import { logWarn } from "./log"; 3 | 4 | export function tryCreateItemStack( 5 | id: string, 6 | amount?: number, 7 | warnMsg = "An error occured while trying to create an ItemStack", 8 | ): ItemStack | undefined { 9 | let itemStack: ItemStack | undefined; 10 | try { 11 | itemStack = new ItemStack(id, amount); 12 | } catch (e) { 13 | logWarn(`${warnMsg}: ${String(e)}.`); 14 | } 15 | return itemStack; 16 | } -------------------------------------------------------------------------------- /packs/BP/scripts/utils/async_generator.ts: -------------------------------------------------------------------------------- 1 | export function* asyncAsGenerator( 2 | asyncFn: () => Promise, 3 | ): Generator { 4 | let isWaiting = true; 5 | let res: T | undefined; 6 | 7 | asyncFn() 8 | .then((result) => { 9 | isWaiting = false; 10 | res = result; 11 | }) 12 | .catch((error: unknown) => { 13 | isWaiting = false; 14 | throw error; 15 | }); 16 | 17 | while (isWaiting as boolean) { 18 | yield; 19 | } 20 | 21 | return res as T; 22 | } 23 | -------------------------------------------------------------------------------- /public_api/src/registration_allowed.ts: -------------------------------------------------------------------------------- 1 | import { system } from "@minecraft/server"; 2 | 3 | const REGISTRATION_MAX_TICK = 20; 4 | 5 | let worldInitializedTick: number | undefined; 6 | 7 | system.beforeEvents.startup.subscribe(() => { 8 | worldInitializedTick = system.currentTick; 9 | }); 10 | 11 | /** 12 | * @internal 13 | */ 14 | export function isRegistrationAllowed(): boolean { 15 | if (worldInitializedTick === undefined) return true; 16 | return system.currentTick - worldInitializedTick <= REGISTRATION_MAX_TICK; 17 | } 18 | -------------------------------------------------------------------------------- /packs/BP/scripts/network_links/network_link_entity.ts: -------------------------------------------------------------------------------- 1 | import { world } from "@minecraft/server"; 2 | 3 | world.afterEvents.entitySpawn.subscribe((e) => { 4 | if (e.entity.typeId !== "fluffyalien_energisticscore:network_link") return; 5 | // we need to do this in this event because these entities can be spawned from the public api 6 | // but the dynamic property can only be set from bedrock energistics core as dynamic properties 7 | // are sandboxed 8 | e.entity.setDynamicProperty("block_location", e.entity.location); 9 | }); 10 | -------------------------------------------------------------------------------- /public_api/src/item_machine_internal.ts: -------------------------------------------------------------------------------- 1 | import { SerializableContainerSlotJson } from "./serialize_utils.js"; 2 | 3 | /** 4 | * @internal 5 | */ 6 | export interface ItemMachineFuncPayload { 7 | slot: SerializableContainerSlotJson; 8 | } 9 | 10 | /** 11 | * @internal 12 | */ 13 | export interface GetItemMachineStoragePayload extends ItemMachineFuncPayload { 14 | type: string; 15 | } 16 | 17 | /** 18 | * @internal 19 | */ 20 | export interface SetItemMachineStoragePayload extends ItemMachineFuncPayload { 21 | type: string; 22 | value: number; 23 | } 24 | -------------------------------------------------------------------------------- /packs/BP/scripts/conduit.ts: -------------------------------------------------------------------------------- 1 | import { BlockCustomComponent, world } from "@minecraft/server"; 2 | import { MachineNetwork } from "./network"; 3 | 4 | export const conduitComponent: BlockCustomComponent = { 5 | onPlace(e) { 6 | if (e.block.typeId === e.previousBlock.type.id) return; 7 | 8 | MachineNetwork.updateAdjacent(e.block); 9 | }, 10 | }; 11 | 12 | world.beforeEvents.playerBreakBlock.subscribe((e) => { 13 | if (!e.block.hasTag("fluffyalien_energisticscore:conduit")) { 14 | return; 15 | } 16 | 17 | MachineNetwork.updateWithBlock(e.block); 18 | }); 19 | -------------------------------------------------------------------------------- /public_api/src/ipc_listener_type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @internal 3 | */ 4 | export enum IpcListenerType { 5 | MachineUpdateUiHandler, 6 | MachineRecieveHandler, 7 | MachineOnButtonPressedEvent, 8 | MachineNetworkStatEvent, 9 | MachineOnStorageSetEvent, 10 | ItemMachineGetIoHandler, 11 | ItemMachineOnStorageSetEvent, 12 | } 13 | 14 | /** 15 | * @internal 16 | */ 17 | export function makeIpcListenerName(id: string, type: IpcListenerType): string { 18 | const [namespace, shortId] = id.split(/:(.*)/); 19 | return `${namespace}:BECAPI${type.toString(36)}${shortId}`; 20 | } 21 | -------------------------------------------------------------------------------- /packs/BP/items/ui_items/ui_empty_slot.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "1.20.80", 3 | "minecraft:item": { 4 | "description": { 5 | "identifier": "fluffyalien_energisticscore:ui_empty_slot", 6 | "menu_category": { 7 | "category": "none" 8 | } 9 | }, 10 | "components": { 11 | "minecraft:tags": { 12 | "tags": ["fluffyalien_energisticscore:ui_item"] 13 | }, 14 | "minecraft:icon": { 15 | "textures": { 16 | "default": "fluffyalien_energisticscore:ui_empty_slot" 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 22.x 20 | cache: "npm" 21 | - name: Install Dependencies 22 | run: | 23 | npm ci 24 | cd scripts 25 | npm ci 26 | - name: Check 27 | run: npm run check 28 | -------------------------------------------------------------------------------- /public_api/src/item_machine_registry_internal.ts: -------------------------------------------------------------------------------- 1 | import { ItemMachineGetIoResponse } from "./item_machine_registry_types.js"; 2 | import { SerializableContainerSlotJson } from "./serialize_utils.js"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export interface RegisteredItemMachineData { 8 | id: string; 9 | maxStorage?: number; 10 | defaultIo?: ItemMachineGetIoResponse; 11 | getIoHandler?: string; 12 | onStorageSetEvent?: string; 13 | } 14 | 15 | /** 16 | * @internal 17 | */ 18 | export interface ItemMachineOnStorageSetPayload { 19 | slot: SerializableContainerSlotJson; 20 | type: string; 21 | value: number; 22 | } 23 | -------------------------------------------------------------------------------- /packs/BP/items/ui_items/ui_disabled_storage_bar_segment.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "1.20.80", 3 | "minecraft:item": { 4 | "description": { 5 | "identifier": "fluffyalien_energisticscore:ui_disabled_storage_bar_segment", 6 | "menu_category": { 7 | "category": "none" 8 | } 9 | }, 10 | "components": { 11 | "minecraft:tags": { 12 | "tags": ["fluffyalien_energisticscore:ui_item"] 13 | }, 14 | "minecraft:icon": { 15 | "textures": { 16 | "default": "fluffyalien_energisticscore:ui_disabled_storage_bar_segment" 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Bedrock Energistics Core](keyart/thumbnail.png) 2 | 3 | APIs for creating tech add-ons. 4 | 5 | ## Features 6 | 7 | - Machines using these APIs work with each other even if they're from different add-ons. 8 | - Simple APIs for basic machines, while still having more powerful APIs for more complicated machines. 9 | - Item storage APIs without persistent entities. 10 | - Easily create machine UI. 11 | - Create your own storage types (water, lava, etc) and share them between add-ons, not just energy. 12 | 13 | ## Getting Started 14 | 15 | See [Getting Started](https://fluffyalien1422.github.io/bedrock-energistics-core/api/documents/Guides.Getting_Started.html). 16 | -------------------------------------------------------------------------------- /packs/BP/items/ui_items/ui_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "1.20.80", 3 | "minecraft:item": { 4 | "description": { 5 | "identifier": "fluffyalien_energisticscore:ui_error", 6 | "menu_category": { 7 | "category": "none" 8 | } 9 | }, 10 | "components": { 11 | "minecraft:tags": { 12 | "tags": ["fluffyalien_energisticscore:ui_item"] 13 | }, 14 | "minecraft:icon": { 15 | "textures": { 16 | "default": "fluffyalien_energisticscore:ui_error" 17 | } 18 | }, 19 | "minecraft:display_name": { 20 | "value": "ERROR: See content log" 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/guides/persistent-entities.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Persistent Entities 3 | --- 4 | 5 | # Persistent Entities 6 | 7 | If your machine needs a persistent entity, you can set `description.persistentEntity` to `true` in your machine definition. 8 | 9 | This disables despawning the entity on hit and enables some internal optimizations. 10 | 11 | This will not destroy your machine when the entity is hit, you will need to implement this (or another system) yourself. 12 | 13 | Call [removeMachine](https://fluffyalien1422.github.io/bedrock-energistics-core/api/functions/API.removeMachine.html) to clean up machine data and update networks. It will not remove the block or the entity. 14 | -------------------------------------------------------------------------------- /docs/guides/debug.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Debugging 3 | --- 4 | 5 | # Debugging 6 | 7 | ## Debug Mode 8 | 9 | Use debug mode to view data and set variables in a machine. 10 | 11 | Enable debug mode with the `/scriptevent fluffyalien_energisticscore:debug.enable_debug_mode` command. It can be ended by restarting the server or running `/reload`. 12 | 13 | Hold a stick while debug mode is active to view information about the machine you're looking at. Sneak while looking at a machine and holding a stick to open the edit variables menu. 14 | 15 | ## Print Networks 16 | 17 | Use the `/scriptevent fluffyalien_energisticscore:debug.print_networks` command to print all networks in the world. 18 | -------------------------------------------------------------------------------- /docs/guides/pistons.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pistons 3 | --- 4 | 5 | # Pistons 6 | 7 | Machines are destroyed when pushed by a piston and nonpersistent machine entities are despawned. However, Persistent entities are not despawned, the `fluffyalien_energisticscore:on_destroyed_by_piston` entity event is triggered instead. 8 | 9 | This allows you to run code before despawning the entity. The event can be handled in the entity event without scripting, or you can use the [dataDrivenEntityTrigger](https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/worldafterevents?view=minecraft-bedrock-stable#datadrivenentitytrigger) world after event to listen for the entity event. 10 | -------------------------------------------------------------------------------- /packs/data/simple_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": [0, 9, 0], 3 | "minEngineVersion": [1, 21, 90], 4 | "scriptModules": [ 5 | { 6 | "name": "@minecraft/server", 7 | "version": "2.0.0" 8 | }, 9 | { 10 | "name": "@minecraft/server-ui", 11 | "version": "2.0.0" 12 | } 13 | ], 14 | "uuids": { 15 | "bp": { 16 | "header": "bda38270-b130-4526-be35-a7cef3f18d5d", 17 | "data": "c007369a-8a1a-4237-ace3-fdad8157a64c", 18 | "script": "6d03063b-abc2-4c68-8d66-3e09b4d9cc57" 19 | }, 20 | "rp": { 21 | "header": "5ca8983e-f3ab-4817-b66b-f33face407af", 22 | "resources": "1a2aed89-2582-4afb-89f4-59885467d379" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: enhancement 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **OS:** 27 | 28 | - OS: [e.g. iOS] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /packs/BP/scripts/utils/log.ts: -------------------------------------------------------------------------------- 1 | import { VERSION_STR } from "../constants"; 2 | 3 | export function makeLogString(logLevel: string, message: string): string { 4 | return `[Bedrock Energistics Core v${VERSION_STR}] ${logLevel} ${message}`; 5 | } 6 | 7 | export function logInfo(message: string): void { 8 | console.info(makeLogString("INFO", message)); 9 | } 10 | 11 | export function logWarn(message: string): void { 12 | console.warn(makeLogString("WARN", message)); 13 | } 14 | 15 | /** 16 | * Note: prefer {@link raise} in most cases. 17 | */ 18 | export function makeErrorString(message: string): string { 19 | return makeLogString("ERROR", message); 20 | } 21 | 22 | export function raise(message: string): never { 23 | throw new Error(makeErrorString(message)); 24 | } 25 | -------------------------------------------------------------------------------- /packs/BP/scripts/ipc_wrapper.ts: -------------------------------------------------------------------------------- 1 | import { BecIpcListener } from "@/public_api/src/bec_ipc_listener"; 2 | import * as ipc from "mcbe-addon-ipc"; 3 | 4 | export const ipcRouter = new ipc.Router("fluffyalien_energisticscore_router"); 5 | 6 | export function registerListener( 7 | id: BecIpcListener, 8 | listener: ipc.ScriptEventListener, 9 | ): void { 10 | ipcRouter.registerListener(id, listener); 11 | } 12 | 13 | export function ipcSend(event: string, payload: ipc.SerializableValue): void { 14 | void ipcRouter.sendAuto({ event, payload }); 15 | } 16 | 17 | export function ipcInvoke( 18 | event: string, 19 | payload: ipc.SerializableValue, 20 | throwFailures = true, 21 | ): Promise { 22 | return ipcRouter.invokeAuto({ event, payload, throwFailures }); 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2024-2025 Shawn Bhumbla 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /public_api/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2024-2025 Shawn Bhumbla 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/deploy_docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | pages: write 9 | id-token: write 10 | 11 | concurrency: 12 | group: "pages" 13 | cancel-in-progress: false 14 | 15 | jobs: 16 | deploy: 17 | environment: 18 | name: github-pages 19 | url: ${{ steps.deployment.outputs.page_url }} 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - name: Setup Pages 25 | uses: actions/configure-pages@v5 26 | - name: Upload artifact 27 | uses: actions/upload-pages-artifact@v3 28 | with: 29 | path: "docs" 30 | - name: Deploy to GitHub Pages 31 | id: deployment 32 | uses: actions/deploy-pages@v4 33 | -------------------------------------------------------------------------------- /public_api/src/ipc_wrapper.ts: -------------------------------------------------------------------------------- 1 | import * as ipc from "mcbe-addon-ipc"; 2 | import { getIpcRouter } from "./init.js"; 3 | import { BecIpcListener } from "./bec_ipc_listener.js"; 4 | 5 | /** 6 | * @internal 7 | */ 8 | export function ipcSendAny( 9 | event: string, 10 | payload: ipc.SerializableValue, 11 | ): void { 12 | void getIpcRouter().sendAuto({ event, payload }); 13 | } 14 | 15 | /** 16 | * @internal 17 | */ 18 | export function ipcSend( 19 | event: BecIpcListener, 20 | payload: ipc.SerializableValue, 21 | ): void { 22 | ipcSendAny(event, payload); 23 | } 24 | 25 | /** 26 | * @internal 27 | */ 28 | export function ipcInvoke( 29 | event: BecIpcListener, 30 | payload: ipc.SerializableValue, 31 | throwFailures = true, 32 | ): Promise { 33 | return getIpcRouter().invokeAuto({ event, payload, throwFailures }); 34 | } 35 | -------------------------------------------------------------------------------- /public_api/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module API 3 | */ 4 | 5 | export { init } from "./init.js"; 6 | 7 | export * from "./network_links/network_link_node.js"; 8 | export * from "./common_registry_types.js"; 9 | export * from "./constants.js"; 10 | export * from "./io.js"; 11 | export * from "./item_machine_registry_types.js"; 12 | export * from "./item_machine_registry.js"; 13 | export * from "./item_machine.js"; 14 | export * from "./machine_data.js"; 15 | export * from "./machine_item_stack.js"; 16 | export * from "./machine_registry_types.js"; 17 | export * from "./machine_registry.js"; 18 | export * from "./machine_ui_elements.js"; 19 | export * from "./machine_utils.js"; 20 | export * from "./misc.js"; 21 | export * from "./network_utils.js"; 22 | export * from "./network.js"; 23 | export * from "./standard_storage_types.js"; 24 | export * from "./storage_type_registry_types.js"; 25 | export * from "./storage_type_registry.js"; 26 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.0", 3 | "configurations": [ 4 | { 5 | "type": "minecraft-js", 6 | "request": "attach", 7 | "name": "Minecraft Stable Debug", 8 | "mode": "listen", 9 | "targetModuleUuid": "6d03063b-abc2-4c68-8d66-3e09b4d9cc57", 10 | "localRoot": "%localappdata%/Packages/Microsoft.MinecraftUWP_8wekyb3d8bbwe/LocalState/games/com.mojang/development_behavior_packs/bedrock_energistics_core_bp/scripts", 11 | "port": 19144 12 | }, 13 | { 14 | "type": "minecraft-js", 15 | "request": "attach", 16 | "name": "Minecraft Preview Debug", 17 | "mode": "listen", 18 | "targetModuleUuid": "6d03063b-abc2-4c68-8d66-3e09b4d9cc57", 19 | "localRoot": "%localappdata%/Packages/Microsoft.MinecraftWindowsBeta_8wekyb3d8bbwe/LocalState/games/com.mojang/development_behavior_packs/bedrock_energistics_core_bp/scripts", 20 | "port": 19144 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packs/BP/scripts/custom_components.ts: -------------------------------------------------------------------------------- 1 | import { system } from "@minecraft/server"; 2 | import { machineComponent, machineNoInteractComponent } from "./machine"; 3 | import { conduitComponent } from "./conduit"; 4 | import { networkLinkComponent } from "./network_links/network_link_component"; 5 | 6 | system.beforeEvents.startup.subscribe((e) => { 7 | e.blockComponentRegistry.registerCustomComponent( 8 | "fluffyalien_energisticscore:machine", 9 | machineComponent, 10 | ); 11 | 12 | e.blockComponentRegistry.registerCustomComponent( 13 | "fluffyalien_energisticscore:machine_no_interact", 14 | machineNoInteractComponent, 15 | ); 16 | 17 | e.blockComponentRegistry.registerCustomComponent( 18 | "fluffyalien_energisticscore:conduit", 19 | conduitComponent, 20 | ); 21 | 22 | e.blockComponentRegistry.registerCustomComponent( 23 | "fluffyalien_energisticscore:network_link", 24 | networkLinkComponent, 25 | ); 26 | }); 27 | -------------------------------------------------------------------------------- /docs/guides/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Guides 3 | children: 4 | - ./conduits.md 5 | - ./debug.md 6 | - ./getting-started.md 7 | - ./item-machines.md 8 | - ./machine-io.md 9 | - ./machine-priority.md 10 | - ./machine-ui.md 11 | - ./network-links.md 12 | - ./persistent-entities.md 13 | - ./pistons.md 14 | - ./storage-type-containers.md 15 | - ./storage-types.md 16 | - ./versioning.md 17 | --- 18 | 19 | # Guides 20 | 21 | ## Index 22 | 23 | - [Conduits](conduits.md) 24 | - [Debugging](debug.md) 25 | - [Getting Started](getting-started.md) 26 | - [Item Machines](item-machines.md) 27 | - [Machine I/O](machine-io.md) 28 | - [Machine Allocation Priority](machine-priority.md) 29 | - [Machine UI](machine-ui.md) 30 | - [Network Links](network-links.md) 31 | - [Persistent Entities](persistent-entities.md) 32 | - [Pistons](pistons.md) 33 | - [Storage Type Containers](storage-type-containers.md) 34 | - [Storage Types](storage-types.md) 35 | - [Versioning](versioning.md) 36 | -------------------------------------------------------------------------------- /packs/BP/entities/network_link.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "1.20.80", 3 | "minecraft:entity": { 4 | "description": { 5 | "identifier": "fluffyalien_energisticscore:network_link", 6 | "is_spawnable": false, 7 | "is_summonable": true, 8 | "is_experimental": false 9 | }, 10 | "components": { 11 | "minecraft:breathable": { 12 | "breathes_water": true 13 | }, 14 | "minecraft:physics": { 15 | "has_gravity": false, 16 | "has_collision": false 17 | }, 18 | "minecraft:damage_sensor": { 19 | "triggers": { 20 | "deals_damage": false 21 | } 22 | }, 23 | "minecraft:pushable": { 24 | "is_pushable": false, 25 | "is_pushable_by_piston": false 26 | }, 27 | "minecraft:knockback_resistance": { 28 | "value": 1 29 | }, 30 | "minecraft:collision_box": { 31 | "width": 0, 32 | "height": 0 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packs/BP/scripts/utils/destroyable.ts: -------------------------------------------------------------------------------- 1 | import { raise } from "./log"; 2 | 3 | export interface Destroyable { 4 | /** 5 | * `true` if this object is valid (has not been destroyed), otherwise `false`. 6 | */ 7 | readonly isValid: boolean; 8 | 9 | /** 10 | * Destroy this object. 11 | * @see {@link Destroyable.isValid} 12 | */ 13 | destroy(): void; 14 | } 15 | 16 | export abstract class DestroyableObject implements Destroyable { 17 | private internalIsValid = true; 18 | 19 | get isValid(): boolean { 20 | return this.internalIsValid; 21 | } 22 | 23 | /** 24 | * @throws if this object is not valid (if it has been destroyed) 25 | * @see {@link Destroyable.isValid}, {@link Destroyable.destroy} 26 | */ 27 | protected ensureValidity(): void { 28 | if (!this.internalIsValid) { 29 | raise("DestroyableObject#ensureValidity: The object has been destroyed."); 30 | } 31 | } 32 | 33 | destroy(): void { 34 | this.internalIsValid = false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public_api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": [ 3 | "NOTE: remember to update the VERSION constant in the api as well" 4 | ], 5 | "name": "bedrock-energistics-core-api", 6 | "description": "Bedrock Energistics Core public API", 7 | "version": "0.8.0", 8 | "author": "Fluffyalien", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fluffyalien1422/bedrock-energistics-core.git" 12 | }, 13 | "readme": "https://fluffyalien1422.github.io/bedrock-energistics-core/", 14 | "license": "ISC", 15 | "type": "module", 16 | "main": "dist/index.js", 17 | "types": "dist/index.d.ts", 18 | "files": [ 19 | "dist" 20 | ], 21 | "scripts": { 22 | "build": "tsc" 23 | }, 24 | "devDependencies": { 25 | "@minecraft/server": "2.0.0", 26 | "@minecraft/vanilla-data": ">=1.21.90" 27 | }, 28 | "peerDependencies": { 29 | "@minecraft/server": "2.0.0", 30 | "@minecraft/vanilla-data": ">=1.21.90" 31 | }, 32 | "dependencies": { 33 | "@minecraft/math": "^2.2.7", 34 | "mcbe-addon-ipc": "^0.9.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/guides/versioning.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Versioning 3 | --- 4 | 5 | # Versioning 6 | 7 | Both Bedrock Energistics Core and Bedrock Energistics Core API use a modified version of [Semantic Versioning v2](https://semver.org/). 8 | 9 | Note that Bedrock Energistics Core and Bedrock Energistics Core API are versioned independently of each other. Each Bedrock Energistics Core API release will note which Bedrock Energistics Core versions it supports. 10 | 11 | The only change to Semantic Versioning is that APIs marked as `@beta` may be changed or removed in any minor update. 12 | 13 | APIs **not** marked as `@beta` can only be changed or removed in major updates, as per Semantic Versioning. 14 | 15 | ## Dependencies 16 | 17 | Each Bedrock Energistics Core release will note which Minecraft versions it supports. See [releases](https://github.com/Fluffyalien1422/bedrock-energistics-core/releases). 18 | 19 | Each Bedrock Energistics Core API release will note which Bedrock Energistics Core and `@minecraft/server` versions it supports. See [releases](https://github.com/Fluffyalien1422/bedrock-energistics-core/releases). 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "bedrock-energistics-core", 4 | "type": "module", 5 | "scripts": { 6 | "fmt": "prettier . -w", 7 | "check-tsc": "tsc && tsc -p scripts && tsc -p public_api --noEmit", 8 | "check-eslint": "eslint .", 9 | "check-typedoc": "typedoc --emit none --treatWarningsAsErrors", 10 | "check": "npm run check-tsc && npm run check-eslint && npm run check-typedoc", 11 | "tsx": "tsx", 12 | "docs": "typedoc" 13 | }, 14 | "devDependencies": { 15 | "@eslint/js": "^9.23.0", 16 | "@minecraft/server": "2.0.0", 17 | "@minecraft/server-ui": "2.0.0", 18 | "@minecraft/vanilla-data": ">=1.21.90", 19 | "@minecraft/math": "^2.2.7", 20 | "mcbe-addon-ipc": "^0.9.0", 21 | "esbuild": "^0.25.1", 22 | "eslint": "^9.23.0", 23 | "prettier": "^3.5.3", 24 | "terser": "^5.39.0", 25 | "tsx": "^4.19.3", 26 | "typedoc": "^0.28.1", 27 | "typescript": "^5.8.2", 28 | "typescript-eslint": "^8.28.0" 29 | }, 30 | "dependencies": { 31 | "@minecraft/math": "^2.2.7", 32 | "mcbe-addon-ipc": "^0.9.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/featured_definitions.js: -------------------------------------------------------------------------------- 1 | const FEATURED_CREATORS = [ 2 | { 3 | name: "Vatonage", 4 | img: "./assets/vatonage.png", 5 | url: "https://vatonage.com/", 6 | }, 7 | ]; 8 | 9 | const FEATURED_CONTENT = [ 10 | { 11 | name: "Advanced Storage Network", 12 | free: true, 13 | author: "Fluffyalien", 14 | img: "https://media.forgecdn.net/avatars/thumbnails/1029/217/64/64/638551137696436806.png", 15 | url: "https://www.curseforge.com/minecraft-bedrock/addons/advanced-storage-network-2", 16 | }, 17 | { 18 | name: "Bedrock Energistics", 19 | free: true, 20 | author: "Fluffyalien", 21 | img: "https://media.forgecdn.net/avatars/thumbnails/1029/245/64/64/638551161203413381.png", 22 | url: "https://www.curseforge.com/minecraft-bedrock/addons/bedrock-energistics", 23 | }, 24 | { 25 | name: "Not Enough Teleporters", 26 | free: true, 27 | author: "Diamond Ruler", 28 | img: "https://media.forgecdn.net/avatars/thumbnails/907/720/64/64/638359197856483557.png", 29 | url: "https://www.curseforge.com/minecraft-bedrock/addons/not-enough-teleporters", 30 | }, 31 | ]; -------------------------------------------------------------------------------- /docs/guides/item-machines.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Item Machines 3 | --- 4 | 5 | # Item Machines 6 | 7 | Item machines are a standardized system for storing storage types inside machines. Register an item machine with the [registerItemMachine](https://fluffyalien1422.github.io/bedrock-energistics-core/api/functions/API.registerItemMachine.html) function. 8 | 9 | ```js 10 | // Register an item machine that can store energy. 11 | registerItemMachine({ 12 | description: { 13 | // There must be an item with this ID. 14 | id: "example:my_item_machine", 15 | defaultIo: { 16 | categories: ["energy"], 17 | }, 18 | }, 19 | }); 20 | ``` 21 | 22 | Interface with item machines via the [ItemMachine](https://fluffyalien1422.github.io/bedrock-energistics-core/api/classes/API.ItemMachine.html) class. 23 | 24 | ```js 25 | // The following code assumes that `player` is holding an item machine. 26 | const inventory = player.getComponent("inventory"); 27 | const itemMachine = new ItemMachine(inventory, player.selectedSlotIndex); 28 | 29 | // Add 1 `energy`. 30 | const storedEnergy = await itemMachine.getStorage("energy"); 31 | itemMachine.setStorage("energy", storedEnergy + 1); 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/guides/machine-priority.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Machine Allocation Priority 3 | --- 4 | 5 | # Machine Allocation Priority 6 | 7 | > [!note] 8 | > Remember to update the machine's networks using if any tags change. The simplest way to manually trigger a network update is with the [MachineNetwork.updateWithBlock](https://fluffyalien1422.github.io/bedrock-energistics-core/api/classes/API.MachineNetwork.html#updateWithBlock) function. 9 | 10 | A machine's priority during network allocation can be changed using the `fluffyalien_energisticscore:priority.{value}` tag. The default priority is `0`. 11 | 12 | **Examples:** `fluffyalien_energisticscore:priority.-1`, `fluffyalien_energisticscore:priority.0`, `fluffyalien_energisticscore:priority.1` 13 | 14 | All machines in the same priority group will recieve an equal amount of remaining budget at the time of allocation. Groups with higher priorities will recieve allocations first. 15 | 16 | For example, if the priority groups are `0` and `-1`, then the machines in group `0` will each recieve an equal split of the budget and the machines in group `-1` will each recieve an equal split of the remaining budget (if applicable) **after** group `0` have received their allocations. 17 | -------------------------------------------------------------------------------- /public_api/src/network_links/ipc_events.ts: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////// 2 | // internal file, do not export to index.ts! // 3 | // do not put any end api user content inside // 4 | // intended for shared types that arent public // 5 | ///////////////////////////////////////////////// 6 | 7 | import { Vector3 } from "@minecraft/server"; 8 | import { SerializableDimensionLocation } from "../serialize_utils.js"; 9 | 10 | export const NETWORK_LINK_BLOCK_TAG = 11 | "fluffyalien_energisticscore:network_link"; 12 | export const NETWORK_LINK_ENTITY_ID = 13 | "fluffyalien_energisticscore:network_link"; 14 | export const NETWORK_LINK_POSITIONS_KEY = 15 | "fluffyalien_energisticscore:linked_positions"; 16 | 17 | export interface NetworkLinkGetRequest { 18 | self: SerializableDimensionLocation; 19 | } 20 | export interface NetworkLinkGetResponse { 21 | locations: Vector3[]; 22 | } 23 | export interface NetworkLinkAddRequest { 24 | self: SerializableDimensionLocation; 25 | other: Vector3; 26 | } 27 | export interface NetworkLinkRemoveRequest { 28 | self: SerializableDimensionLocation; 29 | other: Vector3; 30 | } 31 | export interface NetworkLinkDestroyRequest { 32 | self: SerializableDimensionLocation; 33 | } 34 | -------------------------------------------------------------------------------- /public_api/src/log.ts: -------------------------------------------------------------------------------- 1 | import { VERSION } from "./constants.js"; 2 | import { __GET_INIT_BEC_VER__, tryGetIpcRouter } from "./init.js"; 3 | 4 | function makeLogString(logLevel: string, message: string): string { 5 | let namespace: string; 6 | 7 | const ipcRouter = tryGetIpcRouter(); 8 | if (ipcRouter) { 9 | namespace = ipcRouter.uid; 10 | } else { 11 | const initBecVer = __GET_INIT_BEC_VER__(); 12 | namespace = initBecVer ? `` : ""; 13 | } 14 | 15 | return `[Bedrock Energistics Core API v${VERSION}] (${namespace}) ${logLevel} ${message}`; 16 | } 17 | 18 | /** 19 | * @internal 20 | */ 21 | export function logInfo(message: string): void { 22 | console.info(makeLogString("INFO", message)); 23 | } 24 | 25 | /** 26 | * @internal 27 | */ 28 | export function logWarn(message: string): void { 29 | console.warn(makeLogString("WARN", message)); 30 | } 31 | 32 | /** 33 | * Note: prefer {@link raise} in most cases. 34 | * @internal 35 | */ 36 | export function makeErrorString(message: string): string { 37 | return makeLogString("ERROR", message); 38 | } 39 | 40 | /** 41 | * @internal 42 | */ 43 | export function raise(message: string): never { 44 | throw new Error(makeErrorString(message)); 45 | } 46 | -------------------------------------------------------------------------------- /packs/BP/scripts/network_links/network_link_component.ts: -------------------------------------------------------------------------------- 1 | import { BlockCustomComponent } from "@minecraft/server"; 2 | import { MachineNetwork } from "../network"; 3 | import { 4 | deserializeDimensionLocation, 5 | SerializableDimensionLocation, 6 | } from "@/public_api/src/serialize_utils"; 7 | import { InternalNetworkLinkNode } from "./network_link_internal"; 8 | import { raise } from "../utils/log"; 9 | 10 | export const networkLinkComponent: BlockCustomComponent = { 11 | onPlace(ev) { 12 | MachineNetwork.updateAdjacent(ev.block); 13 | }, 14 | 15 | onPlayerBreak(ev) { 16 | const linkNode = InternalNetworkLinkNode.tryGetAt( 17 | ev.dimension, 18 | ev.block.location, 19 | ); 20 | 21 | // remove all incoming and outbound links to this node in the network 22 | if (linkNode) linkNode.destroyNode(); 23 | 24 | // update the rest of the blocks in the network. 25 | MachineNetwork.updateWithBlock(ev.block); 26 | }, 27 | }; 28 | 29 | export function getNetworkLinkNode( 30 | self: SerializableDimensionLocation, 31 | ): InternalNetworkLinkNode { 32 | const location = deserializeDimensionLocation(self); 33 | const block = location.dimension.getBlock(location); 34 | if (!block) raise(`_getNetwork failed to get block`); 35 | return InternalNetworkLinkNode.fromBlock(block); 36 | } 37 | -------------------------------------------------------------------------------- /public_api/src/init.ts: -------------------------------------------------------------------------------- 1 | import * as ipc from "mcbe-addon-ipc"; 2 | import { raise } from "./log.js"; 3 | import { isBedrockEnergisticsCoreInWorld } from "./misc.js"; 4 | 5 | let ipcRouter: ipc.Router | undefined; 6 | let initBecVersion: string | undefined; 7 | 8 | /** 9 | * Initializes this package. Some APIs require this to be called. 10 | * @beta 11 | */ 12 | export function init(namespace: string): void { 13 | if (ipcRouter) { 14 | raise("Library already initialized."); 15 | } 16 | 17 | if (!isBedrockEnergisticsCoreInWorld()) { 18 | raise( 19 | `Cannot initialize library (namespace: '${namespace}'). Bedrock Energistics Core is not in the world.`, 20 | ); 21 | } 22 | 23 | ipcRouter = new ipc.Router(namespace); 24 | } 25 | 26 | /** 27 | * @internal 28 | */ 29 | export function getIpcRouter(): ipc.Router { 30 | if (!ipcRouter) { 31 | raise("Library not initialized."); 32 | } 33 | 34 | return ipcRouter; 35 | } 36 | 37 | /** 38 | * @internal 39 | */ 40 | export function tryGetIpcRouter(): ipc.Router | undefined { 41 | return ipcRouter; 42 | } 43 | 44 | /** 45 | * @internal 46 | */ 47 | export function __INIT_BEC__(version: string): void { 48 | initBecVersion = version; 49 | } 50 | 51 | /** 52 | * @internal 53 | */ 54 | export function __GET_INIT_BEC_VER__(): string | undefined { 55 | return initBecVersion; 56 | } 57 | -------------------------------------------------------------------------------- /public_api/src/network_internal.ts: -------------------------------------------------------------------------------- 1 | import { NetworkConnectionType } from "./network_utils.js"; 2 | import { SerializableDimensionLocation } from "./serialize_utils.js"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export interface NetworkInstanceMethodPayload { 8 | networkId: number; 9 | } 10 | 11 | /** 12 | * @internal 13 | */ 14 | export interface NetworkQueueSendPayload extends NetworkInstanceMethodPayload { 15 | loc: SerializableDimensionLocation; 16 | type: string; 17 | amount: number; 18 | } 19 | 20 | /** 21 | * @internal 22 | */ 23 | export interface NetworkEstablishPayload { 24 | ioTypeId: string; 25 | location: SerializableDimensionLocation; 26 | } 27 | 28 | /** 29 | * @internal 30 | */ 31 | export interface NetworkGetWithPayload extends NetworkEstablishPayload { 32 | connectionType: NetworkConnectionType; 33 | } 34 | 35 | /** 36 | * @internal 37 | */ 38 | export interface NetworkGetAllWithPayload { 39 | loc: SerializableDimensionLocation; 40 | type: NetworkConnectionType; 41 | } 42 | 43 | /** 44 | * @internal 45 | */ 46 | export interface NetworkIsPartOfNetworkPayload 47 | extends NetworkInstanceMethodPayload { 48 | loc: SerializableDimensionLocation; 49 | type: NetworkConnectionType; 50 | } 51 | 52 | /** 53 | * @internal 54 | */ 55 | export interface GeneratePayload { 56 | loc: SerializableDimensionLocation; 57 | type: string; 58 | amount: number; 59 | } 60 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from "@eslint/js"; 4 | import tseslint from "typescript-eslint"; 5 | 6 | export default tseslint.config( 7 | { 8 | ignores: ["build/**/*", "public_api/dist/**/*", "docs/**/*"], 9 | extends: [eslint.configs.recommended], 10 | rules: { 11 | eqeqeq: "error", 12 | }, 13 | }, 14 | { 15 | files: ["**/*.ts"], 16 | ignores: ["public_api/dist/**/*"], 17 | extends: [ 18 | ...tseslint.configs.strictTypeChecked, 19 | ...tseslint.configs.stylisticTypeChecked, 20 | ], 21 | languageOptions: { 22 | parserOptions: { 23 | project: true, 24 | // @ts-expect-error no dirname 25 | tsconfigRootDir: import.meta.dirname, 26 | }, 27 | }, 28 | rules: { 29 | // non-null assertions are useful when working with the minecraft api 30 | "@typescript-eslint/no-non-null-assertion": "off", 31 | // this rule conflicts with prettier 32 | "@typescript-eslint/no-confusing-non-null-assertion": "off", 33 | // @minecraft/server@1.18.0 deprecates things without giving alternatives, ignore 34 | "@typescript-eslint/no-deprecated": "off", 35 | 36 | "@typescript-eslint/explicit-function-return-type": "error", 37 | "@typescript-eslint/prefer-readonly": "error", 38 | "@typescript-eslint/switch-exhaustiveness-check": "error", 39 | }, 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /public_api/src/machine_data_internal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DimensionLocation, 3 | ScoreboardObjective, 4 | world, 5 | } from "@minecraft/server"; 6 | import { SerializableDimensionLocation } from "./serialize_utils.js"; 7 | 8 | /** 9 | * @internal 10 | */ 11 | export interface GetMachineSlotPayload { 12 | loc: SerializableDimensionLocation; 13 | slot: string; 14 | } 15 | 16 | /** 17 | * @internal 18 | */ 19 | export interface SetMachineSlotPayload extends GetMachineSlotPayload { 20 | item?: string; 21 | } 22 | 23 | /** 24 | * @internal 25 | */ 26 | export function getBlockUniqueId(loc: DimensionLocation): string { 27 | return ( 28 | Math.floor(loc.x).toString() + 29 | "," + 30 | Math.floor(loc.y).toString() + 31 | "," + 32 | Math.floor(loc.z).toString() + 33 | "," + 34 | loc.dimension.id 35 | ); 36 | } 37 | 38 | /** 39 | * @internal 40 | */ 41 | export function getStorageScoreboardObjective( 42 | type: string, 43 | ): ScoreboardObjective | undefined { 44 | const id = `fluffyalien_energisticscore:storage${type}`; 45 | return world.scoreboard.getObjective(id); 46 | } 47 | 48 | /** 49 | * @internal 50 | */ 51 | export function getScore( 52 | objective: ScoreboardObjective, 53 | participant: string, 54 | ): number | undefined { 55 | if (!objective.hasParticipant(participant)) { 56 | return; 57 | } 58 | 59 | return objective.getScore(participant); 60 | } 61 | -------------------------------------------------------------------------------- /scripts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "imagescript": "^1.3.0" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^20.12.12" 12 | } 13 | }, 14 | "node_modules/@types/node": { 15 | "version": "20.14.9", 16 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", 17 | "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", 18 | "dev": true, 19 | "license": "MIT", 20 | "dependencies": { 21 | "undici-types": "~5.26.4" 22 | } 23 | }, 24 | "node_modules/imagescript": { 25 | "version": "1.3.0", 26 | "resolved": "https://registry.npmjs.org/imagescript/-/imagescript-1.3.0.tgz", 27 | "integrity": "sha512-lCYzQrWzdnA68K03oMj/BUlBJrVBnslzDOgGFymAp49NmdGEJxGeN7sHh5mCva0nQkq+kkKSuru2zLf1m04+3A==", 28 | "license": "(AGPL-3.0-or-later OR MIT)", 29 | "engines": { 30 | "node": ">=14.0.0" 31 | } 32 | }, 33 | "node_modules/undici-types": { 34 | "version": "5.26.5", 35 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 36 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 37 | "dev": true, 38 | "license": "MIT" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/api/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJydmVtv2zgQhf+Koedg06bbbDZvvm0qoGsbvrRYFIHASBOFsEwaFJ1sUPS/F7Rp62JqOPSzznxH4hyOZPrHz0jD/zq6jx52PIMyuoq2TL9E91Em090GhC6vD1f+eNGbIrqK1lxk0f3dp7u72w93V1H6wotMgYjuf5xQQymyHdcY7ChxUn9dnVAjeNrlORc5wjppfLAH0JqLvLfQTGnIEKRVJlbpA8caNr1/WfrCBbqERpccdT6o1fXi6ymCtKokTqZUYL8oZMo0l6I3U1wqrt8JBlVVcqyiGq5iAn8V+3AT0G9SrXtfuVhjq2x1yV7ng85AlbzUIHRvLDTXHG1gpU6Oaq8BL7UUKPSg8IEWWiqWQ2/5voXeUArNuACFgW1FYiqSqiLEiIr3Qr+ZlZMC38mVyI17rAH7s1qqNjLbFVBe92etFN24J5SNyFAKAanJtHmGCgditznAnMLWzTWWjomMqcwuzZBpyKV6d5E7pGR21x07ZN3MWA7Zlj3xohX8tGBladezqWmyPt60x6Ddzh2oSoBxrMSoF5qlazesrSIQbTdRntUQaCs+LmCfXxRYyTCmtTUzayKzjvVriTDeHHIzqBRk3q44pTQ2kRvEdObbze3MeCuXjQSPmGYVmAsN6pmlzrAbaWuefL51B37IiuKJpeu+yrvhTjnRYATPXHAzgij8Sh2MH0GZKr4Nd6oVBpuOX5v7iOJ3qAm2+sJEVjRenBSzYxXR7gF0LOdQbqUogeLUKCCaTIXdAAvQxNy1SjCjgGCHhZoe6KAwXxTkS0McGuALwhse3ItCewrGYKe1FDMFZQnZ/vYILe8oIxlWSdyX+a1aBQSTOaQcXsGuA+F5zgoIJqttxjSsON3lvCLcxjtc8DLM0H5mLDTTpTcILjEJfnp1oy9ktxw3eGt/Gk73+7l743SXYEbNrHj74ZZjBtQ1ClicutQ/f51qDL/ih3Fgv3gJFp0VATaHiPuajBXhZvtQFFKHPFVnDW7lfwjSHc+UzM1EjkXGU6alQoBn2kB0yKr4i3FzG8gBC3JFqgLtqEnDCzHTASsh3qbHT6nKQ5tTlj2+JWnRPvz918fPN/iPFBfVIbuAPGEbINKNNMBh/24hPkBDG+pBeISTLoBt5z6BXlP6+IS+BvaU2M8Leknt4yU9pPQvtHe0voX3rPZSHcqiPporalvjY57OmFzzsMI6ZH7y2cw23/qg3fwOsXdNlv3JqD8fJYvldN5/GCfL/2bjZDT+J57Ey3g6WVRur0xx9nQ87PUWNp0/1U2/jeeLeDrpQtvLCCAHAYrpWi6ed2J/SnwgHK83Ebd/NhB6UMh07TmQbnORItzMxtV8mZiJgzi0lDTsIbYE6kGIQE08uzjmGlZaDiBTMl2PBShzVMjTcigVxOK7VEXWScXLEENlDySdJ61NE4eUACZC6UDnGasb2nnG2gJv5Ct477MmQmAlOafnShoWz+mZEINu2Zs4vlrMX4LvndQzJYLdlYD+39NEu9Vn+Mdfj78BlNgClA==" -------------------------------------------------------------------------------- /packs/BP/scripts/utils/dynamic_property.ts: -------------------------------------------------------------------------------- 1 | import { getBlockUniqueId } from "@/public_api/src/machine_data_internal"; 2 | import { DimensionLocation, Vector3, world } from "@minecraft/server"; 3 | 4 | export type DynamicPropertyValue = boolean | number | string | Vector3; 5 | 6 | function makeBlockBaseDynamicPropertyId(loc: DimensionLocation): string { 7 | return "_bdp" + getBlockUniqueId(loc); 8 | } 9 | 10 | export function getBlockDynamicProperty( 11 | loc: DimensionLocation, 12 | id: string, 13 | ): DynamicPropertyValue | undefined { 14 | return world.getDynamicProperty(makeBlockBaseDynamicPropertyId(loc) + id); 15 | } 16 | 17 | export function setBlockDynamicProperty( 18 | loc: DimensionLocation, 19 | id: string, 20 | value?: DynamicPropertyValue, 21 | ): void { 22 | world.setDynamicProperty(makeBlockBaseDynamicPropertyId(loc) + id, value); 23 | } 24 | 25 | export function getBlockDynamicProperties(loc: DimensionLocation): string[] { 26 | const blockBaseId = makeBlockBaseDynamicPropertyId(loc); 27 | 28 | return world 29 | .getDynamicPropertyIds() 30 | .filter((id) => id.startsWith(blockBaseId)) 31 | .map((id) => id.slice(blockBaseId.length)); 32 | } 33 | 34 | export function removeAllDynamicPropertiesForBlock( 35 | loc: DimensionLocation, 36 | ): void { 37 | const blockBaseId = makeBlockBaseDynamicPropertyId(loc); 38 | 39 | const properties = world 40 | .getDynamicPropertyIds() 41 | .filter((id) => id.startsWith(blockBaseId)); 42 | 43 | for (const property of properties) { 44 | world.setDynamicProperty(property); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packs/BP/scripts/debug_commands.ts: -------------------------------------------------------------------------------- 1 | import { Player, system } from "@minecraft/server"; 2 | import { enableDebugMode, isDebugModeEnabled } from "./debug_mode"; 3 | import { makeErrorString, makeLogString } from "./utils/log"; 4 | import { MachineNetwork } from "./network"; 5 | 6 | system.afterEvents.scriptEventReceive.subscribe( 7 | (e) => { 8 | // e.sourceEntity is a really expensive api call, only do if necessary 9 | if ( 10 | !e.id.startsWith("fluffyalien_energisticscore:debug.") || 11 | !(e.sourceEntity instanceof Player) 12 | ) { 13 | return; 14 | } 15 | 16 | switch (e.id) { 17 | case "fluffyalien_energisticscore:debug.enable_debug_mode": 18 | if (isDebugModeEnabled()) { 19 | e.sourceEntity.sendMessage( 20 | makeErrorString("Debug mode is already enabled."), 21 | ); 22 | return; 23 | } 24 | 25 | enableDebugMode(); 26 | break; 27 | case "fluffyalien_energisticscore:debug.print_networks": { 28 | const networks = MachineNetwork.getAll(); 29 | const lines = []; 30 | for (const [networkId, network] of networks) { 31 | lines.push( 32 | `{ §sid§r: §p${networkId.toString()}§r, §sioType§r: { §scategory§r: §p${network.ioType.category}§r, §sid§r: §p${network.ioType.id}§r } }`, 33 | ); 34 | } 35 | e.sourceEntity.sendMessage( 36 | makeLogString("DEBUG", `Networks: [\n${lines.join(",\n")}\n]`), 37 | ); 38 | break; 39 | } 40 | } 41 | }, 42 | { 43 | namespaces: ["fluffyalien_energisticscore"], 44 | }, 45 | ); 46 | -------------------------------------------------------------------------------- /public_api/src/storage_type_registry_types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A storage type texture description. This is the texture that will be used by default for storage bars of this type in machine UI. 3 | * @beta 4 | */ 5 | export interface StorageTypeTextureDescription { 6 | /** 7 | * Base ID for all 16 UI items. 8 | * @remarks 9 | * The index will be appended to the end of the base ID. For example, if the base ID is 'example:example' then 'example:example0' to 'example:example15' will be used. All items must have the `fluffyalien_energisticscore:ui_item` tag. 10 | * @beta 11 | */ 12 | baseId: string; 13 | /** 14 | * Formatting code to prefix the label. ONLY include the formatting code, NOT the '§'. Multiple formatting codes can be used. 15 | * @remarks 16 | * To use multiple formatting codes, string them together with no separator. For example, "lc" will make the label bold (l) and red (c). 17 | * @beta 18 | */ 19 | formattingCode?: string; 20 | } 21 | 22 | /** 23 | * A storage type texture preset. This is the texture that will be used by default for storage bars of this type in machine UI. 24 | * @beta 25 | */ 26 | export type StorageTypeTexturePreset = 27 | | "black" 28 | | "orange" 29 | | "pink" 30 | | "purple" 31 | | "red" 32 | | "yellow" 33 | | "blue" 34 | | "white" 35 | | "green"; 36 | 37 | /** 38 | * @beta 39 | */ 40 | export interface StorageTypeDefinition { 41 | id: string; 42 | category: string; 43 | /** 44 | * The texture that will be used by default for storage bars of this type in machine UI. This can be a preset or a custom texture. Machines can override the texture for their own UI. 45 | * @beta 46 | */ 47 | texture: StorageTypeTextureDescription | StorageTypeTexturePreset; 48 | name: string; 49 | } 50 | -------------------------------------------------------------------------------- /public_api/src/machine_utils.ts: -------------------------------------------------------------------------------- 1 | import { Block, DimensionLocation, Entity } from "@minecraft/server"; 2 | import { ipcInvoke } from "./ipc_wrapper.js"; 3 | import { BecIpcListener } from "./bec_ipc_listener.js"; 4 | import { makeSerializableDimensionLocation } from "./serialize_utils.js"; 5 | import { RegisteredMachine } from "./machine_registry.js"; 6 | 7 | /** 8 | * Cleans up machine data and updates it's networks. 9 | * @beta 10 | * @remarks 11 | * This is automatically done by Bedrock Energistics Core when a machine is destroyed by a player. 12 | * If you destroy a machine from script, call this function before the block is removed. 13 | * @param loc The machine block location. 14 | */ 15 | export async function removeMachine(loc: DimensionLocation): Promise { 16 | await ipcInvoke( 17 | BecIpcListener.RemoveMachine, 18 | makeSerializableDimensionLocation(loc), 19 | ); 20 | } 21 | 22 | /** 23 | * Spawns the machine entity for the machine at the specified location, if it doesn't already exist. 24 | * @param block The machine. 25 | * @returns The new entity or the one that was already there. 26 | * @throws Throws if the machine does not exist in the registry. 27 | */ 28 | export async function spawnMachineEntity(block: Block): Promise { 29 | // there is a similar function to this one in the add-on. 30 | // if this is changed, then ensure the add-on function is 31 | // changed as well. 32 | 33 | const definition = await RegisteredMachine.forceGet(block.typeId); 34 | 35 | const existingEntity = block.dimension 36 | .getEntitiesAtBlockLocation(block.location) 37 | .find((entity) => entity.typeId === definition.entityId); 38 | if (existingEntity) return existingEntity; 39 | 40 | const newEntity = block.dimension.spawnEntity( 41 | definition.entityId, 42 | block.bottomCenter(), 43 | ); 44 | newEntity.nameTag = block.typeId; 45 | return newEntity; 46 | } 47 | -------------------------------------------------------------------------------- /docs/guides/storage-types.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Storage Types 3 | --- 4 | 5 | # Storage Types 6 | 7 | A storage type is something that a machine can consume or generate. All storage types have a category. For example, a storage type with ID `water` may have the category `fluid`. 8 | 9 | Energy is registered by default. It's ID is `energy` and it's category is `energy`. 10 | 11 | To register a new storage type, use [registerStorageType](https://fluffyalien1422.github.io/bedrock-energistics-core/api/functions/API.registerStorageType.html). However, you should always **prefer using [standard storage types](#standard-storage-types)** instead of registering your own. 12 | 13 | If you are registering your own storage type, it should be namespaced to avoid conflicts with standard storage types as well as storage types from other add-ons. 14 | 15 | ## Standard Storage Types 16 | 17 | Bedrock Energistics Core API contains many storage type definitions that you can use instead of registering your own. 18 | 19 | These are not registered by default (except `energy`). Use [useStandardStorageType](https://fluffyalien1422.github.io/bedrock-energistics-core/api/functions/API.useStandardStorageType.html) to register a standard storage type 20 | for use in your add-on. 21 | 22 | ```ts 23 | useStandardStorageType(StandardStorageType.Water); 24 | ``` 25 | 26 | ## Standard Storage Categories 27 | 28 | There are three standard storage categories: energy, gas, and fluid. If you are registering a custom storage type, you should use the [StandardStorageCategory](https://fluffyalien1422.github.io/bedrock-energistics-core/api/enumerations/API.StandardStorageCategory.html) enum if possible. 29 | 30 | ```ts 31 | registerStorageType({ 32 | category: StandardStorageCategory.Fluid, 33 | color: "blue", 34 | id: "example:custom_fluid", 35 | name: "custom fluid", 36 | }); 37 | ``` 38 | 39 | If you are using a custom storage category, it should be namespaced to avoid conflicts with standard storage categories as well as storage categories from other add-ons. 40 | -------------------------------------------------------------------------------- /public_api/src/network_utils.ts: -------------------------------------------------------------------------------- 1 | import { Block, BlockPermutation, DimensionLocation } from "@minecraft/server"; 2 | import { GeneratePayload } from "./network_internal.js"; 3 | import { makeSerializableDimensionLocation } from "./serialize_utils.js"; 4 | import { ipcSend } from "./ipc_wrapper.js"; 5 | import { BecIpcListener } from "./bec_ipc_listener.js"; 6 | 7 | /** 8 | * @beta 9 | */ 10 | export enum NetworkConnectionType { 11 | Conduit = "Conduit", 12 | Machine = "Machine", 13 | NetworkLink = "NetworkLink", 14 | } 15 | 16 | /** 17 | * @beta 18 | */ 19 | export function getBlockNetworkConnectionType( 20 | block: Block | BlockPermutation, 21 | ): NetworkConnectionType | null { 22 | if (block.hasTag("fluffyalien_energisticscore:conduit")) 23 | return NetworkConnectionType.Conduit; 24 | if (block.hasTag("fluffyalien_energisticscore:machine")) 25 | return NetworkConnectionType.Machine; 26 | if (block.hasTag("fluffyalien_energisticscore:network_link")) 27 | return NetworkConnectionType.NetworkLink; 28 | return null; 29 | } 30 | 31 | /** 32 | * Sends a storage type over a machine network. Includes reserve storage as well. 33 | * @beta 34 | * @remarks 35 | * This function should be called every block tick for generators even if the generation is `0` because it sends reserve storage. 36 | * Automatically sets the machine's reserve storage to the amount that was not received. 37 | * This function is a wrapper around {@link MachineNetwork.queueSend}. 38 | * @param blockLocation The location of the machine that is generating. 39 | * @param type The storage type to generate. 40 | * @param amount The amount to generate. 41 | * @see {@link MachineNetwork.queueSend} 42 | */ 43 | export function generate( 44 | blockLocation: DimensionLocation, 45 | type: string, 46 | amount: number, 47 | ): void { 48 | const payload: GeneratePayload = { 49 | loc: makeSerializableDimensionLocation(blockLocation), 50 | type, 51 | amount, 52 | }; 53 | 54 | ipcSend(BecIpcListener.Generate, payload); 55 | } 56 | -------------------------------------------------------------------------------- /scripts/filters/simple_manifest.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as simpleManifest from "@/packs/data/simple_manifest.json"; 4 | import { TMP_DIR } from "./common"; 5 | 6 | fs.writeFileSync( 7 | path.join(TMP_DIR, "BP/manifest.json"), 8 | JSON.stringify({ 9 | format_version: 2, 10 | header: { 11 | name: "pack.name", 12 | description: "pack.description", 13 | min_engine_version: simpleManifest.minEngineVersion, 14 | uuid: simpleManifest.uuids.bp.header, 15 | version: simpleManifest.version, 16 | }, 17 | modules: [ 18 | { 19 | type: "data", 20 | uuid: simpleManifest.uuids.bp.data, 21 | version: [1, 0, 0], 22 | }, 23 | { 24 | type: "script", 25 | language: "javascript", 26 | uuid: simpleManifest.uuids.bp.script, 27 | entry: "scripts/__bundle.js", 28 | version: [1, 0, 0], 29 | }, 30 | ], 31 | dependencies: [ 32 | { 33 | uuid: simpleManifest.uuids.rp.header, 34 | version: simpleManifest.version, 35 | }, 36 | ...simpleManifest.scriptModules.map((scriptMod) => ({ 37 | module_name: scriptMod.name, 38 | version: scriptMod.version, 39 | })), 40 | ], 41 | }), 42 | ); 43 | 44 | fs.writeFileSync( 45 | path.join(TMP_DIR, "RP/manifest.json"), 46 | JSON.stringify({ 47 | format_version: 2, 48 | header: { 49 | name: "pack.name", 50 | description: "pack.description", 51 | pack_scope: "world", 52 | min_engine_version: simpleManifest.minEngineVersion, 53 | uuid: simpleManifest.uuids.rp.header, 54 | version: simpleManifest.version, 55 | }, 56 | modules: [ 57 | { 58 | type: "resources", 59 | uuid: simpleManifest.uuids.rp.resources, 60 | version: [1, 0, 0], 61 | }, 62 | ], 63 | dependencies: [ 64 | { 65 | uuid: simpleManifest.uuids.bp.header, 66 | version: simpleManifest.version, 67 | }, 68 | ], 69 | }), 70 | ); 71 | -------------------------------------------------------------------------------- /public_api/src/bec_ipc_listener.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @internal 3 | */ 4 | export enum BecIpcListener { 5 | RegisterMachine = "fluffyalien_energisticscore:ipc.registerMachine", 6 | RegisterStorageType = "fluffyalien_energisticscore:ipc.registerStorageType", 7 | RegisterItemMachine = "fluffyalien_energisticscore:ipc.registerItemMachine", 8 | SetMachineSlot = "fluffyalien_energisticscore:ipc.setMachineSlot", 9 | GetMachineSlot = "fluffyalien_energisticscore:ipc.getMachineSlot", 10 | DestroyNetwork = "fluffyalien_energisticscore:ipc.destroyNetwork", 11 | NetworkQueueSend = "fluffyalien_energisticscore:ipc.networkQueueSend", 12 | Generate = "fluffyalien_energisticscore:ipc.generate", 13 | EstablishNetwork = "fluffyalien_energisticscore:ipc.establishNetwork", 14 | GetNetworkWith = "fluffyalien_energisticscore:ipc.getNetworkWith", 15 | GetAllNetworksWith = "fluffyalien_energisticscore:ipc.getAllNetworksWith", 16 | GetOrEstablishNetwork = "fluffyalien_energisticscore:ipc.getOrEstablishNetwork", 17 | IsPartOfNetwork = "fluffyalien_energisticscore:ipc.isPartOfNetwork", 18 | GetRegisteredMachine = "fluffyalien_energisticscore:ipc.getRegisteredMachine", 19 | GetRegisteredStorageType = "fluffyalien_energisticscore:ipc.getRegisteredStorageType", 20 | GetRegisteredItemMachine = "fluffyalien_energisticscore:ipc.getRegisteredItemMachine", 21 | GetAllRegisteredStorageTypes = "fluffyalien_energisticscore:ipc.getAllRegisteredStorageTypes", 22 | GetNetworkLink = "fluffyalien_energisticscore:ipc.getNetworkLink", 23 | AddNetworkLink = "fluffyalien_energisticscore:ipc.addNetworkLink", 24 | RemoveNetworkLink = "fluffyalien_energisticscore:ipc.removeNetworkLink", 25 | DestroyNetworkLink = "fluffyalien_energisticscore:ipc.destroyNetworkLink", 26 | GetItemMachineStorage = "fluffyalien_energisticscore:ipc.getItemMachineStorage", 27 | SetItemMachineStorage = "fluffyalien_energisticscore:ipc.setItemMachineStorage", 28 | GetItemMachineIo = "fluffyalien_energisticscore:ipc.getItemMachineIo", 29 | RemoveMachine = "fluffyalien_energisticscore:ipc.removeMachine", 30 | } 31 | -------------------------------------------------------------------------------- /packs/RP/ui/fluffyalien/energisticscore/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "fluffyalien_energisticscore:common", 3 | "container_title": { 4 | "type": "label", 5 | "anchor_from": "top_left", 6 | "anchor_to": "top_left", 7 | "offset": [6, -3], 8 | "color": "$title_text_color" 9 | }, 10 | "container_item_slot": { 11 | "type": "stack_panel", 12 | "$index": 0, 13 | "collection_name": "container_items", 14 | "$slot_size": [18, 18], 15 | "$slot_image_size": [18, 18], 16 | "anchor_from": "top_left", 17 | "anchor_to": "top_left", 18 | "controls": [ 19 | { 20 | "slot@chest.chest_grid_item": { 21 | "ignored": false, 22 | "$cell_image_size": "$slot_image_size", 23 | "size": "$slot_size", 24 | "collection_index": "$index" 25 | } 26 | } 27 | ] 28 | }, 29 | "container_item_slot_nobg@fluffyalien_energisticscore:common.container_item_slot": { 30 | "$slot_size": [16, 16], 31 | "$slot_image_size": [0, 0] 32 | }, 33 | "container_slot_icon@fluffyalien_energisticscore:common.container_item_slot": { 34 | "$slot_size": [0, 0], 35 | "$slot_image_size": [0, 0], 36 | "enabled": false 37 | }, 38 | "machine_storage_bar": { 39 | "type": "panel", 40 | "$start_index": 0, 41 | "controls": [ 42 | { 43 | "segment1@fluffyalien_energisticscore:common.container_item_slot_nobg": { 44 | "$index": "$start_index" 45 | } 46 | }, 47 | { 48 | "segment2@fluffyalien_energisticscore:common.container_item_slot_nobg": { 49 | "$index": "($start_index+1)", 50 | "offset": [0, 16] 51 | } 52 | }, 53 | { 54 | "segment3@fluffyalien_energisticscore:common.container_item_slot_nobg": { 55 | "$index": "($start_index+2)", 56 | "offset": [0, 32] 57 | } 58 | }, 59 | { 60 | "segment4@fluffyalien_energisticscore:common.container_item_slot_nobg": { 61 | "$index": "($start_index+3)", 62 | "offset": [0, 48] 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packs/BP/scripts/data_ipc.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetMachineSlotPayload, 3 | SetMachineSlotPayload, 4 | } from "@/public_api/src/machine_data_internal"; 5 | import * as ipc from "mcbe-addon-ipc"; 6 | import { getMachineSlotItemRaw, setMachineSlotItem } from "./data"; 7 | import { 8 | deserializeDimensionLocation, 9 | SerializableDimensionLocation, 10 | } from "@/public_api/src/serialize_utils"; 11 | import { InternalRegisteredMachine } from "./machine_registry"; 12 | import { removeMachine } from "./machine"; 13 | import { deserializeMachineItemStack } from "@/public_api/src/serialize_machine_item_stack"; 14 | import { raise } from "./utils/log"; 15 | import { Vector3Utils } from "@minecraft/math"; 16 | 17 | export function getMachineSlotListener( 18 | payload: ipc.SerializableValue, 19 | ): string | null { 20 | const data = payload as GetMachineSlotPayload; 21 | return ( 22 | getMachineSlotItemRaw(deserializeDimensionLocation(data.loc), data.slot) ?? 23 | null 24 | ); 25 | } 26 | 27 | export function setMachineSlotListener(payload: ipc.SerializableValue): null { 28 | const data = payload as SetMachineSlotPayload; 29 | const loc = deserializeDimensionLocation(data.loc); 30 | const block = loc.dimension.getBlock(loc); 31 | if (!block) { 32 | raise( 33 | `Failed to set machine slot item. Block not found at ${Vector3Utils.toString(loc)} in '${loc.dimension.id}'.`, 34 | ); 35 | } 36 | 37 | setMachineSlotItem( 38 | block, 39 | data.slot, 40 | data.item ? deserializeMachineItemStack(data.item) : undefined, 41 | ); 42 | 43 | return null; 44 | } 45 | 46 | export function removeMachineListener(payload: ipc.SerializableValue): null { 47 | const data = payload as SerializableDimensionLocation; 48 | 49 | const loc = deserializeDimensionLocation(data); 50 | const block = loc.dimension.getBlock(loc); 51 | if (!block) { 52 | raise( 53 | `Expected a block at ${Vector3Utils.toString(loc)} in '${loc.dimension.id}'.`, 54 | ); 55 | } 56 | 57 | const def = InternalRegisteredMachine.forceGetInternal(block.typeId); 58 | 59 | removeMachine(block, def); 60 | 61 | return null; 62 | } 63 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/Bedrock-OSS/regolith-schemas/main/config/v1.4.json", 3 | "author": "Fluffyalien", 4 | "name": "bedrock_energistics_core", 5 | "packs": { 6 | "behaviorPack": "./packs/BP", 7 | "resourcePack": "./packs/RP" 8 | }, 9 | "regolith": { 10 | "formatVersion": "1.4.0", 11 | "dataPath": "./packs/data", 12 | "filterDefinitions": { 13 | "build_scripts": { 14 | "runWith": "shell", 15 | "command": "npx esbuild BP/scripts/index.ts --outfile=BP/scripts/__bundle.js --bundle --format=esm --log-level=warning --preserve-symlinks --external:@minecraft/common --external:@minecraft/debug-utilities --external:@minecraft/server --external:@minecraft/server-*" 16 | }, 17 | "prod_finish_up_build_scripts": { 18 | "runWith": "shell", 19 | "command": "npx terser BP/scripts/__bundle.js --module -cmo BP/scripts/__bundle.js; Remove-Item BP/scripts/* -Recurse -Exclude __bundle.js" 20 | }, 21 | "simple_manifest": { 22 | "runWith": "shell", 23 | "command": "npm run tsx scripts/filters/simple_manifest" 24 | }, 25 | "gen_ui_bars": { 26 | "runWith": "shell", 27 | "command": "npm run tsx scripts/filters/gen_ui_bars" 28 | } 29 | }, 30 | "profiles": { 31 | "default": { 32 | "export": { 33 | "target": "development", 34 | "build": "standard" 35 | }, 36 | "filters": [ 37 | { 38 | "filter": "build_scripts" 39 | }, 40 | { 41 | "filter": "simple_manifest" 42 | }, 43 | { 44 | "filter": "gen_ui_bars" 45 | } 46 | ] 47 | }, 48 | "preview": { 49 | "export": { 50 | "target": "development", 51 | "build": "preview" 52 | }, 53 | "filters": [ 54 | { 55 | "profile": "default" 56 | } 57 | ] 58 | }, 59 | "prod": { 60 | "export": { 61 | "target": "local" 62 | }, 63 | "filters": [ 64 | { 65 | "profile": "default" 66 | }, 67 | { 68 | "filter": "prod_finish_up_build_scripts" 69 | } 70 | ] 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Code 2 | 3 | Note: this is a guide for contributing code, not issues. Create an issue [here](https://github.com/Fluffyalien1422/bedrock-energistics-core/issues/new/choose). 4 | 5 | ## Preparing Your Environment 6 | 7 | This project is configured for Windows 10/11 machines. If you're using another OS, it may not work properly. 8 | 9 | ### Prerequisites 10 | 11 | Ensure you have the following programs installed and up to date: 12 | 13 | - [VSCode](https://code.visualstudio.com/) 14 | - [Node.js LTS and npm](https://nodejs.org/) 15 | - [Regolith](https://bedrock-oss.github.io/regolith/) 16 | - [Minecraft (Bedrock Edition Stable)](https://www.xbox.com/en-US/games/store/minecraft/9MVXMVT8ZKWC) 17 | 18 | Please read the [coding guidelines](CODING_GUIDELINES.md) as well before contributing. 19 | 20 | ### Setting Up 21 | 22 | 1. Run `npm i` 23 | 2. Run `npm i` again in the `scripts` directory 24 | 25 | ## Editing Documentation 26 | 27 | Documentation is generated for the public API (`public_api` directory) using [TypeDoc](https://typedoc.org/) based on source code. 28 | 29 | Markdown guides are also included. These guides can be found in `public_api/guides`. 30 | 31 | To add a new guide, add it to the `children` frontmatter property in `public_api/guides/index.md`. 32 | 33 | Note: do not generate the documentation, the documentation will be generated when a new version is released. 34 | 35 | ## Checking Your Code 36 | 37 | To check your code before commiting, run `npm run check`. 38 | 39 | To format your code before commiting, run `npm run fmt`. 40 | 41 | You can also run `npm run fmt-check` to format and check. 42 | 43 | If your code is not properly formatted or `npm run check` fails, it will not be accepted. 44 | 45 | Ensure that you test your new code in Minecraft before pushing changes. 46 | 47 | ## Building Your Code 48 | 49 | To build your code, simply run `regolith run`. 50 | 51 | ## Before Pushing 52 | 53 | Before pushing new code, ensure that your code is formatted (`npm run fmt`) and checked (`npm run check`). 54 | 55 | ## Before Submitting a PR 56 | 57 | Before submitting a PR, follow this checklist: 58 | 59 | - Your code is formatted (`npm run fmt`) and checked (`npm run check`). 60 | - Your code adheres to the [coding guidelines](CODING_GUIDELINES.md). 61 | - You have tested your changes in Minecraft. 62 | -------------------------------------------------------------------------------- /docs/featured.js: -------------------------------------------------------------------------------- 1 | const shuffle = (arr) => arr.sort(() => Math.random() - 0.5); 2 | 3 | const shuffledCreators = shuffle(FEATURED_CREATORS); 4 | const featuredCreatorsContainer = document.getElementById( 5 | "featured-creators-container", 6 | ); 7 | 8 | for (const creator of shuffledCreators) { 9 | const a = document.createElement("a"); 10 | a.className = "featured-content"; 11 | a.href = creator.url; 12 | a.target = "_blank"; 13 | 14 | const img = document.createElement("img"); 15 | img.className = "featured-content-img"; 16 | img.src = creator.img; 17 | 18 | const name = document.createElement("span"); 19 | name.className = "featured-content-name"; 20 | name.innerText = creator.name; 21 | 22 | a.appendChild(img); 23 | a.appendChild(name); 24 | 25 | featuredCreatorsContainer.appendChild(a); 26 | } 27 | 28 | const featuredContentContainer = document.getElementById( 29 | "featured-addons-container", 30 | ); 31 | const shuffledFeaturedAddons = shuffle(FEATURED_CONTENT); 32 | 33 | for (const addon of shuffledFeaturedAddons) { 34 | const a = document.createElement("a"); 35 | a.className = "featured-content"; 36 | a.href = addon.url; 37 | a.target = "_blank"; 38 | 39 | const img = document.createElement("img"); 40 | img.className = "featured-content-img"; 41 | img.src = addon.img; 42 | 43 | const subcontainer = document.createElement("div"); 44 | 45 | const name = document.createElement("span"); 46 | name.className = "featured-content-name"; 47 | name.innerText = addon.name; 48 | 49 | const infoContainer = document.createElement("div"); 50 | infoContainer.className = "featured-content-info-container"; 51 | 52 | const author = document.createElement("span"); 53 | author.className = "featured-content-author"; 54 | author.innerText = `by ${addon.author}`; 55 | 56 | const tag = document.createElement("span"); 57 | tag.className = "featured-content-tag"; 58 | if (addon.free) { 59 | tag.innerText = "Free"; 60 | tag.style.backgroundColor = "green"; 61 | } else { 62 | tag.innerText = "Paid"; 63 | tag.style.backgroundColor = "goldenrod"; 64 | } 65 | 66 | infoContainer.appendChild(author); 67 | infoContainer.appendChild(tag); 68 | 69 | subcontainer.appendChild(name); 70 | subcontainer.appendChild(infoContainer); 71 | 72 | a.appendChild(img); 73 | a.appendChild(subcontainer); 74 | 75 | featuredContentContainer.appendChild(a); 76 | } 77 | -------------------------------------------------------------------------------- /public_api/src/item_machine_registry_types.ts: -------------------------------------------------------------------------------- 1 | import { BaseIpcCallback } from "./common_registry_types.js"; 2 | import { ItemMachine } from "./item_machine.js"; 3 | 4 | /** 5 | * @beta 6 | */ 7 | export interface ItemMachineDefinitionDescription { 8 | id: string; 9 | /** 10 | * Max amount of each storage type in this machine. 11 | * @default 6400 12 | */ 13 | maxStorage?: number; 14 | /** 15 | * Default I/O for the item machine. This is used to fill in any `undefined` values returned by `getIo`. 16 | */ 17 | defaultIo?: ItemMachineGetIoResponse; 18 | } 19 | 20 | // common callback types 21 | 22 | /** 23 | * @beta 24 | */ 25 | export interface ItemMachineCallbackArg { 26 | itemMachine: ItemMachine; 27 | } 28 | 29 | /** 30 | * @beta 31 | */ 32 | export type ItemMachineCallback< 33 | TArg extends ItemMachineCallbackArg, 34 | TReturn, 35 | > = BaseIpcCallback; 36 | 37 | /** 38 | * @beta 39 | */ 40 | export type ItemMachineEventCallback = 41 | BaseIpcCallback; 42 | 43 | // handlers 44 | 45 | /** 46 | * @beta 47 | */ 48 | export interface ItemMachineGetIoResponse { 49 | /** 50 | * Accept any storage type. If this is `true`, `categories` and `types` are ignored. 51 | */ 52 | acceptsAny?: boolean; 53 | /** 54 | * Accepted storage type categories. 55 | */ 56 | categories?: string[]; 57 | /** 58 | * Accepted storage types. 59 | */ 60 | types?: string[]; 61 | } 62 | 63 | /** 64 | * @beta 65 | */ 66 | export interface ItemMachineDefinitionHandlers { 67 | getIo?: ItemMachineCallback; 68 | } 69 | 70 | /** 71 | * @beta 72 | */ 73 | export type ItemMachineHandlerName = keyof ItemMachineDefinitionHandlers; 74 | 75 | // events 76 | 77 | /** 78 | * @beta 79 | */ 80 | export interface ItemMachineOnStorageSetArg extends ItemMachineCallbackArg { 81 | type: string; 82 | value: number; 83 | } 84 | 85 | /** 86 | * @beta 87 | */ 88 | export interface ItemMachineDefinitionEvents { 89 | onStorageSet?: ItemMachineEventCallback; 90 | } 91 | 92 | /** 93 | * @beta 94 | */ 95 | export type ItemMachineEventName = keyof ItemMachineDefinitionEvents; 96 | 97 | // definition 98 | 99 | /** 100 | * @beta 101 | */ 102 | export type ItemMachineCallbackName = 103 | | ItemMachineHandlerName 104 | | ItemMachineEventName; 105 | 106 | /** 107 | * @beta 108 | */ 109 | export interface ItemMachineDefinition { 110 | description: ItemMachineDefinitionDescription; 111 | handlers?: ItemMachineDefinitionHandlers; 112 | events?: ItemMachineDefinitionEvents; 113 | } 114 | -------------------------------------------------------------------------------- /docs/guides/machine-ui.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Machine UI 3 | --- 4 | 5 | # Machine UI 6 | 7 | Machine UIs should be created using JSON UI and elements should be defined in the machine definition so Bedrock Energistics Core can handle the UI backend. This guide will not go over JSON UI, you can learn more about that [here](https://wiki.bedrock.dev/json-ui/json-ui-intro.html). 8 | 9 | Machine UIs are actually just entity inventories. Each element takes up a certain amount of slots. Make sure that your machine entity has the proper inventory size. 10 | 11 | ## Shared JSON UI Controls 12 | 13 | Bedrock Energistics Core provides some JSON UI controls that your UIs can use. Their source can be found [here](https://github.com/Fluffyalien1422/bedrock-energistics-core/blob/main/packs/RP/ui/fluffyalien/energisticscore/common.json) (you do not need to copy it into your add-on). 14 | 15 | - `fluffyalien_energisticscore:common.container_title` - A label that can be used for your machine title. Define the text with the `text` property. 16 | - `fluffyalien_energisticscore:common.container_item_slot` - An item slot. Set the slot index with the `$index` variable. 17 | - `fluffyalien_energisticscore:common.container_item_slot_nobg` - An item slot with no background. Set the slot index with the `$index` variable. 18 | - `fluffyalien_energisticscore:common.container_slot_icon` - An item slot for icons (such as progress indicators). This slot cannot be selected and has no background. Set the slot index with the `$index` variable. 19 | - `fluffyalien_energisticscore:common.machine_storage_bar` - Four item slots placed vertically for storage bars. Set the starting slot index with the `$start_index` variable. 20 | 21 | ## Updating UI Elements 22 | 23 | Machine definitions can have an `updateUi` handler. This is a function that is called regularly and returns an object defining the state of storage bars and progress indicators. 24 | 25 | ## Storage Bars 26 | 27 | Storage bars indicate how much of a specific storage type are in the machine. These elements take up four slots. The storage type that each bar displays should be set using the `updateUi` handler. If the `updateUi` handler does not reference a storage bar, then it will show up as "Disabled". 28 | 29 | ## Progress Indicators 30 | 31 | Progress indicators can be an arrow or a flame (the indicators from the Minecraft furnace). They take up one slot. The progress value should be set using the `updateUi` handler. Note that the minimum progress value is 0 and the maximum value varies depending on the indicator type. 32 | 33 | Indicator max progress values: 34 | 35 | - Arrow: 16 36 | - Flame: 13 37 | 38 | ## Other Elements 39 | 40 | Some elements are not listed here. Find more information on these elements in the reference documentation. All other elements use one slot. 41 | -------------------------------------------------------------------------------- /public_api/src/machine_ui_elements.ts: -------------------------------------------------------------------------------- 1 | import { UiElementDefinition } from "./machine_registry_types.js"; 2 | 3 | /** 4 | * Represents the UI elements of a machine. 5 | * @beta 6 | */ 7 | export class MachineUiElements 8 | implements Iterable<[string, UiElementDefinition]> 9 | { 10 | constructor(private readonly elements: Record) {} 11 | 12 | /** 13 | * Test if a UI element with the given ID exists. 14 | * @beta 15 | * @returns A boolean indicating whether the UI element with the specified ID exists. 16 | */ 17 | has(id: string): boolean { 18 | return Object.hasOwn(this.elements, id); 19 | } 20 | 21 | /** 22 | * Gets a UI element by its ID. 23 | * @beta 24 | * @param id The ID of the UI element to get. 25 | * @returns A deep copy of the UI element with the specified ID, or `undefined` if it doesn't exist. 26 | */ 27 | get(id: string): UiElementDefinition | undefined { 28 | if (!this.has(id)) return; 29 | const element = this.elements[id]; 30 | 31 | // deep copy the object to prevent mutation of the local cache 32 | return JSON.parse(JSON.stringify(element)) as UiElementDefinition; 33 | } 34 | 35 | /** 36 | * Gets the IDs of all the UI elements. 37 | * @beta 38 | * @returns An array containing the IDs of all the UI elements. 39 | */ 40 | ids(): string[] { 41 | return Object.keys(this.elements); 42 | } 43 | 44 | /** 45 | * Creates an iterable of the definitions of all the UI elements. 46 | * @beta 47 | * @returns An iterable of the definitions of all the UI elements. 48 | */ 49 | definitions(): Iterable { 50 | return { 51 | [Symbol.iterator]: (): Iterator => { 52 | const ids = this.ids(); 53 | let index = 0; 54 | return { 55 | next: (): IteratorResult => { 56 | if (index < ids.length) { 57 | const id = ids[index++]; 58 | const value = this.get(id)!; 59 | return { value, done: false }; 60 | } 61 | return { value: undefined, done: true }; 62 | }, 63 | }; 64 | }, 65 | }; 66 | } 67 | 68 | /** 69 | * Enables iteration over the UI elements. 70 | * @beta 71 | */ 72 | [Symbol.iterator](): Iterator<[string, UiElementDefinition]> { 73 | const ids = this.ids(); 74 | let index = 0; 75 | return { 76 | next: (): IteratorResult<[string, UiElementDefinition]> => { 77 | if (index < ids.length) { 78 | const id = ids[index++]; 79 | const value = this.get(id)!; 80 | return { value: [id, value], done: false }; 81 | } 82 | return { value: undefined, done: true }; 83 | }, 84 | }; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /docs/guides/machine-io.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Machine I/O 3 | --- 4 | 5 | # Machine I/O 6 | 7 | > [!note] 8 | > Remember to update the machine's networks using if any tags change. The simplest way to manually trigger a network update is with the [MachineNetwork.updateWithBlock](https://fluffyalien1422.github.io/bedrock-energistics-core/api/classes/API.MachineNetwork.html#updateWithBlock) function. 9 | 10 | ## Network Connection 11 | 12 | In order for a machine to connect to a machine network, it must be adjacent and share one or more I/O types with that network. 13 | To define which I/O types your machine uses, use the `fluffyalien_energisticscore:io.` tag. 14 | 15 | **Syntax (Default):** `fluffyalien_energisticscore:io.any | fluffyalien_energisticscore:io.{type|category}.XYZ` 16 | 17 | For example, use the `fluffyalien_energisticscore:io.type.energy` tag to make your machine connect to networks that share the `energy` storage type, or `fluffyalien_energisticscore:io.category.gas` to make your machine connect to networks that share any storage type with the `gas` category. 18 | 19 | ### Explicit Sides 20 | 21 | **Syntax (Explicit Sides):** `fluffyalien_energisticscore:io.any.{north|east|south|west|up|down|side|network_link} | fluffyalien_energisticscore:io.{type|category}.XYZ.{north|east|south|west|up|down|side|network_link}` 22 | 23 | `.{north|east|south|west|up|down|side|network_link}` can be appended at the end of the tag to only connect to networks that from specific directions. This will only be parsed if the `fluffyalien_energisticscore:explicit_sides` tag is added as well. 24 | 25 | For example, use the `fluffyalien_energisticscore:io.type.energy.north` to only connect to adjacent `energy` networks if they connect to the north face of this block. 26 | 27 | The `network_link` value is used when the block is connected wirelessly via a [network link](./network-links.md). 28 | 29 | ## Consumer 30 | 31 | In order for machine networks to allocate something to your machine, you need to define it using the `fluffyalien_energistics:consumer.` tag. 32 | 33 | **Syntax:** `fluffyalien_energisticscore:consumer.any | fluffyalien_energisticscore:consumer.{type|category}.XYZ` 34 | 35 | For example, use the `fluffyalien_energisticscore:consumer.type.energy` tag to make machine networks send energy to your machine. Use the `fluffyalien_energisticscore:consumer.category.gas` tag to make machine networks send any storage type with the `gas` category to your machine. 36 | 37 | > [!note] 38 | > Your machine must be connected to a network in the first place to have that network allocate storage types to it. For example, a machine that consumes `energy` must have both the `fluffyalien_energisticscore:io.type.energy` to connect to an `energy` network and `fluffyalien_energisticscore:consumer.type.energy` to tell that network to send `energy` to this machine. 39 | -------------------------------------------------------------------------------- /packs/BP/scripts/item_machine_registry.ts: -------------------------------------------------------------------------------- 1 | import * as ipc from "mcbe-addon-ipc"; 2 | import { 3 | ItemMachineGetIoResponse, 4 | RegisteredItemMachine, 5 | } from "@/public_api/src"; 6 | import { SerializableContainerSlot } from "@/public_api/src/serialize_utils"; 7 | import { logWarn, raise } from "./utils/log"; 8 | import { ipcInvoke, ipcSend } from "./ipc_wrapper"; 9 | import { 10 | ItemMachineOnStorageSetPayload, 11 | RegisteredItemMachineData, 12 | } from "@/public_api/src/item_machine_registry_internal"; 13 | 14 | const itemMachineRegistry = new Map(); 15 | 16 | // @ts-expect-error extending private class for internal use 17 | export class InternalRegisteredItemMachine extends RegisteredItemMachine { 18 | // override to make public 19 | public constructor(data: RegisteredItemMachineData) { 20 | super(data); 21 | } 22 | 23 | getData(): RegisteredItemMachineData { 24 | return this.data; 25 | } 26 | 27 | invokeGetIoHandler( 28 | serializableSlot: SerializableContainerSlot, 29 | ): Promise { 30 | if (!this.data.getIoHandler) { 31 | raise(`Trying to call the 'getIo' handler but it is not defined.`); 32 | } 33 | 34 | return ipcInvoke( 35 | this.data.getIoHandler, 36 | serializableSlot.toJson(), 37 | ) as Promise; 38 | } 39 | 40 | callOnStorageSetEvent( 41 | serializableSlot: SerializableContainerSlot, 42 | type: string, 43 | value: number, 44 | ): void { 45 | if (!this.data.onStorageSetEvent) { 46 | raise(`Trying to call the 'onStorageSet' event but it is not defined.`); 47 | } 48 | 49 | const payload: ItemMachineOnStorageSetPayload = { 50 | slot: serializableSlot.toJson(), 51 | type, 52 | value, 53 | }; 54 | 55 | ipcSend(this.data.onStorageSetEvent, payload); 56 | } 57 | 58 | static getInternal(id: string): InternalRegisteredItemMachine | undefined { 59 | return itemMachineRegistry.get(id); 60 | } 61 | 62 | static forceGetInternal(id: string): InternalRegisteredItemMachine { 63 | const registered = InternalRegisteredItemMachine.getInternal(id); 64 | if (!registered) { 65 | raise( 66 | `Expected '${id}' to be registered as an item machine, but it could not be found in the item machine registry.`, 67 | ); 68 | } 69 | return registered; 70 | } 71 | } 72 | 73 | function registerItemMachine(data: RegisteredItemMachineData): void { 74 | if (itemMachineRegistry.has(data.id)) { 75 | logWarn(`Overrode item machine '${data.id}'.`); 76 | } 77 | 78 | const registered = new InternalRegisteredItemMachine(data); 79 | itemMachineRegistry.set(data.id, registered); 80 | } 81 | 82 | export function registerItemMachineListener( 83 | payload: ipc.SerializableValue, 84 | ): null { 85 | registerItemMachine(payload as RegisteredItemMachineData); 86 | return null; 87 | } 88 | -------------------------------------------------------------------------------- /public_api/src/machine_registry_internal.ts: -------------------------------------------------------------------------------- 1 | import { DimensionLocation } from "@minecraft/server"; 2 | import { 3 | NetworkStorageTypeData, 4 | UiElementDefinition, 5 | } from "./machine_registry_types.js"; 6 | import { 7 | makeSerializableDimensionLocation, 8 | SerializableDimensionLocation, 9 | } from "./serialize_utils.js"; 10 | import { raise } from "./log.js"; 11 | import { RegisteredMachine } from "./machine_registry.js"; 12 | import { ipcSendAny } from "./ipc_wrapper.js"; 13 | 14 | /** 15 | * @internal 16 | */ 17 | export interface RegisteredMachineData { 18 | // definition 19 | id: string; 20 | entityId?: string; 21 | persistentEntity?: boolean; 22 | maxStorage?: number; 23 | uiElements?: Record; 24 | // script events 25 | updateUiEvent?: string; 26 | receiveHandlerEvent?: string; 27 | onButtonPressedEvent?: string; 28 | networkStatEvent?: string; 29 | onStorageSetEvent?: string; 30 | } 31 | 32 | /** 33 | * @internal 34 | */ 35 | export interface IpcMachineCallbackArg { 36 | blockLocation: SerializableDimensionLocation; 37 | } 38 | 39 | /** 40 | * @internal 41 | */ 42 | export interface IpcRecieveHandlerPayload extends IpcMachineCallbackArg { 43 | recieveType: string; 44 | recieveAmount: number; 45 | } 46 | 47 | /** 48 | * @internal 49 | */ 50 | export interface IpcOnButtonPressedPayload extends IpcMachineCallbackArg { 51 | playerId: string; 52 | entityId: string; 53 | elementId: string; 54 | } 55 | 56 | /** 57 | * @internal 58 | */ 59 | export interface IpcNetworkStatsEventArg extends IpcMachineCallbackArg { 60 | networkData: Record; 61 | } 62 | 63 | /** 64 | * @internal 65 | */ 66 | export interface IpcMachineUpdateUiHandlerArg extends IpcMachineCallbackArg { 67 | entityId: string; 68 | } 69 | 70 | /** 71 | * @internal 72 | */ 73 | export interface IpcMachineOnStorageSetEventArg extends IpcMachineCallbackArg { 74 | type: string; 75 | value: number; 76 | } 77 | 78 | /** 79 | * @internal 80 | */ 81 | export function callMachineOnStorageSetEvent( 82 | registeredMachine: RegisteredMachine, 83 | blockLocation: DimensionLocation, 84 | type: string, 85 | value: number, 86 | ): void { 87 | // There is a similar function to this in the add-on. 88 | // Make sure changes are reflected in both. 89 | 90 | // @ts-expect-error internal functions are allowed to access internal properties 91 | const data = registeredMachine.data; 92 | 93 | if (!data.onStorageSetEvent) { 94 | raise("Trying to call the 'onStorageSet' event but it is not defined."); 95 | } 96 | 97 | const payload: IpcMachineOnStorageSetEventArg = { 98 | blockLocation: makeSerializableDimensionLocation(blockLocation), 99 | type, 100 | value, 101 | }; 102 | 103 | ipcSendAny(data.onStorageSetEvent, payload); 104 | } 105 | -------------------------------------------------------------------------------- /docs/api/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #0451A5; 9 | --dark-hl-3: #9CDCFE; 10 | --light-hl-4: #008000; 11 | --dark-hl-4: #6A9955; 12 | --light-hl-5: #0000FF; 13 | --dark-hl-5: #569CD6; 14 | --light-hl-6: #098658; 15 | --dark-hl-6: #B5CEA8; 16 | --light-hl-7: #AF00DB; 17 | --dark-hl-7: #C586C0; 18 | --light-hl-8: #001080; 19 | --dark-hl-8: #9CDCFE; 20 | --light-hl-9: #0070C1; 21 | --dark-hl-9: #4FC1FF; 22 | --light-code-background: #FFFFFF; 23 | --dark-code-background: #1E1E1E; 24 | } 25 | 26 | @media (prefers-color-scheme: light) { :root { 27 | --hl-0: var(--light-hl-0); 28 | --hl-1: var(--light-hl-1); 29 | --hl-2: var(--light-hl-2); 30 | --hl-3: var(--light-hl-3); 31 | --hl-4: var(--light-hl-4); 32 | --hl-5: var(--light-hl-5); 33 | --hl-6: var(--light-hl-6); 34 | --hl-7: var(--light-hl-7); 35 | --hl-8: var(--light-hl-8); 36 | --hl-9: var(--light-hl-9); 37 | --code-background: var(--light-code-background); 38 | } } 39 | 40 | @media (prefers-color-scheme: dark) { :root { 41 | --hl-0: var(--dark-hl-0); 42 | --hl-1: var(--dark-hl-1); 43 | --hl-2: var(--dark-hl-2); 44 | --hl-3: var(--dark-hl-3); 45 | --hl-4: var(--dark-hl-4); 46 | --hl-5: var(--dark-hl-5); 47 | --hl-6: var(--dark-hl-6); 48 | --hl-7: var(--dark-hl-7); 49 | --hl-8: var(--dark-hl-8); 50 | --hl-9: var(--dark-hl-9); 51 | --code-background: var(--dark-code-background); 52 | } } 53 | 54 | :root[data-theme='light'] { 55 | --hl-0: var(--light-hl-0); 56 | --hl-1: var(--light-hl-1); 57 | --hl-2: var(--light-hl-2); 58 | --hl-3: var(--light-hl-3); 59 | --hl-4: var(--light-hl-4); 60 | --hl-5: var(--light-hl-5); 61 | --hl-6: var(--light-hl-6); 62 | --hl-7: var(--light-hl-7); 63 | --hl-8: var(--light-hl-8); 64 | --hl-9: var(--light-hl-9); 65 | --code-background: var(--light-code-background); 66 | } 67 | 68 | :root[data-theme='dark'] { 69 | --hl-0: var(--dark-hl-0); 70 | --hl-1: var(--dark-hl-1); 71 | --hl-2: var(--dark-hl-2); 72 | --hl-3: var(--dark-hl-3); 73 | --hl-4: var(--dark-hl-4); 74 | --hl-5: var(--dark-hl-5); 75 | --hl-6: var(--dark-hl-6); 76 | --hl-7: var(--dark-hl-7); 77 | --hl-8: var(--dark-hl-8); 78 | --hl-9: var(--dark-hl-9); 79 | --code-background: var(--dark-code-background); 80 | } 81 | 82 | .hl-0 { color: var(--hl-0); } 83 | .hl-1 { color: var(--hl-1); } 84 | .hl-2 { color: var(--hl-2); } 85 | .hl-3 { color: var(--hl-3); } 86 | .hl-4 { color: var(--hl-4); } 87 | .hl-5 { color: var(--hl-5); } 88 | .hl-6 { color: var(--hl-6); } 89 | .hl-7 { color: var(--hl-7); } 90 | .hl-8 { color: var(--hl-8); } 91 | .hl-9 { color: var(--hl-9); } 92 | pre, code { background: var(--code-background); } 93 | -------------------------------------------------------------------------------- /packs/BP/scripts/storage_type_registry.ts: -------------------------------------------------------------------------------- 1 | import * as ipc from "mcbe-addon-ipc"; 2 | import { world } from "@minecraft/server"; 3 | import { logWarn, raise } from "./utils/log"; 4 | import { 5 | RegisteredStorageType, 6 | STANDARD_STORAGE_TYPE_DEFINITIONS, 7 | StorageTypeDefinition, 8 | } from "@/public_api/src"; 9 | 10 | const storageTypeRegistry = new Map(); 11 | 12 | // @ts-expect-error extending private class for internal use 13 | export class InternalRegisteredStorageType extends RegisteredStorageType { 14 | // override to make it public 15 | public constructor(definition: StorageTypeDefinition) { 16 | super(definition); 17 | } 18 | 19 | getDefinition(): StorageTypeDefinition { 20 | return this.definition; 21 | } 22 | 23 | static getInternal(id: string): InternalRegisteredStorageType | undefined { 24 | return storageTypeRegistry.get(id); 25 | } 26 | 27 | static getAllIdsInternal(): MapIterator { 28 | return storageTypeRegistry.keys(); 29 | } 30 | 31 | static forceGetInternal(id: string): InternalRegisteredStorageType { 32 | const registered = InternalRegisteredStorageType.getInternal(id); 33 | if (!registered) { 34 | raise( 35 | `Expected '${id}' to be registered as a storage type, but it could not be found in the storage type registry.`, 36 | ); 37 | } 38 | return registered; 39 | } 40 | } 41 | 42 | // register energy by default 43 | world.afterEvents.worldLoad.subscribe(() => { 44 | registerStorageType(STANDARD_STORAGE_TYPE_DEFINITIONS.energy); 45 | }); 46 | 47 | function registerStorageType(data: StorageTypeDefinition): void { 48 | const existing = storageTypeRegistry.get(data.id); 49 | 50 | if (existing !== undefined) { 51 | if (existing.category !== data.category) { 52 | logWarn( 53 | `Overrode category of storage type '${data.id}', originally was '${existing.category}', now is '${data.category}'.`, 54 | ); 55 | } 56 | 57 | if (existing.texture !== data.texture) { 58 | logWarn( 59 | `Overrode color of storage type '${data.id}', originally was '${JSON.stringify(existing.texture)}', now is '${JSON.stringify(data.texture)}'.`, 60 | ); 61 | } 62 | 63 | if (existing.name !== data.name) { 64 | logWarn( 65 | `Overrode name of storage type '${data.id}', originally was '${existing.name}', now is '${data.name}'.`, 66 | ); 67 | } 68 | } 69 | 70 | const registered = new InternalRegisteredStorageType(data); 71 | storageTypeRegistry.set(data.id, registered); 72 | 73 | const objectiveId = `fluffyalien_energisticscore:storage${data.id}`; 74 | 75 | if (!world.scoreboard.getObjective(objectiveId)) { 76 | world.scoreboard.addObjective(objectiveId); 77 | } 78 | } 79 | 80 | export function registerStorageTypeListener( 81 | payload: ipc.SerializableValue, 82 | ): null { 83 | const data = payload as StorageTypeDefinition; 84 | registerStorageType(data); 85 | return null; 86 | } 87 | -------------------------------------------------------------------------------- /public_api/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bedrock-energistics-core-api", 3 | "version": "0.8.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bedrock-energistics-core-api", 9 | "version": "0.8.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@minecraft/math": "^2.2.7", 13 | "mcbe-addon-ipc": "^0.9.0" 14 | }, 15 | "devDependencies": { 16 | "@minecraft/server": "2.0.0", 17 | "@minecraft/vanilla-data": ">=1.21.90" 18 | }, 19 | "peerDependencies": { 20 | "@minecraft/server": "2.0.0", 21 | "@minecraft/vanilla-data": ">=1.21.90" 22 | } 23 | }, 24 | "../../Addon-Deps/mcbe-addon-ipc": { 25 | "version": "0.8.1", 26 | "extraneous": true, 27 | "license": "ISC", 28 | "devDependencies": { 29 | "@eslint/js": "^9.26.0", 30 | "@minecraft/server": "2.0.0", 31 | "eslint": "^9.26.0", 32 | "prettier": "^3.5.3", 33 | "typedoc": "^0.28.4", 34 | "typescript": "^5.8.3", 35 | "typescript-eslint": "^8.31.1" 36 | }, 37 | "peerDependencies": { 38 | "@minecraft/server": "^2.0.0" 39 | } 40 | }, 41 | "../../Addon-Deps/minecraft-scripting-libraries/libraries/math": { 42 | "name": "scripting-2.0.0-math", 43 | "version": "3.0.1", 44 | "extraneous": true, 45 | "license": "MIT", 46 | "devDependencies": { 47 | "@minecraft/core-build-tasks": "*", 48 | "@minecraft/server": "^2.0.0", 49 | "@minecraft/tsconfig": "*", 50 | "just-scripts": "^2.3.3", 51 | "prettier": "^3.5.3", 52 | "vitest": "^3.0.8" 53 | }, 54 | "peerDependencies": { 55 | "@minecraft/server": "^2.0.0" 56 | } 57 | }, 58 | "node_modules/@minecraft/common": { 59 | "version": "1.2.0", 60 | "license": "MIT" 61 | }, 62 | "node_modules/@minecraft/math": { 63 | "version": "2.2.7", 64 | "resolved": "https://registry.npmjs.org/@minecraft/math/-/math-2.2.7.tgz", 65 | "integrity": "sha512-7olbdWXp9aCAIPrNNHcUAlG2MQx9X7qCanC6SSkpvi8uiOkPI/1mErndJfhA8gETTIGjvibCiTUsT6SJ5/qtmw==", 66 | "license": "MIT", 67 | "peerDependencies": { 68 | "@minecraft/server": "^1.15.0 || ^2.0.0" 69 | } 70 | }, 71 | "node_modules/@minecraft/server": { 72 | "version": "2.0.0", 73 | "license": "MIT", 74 | "dependencies": { 75 | "@minecraft/common": "^1.1.0" 76 | }, 77 | "peerDependencies": { 78 | "@minecraft/vanilla-data": ">=1.20.70" 79 | } 80 | }, 81 | "node_modules/@minecraft/vanilla-data": { 82 | "version": "1.21.90", 83 | "license": "MIT" 84 | }, 85 | "node_modules/mcbe-addon-ipc": { 86 | "version": "0.9.0", 87 | "resolved": "https://registry.npmjs.org/mcbe-addon-ipc/-/mcbe-addon-ipc-0.9.0.tgz", 88 | "integrity": "sha512-2vZga0KPfy7I93YFQLkYZ+kyZBML6ixmN01aSX5yvqTTKDJrEOJIOV9HB2ilVgadnM4+NJhG9sCeamwV2RxzFg==", 89 | "license": "ISC", 90 | "peerDependencies": { 91 | "@minecraft/server": "^2.0.0" 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packs/BP/scripts/item_machine_ipc.ts: -------------------------------------------------------------------------------- 1 | import * as ipc from "mcbe-addon-ipc"; 2 | import { 3 | GetItemMachineStoragePayload, 4 | ItemMachineFuncPayload, 5 | SetItemMachineStoragePayload, 6 | } from "@/public_api/src/item_machine_internal"; 7 | import { SerializableContainerSlot } from "@/public_api/src/serialize_utils"; 8 | import { InternalRegisteredStorageType } from "./storage_type_registry"; 9 | import { InternalRegisteredItemMachine } from "./item_machine_registry"; 10 | import { IoCapabilitiesData } from "@/public_api/src"; 11 | 12 | export function getItemMachineStorageHandler( 13 | payloadRaw: ipc.SerializableValue, 14 | ): number { 15 | const payload = payloadRaw as GetItemMachineStoragePayload; 16 | 17 | // ensure the storage type exists 18 | InternalRegisteredStorageType.forceGetInternal(payload.type); 19 | 20 | const containerSlot = SerializableContainerSlot.fromJson( 21 | payload.slot, 22 | ).toContainerSlot(); 23 | 24 | return ( 25 | (containerSlot.getDynamicProperty( 26 | `item_machine_storage_${payload.type}`, 27 | ) as number | undefined) ?? 0 28 | ); 29 | } 30 | 31 | export function setItemMachineStorageListener( 32 | payloadRaw: ipc.SerializableValue, 33 | ): null { 34 | const payload = payloadRaw as SetItemMachineStoragePayload; 35 | 36 | // ensure the storage type exists 37 | InternalRegisteredStorageType.forceGetInternal(payload.type); 38 | 39 | const serializableSlot = SerializableContainerSlot.fromJson(payload.slot); 40 | const containerSlot = serializableSlot.toContainerSlot(); 41 | 42 | const itemMachine = InternalRegisteredItemMachine.forceGetInternal( 43 | containerSlot.typeId, 44 | ); 45 | 46 | containerSlot.setDynamicProperty( 47 | `item_machine_storage_${payload.type}`, 48 | payload.value, 49 | ); 50 | 51 | if (itemMachine.hasCallback("onStorageSet")) { 52 | itemMachine.callOnStorageSetEvent( 53 | serializableSlot, 54 | payload.type, 55 | payload.value, 56 | ); 57 | } 58 | 59 | return null; 60 | } 61 | 62 | export async function getItemMachineIoHandler( 63 | payloadRaw: ipc.SerializableValue, 64 | ): Promise { 65 | const payload = payloadRaw as ItemMachineFuncPayload; 66 | 67 | const serializableContainerSlot = SerializableContainerSlot.fromJson( 68 | payload.slot, 69 | ); 70 | 71 | const containerSlot = serializableContainerSlot.toContainerSlot(); 72 | 73 | const registeredItemMachine = InternalRegisteredItemMachine.forceGetInternal( 74 | containerSlot.typeId, 75 | ); 76 | 77 | const registeredItemMachineData = registeredItemMachine.getData(); 78 | 79 | const io = registeredItemMachineData.getIoHandler 80 | ? await registeredItemMachine.invokeGetIoHandler(serializableContainerSlot) 81 | : {}; 82 | 83 | if ( 84 | io.acceptsAny === true || 85 | (io.acceptsAny === undefined && 86 | registeredItemMachineData.defaultIo?.acceptsAny) 87 | ) { 88 | return { 89 | onlyAllowConduitConnections: false, 90 | acceptsAny: true, 91 | categories: [], 92 | types: [], 93 | }; 94 | } 95 | 96 | return { 97 | onlyAllowConduitConnections: false, 98 | acceptsAny: false, 99 | categories: 100 | io.categories ?? registeredItemMachineData.defaultIo?.categories ?? [], 101 | types: io.types ?? registeredItemMachineData.defaultIo?.types ?? [], 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /CODING_GUIDELINES.md: -------------------------------------------------------------------------------- 1 | # Coding Guidelines 2 | 3 | Please follow these guidelines when contributing code to Bedrock Energistics Core. 4 | 5 | This style guide uses elements from the [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html) and [TypeScript's Contributor Coding Guidelines](https://github.com/microsoft/TypeScript/wiki/Coding-guidelines). 6 | 7 | ## Naming 8 | 9 | - Do not use trailing or leading underscores. 10 | - Do not prefix interfaces with `I`. 11 | - Avoid abbreviations where possible. 12 | - Identifiers must only use ASCII letters, digits, and underscores. 13 | - Treat abbreviations in names as whole words. (eg. `loadHttpUrl`, not `loadHTTPUrl`). 14 | - Type parameters must be prefixed with `T`. 15 | - Do not use one letter names for type parameters unless there is only one type parameter, in which case, it _may_ use the single letter name `T`. 16 | - Use `PascalCase` for classes, interfaces, types, enums, enum members, type parameters. 17 | - Use `camelCase` for variables, parameters, functions, methods, properties, and module aliases. 18 | - Use `SCREAMING_SNAKE_CASE` for global constant values. 19 | - Use `snake_case` for file names. 20 | 21 | ## Constants 22 | 23 | - `SCREAMING_SNAKE_CASE` indicates that a value should be considered deeply immutable. For example, if an object is named with `SCREAMING_SNAKE_CASE` then it's properties should not be modified, even if they are technically mutable. 24 | - A constant can also be a `static readonly` property of a class. 25 | - Only global (module level and `static` fields of module level classes) symbols _may_ use `SCREAMING_SNAKE_CASE`. 26 | 27 | ## Comments 28 | 29 | - Use `/** TSDoc */` comments for documentation, i.e. comments a user of the code should read. 30 | - Use `// line comments` for implementation comments, i.e. comments that only concern the implementation of the code itself. 31 | - Do not use nonstandard tags in doc comments. See [TSDoc tags](https://tsdoc.org/pages/tags/alpha/). 32 | 33 | ## `null` and `undefined` 34 | 35 | - Prefer using `undefined` instead of `null` in most cases. 36 | 37 | ## `for..of` and `forEach` 38 | 39 | - Prefer using `for..of` instead of `Array.prototype.forEach` in most cases. 40 | 41 | ## Private Fields 42 | 43 | - Do not use private fields (`#myProperty`). Use the `private` keyword instead (`private myProperty`). 44 | 45 | ## Accessors 46 | 47 | - If an accessor is used to wrap a class property, the wrapped property _may_ be prefixed with `internal`. 48 | 49 | ```ts 50 | class Foo { 51 | private internalBar = ""; 52 | get bar() { 53 | return this.internalBar; 54 | } 55 | } 56 | ``` 57 | 58 | Note: use the `readonly` keyword where possible instead of simply creating a getter with no setter. 59 | 60 | ## Diagnostic Messsages 61 | 62 | - Use `logInfo`, `logWarn`, and `raise` from `packs/BP/scripts/utils/log.ts` for all logging purposes within the Bedrock Energistics Core add-on (not the public API). 63 | - Use `logInfo`, `logWarn`, and `raise` from `public_api/src/log.ts` for all logging purposes within the Bedrock Energistics Core public API (not the add-on). 64 | - Diagnostic messages should be clear and grammatically correct (start with a capital letter, end with period, etc). Definite entities (variable names, IDs, etc) should be surrounded in single quotes (eg. "The entity 'example:entity' ..."). 65 | 66 | ## Other 67 | 68 | - ESLint and Prettier will enforce other style guidelines. Remember to check (`npm run check`) and format (`npm run fmt`) your code before pushing. 69 | -------------------------------------------------------------------------------- /public_api/src/serialize_utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BlockComponentTypes, 3 | BlockInventoryComponent, 4 | ContainerSlot, 5 | DimensionLocation, 6 | EntityComponentTypes, 7 | EntityInventoryComponent, 8 | Vector3, 9 | world, 10 | } from "@minecraft/server"; 11 | import { raise } from "./log.js"; 12 | import { Vector3Utils } from "@minecraft/math"; 13 | 14 | /** 15 | * @internal 16 | */ 17 | export interface SerializableDimensionLocation extends Vector3 { 18 | /** 19 | * dimension id 20 | */ 21 | d: string; 22 | } 23 | 24 | /** 25 | * @internal 26 | */ 27 | export function makeSerializableDimensionLocation( 28 | loc: DimensionLocation, 29 | ): SerializableDimensionLocation { 30 | return { 31 | d: loc.dimension.id, 32 | x: loc.x, 33 | y: loc.y, 34 | z: loc.z, 35 | }; 36 | } 37 | 38 | /** 39 | * @internal 40 | */ 41 | export function deserializeDimensionLocation( 42 | loc: SerializableDimensionLocation, 43 | ): DimensionLocation { 44 | return { 45 | dimension: world.getDimension(loc.d), 46 | x: loc.x, 47 | y: loc.y, 48 | z: loc.z, 49 | }; 50 | } 51 | 52 | /** 53 | * @internal 54 | */ 55 | export enum SerializableContainerSlotActorType { 56 | Entity, 57 | Block, 58 | } 59 | 60 | /** 61 | * @internal 62 | */ 63 | export interface SerializableContainerSlotJson { 64 | slot: number; 65 | actor: 66 | | { 67 | type: SerializableContainerSlotActorType.Block; 68 | loc: SerializableDimensionLocation; 69 | } 70 | | { 71 | type: SerializableContainerSlotActorType.Entity; 72 | id: string; 73 | }; 74 | } 75 | 76 | /** 77 | * @internal 78 | */ 79 | export class SerializableContainerSlot { 80 | constructor( 81 | readonly inventory: BlockInventoryComponent | EntityInventoryComponent, 82 | readonly slot: number, 83 | ) {} 84 | 85 | toContainerSlot(): ContainerSlot { 86 | if (!this.inventory.container) { 87 | raise("This inventory is no longer valid."); 88 | } 89 | 90 | return this.inventory.container.getSlot(this.slot); 91 | } 92 | 93 | toJson(): SerializableContainerSlotJson { 94 | if (this.inventory instanceof BlockInventoryComponent) { 95 | return { 96 | slot: this.slot, 97 | actor: { 98 | type: SerializableContainerSlotActorType.Block, 99 | loc: makeSerializableDimensionLocation(this.inventory.block), 100 | }, 101 | }; 102 | } 103 | 104 | return { 105 | slot: this.slot, 106 | actor: { 107 | type: SerializableContainerSlotActorType.Entity, 108 | id: this.inventory.entity.id, 109 | }, 110 | }; 111 | } 112 | 113 | static fromJson( 114 | obj: SerializableContainerSlotJson, 115 | ): SerializableContainerSlot { 116 | if (obj.actor.type === SerializableContainerSlotActorType.Block) { 117 | const loc = deserializeDimensionLocation(obj.actor.loc); 118 | const block = loc.dimension.getBlock(loc); 119 | if (!block) { 120 | raise( 121 | `Could not get block at ${Vector3Utils.toString(loc)} in '${loc.dimension.id}'.`, 122 | ); 123 | } 124 | 125 | const inv = block.getComponent(BlockComponentTypes.Inventory)!; 126 | 127 | return new SerializableContainerSlot(inv, obj.slot); 128 | } 129 | 130 | const entity = world.getEntity(obj.actor.id); 131 | if (!entity) { 132 | raise(`Could not get entity with ID '${obj.actor.id}'.`); 133 | } 134 | 135 | const inv = entity.getComponent(EntityComponentTypes.Inventory)!; 136 | 137 | return new SerializableContainerSlot(inv, obj.slot); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /public_api/src/standard_storage_types.ts: -------------------------------------------------------------------------------- 1 | import { StorageTypeDefinition } from "./storage_type_registry_types.js"; 2 | import { registerStorageType } from "./storage_type_registry.js"; 3 | 4 | /** 5 | * An enumeration of the standard storage type categories. 6 | * @beta 7 | */ 8 | export enum StandardStorageCategory { 9 | /** 10 | * A category for the `energy` storage type. 11 | */ 12 | Energy = "energy", 13 | /** 14 | * A category for gaseous substances. 15 | */ 16 | Gas = "gas", 17 | /** 18 | * A category for liquid substances. 19 | */ 20 | Fluid = "fluid", 21 | } 22 | 23 | /** 24 | * An enumeration of the standard storage types. 25 | * @beta 26 | */ 27 | export enum StandardStorageType { 28 | Energy = "energy", 29 | Lava = "lava", 30 | /** 31 | * Ammonia in it's liquid form. {@link StandardStorageType.Ammonia} refers to the gaseous form of ammonia. 32 | */ 33 | LiquidAmmonia = "liquid_ammonia", 34 | /** 35 | * Petroleum or crude oil. 36 | */ 37 | Oil = "oil", 38 | Water = "water", 39 | /** 40 | * Ammonia in it's gaseous form. {@link StandardStorageType.LiquidAmmonia} refers to the liquid form of ammonia. 41 | */ 42 | Ammonia = "ammonia", 43 | /** 44 | * Carbon dioxide. 45 | */ 46 | Carbon = "carbon", 47 | Hydrogen = "hydrogen", 48 | Nitrogen = "nitrogen", 49 | Oxygen = "oxygen", 50 | /** 51 | * Water vapor. 52 | */ 53 | Steam = "steam", 54 | } 55 | 56 | /** 57 | * Definitions for all standard storage types. 58 | * @beta 59 | * @see {@link StandardStorageType} 60 | */ 61 | export const STANDARD_STORAGE_TYPE_DEFINITIONS: Record< 62 | StandardStorageType, 63 | StorageTypeDefinition 64 | > = { 65 | energy: { 66 | category: StandardStorageCategory.Energy, 67 | texture: "yellow", 68 | id: StandardStorageType.Energy, 69 | name: "energy", 70 | }, 71 | lava: { 72 | category: StandardStorageCategory.Fluid, 73 | texture: "red", 74 | id: StandardStorageType.Lava, 75 | name: "lava", 76 | }, 77 | liquid_ammonia: { 78 | category: StandardStorageCategory.Fluid, 79 | texture: "orange", 80 | id: StandardStorageType.LiquidAmmonia, 81 | name: "liquid ammonia", 82 | }, 83 | oil: { 84 | category: StandardStorageCategory.Fluid, 85 | texture: "black", 86 | id: StandardStorageType.Oil, 87 | name: "oil", 88 | }, 89 | water: { 90 | category: StandardStorageCategory.Fluid, 91 | texture: "blue", 92 | id: StandardStorageType.Water, 93 | name: "water", 94 | }, 95 | ammonia: { 96 | category: StandardStorageCategory.Gas, 97 | texture: "orange", 98 | id: StandardStorageType.Ammonia, 99 | name: "ammonia", 100 | }, 101 | carbon: { 102 | category: StandardStorageCategory.Gas, 103 | texture: "red", 104 | id: StandardStorageType.Carbon, 105 | name: "carbon", 106 | }, 107 | hydrogen: { 108 | category: StandardStorageCategory.Gas, 109 | texture: "pink", 110 | id: StandardStorageType.Hydrogen, 111 | name: "hydrogen", 112 | }, 113 | nitrogen: { 114 | category: StandardStorageCategory.Gas, 115 | texture: "purple", 116 | id: StandardStorageType.Nitrogen, 117 | name: "nitrogen", 118 | }, 119 | oxygen: { 120 | category: StandardStorageCategory.Gas, 121 | texture: "white", 122 | id: StandardStorageType.Oxygen, 123 | name: "oxygen", 124 | }, 125 | steam: { 126 | category: StandardStorageCategory.Gas, 127 | texture: "white", 128 | id: StandardStorageType.Steam, 129 | name: "steam", 130 | }, 131 | }; 132 | 133 | /** 134 | * Register a standard storage type for use in your add-on. 135 | * @beta 136 | * @remarks 137 | * This is a wrapper around {@link registerStorageType} that uses 138 | * the definitions defined in {@link STANDARD_STORAGE_TYPE_DEFINITIONS}. 139 | */ 140 | export function useStandardStorageType(id: StandardStorageType): void { 141 | registerStorageType(STANDARD_STORAGE_TYPE_DEFINITIONS[id]); 142 | } 143 | -------------------------------------------------------------------------------- /packs/BP/scripts/utils/direction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | VECTOR3_DOWN, 3 | VECTOR3_EAST, 4 | VECTOR3_UP, 5 | VECTOR3_WEST, 6 | } from "@minecraft/math"; 7 | import { Block, Direction, Vector3 } from "@minecraft/server"; 8 | 9 | export { DIRECTION_VECTORS } from "@/public_api/src/misc_internal"; 10 | 11 | export const STR_CARDINAL_DIRECTIONS = [ 12 | "north", 13 | "east", 14 | "south", 15 | "west", 16 | ] as const; 17 | export type StrCardinalDirection = (typeof STR_CARDINAL_DIRECTIONS)[number]; 18 | 19 | export const STR_VERTICAL_DIRECTIONS = ["up", "down"] as const; 20 | export type StrVerticalDirection = (typeof STR_VERTICAL_DIRECTIONS)[number]; 21 | 22 | export const STR_DIRECTIONS = [ 23 | "north", 24 | "east", 25 | "south", 26 | "west", 27 | "up", 28 | "down", 29 | ] as const; 30 | export type StrDirection = (typeof STR_DIRECTIONS)[number]; 31 | 32 | export function getDirectionVector( 33 | direction: Direction | StrDirection, 34 | ): Vector3 { 35 | switch (direction) { 36 | case Direction.North: 37 | case "north": 38 | // VECTOR3_NORTH is wrong 39 | return { x: 0, y: 0, z: -1 }; 40 | case Direction.East: 41 | case "east": 42 | return VECTOR3_EAST; 43 | case Direction.South: 44 | case "south": 45 | // VECTOR3_SOUTH is wrong 46 | return { x: 0, y: 0, z: 1 }; 47 | case Direction.West: 48 | case "west": 49 | return VECTOR3_WEST; 50 | case Direction.Up: 51 | case "up": 52 | return VECTOR3_UP; 53 | case Direction.Down: 54 | case "down": 55 | return VECTOR3_DOWN; 56 | } 57 | } 58 | 59 | export function getBlockInDirection( 60 | block: Block, 61 | direction: Direction | StrDirection, 62 | ): Block | undefined { 63 | switch (direction) { 64 | case "north": 65 | case Direction.North: 66 | return block.north(); 67 | case "east": 68 | case Direction.East: 69 | return block.east(); 70 | case "south": 71 | case Direction.South: 72 | return block.south(); 73 | case "west": 74 | case Direction.West: 75 | return block.west(); 76 | case "up": 77 | case Direction.Up: 78 | return block.above(); 79 | case "down": 80 | case Direction.Down: 81 | return block.below(); 82 | } 83 | } 84 | 85 | export function reverseDirection(dir: Direction): Direction; 86 | export function reverseDirection( 87 | dir: StrCardinalDirection, 88 | ): StrCardinalDirection; 89 | export function reverseDirection( 90 | dir: StrVerticalDirection, 91 | ): StrVerticalDirection; 92 | export function reverseDirection(dir: StrDirection): StrDirection; 93 | export function reverseDirection( 94 | dir: Direction | StrDirection, 95 | ): Direction | StrDirection { 96 | switch (dir) { 97 | case Direction.North: 98 | return Direction.South; 99 | case Direction.East: 100 | return Direction.West; 101 | case Direction.South: 102 | return Direction.North; 103 | case Direction.West: 104 | return Direction.West; 105 | case Direction.Up: 106 | return Direction.Down; 107 | case Direction.Down: 108 | return Direction.Up; 109 | case "north": 110 | return "south"; 111 | case "east": 112 | return "west"; 113 | case "south": 114 | return "north"; 115 | case "west": 116 | return "east"; 117 | case "up": 118 | return "down"; 119 | case "down": 120 | return "up"; 121 | } 122 | } 123 | 124 | export function isCardinalDirection(direction: string): boolean { 125 | return (STR_CARDINAL_DIRECTIONS as readonly string[]).includes(direction); 126 | } 127 | 128 | export function strDirectionToDirection(strDir: StrDirection): Direction { 129 | switch (strDir) { 130 | case "north": 131 | return Direction.North; 132 | case "east": 133 | return Direction.East; 134 | case "south": 135 | return Direction.South; 136 | case "west": 137 | return Direction.West; 138 | case "up": 139 | return Direction.Up; 140 | case "down": 141 | return Direction.Down; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /public_api/src/item_machine.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BlockInventoryComponent, 3 | ContainerSlot, 4 | EntityInventoryComponent, 5 | } from "@minecraft/server"; 6 | import { 7 | SerializableContainerSlot, 8 | SerializableContainerSlotJson, 9 | } from "./serialize_utils.js"; 10 | import { raise } from "./log.js"; 11 | import { ipcInvoke, ipcSend } from "./ipc_wrapper.js"; 12 | import { 13 | GetItemMachineStoragePayload, 14 | ItemMachineFuncPayload, 15 | SetItemMachineStoragePayload, 16 | } from "./item_machine_internal.js"; 17 | import { BecIpcListener } from "./bec_ipc_listener.js"; 18 | import { IoCapabilities, IoCapabilitiesData } from "./io.js"; 19 | 20 | /** 21 | * Representation of an item machine. 22 | * @beta 23 | * @see {@link registerItemMachine}. 24 | */ 25 | export class ItemMachine { 26 | private readonly containerSlotJson: SerializableContainerSlotJson; 27 | /** 28 | * The item type ID. 29 | * @beta 30 | */ 31 | readonly typeId: string; 32 | 33 | /** 34 | * @throws Throws if an item is not found in the specified slot. 35 | */ 36 | constructor( 37 | /** 38 | * The inventory that the item is in. 39 | * @beta 40 | */ 41 | readonly inventory: BlockInventoryComponent | EntityInventoryComponent, 42 | /** 43 | * The slot index that the item is in. 44 | * @beta 45 | */ 46 | readonly slot: number, 47 | ) { 48 | const typeId = inventory.container?.getItem(slot)?.typeId; 49 | if (!typeId) { 50 | raise("Could not get the item in the specified slot."); 51 | } 52 | 53 | this.typeId = typeId; 54 | 55 | const serializableContainerSlot = new SerializableContainerSlot( 56 | inventory, 57 | slot, 58 | ); 59 | 60 | this.containerSlotJson = serializableContainerSlot.toJson(); 61 | } 62 | 63 | /** 64 | * Is this object valid? 65 | * @beta 66 | * @returns `true` if the type ID of the item in the specified slot has NOT changed since the creation of this object, otherwise `false`. 67 | */ 68 | isValid(): boolean { 69 | return this.inventory.container?.getItem(this.slot)?.typeId === this.typeId; 70 | } 71 | 72 | /** 73 | * Get the container slot that this item is in. 74 | * @beta 75 | * @throws Throws if this object is not valid. 76 | */ 77 | getContainerSlot(): ContainerSlot { 78 | this.ensureValidity(); 79 | return this.inventory.container!.getSlot(this.slot); 80 | } 81 | 82 | /** 83 | * Gets the storage of a specific type in the item machine. 84 | * @beta 85 | * @param type The type of storage to get. 86 | * @throws Throws if the storage type does not exist 87 | * @throws Throws if this object is not valid. 88 | */ 89 | getStorage(type: string): Promise { 90 | this.ensureValidity(); 91 | 92 | const payload: GetItemMachineStoragePayload = { 93 | slot: this.containerSlotJson, 94 | type, 95 | }; 96 | 97 | return ipcInvoke( 98 | BecIpcListener.GetItemMachineStorage, 99 | payload, 100 | ) as Promise; 101 | } 102 | 103 | /** 104 | * Sets the storage of a specific type in the item machine. 105 | * @beta 106 | * @param type The type of storage to set. 107 | * @param value The new value. Must be an integer. 108 | * @throws Throws if this object is not valid. 109 | */ 110 | setStorage(type: string, value: number): void { 111 | this.ensureValidity(); 112 | 113 | const payload: SetItemMachineStoragePayload = { 114 | slot: this.containerSlotJson, 115 | type, 116 | value, 117 | }; 118 | 119 | ipcSend(BecIpcListener.SetItemMachineStorage, payload); 120 | } 121 | 122 | /** 123 | * Get the I/O capabilities of this item machine. 124 | * @beta 125 | * @throws Throws if this object is not valid. 126 | */ 127 | async getIo(): Promise { 128 | this.ensureValidity(); 129 | 130 | const payload: ItemMachineFuncPayload = { 131 | slot: this.containerSlotJson, 132 | }; 133 | 134 | const ioData = (await ipcInvoke( 135 | BecIpcListener.GetItemMachineIo, 136 | payload, 137 | )) as IoCapabilitiesData; 138 | 139 | return new IoCapabilities(ioData); 140 | } 141 | 142 | private ensureValidity(): void { 143 | if (!this.isValid()) { 144 | raise( 145 | "The type ID of the item in the specified slot has changed since the creation of this object.", 146 | ); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /packs/BP/scripts/network_ipc.ts: -------------------------------------------------------------------------------- 1 | import * as ipc from "mcbe-addon-ipc"; 2 | import { deserializeDimensionLocation } from "@/public_api/src/serialize_utils"; 3 | import { 4 | GeneratePayload, 5 | NetworkGetAllWithPayload, 6 | NetworkInstanceMethodPayload, 7 | NetworkIsPartOfNetworkPayload, 8 | NetworkQueueSendPayload, 9 | NetworkEstablishPayload, 10 | NetworkGetWithPayload, 11 | } from "@/public_api/src/network_internal"; 12 | import { MachineNetwork } from "./network"; 13 | import { getMachineStorage } from "./data"; 14 | import { InternalRegisteredStorageType } from "./storage_type_registry"; 15 | 16 | export function networkDestroyListener(payload: ipc.SerializableValue): null { 17 | const data = payload as NetworkInstanceMethodPayload; 18 | const networkId = data.networkId; 19 | MachineNetwork.getFromId(networkId)?.destroy(); 20 | return null; 21 | } 22 | 23 | export function networkQueueSendListener(payload: ipc.SerializableValue): null { 24 | const data = payload as NetworkQueueSendPayload; 25 | const networkId = data.networkId; 26 | const location = deserializeDimensionLocation(data.loc); 27 | const type = data.type; 28 | const amount = data.amount; 29 | 30 | const block = location.dimension.getBlock(location); 31 | if (!block) return null; 32 | 33 | MachineNetwork.getFromId(networkId)?.queueSend(block, type, amount); 34 | 35 | return null; 36 | } 37 | 38 | export function networkEstablishHandler( 39 | payload: ipc.SerializableValue, 40 | ): number | null { 41 | const data = payload as NetworkEstablishPayload; 42 | const ioTypeId = data.ioTypeId; 43 | const location = deserializeDimensionLocation(data.location); 44 | 45 | const block = location.dimension.getBlock(location); 46 | if (!block) return null; 47 | 48 | const ioType = InternalRegisteredStorageType.forceGetInternal(ioTypeId); 49 | 50 | return MachineNetwork.establish(ioType, block)?.id ?? null; 51 | } 52 | 53 | export function networkGetWithHandler( 54 | payload: ipc.SerializableValue, 55 | ): number | null { 56 | const data = payload as NetworkGetWithPayload; 57 | const ioTypeId = data.ioTypeId; 58 | const location = deserializeDimensionLocation(data.location); 59 | const connectionType = data.connectionType; 60 | 61 | const ioType = InternalRegisteredStorageType.forceGetInternal(ioTypeId); 62 | 63 | return MachineNetwork.getWith(ioType, location, connectionType)?.id ?? null; 64 | } 65 | 66 | export function networkGetAllWithHandler( 67 | payload: ipc.SerializableValue, 68 | ): number[] { 69 | const data = payload as NetworkGetAllWithPayload; 70 | const location = deserializeDimensionLocation(data.loc); 71 | const type = data.type; 72 | 73 | return MachineNetwork.getAllWith(location, type).map((network) => network.id); 74 | } 75 | 76 | export function networkGetOrEstablishHandler( 77 | payload: ipc.SerializableValue, 78 | ): number | null { 79 | const data = payload as NetworkEstablishPayload; 80 | const ioTypeId = data.ioTypeId; 81 | const location = deserializeDimensionLocation(data.location); 82 | 83 | const block = location.dimension.getBlock(location); 84 | if (!block) return null; 85 | 86 | const ioType = InternalRegisteredStorageType.forceGetInternal(ioTypeId); 87 | 88 | return ( 89 | ( 90 | MachineNetwork.getWithBlock(ioType, block) ?? 91 | MachineNetwork.establish(ioType, block) 92 | )?.id ?? null 93 | ); 94 | } 95 | 96 | export function networkIsPartOfNetworkHandler( 97 | payload: ipc.SerializableValue, 98 | ): boolean { 99 | const data = payload as NetworkIsPartOfNetworkPayload; 100 | const networkId = data.networkId; 101 | const location = deserializeDimensionLocation(data.loc); 102 | const type = data.type; 103 | 104 | return ( 105 | MachineNetwork.getFromId(networkId)?.isPartOfNetwork(location, type) ?? 106 | false 107 | ); 108 | } 109 | 110 | export function generateListener(payload: ipc.SerializableValue): null { 111 | const data = payload as GeneratePayload; 112 | const location = deserializeDimensionLocation(data.loc); 113 | const type = data.type; 114 | const amount = data.amount; 115 | 116 | const block = location.dimension.getBlock(location); 117 | if (!block) return null; 118 | 119 | const fullAmount = amount + getMachineStorage(location, type); 120 | if (!fullAmount) return null; 121 | 122 | const storageType = InternalRegisteredStorageType.forceGetInternal(type); 123 | 124 | MachineNetwork.getOrEstablish(storageType, block)?.queueSend( 125 | block, 126 | type, 127 | fullAmount, 128 | ); 129 | 130 | return null; 131 | } 132 | -------------------------------------------------------------------------------- /packs/BP/scripts/ipc_listeners.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NetworkLinkGetRequest, 3 | NetworkLinkGetResponse, 4 | NetworkLinkAddRequest, 5 | NetworkLinkRemoveRequest, 6 | NetworkLinkDestroyRequest, 7 | } from "@/public_api/src/network_links/ipc_events"; 8 | import { getNetworkLinkNode } from "./network_links/network_link_component"; 9 | import { 10 | generateListener, 11 | networkDestroyListener, 12 | networkEstablishHandler, 13 | networkGetAllWithHandler, 14 | networkGetOrEstablishHandler, 15 | networkGetWithHandler, 16 | networkIsPartOfNetworkHandler, 17 | networkQueueSendListener, 18 | } from "./network_ipc"; 19 | import { 20 | InternalRegisteredMachine, 21 | registerMachineListener, 22 | } from "./machine_registry"; 23 | import { 24 | InternalRegisteredStorageType, 25 | registerStorageTypeListener, 26 | } from "./storage_type_registry"; 27 | import { registerListener } from "./ipc_wrapper"; 28 | import { BecIpcListener } from "@/public_api/src/bec_ipc_listener"; 29 | import { 30 | InternalRegisteredItemMachine, 31 | registerItemMachineListener, 32 | } from "./item_machine_registry"; 33 | import { 34 | getItemMachineIoHandler, 35 | getItemMachineStorageHandler, 36 | setItemMachineStorageListener, 37 | } from "./item_machine_ipc"; 38 | import { 39 | getMachineSlotListener, 40 | removeMachineListener, 41 | setMachineSlotListener, 42 | } from "./data_ipc"; 43 | 44 | registerListener(BecIpcListener.RegisterMachine, registerMachineListener); 45 | registerListener( 46 | BecIpcListener.RegisterStorageType, 47 | registerStorageTypeListener, 48 | ); 49 | registerListener(BecIpcListener.SetMachineSlot, setMachineSlotListener); 50 | registerListener(BecIpcListener.GetMachineSlot, getMachineSlotListener); 51 | registerListener(BecIpcListener.DestroyNetwork, networkDestroyListener); 52 | registerListener(BecIpcListener.NetworkQueueSend, networkQueueSendListener); 53 | registerListener(BecIpcListener.Generate, generateListener); 54 | registerListener(BecIpcListener.EstablishNetwork, networkEstablishHandler); 55 | registerListener(BecIpcListener.GetNetworkWith, networkGetWithHandler); 56 | registerListener(BecIpcListener.GetAllNetworksWith, networkGetAllWithHandler); 57 | registerListener( 58 | BecIpcListener.GetOrEstablishNetwork, 59 | networkGetOrEstablishHandler, 60 | ); 61 | registerListener( 62 | BecIpcListener.RegisterItemMachine, 63 | registerItemMachineListener, 64 | ); 65 | registerListener(BecIpcListener.IsPartOfNetwork, networkIsPartOfNetworkHandler); 66 | registerListener( 67 | BecIpcListener.GetRegisteredMachine, 68 | (payload) => 69 | InternalRegisteredMachine.getInternal(payload as string)?.getData() ?? null, 70 | ); 71 | registerListener( 72 | BecIpcListener.GetRegisteredStorageType, 73 | (payload) => 74 | InternalRegisteredStorageType.getInternal( 75 | payload as string, 76 | )?.getDefinition() ?? null, 77 | ); 78 | registerListener(BecIpcListener.GetAllRegisteredStorageTypes, () => [ 79 | ...InternalRegisteredStorageType.getAllIdsInternal(), 80 | ]); 81 | registerListener(BecIpcListener.GetNetworkLink, (payload) => { 82 | const data = payload as NetworkLinkGetRequest; 83 | const link = getNetworkLinkNode(data.self); 84 | const result: NetworkLinkGetResponse = { locations: link.getConnections() }; 85 | return result; 86 | }); 87 | registerListener(BecIpcListener.AddNetworkLink, (payload) => { 88 | const data = payload as NetworkLinkAddRequest; 89 | const link = getNetworkLinkNode(data.self); 90 | link.addConnection(data.other); 91 | return null; 92 | }); 93 | registerListener(BecIpcListener.RemoveNetworkLink, (payload) => { 94 | const data = payload as NetworkLinkRemoveRequest; 95 | const link = getNetworkLinkNode(data.self); 96 | link.removeConnection(data.other); 97 | return null; 98 | }); 99 | registerListener(BecIpcListener.DestroyNetworkLink, (payload) => { 100 | const data = payload as NetworkLinkDestroyRequest; 101 | const link = getNetworkLinkNode(data.self); 102 | link.destroyNode(); 103 | return null; 104 | }); 105 | registerListener( 106 | BecIpcListener.GetRegisteredItemMachine, 107 | (payload) => 108 | InternalRegisteredItemMachine.getInternal(payload as string)?.getData() ?? 109 | null, 110 | ); 111 | registerListener( 112 | BecIpcListener.GetItemMachineStorage, 113 | getItemMachineStorageHandler, 114 | ); 115 | registerListener( 116 | BecIpcListener.SetItemMachineStorage, 117 | setItemMachineStorageListener, 118 | ); 119 | registerListener(BecIpcListener.GetItemMachineIo, getItemMachineIoHandler); 120 | registerListener(BecIpcListener.RemoveMachine, removeMachineListener); 121 | -------------------------------------------------------------------------------- /public_api/src/storage_type_registry.ts: -------------------------------------------------------------------------------- 1 | import { BecIpcListener } from "./bec_ipc_listener.js"; 2 | import { ipcInvoke, ipcSend } from "./ipc_wrapper.js"; 3 | import { raise } from "./log.js"; 4 | import { isRegistrationAllowed } from "./registration_allowed.js"; 5 | import { 6 | StorageTypeTextureDescription, 7 | StorageTypeTexturePreset, 8 | StorageTypeDefinition, 9 | } from "./storage_type_registry_types.js"; 10 | 11 | /** 12 | * value should be `undefined` if the storage type does not exist 13 | */ 14 | const storageTypeCache = new Map(); 15 | 16 | let storageTypeIdCache: string[] | undefined; 17 | 18 | /** 19 | * @beta 20 | */ 21 | export interface StorageTypeData { 22 | id: string; 23 | category: string; 24 | } 25 | 26 | /** 27 | * Representation of a storage type definition that has been registered. 28 | * @beta 29 | * @see {@link StorageTypeDefinition}, {@link registerStorageType} 30 | */ 31 | export class RegisteredStorageType implements StorageTypeData { 32 | private constructor( 33 | /** 34 | * @internal 35 | */ 36 | protected readonly definition: StorageTypeDefinition, 37 | ) {} 38 | 39 | /** 40 | * @returns The ID of this storage type. 41 | * @beta 42 | */ 43 | get id(): string { 44 | return this.definition.id; 45 | } 46 | 47 | /** 48 | * @returns The category of this storage type. 49 | * @beta 50 | */ 51 | get category(): string { 52 | return this.definition.category; 53 | } 54 | 55 | /** 56 | * @returns The texture preset or description of this storage type. 57 | * @beta 58 | */ 59 | get texture(): StorageTypeTextureDescription | StorageTypeTexturePreset { 60 | return this.definition.texture; 61 | } 62 | 63 | /** 64 | * @returns The name of this storage type. 65 | * @beta 66 | */ 67 | get name(): string { 68 | return this.definition.name; 69 | } 70 | 71 | /** 72 | * Get a registered storage type by its ID. 73 | * @beta 74 | * @param id The ID of the storage type to get. 75 | * @returns The registered storage type, or `undefined` if it does not exist. 76 | */ 77 | static async get(id: string): Promise { 78 | if (storageTypeCache.has(id)) { 79 | return storageTypeCache.get(id); 80 | } 81 | 82 | const def = (await ipcInvoke( 83 | BecIpcListener.GetRegisteredStorageType, 84 | id, 85 | )) as StorageTypeDefinition | null; 86 | 87 | const result = def ? new RegisteredStorageType(def) : undefined; 88 | 89 | if (!isRegistrationAllowed()) { 90 | storageTypeCache.set(id, result); 91 | } 92 | 93 | return result; 94 | } 95 | 96 | /** 97 | * Get all registered storage type IDs. 98 | * @beta 99 | * @returns All registered storage type IDs. 100 | */ 101 | static async getAllIds(): Promise { 102 | if (storageTypeIdCache) { 103 | return [...storageTypeIdCache]; 104 | } 105 | 106 | const ids = (await ipcInvoke( 107 | BecIpcListener.GetAllRegisteredStorageTypes, 108 | null, 109 | )) as string[]; 110 | 111 | if (!isRegistrationAllowed()) { 112 | storageTypeIdCache = [...ids]; 113 | } 114 | 115 | return ids; 116 | } 117 | } 118 | 119 | /** 120 | * Registers a storage type. This function should be called in the `worldInitialize` after event. 121 | * @beta 122 | * @throws Throws if registration has been closed. 123 | * @throws Throws if the definition ID or category is invalid. 124 | */ 125 | export function registerStorageType(definition: StorageTypeDefinition): void { 126 | if (!isRegistrationAllowed()) { 127 | raise( 128 | `Attempted to register storage type '${definition.id}' after registration was closed.`, 129 | ); 130 | } 131 | 132 | if (definition.id.startsWith("_") || definition.category.startsWith("_")) { 133 | raise( 134 | `Failed to register storage type '${definition.id}' (category: '${definition.category}'). Storage type IDs and categories cannot start with '_'.`, 135 | ); 136 | } 137 | 138 | if (definition.id.includes(".") || definition.category.includes(".")) { 139 | raise( 140 | `Failed to register storage type '${definition.id}' (category: '${definition.category}'). Storage type IDs and categories cannot include '.'.`, 141 | ); 142 | } 143 | 144 | // reconstruct the definition in case the passed `definition` contains unnecessary keys 145 | const payload: StorageTypeDefinition = { 146 | id: definition.id, 147 | category: definition.category, 148 | texture: definition.texture, 149 | name: definition.name, 150 | }; 151 | 152 | ipcSend(BecIpcListener.RegisterStorageType, payload); 153 | } 154 | -------------------------------------------------------------------------------- /public_api/src/machine_item_stack.ts: -------------------------------------------------------------------------------- 1 | import { Enchantment, ItemStack } from "@minecraft/server"; 2 | import { logWarn } from "./log.js"; 3 | 4 | /** 5 | * Additional options for creating a new {@link MachineItemStack}. 6 | * @beta 7 | */ 8 | export interface NewMachineItemStackOptions { 9 | nameTag?: string; 10 | damage?: number; 11 | lore?: string[]; 12 | enchantments?: Enchantment[]; 13 | } 14 | 15 | /** 16 | * Represents an item stack that may be stored in a machine UI item slot. 17 | * @beta 18 | */ 19 | export class MachineItemStack { 20 | nameTag?: string; 21 | damage: number; 22 | lore: string[]; 23 | enchantments: Enchantment[]; 24 | 25 | constructor( 26 | public typeId: string, 27 | public amount = 1, 28 | options: NewMachineItemStackOptions = {}, 29 | ) { 30 | this.nameTag = options.nameTag; 31 | this.damage = options.damage ?? 0; 32 | this.lore = options.lore ?? []; 33 | this.enchantments = options.enchantments ?? []; 34 | } 35 | 36 | /** 37 | * Converts a Minecraft ItemStack to a MachineItemStack. 38 | * @param itemStack The Minecraft ItemStack to convert. 39 | * @beta 40 | */ 41 | static fromItemStack(itemStack: ItemStack): MachineItemStack { 42 | const id = itemStack.typeId; 43 | const amount = itemStack.amount; 44 | const nameTag = itemStack.nameTag; 45 | const damage = itemStack.getComponent("durability")?.damage ?? 0; 46 | const lore = itemStack.getLore(); 47 | const enchantments = 48 | itemStack.getComponent("enchantable")?.getEnchantments() ?? []; 49 | 50 | return new MachineItemStack(id, amount, { 51 | nameTag, 52 | damage, 53 | lore, 54 | enchantments, 55 | }); 56 | } 57 | 58 | /** 59 | * Converts this MachineItemStack to a Minecraft ItemStack. 60 | * @beta 61 | * @returns A Minecraft ItemStack with the same properties as this MachineItemStack. 62 | */ 63 | toItemStack(): ItemStack { 64 | const result = new ItemStack(this.typeId, this.amount); 65 | 66 | result.nameTag = this.nameTag; 67 | 68 | { 69 | const durabilityComponent = result.getComponent("durability"); 70 | if (durabilityComponent) { 71 | durabilityComponent.damage = this.damage; 72 | } 73 | } 74 | 75 | try { 76 | // lore may be invalid, this has caused issues before. 77 | 78 | result.setLore(this.lore); 79 | } catch (e) { 80 | logWarn( 81 | "A recoverable error occured while converting MachineItemStack to ItemStack: Failed to set lore: " + 82 | String(e), 83 | ); 84 | } 85 | 86 | try { 87 | // just in case the enchantments are invalid 88 | 89 | result.getComponent("enchantable")?.addEnchantments(this.enchantments); 90 | } catch (e) { 91 | logWarn( 92 | "A recoverable error occured while converting MachineItemStack to ItemStack: Failed to add enchantment: " + 93 | String(e), 94 | ); 95 | } 96 | 97 | return result; 98 | } 99 | 100 | /** 101 | * Tests if all properties of two MachineItemStacks, except 'amount', are the same. 102 | * @beta 103 | * @param other The other MachineItemStack to compare with. 104 | * @returns Whether the two MachineItemStacks are similar. 105 | */ 106 | isSimilarTo(other: MachineItemStack): boolean { 107 | return ( 108 | this.typeId === other.typeId && 109 | this.damage === other.damage && 110 | this.nameTag === other.nameTag && 111 | // lore 112 | this.lore.length === other.lore.length && 113 | this.lore.every((v, i) => other.lore[i] === v) && 114 | // enchantments 115 | this.enchantments.length === other.enchantments.length && 116 | this.enchantments.every((enchantment) => 117 | other.enchantments.some( 118 | (otherEnchantment) => 119 | enchantment.level === otherEnchantment.level && 120 | enchantment.type.id === otherEnchantment.type.id, 121 | ), 122 | ) 123 | ); 124 | } 125 | 126 | /** 127 | * Clones this object. 128 | * @beta 129 | * @returns A new MachineItemStack with the same properties as this one. 130 | */ 131 | clone(): MachineItemStack { 132 | return new MachineItemStack(this.typeId, this.amount, { 133 | nameTag: this.nameTag, 134 | damage: this.damage, 135 | lore: this.lore, 136 | enchantments: this.enchantments, 137 | }); 138 | } 139 | 140 | /** 141 | * Clones this object and sets the amount to the given value. 142 | * @beta 143 | * @param amount The new amount. 144 | * @returns A new MachineItemStack with the same properties as this one, but with the given amount.s 145 | */ 146 | withAmount(amount: number): MachineItemStack { 147 | return new MachineItemStack(this.typeId, amount, { 148 | nameTag: this.nameTag, 149 | damage: this.damage, 150 | lore: this.lore, 151 | enchantments: this.enchantments, 152 | }); 153 | } 154 | } -------------------------------------------------------------------------------- /public_api/src/machine_data.ts: -------------------------------------------------------------------------------- 1 | import { Block, DimensionLocation } from "@minecraft/server"; 2 | import { 3 | getBlockUniqueId, 4 | GetMachineSlotPayload, 5 | getScore, 6 | getStorageScoreboardObjective, 7 | SetMachineSlotPayload, 8 | } from "./machine_data_internal.js"; 9 | import { makeSerializableDimensionLocation } from "./serialize_utils.js"; 10 | import { ipcInvoke, ipcSend } from "./ipc_wrapper.js"; 11 | import { BecIpcListener } from "./bec_ipc_listener.js"; 12 | import { raise } from "./log.js"; 13 | import { RegisteredMachine } from "./machine_registry.js"; 14 | import { callMachineOnStorageSetEvent } from "./machine_registry_internal.js"; 15 | import { MachineItemStack } from "./machine_item_stack.js"; 16 | import { 17 | deserializeMachineItemStack, 18 | serializeMachineItemStack, 19 | } from "./serialize_machine_item_stack.js"; 20 | 21 | /** 22 | * Gets the storage of a specific type in a machine. 23 | * @beta 24 | * @param loc The location of the machine. 25 | * @param type The type of storage to get. 26 | * @throws Throws if the storage type does not exist 27 | */ 28 | export function getMachineStorage( 29 | loc: DimensionLocation, 30 | type: string, 31 | ): number { 32 | const objective = getStorageScoreboardObjective(type); 33 | 34 | if (!objective) { 35 | raise( 36 | `Failed to get machine storage. Storage type '${type}' doesn't exist.`, 37 | ); 38 | } 39 | 40 | return getScore(objective, getBlockUniqueId(loc)) ?? 0; 41 | } 42 | 43 | /** 44 | * Sets the storage of a specific type in a machine. 45 | * @beta 46 | * @param block The machine block. 47 | * @param type The type of storage to set. 48 | * @param value The new value. Must be an integer. 49 | * @param callOnStorageSet Whether to call the `onStorageSet` event on the machine, if applicable. 50 | * @throws Throws if the storage type does not exist. 51 | * @throws Throws if the new value isn't a non-negative integer. 52 | * @throws Throws if the block is not valid. 53 | * @throws Throws if the block is not registered as a machine. 54 | */ 55 | export async function setMachineStorage( 56 | block: Block, 57 | type: string, 58 | value: number, 59 | callOnStorageSet = true, 60 | ): Promise { 61 | // There is a similar function to this in the add-on. 62 | // Make sure changes are reflected in both. 63 | 64 | // To avoid unnecessary IPC calls, this function calls the 'onStorageSet' 65 | // event on machines directly, without routing through Bedrock Energistics Core. 66 | // This also allows the local machine registry cache to be used, avoiding any 67 | // IPC calls for machines that don't have the 'onStorageSet' event. 68 | 69 | if (!block.isValid) { 70 | raise("Failed to set machine storage. The block is invalid."); 71 | } 72 | 73 | if (value < 0) { 74 | raise( 75 | `Failed to set machine storage of type '${type}' to ${value.toString()}. The minimum value is 0.`, 76 | ); 77 | } 78 | 79 | const objective = getStorageScoreboardObjective(type); 80 | if (!objective) { 81 | raise( 82 | `Failed to set machine storage. Storage type '${type}' doesn't exist.`, 83 | ); 84 | } 85 | 86 | const registered = await RegisteredMachine.forceGet(block.typeId); 87 | 88 | objective.setScore(getBlockUniqueId(block), value); 89 | 90 | if (callOnStorageSet && registered.hasCallback("onStorageSet")) { 91 | callMachineOnStorageSetEvent(registered, block, type, value); 92 | } 93 | } 94 | 95 | /** 96 | * Gets an item from a machine inventory. 97 | * @beta 98 | * @param loc The location of the machine. 99 | * @param elementId The ID of the item slot element. 100 | * @returns The {@link MachineItemStack} or `undefined` if there is no item in the specified slot. 101 | */ 102 | export async function getMachineSlotItem( 103 | loc: DimensionLocation, 104 | elementId: string, 105 | ): Promise { 106 | const payload: GetMachineSlotPayload = { 107 | loc: makeSerializableDimensionLocation(loc), 108 | slot: elementId, 109 | }; 110 | 111 | const data = (await ipcInvoke(BecIpcListener.GetMachineSlot, payload)) as 112 | | string 113 | | null; 114 | 115 | return data ? deserializeMachineItemStack(data) : undefined; 116 | } 117 | 118 | /** 119 | * Sets an item in a machine inventory. 120 | * @beta 121 | * @param loc The location of the machine. 122 | * @param elementId The ID of the item slot element. 123 | * @param newItemStack The {@link MachineItemStack} to put in the slot. Pass `undefined` to remove the item in the slot. 124 | */ 125 | export function setMachineSlotItem( 126 | loc: DimensionLocation, 127 | elementId: string, 128 | newItemStack?: MachineItemStack, 129 | ): void { 130 | const payload: SetMachineSlotPayload = { 131 | loc: makeSerializableDimensionLocation(loc), 132 | slot: elementId, 133 | item: newItemStack ? serializeMachineItemStack(newItemStack) : undefined, 134 | }; 135 | 136 | ipcSend(BecIpcListener.SetMachineSlot, payload); 137 | } 138 | -------------------------------------------------------------------------------- /packs/BP/scripts/debug_mode.ts: -------------------------------------------------------------------------------- 1 | import { Block, EquipmentSlot, Player, system, world } from "@minecraft/server"; 2 | import { ModalFormData } from "@minecraft/server-ui"; 3 | import { getMachineStorage, setMachineStorage } from "./data"; 4 | import { logInfo, makeLogString, raise } from "./utils/log"; 5 | import { InternalRegisteredStorageType } from "./storage_type_registry"; 6 | import { 7 | getBlockDynamicProperties, 8 | getBlockDynamicProperty, 9 | setBlockDynamicProperty, 10 | } from "./utils/dynamic_property"; 11 | import { MachineNetwork } from "./network"; 12 | 13 | const DEBUG_ACTIONBAR_MAX_WIDTH_CHARS = 50; 14 | 15 | const playersInSetStorageForm = new Set(); 16 | 17 | let debugMode = false; 18 | 19 | export function isDebugModeEnabled(): boolean { 20 | return debugMode; 21 | } 22 | 23 | export function enableDebugMode(): void { 24 | if (debugMode) return; 25 | debugMode = true; 26 | world.sendMessage( 27 | makeLogString( 28 | "INFO", 29 | "Debug mode enabled. Reload the world to disable debug mode.", 30 | ), 31 | ); 32 | logInfo("Debug mode enabled. Reload the world to disable debug mode."); 33 | 34 | system.runInterval(() => { 35 | for (const player of world.getAllPlayers()) { 36 | if (playersInSetStorageForm.has(player.id)) continue; 37 | 38 | const equippable = player.getComponent("equippable")!; 39 | if ( 40 | equippable.getEquipment(EquipmentSlot.Mainhand)?.typeId !== 41 | "minecraft:stick" 42 | ) { 43 | continue; 44 | } 45 | 46 | showDebugUi(player); 47 | } 48 | }, 2); 49 | } 50 | 51 | function showDebugUi(player: Player): void { 52 | const block = player.getBlockFromViewDirection({ maxDistance: 7 })?.block; 53 | if (!block?.hasTag("fluffyalien_energisticscore:machine")) { 54 | player.onScreenDisplay.setActionBar( 55 | `§sBlock§r: §p${block?.typeId ?? "undefined"}\n§cNot a machine.`, 56 | ); 57 | return; 58 | } 59 | 60 | if (player.isSneaking) { 61 | showSetStorageForm(block, player); 62 | return; 63 | } 64 | 65 | let info = `§sBlock§r: §p${block.typeId}\n§sNetworks§r: ${MachineNetwork.getAllWithBlock( 66 | block, 67 | ) 68 | .map((network) => `§p${network.id.toString()} §r(§p${network.ioType.id}§r)`) 69 | .join(", ")}`; 70 | 71 | let line = ""; 72 | 73 | for (const storageType of InternalRegisteredStorageType.getAllIdsInternal()) { 74 | const value = getMachineStorage(block, storageType); 75 | if (!value) continue; 76 | line += `§ustorage§r.§s${storageType}§r=§p${value.toString()} `; 77 | if (line.length > DEBUG_ACTIONBAR_MAX_WIDTH_CHARS) { 78 | info += `\n${line}`; 79 | line = ""; 80 | } 81 | } 82 | for (const dynamicProp of getBlockDynamicProperties(block)) { 83 | const value = getBlockDynamicProperty(block, dynamicProp); 84 | line += `§uproperty§r.§s${dynamicProp}§r=§p${value ? JSON.stringify(value) : "undefined"} `; 85 | if (line.length > DEBUG_ACTIONBAR_MAX_WIDTH_CHARS) { 86 | info += `\n${line}`; 87 | line = ""; 88 | } 89 | } 90 | 91 | info += `\n${line}`; 92 | 93 | player.onScreenDisplay.setActionBar(info); 94 | } 95 | 96 | function showSetStorageForm(block: Block, player: Player): void { 97 | playersInSetStorageForm.add(player.id); 98 | 99 | const form = new ModalFormData() 100 | .title("Set Variable") 101 | .textField( 102 | "Set the value of a variable in the machine.\n\nNOTE: The value will not be verified. Setting the value of a variable to an invalid type may cause unexpected issues. IF YOU DON'T KNOW WHAT YOU'RE DOING, CLOSE THIS MENU.\n\nVariable", 103 | "storage.energy", 104 | ) 105 | .textField("Value", "0"); 106 | 107 | void form.show(player).then((response) => { 108 | playersInSetStorageForm.delete(player.id); 109 | 110 | if (!response.formValues) return; 111 | 112 | const varName = response.formValues[0] as string; 113 | let value: unknown; 114 | try { 115 | value = JSON.parse(response.formValues[1] as string) as unknown; 116 | } catch (err) { 117 | raise(`Debug menu: Invalid JSON value. Error: ${String(err)}.`); 118 | } 119 | 120 | if ( 121 | typeof value !== "number" && 122 | typeof value !== "string" && 123 | typeof value !== "boolean" 124 | ) { 125 | raise("Debug menu: Expected a number, string, or boolean."); 126 | } 127 | 128 | if (varName.startsWith("storage.")) { 129 | const storageType = varName.slice("storage.".length); 130 | if (typeof value !== "number") { 131 | raise("Debug menu: Expected a number to set a storage type."); 132 | } 133 | setMachineStorage(block, storageType, value); 134 | return; 135 | } 136 | 137 | if (varName.startsWith("property.")) { 138 | const property = varName.slice("property.".length); 139 | setBlockDynamicProperty(block, property, value); 140 | return; 141 | } 142 | 143 | raise( 144 | `Debug menu: Invalid variable domain. Expected 'storage.' or 'property.' but got '${varName.split(".")[0]}'.`, 145 | ); 146 | }); 147 | } 148 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-background-primary: #2b2e33; 3 | --color-background-secondary: #1e2024; 4 | --color-background-accent: #9096a2; 5 | --color-text-light: #ffffff; 6 | --color-text-dark: #1e2024; 7 | --color-theme-light: #fef364; 8 | --color-theme-dark: #d8cd56; 9 | } 10 | 11 | html, 12 | body { 13 | padding: 0; 14 | margin: 0; 15 | min-width: 100%; 16 | min-height: 100%; 17 | background-color: var(--color-background-primary); 18 | color: var(--color-text-light); 19 | font-family: 20 | -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, 21 | Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 22 | } 23 | 24 | .top-nav { 25 | display: flex; 26 | flex-wrap: wrap; 27 | justify-content: space-between; 28 | background-color: var(--color-background-secondary); 29 | border-bottom: 1px solid var(--color-background-accent); 30 | } 31 | 32 | .top-nav-group { 33 | display: flex; 34 | flex-wrap: wrap; 35 | align-items: center; 36 | margin: 0 10px; 37 | } 38 | 39 | .top-nav-logo { 40 | width: 50px; 41 | height: 50px; 42 | margin-right: 5px; 43 | } 44 | 45 | .top-nav-link { 46 | color: var(--color-text-light); 47 | text-decoration: none; 48 | transition-duration: 0.4s; 49 | } 50 | 51 | .top-nav-link:hover { 52 | color: var(--color-theme-dark); 53 | text-decoration: underline; 54 | } 55 | 56 | .top-nav-button-link { 57 | display: block; 58 | color: var(--color-text-light); 59 | text-decoration: none; 60 | transition-duration: 0.4s; 61 | padding: 5px 8px; 62 | border-radius: 5px; 63 | } 64 | 65 | .top-nav-button-link:hover { 66 | background-color: var(--color-theme-dark); 67 | box-shadow: 0 0 10px var(--color-theme-light); 68 | } 69 | 70 | .top-nav-dropdown:hover > .top-nav-dropdown-content { 71 | opacity: 1; 72 | pointer-events: all; 73 | } 74 | 75 | .top-nav-dropdown-label { 76 | transition-duration: 0.4s; 77 | } 78 | 79 | .top-nav-dropdown:hover > .top-nav-dropdown-label { 80 | color: var(--color-theme-dark); 81 | } 82 | 83 | .top-nav-dropdown-content { 84 | float: left; 85 | opacity: 0; 86 | pointer-events: none; 87 | position: absolute; 88 | transition-duration: 0.4s; 89 | background-color: var(--color-background-secondary); 90 | border: 1px solid var(--color-background-accent); 91 | padding: 5px; 92 | } 93 | 94 | .button-list-dropdown-content > * { 95 | margin: 5px 0; 96 | } 97 | 98 | .title { 99 | font-weight: bold; 100 | } 101 | 102 | .main-description-highlighted { 103 | color: var(--color-theme-dark); 104 | text-decoration: underline; 105 | } 106 | 107 | .button { 108 | display: block; 109 | color: var(--color-text-light); 110 | background-color: var(--color-background-secondary); 111 | text-decoration: none; 112 | padding: 5px 8px; 113 | border-radius: 5px; 114 | width: fit-content; 115 | transition-duration: 0.4s; 116 | font-size: 1.2em; 117 | } 118 | 119 | .button:hover { 120 | background-color: var(--color-theme-dark); 121 | box-shadow: 0 0 10px var(--color-theme-light); 122 | } 123 | 124 | .center-container { 125 | display: flex; 126 | flex-wrap: wrap; 127 | align-items: center; 128 | justify-content: center; 129 | } 130 | 131 | .feature-showcase-container { 132 | display: flex; 133 | flex-wrap: wrap; 134 | justify-content: center; 135 | } 136 | 137 | .feature-showcase { 138 | display: flex; 139 | flex-wrap: wrap; 140 | flex-direction: column; 141 | align-items: center; 142 | background-color: var(--color-background-secondary); 143 | margin: 5px; 144 | padding: 0 10px; 145 | border-radius: 10px; 146 | } 147 | 148 | h1 { 149 | font-size: 2.5em; 150 | } 151 | 152 | h2 { 153 | font-size: 2em; 154 | } 155 | 156 | h3 { 157 | font-size: 1.5em; 158 | } 159 | 160 | pre { 161 | white-space: pre-wrap; 162 | width: 100%; 163 | } 164 | 165 | pre > code { 166 | border-radius: 5px; 167 | } 168 | 169 | .featured-content-container { 170 | display: flex; 171 | flex-wrap: wrap; 172 | justify-content: center; 173 | } 174 | 175 | .featured-content { 176 | display: flex; 177 | flex-wrap: wrap; 178 | align-items: center; 179 | margin: 0 10px; 180 | background-color: var(--color-background-secondary); 181 | border-radius: 10px; 182 | padding: 10px; 183 | color: var(--color-text-light); 184 | text-decoration: none; 185 | transition-duration: 0.4s; 186 | } 187 | 188 | .featured-content:hover { 189 | background-color: var(--color-theme-dark); 190 | box-shadow: 0 0 10px var(--color-theme-light); 191 | } 192 | 193 | .featured-content-img { 194 | width: 50px; 195 | height: 50px; 196 | margin-right: 10px; 197 | } 198 | 199 | .featured-content-name { 200 | display: block; 201 | font-weight: bold; 202 | font-size: 1em; 203 | } 204 | 205 | .featured-content-info-container { 206 | display: flex; 207 | justify-content: space-between; 208 | font-size: 0.9em; 209 | } 210 | 211 | .featured-content-tag { 212 | padding: 1px 5px; 213 | border-radius: 5px; 214 | margin-left: 5px; 215 | } 216 | 217 | @media (min-width: 390px) { 218 | .links-dropdown { 219 | right: 10px; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /packs/BP/scripts/network_links/network_link_internal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NETWORK_LINK_BLOCK_TAG, 3 | NETWORK_LINK_ENTITY_ID, 4 | NETWORK_LINK_POSITIONS_KEY, 5 | } from "@/public_api/src/network_links/ipc_events"; 6 | import { Vector3Utils } from "@minecraft/math"; 7 | import { Block, Dimension, Entity, Vector3 } from "@minecraft/server"; 8 | import { raise } from "../utils/log"; 9 | import { MachineNetwork } from "../network"; 10 | 11 | /** 12 | * Internal version of the `NetworkLinkNode` class 13 | * @remarks 14 | * There is a difference between the public api facing class since, all entity properties 15 | * have to be accessed and created from the core pack, since they get sandboxed. 16 | */ 17 | export class InternalNetworkLinkNode { 18 | private readonly entity: Entity; 19 | private readonly blockPos: Vector3; 20 | 21 | private constructor(entity: Entity, blockPos: Vector3) { 22 | this.entity = entity; 23 | this.blockPos = blockPos; 24 | } 25 | 26 | public static fromEntity(entity: Entity): InternalNetworkLinkNode { 27 | return new InternalNetworkLinkNode(entity, entity.location); 28 | } 29 | 30 | public static fromBlock(block: Block): InternalNetworkLinkNode { 31 | let dataStorageEntity = block.dimension 32 | .getEntitiesAtBlockLocation(block.location) 33 | .find((e) => e.typeId === NETWORK_LINK_ENTITY_ID); 34 | 35 | // Only verify the block tag when creating an entity, this is easier for after events when the network link block 36 | // is destroyed, but we still need to get it to cleanup. 37 | if (!dataStorageEntity && !block.hasTag(NETWORK_LINK_BLOCK_TAG)) 38 | raise( 39 | `NetworkLinks::getNetworkLink expected block of id: '${block.typeId}' to have the '${NETWORK_LINK_BLOCK_TAG}' tag before creating a network link storage entity at this location`, 40 | ); 41 | 42 | // Spawn entity if tag check passed and it is null. 43 | dataStorageEntity ??= block.dimension.spawnEntity( 44 | NETWORK_LINK_ENTITY_ID, 45 | block.location, 46 | ); 47 | return new InternalNetworkLinkNode(dataStorageEntity, block.location); 48 | } 49 | 50 | public static tryGetAt( 51 | dimension: Dimension, 52 | location: Vector3, 53 | ): InternalNetworkLinkNode | undefined { 54 | const dataStorageEntity = dimension 55 | .getEntitiesAtBlockLocation(location) 56 | .find((e) => e.typeId === NETWORK_LINK_ENTITY_ID); 57 | 58 | if (dataStorageEntity === undefined) return undefined; 59 | return new InternalNetworkLinkNode(dataStorageEntity, location); 60 | } 61 | 62 | public getConnections(): Vector3[] { 63 | this.ensureValid(); 64 | const rawData = this.entity.getDynamicProperty( 65 | NETWORK_LINK_POSITIONS_KEY, 66 | ) as string | undefined; 67 | return JSON.parse(rawData ?? "[]") as Vector3[]; 68 | } 69 | 70 | public addConnection(location: Vector3): void { 71 | const otherBlock = this.entity.dimension.getBlock(location)!; 72 | const other = InternalNetworkLinkNode.fromBlock(otherBlock); 73 | 74 | other.selfAddConnection(this.blockPos); 75 | this.selfAddConnection(other.blockPos); 76 | 77 | const thisBlock = this.entity.dimension.getBlock(this.blockPos)!; 78 | MachineNetwork.updateWithBlock(thisBlock); 79 | MachineNetwork.updateWithBlock(otherBlock); 80 | } 81 | 82 | public removeConnection(location: Vector3): void { 83 | const otherBlock = this.entity.dimension.getBlock(location)!; 84 | const other = InternalNetworkLinkNode.fromBlock(otherBlock); 85 | 86 | other.selfRemoveConnection(this.blockPos); 87 | this.selfRemoveConnection(other.blockPos); 88 | 89 | const thisBlock = this.entity.dimension.getBlock(this.blockPos)!; 90 | MachineNetwork.updateWithBlock(thisBlock); 91 | MachineNetwork.updateWithBlock(otherBlock); 92 | } 93 | 94 | public destroyNode(): void { 95 | const outboundConnections = this.getConnections(); 96 | 97 | // links are two way, remove the inbound links to this block. 98 | for (const connection of outboundConnections) { 99 | const block = this.entity.dimension.getBlock(connection)!; 100 | const node = InternalNetworkLinkNode.fromBlock(block); 101 | node.removeConnection(this.blockPos); 102 | } 103 | 104 | this.entity.remove(); 105 | } 106 | 107 | public isValid(): boolean { 108 | return this.entity.isValid; 109 | } 110 | 111 | private selfRemoveConnection(location: Vector3): void { 112 | const filtered = this.getConnections().filter( 113 | (outbound) => !Vector3Utils.equals(outbound, location), 114 | ); 115 | this.selfSerializeConnections(filtered); 116 | } 117 | 118 | private selfAddConnection(location: Vector3): void { 119 | this.selfSerializeConnections([...this.getConnections(), location]); 120 | } 121 | 122 | private selfSerializeConnections(connections: Vector3[]): void { 123 | this.ensureValid(); 124 | this.entity.setDynamicProperty( 125 | NETWORK_LINK_POSITIONS_KEY, 126 | JSON.stringify(connections), 127 | ); 128 | } 129 | 130 | private ensureValid(): void { 131 | if (!this.entity.isValid) raise(`NetworkLinkNode instance is not valid.`); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /docs/api/variables/API.VERSION.html: -------------------------------------------------------------------------------- 1 | VERSION | Bedrock Energistics Core
Bedrock Energistics Core
    Preparing search index...

    Variable VERSIONConst

    VERSION: "0.8.0"
    2 | -------------------------------------------------------------------------------- /public_api/src/item_machine_registry.ts: -------------------------------------------------------------------------------- 1 | import { IpcListenerType, makeIpcListenerName } from "./ipc_listener_type.js"; 2 | import { ipcInvoke, ipcSend } from "./ipc_wrapper.js"; 3 | import { 4 | ItemMachineOnStorageSetPayload, 5 | RegisteredItemMachineData, 6 | } from "./item_machine_registry_internal.js"; 7 | import { 8 | ItemMachineCallbackName, 9 | ItemMachineDefinition, 10 | } from "./item_machine_registry_types.js"; 11 | import { raise } from "./log.js"; 12 | import { isRegistrationAllowed } from "./registration_allowed.js"; 13 | import { 14 | SerializableContainerSlot, 15 | SerializableContainerSlotJson, 16 | } from "./serialize_utils.js"; 17 | import { BecIpcListener } from "./bec_ipc_listener.js"; 18 | import { getIpcRouter } from "./init.js"; 19 | import { ItemMachine } from "./item_machine.js"; 20 | 21 | /** 22 | * value should be `undefined` if the item machine does not exist 23 | */ 24 | const itemMachineCache = new Map(); 25 | 26 | /** 27 | * Representation of an item machine definition that has been registered. 28 | * @beta 29 | * @see {@link ItemMachineDefinition}, {@link registerItemMachine} 30 | */ 31 | export class RegisteredItemMachine { 32 | private constructor( 33 | /** 34 | * @internal 35 | */ 36 | protected readonly data: RegisteredItemMachineData, 37 | ) {} 38 | 39 | get id(): string { 40 | return this.data.id; 41 | } 42 | 43 | get maxStorage(): number { 44 | return this.data.maxStorage ?? 6400; 45 | } 46 | 47 | /** 48 | * Tests if the registered item machine has a specific callback (event or handler). 49 | * @beta 50 | * @param name The name of the callback. 51 | * @returns Whether the item machine defines the specified callback. 52 | */ 53 | hasCallback(name: ItemMachineCallbackName): boolean { 54 | switch (name) { 55 | case "getIo": 56 | return !!this.data.getIoHandler; 57 | case "onStorageSet": 58 | return !!this.data.onStorageSetEvent; 59 | } 60 | } 61 | 62 | /** 63 | * Get a registered item machine by its ID. 64 | * @beta 65 | * @param id The ID of the item machine to get. 66 | * @returns The registered item machine, or `undefined` if it does not exist. 67 | */ 68 | static async get(id: string): Promise { 69 | if (itemMachineCache.has(id)) { 70 | return itemMachineCache.get(id); 71 | } 72 | 73 | const data = (await ipcInvoke( 74 | BecIpcListener.GetRegisteredItemMachine, 75 | id, 76 | )) as RegisteredItemMachineData | null; 77 | 78 | const result = data ? new RegisteredItemMachine(data) : undefined; 79 | 80 | if (!isRegistrationAllowed()) { 81 | itemMachineCache.set(id, result); 82 | } 83 | 84 | return result; 85 | } 86 | } 87 | 88 | /** 89 | * Registers an item machine. This function should be called in the `worldInitialize` after event. 90 | * @beta 91 | * @throws Throws if registration has been closed. 92 | */ 93 | export function registerItemMachine(definition: ItemMachineDefinition): void { 94 | if (!isRegistrationAllowed()) { 95 | raise( 96 | `Attempted to register item machine '${definition.description.id}' after registration was closed.`, 97 | ); 98 | } 99 | 100 | const ipcRouter = getIpcRouter(); 101 | 102 | let getIoHandler: string | undefined; 103 | if (definition.handlers?.getIo) { 104 | getIoHandler = makeIpcListenerName( 105 | definition.description.id, 106 | IpcListenerType.ItemMachineGetIoHandler, 107 | ); 108 | 109 | const callback = definition.handlers.getIo.bind(null); 110 | 111 | ipcRouter.registerListener(getIoHandler, (payload) => { 112 | const serializableSlot = SerializableContainerSlot.fromJson( 113 | payload as SerializableContainerSlotJson, 114 | ); 115 | 116 | return callback({ 117 | itemMachine: new ItemMachine( 118 | serializableSlot.inventory, 119 | serializableSlot.slot, 120 | ), 121 | }); 122 | }); 123 | } 124 | 125 | let onStorageSetEvent: string | undefined; 126 | if (definition.events?.onStorageSet) { 127 | onStorageSetEvent = makeIpcListenerName( 128 | definition.description.id, 129 | IpcListenerType.ItemMachineOnStorageSetEvent, 130 | ); 131 | 132 | const callback = definition.events.onStorageSet.bind(null); 133 | 134 | ipcRouter.registerListener(onStorageSetEvent, (payloadRaw) => { 135 | const payload = payloadRaw as ItemMachineOnStorageSetPayload; 136 | 137 | const serializableSlot = SerializableContainerSlot.fromJson(payload.slot); 138 | 139 | void callback({ 140 | itemMachine: new ItemMachine( 141 | serializableSlot.inventory, 142 | serializableSlot.slot, 143 | ), 144 | type: payload.type, 145 | value: payload.value, 146 | }); 147 | 148 | return null; 149 | }); 150 | } 151 | 152 | const payload: RegisteredItemMachineData = { 153 | id: definition.description.id, 154 | maxStorage: definition.description.maxStorage, 155 | defaultIo: definition.description.defaultIo, 156 | getIoHandler, 157 | onStorageSetEvent, 158 | }; 159 | 160 | ipcSend(BecIpcListener.RegisterItemMachine, payload); 161 | } 162 | -------------------------------------------------------------------------------- /scripts/filters/gen_ui_bars.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates the ui bar items & textures based on the composite images in packs/data/ui_bars 3 | */ 4 | 5 | import * as imgManip from "imagescript"; 6 | import * as fs from "fs"; 7 | import * as path from "path"; 8 | import { TMP_DIR } from "./common"; 9 | 10 | const STORAGE_BAR_COLORS: string[] = [ 11 | "black", 12 | "orange", 13 | "pink", 14 | "purple", 15 | "red", 16 | "yellow", 17 | "blue", 18 | "white", 19 | "green", 20 | ]; 21 | 22 | const itemTexturePath = path.join(TMP_DIR, "RP/textures/item_texture.json"); 23 | 24 | const itemTexture = JSON.parse(fs.readFileSync(itemTexturePath, "utf8")) as { 25 | texture_data: Record; 26 | }; 27 | 28 | function readImg(imgPath: string): Promise { 29 | return imgManip.decode(fs.readFileSync(imgPath)) as Promise; 30 | } 31 | 32 | function createUiItem(itemId: string): string { 33 | return JSON.stringify({ 34 | format_version: "1.20.80", 35 | "minecraft:item": { 36 | description: { 37 | identifier: itemId, 38 | menu_category: { 39 | category: "none", 40 | }, 41 | }, 42 | components: { 43 | "minecraft:tags": { 44 | tags: ["fluffyalien_energisticscore:ui_item"], 45 | }, 46 | "minecraft:icon": { 47 | textures: { 48 | default: itemId, 49 | }, 50 | }, 51 | }, 52 | }, 53 | }); 54 | } 55 | 56 | async function makeStorageBar( 57 | baseShortId: string, 58 | onImg: imgManip.Image, 59 | offImg: imgManip.Image, 60 | ): Promise { 61 | for (let poweredCount = 0; poweredCount <= 16; poweredCount++) { 62 | const shortId = baseShortId + poweredCount.toString(); 63 | const itemId = `fluffyalien_energisticscore:${shortId}`; 64 | 65 | fs.writeFileSync( 66 | path.join(TMP_DIR, `BP/items/${shortId}.json`), 67 | createUiItem(itemId), 68 | ); 69 | 70 | const texturePath = `textures/fluffyalien/energisticscore/${shortId}`; 71 | itemTexture.texture_data[itemId] = { textures: texturePath }; 72 | 73 | const img = offImg.clone(); 74 | 75 | if (poweredCount > 0) { 76 | const compositeImg = onImg.clone(); 77 | compositeImg.crop(0, 16 - poweredCount, 16, poweredCount); 78 | 79 | img.composite(compositeImg, 0, 16 - poweredCount); 80 | } 81 | 82 | fs.writeFileSync( 83 | path.join(TMP_DIR, "RP", `${texturePath}.png`), 84 | await img.encode(), 85 | ); 86 | } 87 | } 88 | 89 | const arrowProgressEmpty = await readImg( 90 | "packs/data/ui_composite/progress_indicators/arrow_empty.png", 91 | ); 92 | const arrowProgressFull = await readImg( 93 | "packs/data/ui_composite/progress_indicators/arrow_full.png", 94 | ); 95 | const flameProgressEmpty = await readImg( 96 | "packs/data/ui_composite/progress_indicators/flame_empty.png", 97 | ); 98 | const flameProgressFull = await readImg( 99 | "packs/data/ui_composite/progress_indicators/flame_full.png", 100 | ); 101 | 102 | // storage bars 103 | for (const color of STORAGE_BAR_COLORS) { 104 | const imgBasePath = `packs/data/ui_composite/storage_bar_segments/${color}`; 105 | 106 | const onImg = await readImg(`${imgBasePath}_on.png`); 107 | const offImg = await readImg(`${imgBasePath}_off.png`); 108 | 109 | await makeStorageBar(`ui_storage_bar_segment_${color}`, onImg, offImg); 110 | } 111 | 112 | // progress bar IDs must match `ui_progress_${indicator}${progress}` 113 | 114 | // arrow progress bar 115 | for (let progress = 0; progress <= 16; progress++) { 116 | const shortId = `ui_progress_arrow${progress.toString()}`; 117 | const itemId = `fluffyalien_energisticscore:${shortId}`; 118 | 119 | fs.writeFileSync( 120 | path.join(TMP_DIR, `BP/items/${shortId}.json`), 121 | createUiItem(itemId), 122 | ); 123 | 124 | const texturePath = `textures/fluffyalien/energisticscore/${shortId}`; 125 | itemTexture.texture_data[itemId] = { textures: texturePath }; 126 | 127 | const img = arrowProgressEmpty.clone(); 128 | 129 | if (progress > 0) { 130 | const compositeImg = arrowProgressFull.clone(); 131 | compositeImg.crop(0, 0, progress, 16); 132 | 133 | img.composite(compositeImg); 134 | } 135 | 136 | fs.writeFileSync( 137 | path.join(TMP_DIR, "RP", `${texturePath}.png`), 138 | await img.encode(), 139 | ); 140 | } 141 | 142 | // flame progress bar 143 | for (let progress = 0; progress <= 13; progress++) { 144 | const shortId = `ui_progress_flame${progress.toString()}`; 145 | const itemId = `fluffyalien_energisticscore:${shortId}`; 146 | 147 | fs.writeFileSync( 148 | path.join(TMP_DIR, `BP/items/${shortId}.json`), 149 | createUiItem(itemId), 150 | ); 151 | 152 | const texturePath = `textures/fluffyalien/energisticscore/${shortId}`; 153 | itemTexture.texture_data[itemId] = { textures: texturePath }; 154 | 155 | const img = flameProgressEmpty.clone(); 156 | 157 | if (progress > 0) { 158 | const compositeImg = flameProgressFull.clone(); 159 | compositeImg.crop(0, 16 - progress, 16, progress); 160 | 161 | img.composite(compositeImg, 0, 16 - progress); 162 | } 163 | 164 | fs.writeFileSync( 165 | path.join(TMP_DIR, "RP", `${texturePath}.png`), 166 | await img.encode(), 167 | ); 168 | } 169 | 170 | fs.writeFileSync(itemTexturePath, JSON.stringify(itemTexture)); 171 | -------------------------------------------------------------------------------- /docs/api/types/API.MachineEventName.html: -------------------------------------------------------------------------------- 1 | MachineEventName | Bedrock Energistics Core
    Bedrock Energistics Core
      Preparing search index...

      Type Alias MachineEventNameBeta

      MachineEventName: keyof MachineDefinitionEvents
      2 | -------------------------------------------------------------------------------- /docs/api/types/API.MachineHandlerName.html: -------------------------------------------------------------------------------- 1 | MachineHandlerName | Bedrock Energistics Core
      Bedrock Energistics Core
        Preparing search index...

        Type Alias MachineHandlerNameBeta

        MachineHandlerName: keyof MachineDefinitionHandlers
        2 | -------------------------------------------------------------------------------- /packs/BP/scripts/machine.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Block, 3 | BlockCustomComponent, 4 | DimensionLocation, 5 | Entity, 6 | system, 7 | world, 8 | } from "@minecraft/server"; 9 | import { 10 | getMachineSlotItemUnsafe, 11 | optionalMachineItemStackToItemStack, 12 | removeBlockFromScoreboards, 13 | } from "./data"; 14 | import { MachineNetwork } from "./network"; 15 | import { raise } from "./utils/log"; 16 | import { Vector3Utils } from "@minecraft/math"; 17 | import { RegisteredMachine } from "@/public_api/src"; 18 | import { 19 | getMachineIdFromEntityId, 20 | InternalRegisteredMachine, 21 | } from "./machine_registry"; 22 | import { removeAllDynamicPropertiesForBlock } from "./utils/dynamic_property"; 23 | 24 | export function removeMachine( 25 | block: Block, 26 | definition: InternalRegisteredMachine, 27 | ): void { 28 | MachineNetwork.updateWithBlock(block); 29 | 30 | system.run(() => { 31 | dropItemsStoredInMachine(block, definition); 32 | removeBlockFromScoreboards(block); 33 | removeAllDynamicPropertiesForBlock(block); 34 | }); 35 | } 36 | 37 | export function spawnMachineEntity(block: Block, entityId: string): Entity { 38 | // there is a similar function to this one in the public api. 39 | // if this is changed, then ensure the public api function is 40 | // changed as well. 41 | const entity = block.dimension.spawnEntity(entityId, block.bottomCenter()); 42 | entity.nameTag = block.typeId; 43 | return entity; 44 | } 45 | 46 | export const machineNoInteractComponent: BlockCustomComponent = { 47 | onPlace(e) { 48 | if (e.block.typeId === e.previousBlock.type.id) return; 49 | MachineNetwork.updateAdjacent(e.block); 50 | 51 | const definition = InternalRegisteredMachine.getInternal(e.block.typeId); 52 | if (!definition) { 53 | raise( 54 | `The block '${e.block.typeId}' uses the 'fluffyalien_energisticscore:machine' custom component but it could not be found in the machine registry.`, 55 | ); 56 | } 57 | 58 | if (definition.persistentEntity) { 59 | spawnMachineEntity(e.block, definition.entityId); 60 | } 61 | }, 62 | }; 63 | 64 | export const machineComponent: BlockCustomComponent = { 65 | ...machineNoInteractComponent, 66 | onPlayerInteract(e) { 67 | const definition = InternalRegisteredMachine.getInternal(e.block.typeId); 68 | if (!definition) { 69 | raise( 70 | `The block '${e.block.typeId}' uses the 'fluffyalien_energisticscore:machine' custom component but it could not be found in the machine registry.`, 71 | ); 72 | } 73 | if (!definition.uiElements || definition.persistentEntity) { 74 | return; 75 | } 76 | 77 | spawnMachineEntity(e.block, definition.entityId); 78 | }, 79 | }; 80 | 81 | export function dropItemsStoredInMachine( 82 | blockLocation: DimensionLocation, 83 | definition: RegisteredMachine, 84 | ): void { 85 | if (!definition.uiElements) { 86 | return; 87 | } 88 | 89 | for (const [elementId, element] of definition.uiElements) { 90 | if (element.type !== "itemSlot") continue; 91 | 92 | const item = getMachineSlotItemUnsafe(blockLocation, elementId); 93 | if (item) { 94 | blockLocation.dimension.spawnItem( 95 | optionalMachineItemStackToItemStack(item), 96 | Vector3Utils.add(blockLocation, { x: 0.5, y: 0.5, z: 0.5 }), 97 | ); 98 | } 99 | } 100 | } 101 | 102 | world.beforeEvents.playerBreakBlock.subscribe((e) => { 103 | if (!e.block.hasTag("fluffyalien_energisticscore:machine")) { 104 | return; 105 | } 106 | 107 | const definition = InternalRegisteredMachine.getInternal(e.block.typeId); 108 | if (!definition) { 109 | raise( 110 | `The block '${e.block.typeId}' has the 'fluffyalien_energisticscore:machine' tag but it could not be found in the machine registry.`, 111 | ); 112 | } 113 | 114 | if (definition.persistentEntity) { 115 | return; 116 | } 117 | 118 | removeMachine(e.block, definition); 119 | }); 120 | 121 | world.afterEvents.entityHitEntity.subscribe((e) => { 122 | if ( 123 | e.damagingEntity.typeId !== "minecraft:player" || 124 | !e.hitEntity.isValid || 125 | !e.hitEntity 126 | .getComponent("type_family") 127 | ?.hasTypeFamily("fluffyalien_energisticscore:machine_entity") 128 | ) { 129 | return; 130 | } 131 | 132 | const machineId = getMachineIdFromEntityId(e.hitEntity.typeId); 133 | if (!machineId) { 134 | raise( 135 | `The entity '${e.hitEntity.typeId}' has the 'fluffyalien_energisticscore:machine_entity' type family but it is not attached to a machine block.`, 136 | ); 137 | } 138 | 139 | const definition = InternalRegisteredMachine.forceGetInternal(machineId); 140 | if (definition.persistentEntity) { 141 | return; 142 | } 143 | 144 | e.hitEntity.remove(); 145 | }); 146 | 147 | world.afterEvents.entitySpawn.subscribe((e) => { 148 | const entity = e.entity; 149 | if (!entity.isValid) return; // Entities can become invalid if they're spawned and removed in the same tick. 150 | 151 | if ( 152 | !entity 153 | .getComponent("type_family") 154 | ?.hasTypeFamily("fluffyalien_energisticscore:machine_entity") 155 | ) { 156 | return; 157 | } 158 | 159 | // machine entities can be spawned via the public api 160 | // but dynamic properties are sandboxed, 161 | // so set the dynamic property in this event 162 | e.entity.setDynamicProperty( 163 | "block_location", 164 | Vector3Utils.floor(e.entity.location), 165 | ); 166 | }); 167 | -------------------------------------------------------------------------------- /docs/api/types/API.ItemMachineEventName.html: -------------------------------------------------------------------------------- 1 | ItemMachineEventName | Bedrock Energistics Core
        Bedrock Energistics Core
          Preparing search index...

          Type Alias ItemMachineEventNameBeta

          ItemMachineEventName: keyof ItemMachineDefinitionEvents
          2 | -------------------------------------------------------------------------------- /docs/api/documents/Guides.Network_Links.html: -------------------------------------------------------------------------------- 1 | Network Links | Bedrock Energistics Core
          Bedrock Energistics Core
            Preparing search index...

            Network Links

            TBD. See the reference documentation for information on network links.

            2 |
            3 | --------------------------------------------------------------------------------