@@ -1,11 +1,11 @@
|
||||
{
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Construct [BP] v1.1.0",
|
||||
"name": "Construct [BP] v1.2.0",
|
||||
"description": "Survival building addon by §aForestOfLight§r.",
|
||||
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
||||
"min_engine_version": [1, 26, 30],
|
||||
"version": [1, 1, 0]
|
||||
"min_engine_version": [1, 26, 40],
|
||||
"version": [1, 2, 0]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
@@ -26,15 +26,19 @@
|
||||
"dependencies": [
|
||||
{
|
||||
"module_name": "@minecraft/server",
|
||||
"version": "2.9.0-beta"
|
||||
"version": "2.10.0-beta"
|
||||
},
|
||||
{
|
||||
"module_name": "@minecraft/server-ui",
|
||||
"version": "2.2.0-beta"
|
||||
},
|
||||
{
|
||||
"module_name": "@minecraft/debug-utilities",
|
||||
"version": "1.0.0-beta"
|
||||
},
|
||||
{
|
||||
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4",
|
||||
"version": [1, 1, 0]
|
||||
"version": [1, 2, 0]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { AddonAPIServer } from "../lib/AddonAPIKit";
|
||||
import { PACK_IDENTIFIER } from "../consts";
|
||||
import { InstancesController } from "./controllers/InstancesController";
|
||||
import { structureCollection } from "../classes/Structure/StructureCollection";
|
||||
import { instanceCollection } from "../classes/Instance/InstanceCollection";
|
||||
import { BuildersController } from "./controllers/BuildersController";
|
||||
import { Builders } from "../classes/Builder/Builders";
|
||||
|
||||
class ConstructAPI extends AddonAPIServer {
|
||||
constructor(version) {
|
||||
super(PACK_IDENTIFIER, version);
|
||||
const instancesController = new InstancesController(structureCollection);
|
||||
const instancesController = new InstancesController(instanceCollection);
|
||||
this.setupController(instancesController);
|
||||
const buildersController = new BuildersController(Builders);
|
||||
this.setupController(buildersController);
|
||||
}
|
||||
}
|
||||
|
||||
export const constructAPI = new ConstructAPI("1.0.0");
|
||||
export const constructAPI = new ConstructAPI("2.0.0");
|
||||
@@ -24,7 +24,8 @@ export const InstanceModel = PROTO.Object({
|
||||
isEnabled: PROTO.Boolean,
|
||||
trackPlayerDistance: PROTO.Int8,
|
||||
particleLifetime: PROTO.Int32
|
||||
})
|
||||
}),
|
||||
renderMode: PROTO.String
|
||||
});
|
||||
|
||||
export const InstancesModel = PROTO.Array(InstanceModel);
|
||||
@@ -52,7 +53,8 @@ export const BuilderModel = PROTO.Object({
|
||||
easyPlace: PROTO.Boolean,
|
||||
fastEasyPlace: PROTO.Boolean,
|
||||
materialGrabber: PROTO.Boolean,
|
||||
materialInstanceName: PROTO.String
|
||||
materialInstanceName: PROTO.String,
|
||||
defaultRenderMode: PROTO.String
|
||||
});
|
||||
|
||||
export const BuilderIdParameterModel = PROTO.Object({
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export const atlasWidth = 1024;
|
||||
export const atlasHeight = 1024;
|
||||
|
||||
export const whiteUvRect = {
|
||||
"h" : 15.0,
|
||||
"w" : 15.0,
|
||||
"x" : 320.5,
|
||||
"y" : 384.5
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
+264
-58
@@ -1080,6 +1080,10 @@ export const blocks = {
|
||||
"west" : "chiseled_bookshelf_side"
|
||||
}
|
||||
},
|
||||
"chiseled_cinnabar" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "chiseled_cinnabar"
|
||||
},
|
||||
"chiseled_copper" : {
|
||||
"sound" : "copper",
|
||||
"textures" : "chiseled_copper"
|
||||
@@ -1139,6 +1143,10 @@ export const blocks = {
|
||||
"sound" : "stone",
|
||||
"textures" : "chiseled_stone_bricks"
|
||||
},
|
||||
"chiseled_sulfur" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "chiseled_sulfur"
|
||||
},
|
||||
"chiseled_tuff" : {
|
||||
"sound" : "tuff",
|
||||
"textures" : {
|
||||
@@ -1163,6 +1171,46 @@ export const blocks = {
|
||||
"sound" : "stone",
|
||||
"textures" : "chorus_plant"
|
||||
},
|
||||
"cinnabar" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar"
|
||||
},
|
||||
"cinnabar_brick_double_slab" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar_bricks"
|
||||
},
|
||||
"cinnabar_brick_slab" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar_bricks"
|
||||
},
|
||||
"cinnabar_brick_stairs" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar_bricks"
|
||||
},
|
||||
"cinnabar_brick_wall" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar_bricks"
|
||||
},
|
||||
"cinnabar_bricks" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar_bricks"
|
||||
},
|
||||
"cinnabar_double_slab" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar"
|
||||
},
|
||||
"cinnabar_slab" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar"
|
||||
},
|
||||
"cinnabar_stairs" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar"
|
||||
},
|
||||
"cinnabar_wall" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "cinnabar"
|
||||
},
|
||||
"clay" : {
|
||||
"isotropic" : true,
|
||||
"sound" : "gravel",
|
||||
@@ -2500,7 +2548,6 @@ export const blocks = {
|
||||
"up" : "still_water_grey"
|
||||
}
|
||||
},
|
||||
"format_version" : "1.21.40",
|
||||
"frame" : {
|
||||
"sound" : "itemframe",
|
||||
"textures" : "itemframe_background"
|
||||
@@ -2569,6 +2616,10 @@ export const blocks = {
|
||||
"sound" : "stone",
|
||||
"textures" : "gold_ore"
|
||||
},
|
||||
"golden_dandelion" : {
|
||||
"sound" : "grass",
|
||||
"textures" : "golden_dandelion"
|
||||
},
|
||||
"golden_rail" : {
|
||||
"sound" : "metal",
|
||||
"textures" : {
|
||||
@@ -4426,6 +4477,26 @@ export const blocks = {
|
||||
"sound" : "stone",
|
||||
"textures" : "polished_blackstone"
|
||||
},
|
||||
"polished_cinnabar" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "polished_cinnabar"
|
||||
},
|
||||
"polished_cinnabar_double_slab" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "polished_cinnabar"
|
||||
},
|
||||
"polished_cinnabar_slab" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "polished_cinnabar"
|
||||
},
|
||||
"polished_cinnabar_stairs" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "polished_cinnabar"
|
||||
},
|
||||
"polished_cinnabar_wall" : {
|
||||
"sound" : "cinnabar",
|
||||
"textures" : "polished_cinnabar"
|
||||
},
|
||||
"polished_deepslate" : {
|
||||
"isotropic" : false,
|
||||
"sound" : "deepslate",
|
||||
@@ -4483,6 +4554,26 @@ export const blocks = {
|
||||
"sound" : "stone",
|
||||
"textures" : "polished_granite"
|
||||
},
|
||||
"polished_sulfur" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "polished_sulfur"
|
||||
},
|
||||
"polished_sulfur_double_slab" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "polished_sulfur"
|
||||
},
|
||||
"polished_sulfur_slab" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "polished_sulfur"
|
||||
},
|
||||
"polished_sulfur_stairs" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "polished_sulfur"
|
||||
},
|
||||
"polished_sulfur_wall" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "polished_sulfur"
|
||||
},
|
||||
"polished_tuff" : {
|
||||
"sound" : "polished_tuff",
|
||||
"textures" : "polished_tuff"
|
||||
@@ -4515,6 +4606,10 @@ export const blocks = {
|
||||
"sound" : "grass",
|
||||
"textures" : "potatoes"
|
||||
},
|
||||
"potent_sulfur" : {
|
||||
"sound" : "potent_sulfur",
|
||||
"textures" : "potent_sulfur"
|
||||
},
|
||||
"powder_snow" : {
|
||||
"isotropic" : false,
|
||||
"sound" : "powder_snow",
|
||||
@@ -5743,6 +5838,57 @@ export const blocks = {
|
||||
"structure_void" : {
|
||||
"textures" : "flattened_structure_void"
|
||||
},
|
||||
"sulfur" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur"
|
||||
},
|
||||
"sulfur_brick_double_slab" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur_bricks"
|
||||
},
|
||||
"sulfur_brick_slab" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur_bricks"
|
||||
},
|
||||
"sulfur_brick_stairs" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur_bricks"
|
||||
},
|
||||
"sulfur_brick_wall" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur_bricks"
|
||||
},
|
||||
"sulfur_bricks" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur_bricks"
|
||||
},
|
||||
"sulfur_double_slab" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur"
|
||||
},
|
||||
"sulfur_slab" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur"
|
||||
},
|
||||
"sulfur_spike" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : {
|
||||
"down" : "sulfur_spike_frustum",
|
||||
"east" : "sulfur_spike_base",
|
||||
"north" : "sulfur_spike_tip",
|
||||
"south" : "sulfur_spike_middle",
|
||||
"up" : "sulfur_spike_base",
|
||||
"west" : "sulfur_spike_merge"
|
||||
}
|
||||
},
|
||||
"sulfur_stairs" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur"
|
||||
},
|
||||
"sulfur_wall" : {
|
||||
"sound" : "sulfur",
|
||||
"textures" : "sulfur"
|
||||
},
|
||||
"sunflower" : {
|
||||
"carried_textures" : "sunflower_carried",
|
||||
"sound" : "grass",
|
||||
@@ -6850,7 +6996,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.ancient_debris",
|
||||
"sound" : "place.ancient_debris",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -6953,7 +7099,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.azalea_leaves",
|
||||
"sound" : "place.azalea_leaves",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7127,7 +7273,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "break.bamboo_wood_hanging_sign",
|
||||
"sound" : "place.bamboo_wood_hanging_sign",
|
||||
"volume" : 1.0
|
||||
}
|
||||
}
|
||||
@@ -7151,7 +7297,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.basalt",
|
||||
"sound" : "place.basalt",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7219,7 +7365,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.bone_block",
|
||||
"sound" : "place.bone_block",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7299,7 +7445,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 1.0,
|
||||
"sound" : "dig.candle",
|
||||
"sound" : "place.candle",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7333,7 +7479,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.cave_vines",
|
||||
"sound" : "place.cave_vines",
|
||||
"volume" : 1.0
|
||||
}
|
||||
},
|
||||
@@ -7359,7 +7505,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.chain",
|
||||
"sound" : "place.chain",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7488,7 +7634,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "break.cherry_wood_hanging_sign",
|
||||
"sound" : "place.cherry_wood_hanging_sign",
|
||||
"volume" : 1.0
|
||||
}
|
||||
}
|
||||
@@ -7535,6 +7681,25 @@ export const block_sounds = {
|
||||
"pitch" : 1.0,
|
||||
"volume" : 1.0
|
||||
},
|
||||
"cinnabar" : {
|
||||
"events" : {
|
||||
"break" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "block.cinnabar.break"
|
||||
},
|
||||
"hit" : {
|
||||
"pitch" : 0.50,
|
||||
"sound" : "block.cinnabar.hit",
|
||||
"volume" : 0.30
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "block.cinnabar.place"
|
||||
}
|
||||
},
|
||||
"pitch" : 1.0,
|
||||
"volume" : 1.0
|
||||
},
|
||||
"cloth" : {
|
||||
"events" : {
|
||||
"break" : {
|
||||
@@ -7554,7 +7719,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.cloth",
|
||||
"sound" : "place.cloth",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7588,7 +7753,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 0.80 ],
|
||||
"sound" : "dig.wood",
|
||||
"sound" : "place.wood",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7630,7 +7795,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.copper",
|
||||
"sound" : "place.copper",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7750,7 +7915,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "break.copper_grate",
|
||||
"sound" : "place.copper_grate",
|
||||
"volume" : 1.0
|
||||
}
|
||||
},
|
||||
@@ -7776,7 +7941,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.coral",
|
||||
"sound" : "place.coral",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -7843,7 +8008,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 1.0,
|
||||
"sound" : "break.decorated_pot",
|
||||
"sound" : "place.decorated_pot",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"shatter_pot" : {
|
||||
@@ -8059,7 +8224,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 1.50,
|
||||
"sound" : "break.frog_spawn",
|
||||
"sound" : "place.frog_spawn",
|
||||
"volume" : 0.20
|
||||
}
|
||||
}
|
||||
@@ -8082,7 +8247,7 @@ export const block_sounds = {
|
||||
"volume" : 1.0
|
||||
},
|
||||
"place" : {
|
||||
"sound" : "break.froglight",
|
||||
"sound" : "place.froglight",
|
||||
"volume" : 1.0
|
||||
}
|
||||
},
|
||||
@@ -8107,7 +8272,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.fungus",
|
||||
"sound" : "place.fungus",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8141,7 +8306,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.stone",
|
||||
"sound" : "place.stone",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8175,7 +8340,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.grass",
|
||||
"sound" : "place.grass",
|
||||
"volume" : 0.80
|
||||
}
|
||||
},
|
||||
@@ -8201,7 +8366,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.grass",
|
||||
"sound" : "place.grass",
|
||||
"volume" : 0.80
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8235,7 +8400,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.gravel",
|
||||
"sound" : "place.gravel",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8303,7 +8468,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "break.hanging_sign",
|
||||
"sound" : "place.hanging_sign",
|
||||
"volume" : 1.0
|
||||
}
|
||||
}
|
||||
@@ -8327,7 +8492,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "break.heavy_core",
|
||||
"sound" : "place.heavy_core",
|
||||
"volume" : 1.0
|
||||
}
|
||||
},
|
||||
@@ -8353,7 +8518,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.honey_block",
|
||||
"sound" : "place.honey_block",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8479,7 +8644,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.wood",
|
||||
"sound" : "place.wood",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8606,7 +8771,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 0.80 ],
|
||||
"sound" : "dig.wood",
|
||||
"sound" : "place.wood",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8640,7 +8805,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.lodestone",
|
||||
"sound" : "place.lodestone",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8745,7 +8910,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 1.20, 1.250 ],
|
||||
"sound" : "dig.stone",
|
||||
"sound" : "place.stone",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8785,7 +8950,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "block.mob_spawner.break"
|
||||
"sound" : "block.mob_spawner.place"
|
||||
}
|
||||
},
|
||||
"pitch" : 1.0,
|
||||
@@ -8925,7 +9090,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.nether_brick",
|
||||
"sound" : "place.nether_brick",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8959,7 +9124,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.nether_gold_ore",
|
||||
"sound" : "place.nether_gold_ore",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -8993,7 +9158,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 1.20, 1.250 ],
|
||||
"sound" : "dig.nether_sprouts",
|
||||
"sound" : "place.nether_sprouts",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9027,7 +9192,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.nether_wart",
|
||||
"sound" : "place.nether_wart",
|
||||
"volume" : 0.70
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9133,7 +9298,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "break.nether_wood_hanging_sign",
|
||||
"sound" : "place.nether_wood_hanging_sign",
|
||||
"volume" : 1.0
|
||||
}
|
||||
}
|
||||
@@ -9157,7 +9322,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.netherite",
|
||||
"sound" : "place.netherite",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9191,7 +9356,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.netherrack",
|
||||
"sound" : "place.netherrack",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9230,7 +9395,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.stone",
|
||||
"sound" : "place.stone",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9264,7 +9429,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 0.80 ],
|
||||
"sound" : "dig.nylium",
|
||||
"sound" : "place.nylium",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9386,6 +9551,28 @@ export const block_sounds = {
|
||||
"pitch" : 1.20,
|
||||
"volume" : 1.0
|
||||
},
|
||||
"potent_sulfur" : {
|
||||
"events" : {
|
||||
"ambient" : {
|
||||
"sound" : "block.potent_sulfur.ambient"
|
||||
},
|
||||
"break" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "block.potent_sulfur.break"
|
||||
},
|
||||
"hit" : {
|
||||
"pitch" : 0.50,
|
||||
"sound" : "block.potent_sulfur.hit",
|
||||
"volume" : 0.30
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "block.potent_sulfur.place"
|
||||
}
|
||||
},
|
||||
"pitch" : 1.0,
|
||||
"volume" : 1.0
|
||||
},
|
||||
"powder_snow" : {
|
||||
"events" : {
|
||||
"break" : {
|
||||
@@ -9405,7 +9592,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.powder_snow",
|
||||
"sound" : "place.powder_snow",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9483,7 +9670,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.roots",
|
||||
"sound" : "place.roots",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9518,7 +9705,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.sand",
|
||||
"sound" : "place.sand",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9587,7 +9774,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "break.sculk",
|
||||
"sound" : "place.sculk",
|
||||
"volume" : 1.0
|
||||
}
|
||||
},
|
||||
@@ -9614,7 +9801,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "break.sculk_catalyst",
|
||||
"sound" : "place.sculk_catalyst",
|
||||
"volume" : 1.0
|
||||
}
|
||||
}
|
||||
@@ -9673,7 +9860,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "break.sculk_shrieker",
|
||||
"sound" : "place.sculk_shrieker",
|
||||
"volume" : 1.0
|
||||
}
|
||||
}
|
||||
@@ -9698,7 +9885,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "break.sculk_vein",
|
||||
"sound" : "place.sculk_vein",
|
||||
"volume" : 1.0
|
||||
}
|
||||
}
|
||||
@@ -9735,7 +9922,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.shroomlight",
|
||||
"sound" : "place.shroomlight",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9769,7 +9956,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "mob.slime.big",
|
||||
"sound" : "place.slime.big",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9834,7 +10021,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.snow",
|
||||
"sound" : "place.snow",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9868,7 +10055,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.soul_sand",
|
||||
"sound" : "place.soul_sand",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9902,7 +10089,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.soul_soil",
|
||||
"sound" : "place.soul_soil",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -9996,7 +10183,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.stem",
|
||||
"sound" : "place.stem",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -10038,7 +10225,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.stone",
|
||||
"sound" : "place.stone",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -10059,6 +10246,25 @@ export const block_sounds = {
|
||||
"pitch" : 1.0,
|
||||
"volume" : 1.0
|
||||
},
|
||||
"sulfur" : {
|
||||
"events" : {
|
||||
"break" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "block.sulfur.break"
|
||||
},
|
||||
"hit" : {
|
||||
"pitch" : 0.50,
|
||||
"sound" : "block.sulfur.hit",
|
||||
"volume" : 0.30
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : 0.80,
|
||||
"sound" : "block.sulfur.place"
|
||||
}
|
||||
},
|
||||
"pitch" : 1.0,
|
||||
"volume" : 1.0
|
||||
},
|
||||
"suspicious_gravel" : {
|
||||
"events" : {
|
||||
"break" : {
|
||||
@@ -10275,7 +10481,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.stone",
|
||||
"sound" : "place.stone",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -10331,7 +10537,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 1.0 ],
|
||||
"sound" : "dig.vines",
|
||||
"sound" : "place.vines",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
@@ -10361,7 +10567,7 @@ export const block_sounds = {
|
||||
"sound" : "step.web"
|
||||
},
|
||||
"place" : {
|
||||
"sound" : "break.web"
|
||||
"sound" : "place.web"
|
||||
}
|
||||
},
|
||||
"pitch" : 1.0,
|
||||
@@ -10462,7 +10668,7 @@ export const block_sounds = {
|
||||
},
|
||||
"place" : {
|
||||
"pitch" : [ 0.80, 0.80 ],
|
||||
"sound" : "dig.wood",
|
||||
"sound" : "place.wood",
|
||||
"volume" : 1.0
|
||||
},
|
||||
"power.off" : {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { world } from "@minecraft/server";
|
||||
import { blockPlacementSignal } from "./BlockPlacementSignal";
|
||||
import { ChangedCellPatcher } from "./Verifier/ChangedCellPatcher";
|
||||
|
||||
export class BlockChangeWatcher {
|
||||
#patcher = new ChangedCellPatcher();
|
||||
|
||||
start() {
|
||||
world.afterEvents.playerPlaceBlock.subscribe((event) => this.#onBlockChanged(event.block));
|
||||
world.afterEvents.playerBreakBlock.subscribe((event) => this.#onBlockChanged(event.block));
|
||||
world.afterEvents.playerInteractWithBlock.subscribe((event) => {
|
||||
if (event.isFirstEvent)
|
||||
this.#onBlockChanged(event.block);
|
||||
});
|
||||
blockPlacementSignal.subscribe((dimensionId, location) => this.#patcher.patchAround(dimensionId, location));
|
||||
}
|
||||
|
||||
#onBlockChanged(block) {
|
||||
this.#patcher.patchAround(block.dimension.id, block.location);
|
||||
}
|
||||
}
|
||||
|
||||
export const blockChangeWatcher = new BlockChangeWatcher();
|
||||
blockChangeWatcher.start();
|
||||
@@ -1,7 +1,9 @@
|
||||
import { GameMode, system, world } from '@minecraft/server';
|
||||
import { Raycaster } from '../classes/Raycaster';
|
||||
import { fetchMatchingItemSlot } from '../utils';
|
||||
import { Block_state, chinese_text, State_values } from './zhMappings';
|
||||
import { Builders } from './Builder/Builders';
|
||||
import { isBannedBlock } from '../options/fastEasyPlace';
|
||||
|
||||
class BlockInfo {
|
||||
static shownToLastTick = new Set();
|
||||
|
||||
@@ -14,41 +16,46 @@ class BlockInfo {
|
||||
}
|
||||
|
||||
static showStructureBlockInfo(player) {
|
||||
const block = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true, useActiveLayer: true });
|
||||
if (!block && this.shownToLastTick.has(player.id)) {
|
||||
const hit = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true, useActiveLayer: true });
|
||||
if (!hit && this.shownToLastTick.has(player.id)) {
|
||||
player.onScreenDisplay.setActionBar({ rawtext: [
|
||||
{ translate: 'construct.blockinfo.header' },
|
||||
{ text: '\n' },
|
||||
this.getFormattedHeader(),
|
||||
{ translate: 'construct.blockinfo.none' }
|
||||
]});
|
||||
this.shownToLastTick.delete(player.id);
|
||||
}
|
||||
if (!block)
|
||||
if (!hit)
|
||||
return;
|
||||
player.onScreenDisplay.setActionBar(this.getFormattedBlockInfo(player, block.permutation));
|
||||
player.onScreenDisplay.setActionBar(this.getFormattedBlockInfo(player, hit));
|
||||
this.shownToLastTick.add(player.id);
|
||||
}
|
||||
|
||||
static getFormattedBlockInfo(player, block) {
|
||||
static getFormattedBlockInfo(player, hit) {
|
||||
const easyPlaceMessage = this.getEasyPlaceMessage(player, hit);
|
||||
const supplyMessage = this.getSupplyMessage(player, hit);
|
||||
return { rawtext: [
|
||||
this.getFormattedHeader(hit.instance),
|
||||
this.getBlockMessage(hit.block),
|
||||
{ text: '\n' },
|
||||
easyPlaceMessage,
|
||||
(easyPlaceMessage.translate && supplyMessage.translate) ? { text: ' ' } : { text: '' },
|
||||
supplyMessage
|
||||
] };
|
||||
}
|
||||
|
||||
static getFormattedHeader(instance = void 0) {
|
||||
return { rawtext: [
|
||||
{ translate: 'construct.blockinfo.header' },
|
||||
this.getSupplyMessage(player, block),
|
||||
{ text: '\n' },
|
||||
this.getBlockMessage(block)
|
||||
] };
|
||||
(instance != void 0) ? { text: ` §7${instance.getName()}\n` } : { text: '\n' }
|
||||
]};
|
||||
}
|
||||
|
||||
static getBlockMessage(block) {
|
||||
if (!block)
|
||||
return { translate: 'construct.blockinfo.unknown' };
|
||||
const message = { rawtext: [{ text: '§a' }]};
|
||||
if (chinese_text[block.type.id] !== undefined) {
|
||||
message.rawtext.push({ text: chinese_text[block.type.id] });
|
||||
} else {
|
||||
message.rawtext.push({ translate: block.localizationKey });
|
||||
}
|
||||
const states = block.getAllStates();
|
||||
if (Object.keys(states).length > 0)
|
||||
const message = { rawtext: [{ text: '§a' }, { translate: block.permutation.localizationKey }] };
|
||||
const states = block.states;
|
||||
if (block.hasStates)
|
||||
message.rawtext.push({ text: `\n§7${this.getFormattedStates(states)}` });
|
||||
if (block.isWaterlogged) {
|
||||
message.rawtext.push({ rawtext: [
|
||||
@@ -60,21 +67,21 @@ class BlockInfo {
|
||||
}
|
||||
|
||||
static getFormattedStates(states) {
|
||||
return Object.entries(states)
|
||||
.map(([stateName, stateValue]) => {
|
||||
if (State_values[stateName] || Block_state[stateName]) {
|
||||
return (`\n§7${Block_state[stateName]}`)
|
||||
.replaceAll('%1', `§3${State_values[stateName][String(stateValue)]}§7`);
|
||||
}
|
||||
})
|
||||
.join('');
|
||||
return Object.entries(states).map(([key, value]) => `§7${key}: §3${value}`).join('\n');
|
||||
}
|
||||
|
||||
static getSupplyMessage(player, block) {
|
||||
const itemStack = fetchMatchingItemSlot(player, block.getItemStack()?.typeId);
|
||||
const isInSurvival = player.getGameMode() === GameMode.Survival;
|
||||
if (!itemStack && isInSurvival)
|
||||
return { translate: 'construct.blockinfo.nosupply' };
|
||||
static getSupplyMessage(player, hit) {
|
||||
if (player.getGameMode() !== GameMode.Survival || fetchMatchingItemSlot(player, hit.block?.permutation.getItemStack()?.typeId))
|
||||
return { text: '' };
|
||||
return { translate: 'construct.blockinfo.nosupply' };
|
||||
}
|
||||
|
||||
static getEasyPlaceMessage(player, hit) {
|
||||
const builder = Builders.get(player.id);
|
||||
if (!builder.isOptionEnabled('easyPlace') && !builder.isOptionEnabled('fastEasyPlace'))
|
||||
return { text: '' };
|
||||
if (isBannedBlock(player, hit.block))
|
||||
return { translate: 'construct.blockinfo.noeasyplace' };
|
||||
return { text: '' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
class BlockPlacementSignal {
|
||||
#callbacks = [];
|
||||
|
||||
subscribe(callback) {
|
||||
this.#callbacks.push(callback);
|
||||
}
|
||||
|
||||
emit(dimensionId, location) {
|
||||
for (const callback of this.#callbacks)
|
||||
callback(dimensionId, location);
|
||||
}
|
||||
}
|
||||
|
||||
export const blockPlacementSignal = new BlockPlacementSignal();
|
||||
@@ -13,6 +13,10 @@ export class Builder {
|
||||
return BuilderOptions.isEnabled(optionId, this.playerId);
|
||||
}
|
||||
|
||||
getOptionValue(optionId) {
|
||||
return BuilderOptions.getValue(optionId, this.playerId);
|
||||
}
|
||||
|
||||
setOption(optionId, value) {
|
||||
return BuilderOptions.setValue(optionId, this.playerId, value);
|
||||
}
|
||||
@@ -27,7 +31,8 @@ export class Builder {
|
||||
easyPlace: this.isOptionEnabled('easyPlace'),
|
||||
fastEasyPlace: this.isOptionEnabled('fastEasyPlace'),
|
||||
materialGrabber: this.isOptionEnabled('materialGrabber'),
|
||||
materialInstanceName: this.materialInstanceName
|
||||
materialInstanceName: this.materialInstanceName,
|
||||
defaultRenderMode: this.getOptionValue('defaultRenderMode')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,6 +40,7 @@ export class Builder {
|
||||
this.setOption('easyPlace', builderOptions.easyPlace);
|
||||
this.setOption('fastEasyPlace', builderOptions.fastEasyPlace);
|
||||
this.setOption('materialGrabber', builderOptions.materialGrabber);
|
||||
this.setOption('defaultRenderMode', builderOptions.defaultRenderMode);
|
||||
this.materialInstanceName = builderOptions.materialInstanceName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { BuilderOption } from "./BuilderOption";
|
||||
import { world } from "@minecraft/server";
|
||||
|
||||
export class BuilderChoiceOption extends BuilderOption {
|
||||
choices;
|
||||
choiceLabels;
|
||||
defaultValue;
|
||||
|
||||
constructor({ identifier, displayName, description, choices, choiceLabels, defaultValue, onChangeCallback = () => {} }) {
|
||||
super({ identifier, displayName, description, howToUse: description });
|
||||
this.choices = choices;
|
||||
this.choiceLabels = choiceLabels;
|
||||
this.defaultValue = defaultValue;
|
||||
this.onChange = onChangeCallback;
|
||||
}
|
||||
|
||||
getValue(playerId) {
|
||||
const stored = world.getDynamicProperty(this.dynamicPropertyKey(playerId));
|
||||
return this.choices.includes(stored) ? stored : this.defaultValue;
|
||||
}
|
||||
|
||||
isEnabled(playerId) {
|
||||
return this.getValue(playerId) !== this.defaultValue;
|
||||
}
|
||||
|
||||
setValue(playerId, value) {
|
||||
if (!this.choices.includes(value))
|
||||
return void 0;
|
||||
const previous = this.getValue(playerId);
|
||||
this.save(playerId, value);
|
||||
if (previous === value)
|
||||
return void 0;
|
||||
this.onChange(playerId, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
getLabel(value) {
|
||||
return this.choiceLabels[this.choices.indexOf(value)];
|
||||
}
|
||||
|
||||
addControlTo(form, playerId) {
|
||||
form.dropdown(this.displayName, this.choiceLabels, {
|
||||
defaultValueIndex: Math.max(this.choices.indexOf(this.getValue(playerId)), 0),
|
||||
tooltip: this.description
|
||||
});
|
||||
}
|
||||
|
||||
applyFormValue(playerId, formValue) {
|
||||
const changedToValue = this.setValue(playerId, this.choices[formValue]);
|
||||
if (changedToValue === void 0)
|
||||
return void 0;
|
||||
return { rawtext: [
|
||||
{ text: `§a` },
|
||||
this.displayName,
|
||||
{ translate: 'construct.option.setto' },
|
||||
this.getLabel(changedToValue),
|
||||
{ text: `§a.` }
|
||||
]};
|
||||
}
|
||||
}
|
||||
@@ -18,31 +18,9 @@ export class BuilderForm {
|
||||
applySettings(formValues) {
|
||||
const optionIds = BuilderOptions.getOptionIds();
|
||||
for (let i = 0; i < optionIds.length; i++) {
|
||||
const option = BuilderOptions.get(optionIds[i]);
|
||||
const changedToValue = option.setValue(this.player.id, formValues[i]);
|
||||
if (changedToValue === true) {
|
||||
this.player.sendMessage({ rawtext: [
|
||||
{ text: `§a` },
|
||||
option.displayName,
|
||||
{ translate: 'construct.option.enabled' },
|
||||
{ text: `§7 ` },
|
||||
option.howToUse
|
||||
]});
|
||||
} else if (changedToValue === false) {
|
||||
this.player.sendMessage({ rawtext: [
|
||||
{ text: `§c` },
|
||||
option.displayName,
|
||||
{ translate: 'construct.option.disabled' }
|
||||
]});
|
||||
}
|
||||
const message = BuilderOptions.get(optionIds[i]).applyFormValue(this.player.id, formValues[i]);
|
||||
if (message)
|
||||
this.player.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
valueChangedToEnabled(hasChanged, newValue) {
|
||||
return hasChanged && newValue === true;
|
||||
}
|
||||
|
||||
valueChangedToDisabled(hasChanged, newValue) {
|
||||
return hasChanged && newValue === false;
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,8 @@ export class BuilderFormBuilder {
|
||||
static buildBuilderOptions(player) {
|
||||
const form = new ModalFormData()
|
||||
.title(MenuFormBuilder.menuTitle);
|
||||
for (const optionId of BuilderOptions.getOptionIds()) {
|
||||
const option = BuilderOptions.get(optionId);
|
||||
form.toggle(option.displayName, { defaultValue: option.isEnabled(player.id), tooltip: option.description });
|
||||
}
|
||||
for (const optionId of BuilderOptions.getOptionIds())
|
||||
BuilderOptions.get(optionId).addControlTo(form, player.id);
|
||||
form.submitButton({ translate: "construct.menu.submit" });
|
||||
return form;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { BuilderOptions } from "./BuilderOptions";
|
||||
import { world } from "@minecraft/server";
|
||||
|
||||
const DP_NAMESPACE = "builderOptions";
|
||||
|
||||
export class BuilderOption {
|
||||
identifier;
|
||||
displayName;
|
||||
@@ -8,7 +10,6 @@ export class BuilderOption {
|
||||
howToUse;
|
||||
#onEnable;
|
||||
#onDisable;
|
||||
#DP_NAMESPACE = "builderOptions";
|
||||
|
||||
constructor({ identifier, displayName, description, howToUse, onEnableCallback = () => {}, onDisableCallback = () => {} }) {
|
||||
this.identifier = identifier;
|
||||
@@ -20,10 +21,18 @@ export class BuilderOption {
|
||||
BuilderOptions.add(this);
|
||||
}
|
||||
|
||||
isEnabled(playerId) {
|
||||
return world.getDynamicProperty(`${this.#DP_NAMESPACE}:${playerId}:${this.identifier}`) === true;
|
||||
dynamicPropertyKey(playerId) {
|
||||
return `${DP_NAMESPACE}:${playerId}:${this.identifier}`;
|
||||
}
|
||||
|
||||
|
||||
isEnabled(playerId) {
|
||||
return world.getDynamicProperty(this.dynamicPropertyKey(playerId)) === true;
|
||||
}
|
||||
|
||||
getValue(playerId) {
|
||||
return this.isEnabled(playerId);
|
||||
}
|
||||
|
||||
setValue(playerId, value) {
|
||||
if (this.isEnabled(playerId) !== value) {
|
||||
this.save(playerId, value);
|
||||
@@ -38,6 +47,29 @@ export class BuilderOption {
|
||||
}
|
||||
|
||||
save(playerId, value) {
|
||||
world.setDynamicProperty(`${this.#DP_NAMESPACE}:${playerId}:${this.identifier}`, value);
|
||||
world.setDynamicProperty(this.dynamicPropertyKey(playerId), value);
|
||||
}
|
||||
}
|
||||
|
||||
addControlTo(form, playerId) {
|
||||
form.toggle(this.displayName, { defaultValue: this.isEnabled(playerId), tooltip: this.description });
|
||||
}
|
||||
|
||||
applyFormValue(playerId, formValue) {
|
||||
const changedToValue = this.setValue(playerId, formValue);
|
||||
if (changedToValue === void 0)
|
||||
return void 0;
|
||||
if (changedToValue)
|
||||
return { rawtext: [
|
||||
{ text: `§a` },
|
||||
this.displayName,
|
||||
{ translate: 'construct.option.enabled' },
|
||||
{ text: `§7 ` },
|
||||
this.howToUse
|
||||
]};
|
||||
return { rawtext: [
|
||||
{ text: `§c` },
|
||||
this.displayName,
|
||||
{ translate: 'construct.option.disabled' }
|
||||
]};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ export class BuilderOptions {
|
||||
return this.options[optionId].isEnabled(playerId);
|
||||
}
|
||||
|
||||
static getValue(optionId, playerId) {
|
||||
return this.options[optionId].getValue(playerId);
|
||||
}
|
||||
|
||||
static setValue(optionId, playerId, value) {
|
||||
return this.options[optionId].setValue(playerId, value);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
export const RenderMode = Object.freeze({
|
||||
Default: "default",
|
||||
Performance: "performance",
|
||||
PreviewOnly: "previewOnly",
|
||||
Classic: "classic"
|
||||
});
|
||||
|
||||
export const RENDER_MODE_ORDER = Object.freeze([
|
||||
RenderMode.Default,
|
||||
RenderMode.Performance,
|
||||
RenderMode.PreviewOnly,
|
||||
RenderMode.Classic
|
||||
]);
|
||||
|
||||
export const RENDER_MODE_LABELS = Object.freeze({
|
||||
[RenderMode.Default]: "construct.instance.option.rendermode.default",
|
||||
[RenderMode.Performance]: "construct.instance.option.rendermode.performance",
|
||||
[RenderMode.PreviewOnly]: "construct.instance.option.rendermode.previewonly",
|
||||
[RenderMode.Classic]: "construct.instance.option.rendermode.classic"
|
||||
});
|
||||
|
||||
const PROFILES = Object.freeze({
|
||||
[RenderMode.Default]: Object.freeze({ debugMarkers: true, particleOverlays: true, blockPreview: true, hybridOutline: true }),
|
||||
[RenderMode.Performance]: Object.freeze({ debugMarkers: true, particleOverlays: false, blockPreview: false, hybridOutline: true }),
|
||||
[RenderMode.PreviewOnly]: Object.freeze({ debugMarkers: false, particleOverlays: true, blockPreview: true, hybridOutline: true }),
|
||||
[RenderMode.Classic]: Object.freeze({ debugMarkers: false, particleOverlays: true, blockPreview: false, hybridOutline: false })
|
||||
});
|
||||
|
||||
export function renderProfileOf(mode) {
|
||||
return PROFILES[mode] ?? PROFILES[RenderMode.Default];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Vector } from "../../lib/Vector.js";
|
||||
|
||||
// The six sides of a block, in the order the baked face table indexes them (see _CULL_DIRECTIONS in tools/bake_block_models/main.py).
|
||||
// A baked face's cull value is a subscript into this, so reordering it alone would cull against the wrong side of the block.
|
||||
export class Side {
|
||||
static Down = 0;
|
||||
static Up = 1;
|
||||
static North = 2;
|
||||
static South = 3;
|
||||
static West = 4;
|
||||
static East = 5;
|
||||
|
||||
static COUNT = 6;
|
||||
|
||||
static OFFSETS = Object.freeze([
|
||||
Vector.down,
|
||||
Vector.up,
|
||||
Vector.north,
|
||||
Vector.south,
|
||||
Vector.west,
|
||||
Vector.east
|
||||
]);
|
||||
|
||||
// The order above is laid out in opposite pairs, so flipping the low bit turns a side into the one facing back at it.
|
||||
static opposite(side) {
|
||||
return side ^ 1;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
import { InputPermissionCategory, world, system } from "@minecraft/server";
|
||||
import { Outliner } from "../Outliner";
|
||||
import { createOutlineRenderer } from "../Render/outline/createOutlineRenderer";
|
||||
import { MENU_ITEM } from "../../consts";
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { PlayerMovement } from "../PlayerMovement";
|
||||
import { Builders } from "../Builder/Builders";
|
||||
|
||||
const MOVE_OUTLINE_TIMING = Object.freeze({ drawIntervalTicks: 1, lifetimeTicks: 1 });
|
||||
|
||||
export class FlexibleInstanceMove {
|
||||
player;
|
||||
instance;
|
||||
outliner;
|
||||
currentInstanceLocation;
|
||||
runner = void 0;
|
||||
#velocity = new Vector();
|
||||
|
||||
constructor(instance, player) {
|
||||
this.instance = instance;
|
||||
@@ -38,10 +41,15 @@ export class FlexibleInstanceMove {
|
||||
prepInstanceForMovement() {
|
||||
this.instance.flexMovingPlayerId = this.player.id;
|
||||
this.instance.disable();
|
||||
const bounds = this.instance.getBounds();
|
||||
const maxWorldLocation = this.currentInstanceLocation.add(Vector.from(bounds.max));
|
||||
this.outliner = new Outliner(this.instance.getDimension(), this.currentInstanceLocation, maxWorldLocation, 1, 1);
|
||||
this.outliner.startDraw();
|
||||
const { min, max } = this.outlineBounds();
|
||||
this.outliner = createOutlineRenderer(
|
||||
this.instance.getRenderMode(),
|
||||
this.instance.getDimension(),
|
||||
min,
|
||||
max,
|
||||
MOVE_OUTLINE_TIMING
|
||||
);
|
||||
this.outliner.start();
|
||||
}
|
||||
|
||||
prepPlayerForMovement() {
|
||||
@@ -63,17 +71,19 @@ export class FlexibleInstanceMove {
|
||||
const speedFactor = 0.5;
|
||||
|
||||
const viewDir = playerMovement.getMajorDirectionFacing();
|
||||
const forward = new Vector(viewDir.x, viewDir.y, viewDir.z);
|
||||
const right = new Vector(forward.z, 0, -forward.x);
|
||||
const moveInput = playerMovement.getMovementVector();
|
||||
let velocity = forward.multiply(moveInput.y).add(right.multiply(moveInput.x));
|
||||
const velocity = this.#velocity.set(
|
||||
viewDir.x * moveInput.y + viewDir.z * moveInput.x,
|
||||
viewDir.y * moveInput.y,
|
||||
viewDir.z * moveInput.y - viewDir.x * moveInput.x
|
||||
);
|
||||
|
||||
if (playerMovement.isJumping())
|
||||
velocity.y += 1;
|
||||
if (playerMovement.isSneaking())
|
||||
velocity.y -= 1;
|
||||
|
||||
return velocity.multiply(speedFactor);
|
||||
return velocity.multiplyInPlace(speedFactor);
|
||||
}
|
||||
|
||||
move(instanceVelocity) {
|
||||
@@ -82,10 +92,17 @@ export class FlexibleInstanceMove {
|
||||
}
|
||||
|
||||
moveOutline() {
|
||||
const { min, max } = this.outlineBounds();
|
||||
this.outliner.setBounds(this.instance.getDimension(), min, max);
|
||||
}
|
||||
|
||||
outlineBounds() {
|
||||
const bounds = this.instance.getBounds();
|
||||
const minWorldLocation = this.currentInstanceLocation.floor()
|
||||
const maxWorldLocation = minWorldLocation.add(Vector.from(bounds.max));
|
||||
this.outliner.setVertices(this.instance.getDimension(), minWorldLocation, maxWorldLocation);
|
||||
const origin = this.currentInstanceLocation.floor();
|
||||
return {
|
||||
min: origin.add(bounds.min),
|
||||
max: origin.add(bounds.max)
|
||||
};
|
||||
}
|
||||
|
||||
onPlayerUseItem(event) {
|
||||
@@ -101,7 +118,7 @@ export class FlexibleInstanceMove {
|
||||
|
||||
finish() {
|
||||
system.clearRun(this.runner);
|
||||
this.outliner.stopDraw();
|
||||
this.outliner.stop();
|
||||
this.outliner = void 0;
|
||||
this.instance.move(this.instance.getDimension().id, this.currentInstanceLocation);
|
||||
this.instance.enable();
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { InstanceExistsError } from '../Errors/InstanceExistsError';
|
||||
import { InstanceNotFoundError } from '../Errors/InstanceNotFoundError';
|
||||
import { StructureNotFoundError } from '../Errors/StructureNotFoundError';
|
||||
import { InstanceOptions } from './InstanceOptions';
|
||||
import { StructureInstance } from './StructureInstance';
|
||||
import { InvalidStructureError, world } from '@minecraft/server';
|
||||
|
||||
class InstanceCollection {
|
||||
instances;
|
||||
|
||||
constructor() {
|
||||
this.instances = {};
|
||||
}
|
||||
|
||||
loadExistingInstances() {
|
||||
world.getDynamicPropertyIds().filter(id => id.startsWith('instanceOptions:')).forEach(id => {
|
||||
const instanceName = id.replace('instanceOptions:', '');
|
||||
let structureId;
|
||||
try {
|
||||
structureId = InstanceOptions.getInstanceStructureId(instanceName);
|
||||
this.instances[instanceName] = new StructureInstance(instanceName, structureId);
|
||||
} catch (e) {
|
||||
world.sendMessage(`§c[Construct] Error loading structure instance '${instanceName}'. It will be removed.`);
|
||||
world.setDynamicProperty(id, void 0);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
add(instanceName, structureId, defaults = {}) {
|
||||
if (this.instances[instanceName])
|
||||
throw new InstanceExistsError(instanceName);
|
||||
const instance = new StructureInstance(instanceName, structureId, defaults);
|
||||
this.instances[instanceName] = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
get(instanceName) {
|
||||
const instance = this.instances[instanceName];
|
||||
if (!instance)
|
||||
throw new InstanceNotFoundError(instanceName);
|
||||
return instance;
|
||||
}
|
||||
|
||||
has(instanceName) {
|
||||
return Boolean(this.instances[instanceName]);
|
||||
}
|
||||
|
||||
delete(instanceName) {
|
||||
const instance = this.get(instanceName);
|
||||
instance.delete();
|
||||
delete this.instances[instanceName];
|
||||
}
|
||||
|
||||
getInstanceNames() {
|
||||
return Object.keys(this.instances).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
getInstanceNamesSortedByEnabled() {
|
||||
const instanceNames = this.getInstanceNames();
|
||||
instanceNames.sort((a, b) => {
|
||||
const aEnabled = this.get(a).isEnabled();
|
||||
const bEnabled = this.get(b).isEnabled();
|
||||
if (aEnabled === bEnabled)
|
||||
return a.localeCompare(b);
|
||||
return bEnabled ? 1 : -1;
|
||||
});
|
||||
return instanceNames;
|
||||
}
|
||||
|
||||
getInstancesAt(dimensionId, location, options = {}) {
|
||||
return Object.values(this.instances).filter(instance => {
|
||||
try {
|
||||
return instance.isLocationActive(dimensionId, instance.toStructureCoords(location), options)
|
||||
} catch (error) {
|
||||
if (error instanceof StructureNotFoundError || error instanceof InvalidStructureError) {
|
||||
this.delete(instance.name);
|
||||
return false;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getInstanceAt(dimensionId, location, options = {}) {
|
||||
return this.getInstancesAt(dimensionId, location, options)[0];
|
||||
}
|
||||
|
||||
fetchStructureBlock(dimensionId, location) {
|
||||
const instance = this.getInstanceAt(dimensionId, location);
|
||||
if (!instance)
|
||||
return void 0;
|
||||
return instance.getBlock(instance.toStructureCoords(location));
|
||||
}
|
||||
|
||||
getWorldStructureIds() {
|
||||
const structureManager = world.structureManager;
|
||||
const packIds = [...new Set(structureManager.getPackStructureIds()
|
||||
.map(id => id.replace('mystructure:', '')))
|
||||
];
|
||||
const packIdSet = new Set(packIds);
|
||||
let worldStructureIds = [];
|
||||
try {
|
||||
worldStructureIds = structureManager.getWorldStructureIds()
|
||||
.filter(id => id.startsWith('mystructure:'))
|
||||
.map(id => id.replace('mystructure:', ''))
|
||||
.filter(id => !packIdSet.has(id));
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch world structure IDs. They will be ignored. Error:', error);
|
||||
}
|
||||
return [...packIds, ...worldStructureIds];
|
||||
}
|
||||
|
||||
rename(instanceName, newName) {
|
||||
const structure = this.get(instanceName);
|
||||
if (this.instances[newName])
|
||||
throw new InstanceExistsError(newName);
|
||||
structure.rename(newName);
|
||||
this.instances[newName] = structure;
|
||||
delete this.instances[instanceName];
|
||||
structure.name = newName;
|
||||
}
|
||||
}
|
||||
|
||||
export const instanceCollection = new InstanceCollection();
|
||||
|
||||
world.afterEvents.worldLoad.subscribe(() => {
|
||||
instanceCollection.loadExistingInstances();
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { structureCollection } from '../Structure/StructureCollection';
|
||||
import { instanceCollection } from './InstanceCollection';
|
||||
import { MenuForm } from '../MenuForm';
|
||||
import { forceShow } from '../../utils';
|
||||
import { InstanceButtons } from '../Enums/InstanceButtons';
|
||||
@@ -6,6 +6,7 @@ import { InstanceFormBuilder } from './InstanceFormBuilder';
|
||||
import { FormCancelationReason } from '@minecraft/server-ui';
|
||||
import { FlexibleInstanceMove } from './FlexibleInstanceMove';
|
||||
import { Builders } from '../Builder/Builders';
|
||||
import { RENDER_MODE_ORDER, RenderMode } from '../Enums/RenderMode';
|
||||
|
||||
export class InstanceForm {
|
||||
instanceName;
|
||||
@@ -38,7 +39,7 @@ export class InstanceForm {
|
||||
constructor(player, instanceName) {
|
||||
this.player = player;
|
||||
this.instanceName = instanceName;
|
||||
this.instance = structureCollection.get(this.instanceName);
|
||||
this.instance = instanceCollection.get(this.instanceName);
|
||||
this.show();
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ export class InstanceForm {
|
||||
this.renameInstanceForm();
|
||||
break;
|
||||
case InstanceButtons.Delete:
|
||||
structureCollection.delete(this.instanceName);
|
||||
instanceCollection.delete(this.instanceName);
|
||||
break;
|
||||
case InstanceButtons.NextLayer:
|
||||
this.instance.increaseLayer();
|
||||
@@ -128,7 +129,7 @@ export class InstanceForm {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
structureCollection.rename(this.instanceName, newName);
|
||||
instanceCollection.rename(this.instanceName, newName);
|
||||
this.instanceName = newName;
|
||||
} catch (e) {
|
||||
this.player.sendMessage({ translate: 'construct.instance.rename.error', with: [e.message] });
|
||||
@@ -176,6 +177,8 @@ export class InstanceForm {
|
||||
return;
|
||||
this.instance.setVerifierEnabled(response.formValues[0]);
|
||||
this.instance.setLayer(parseInt(response.formValues[1]));
|
||||
this.instance.setRenderMode(RENDER_MODE_ORDER[response.formValues[2]] ?? RenderMode.Default);
|
||||
this.instance.setVerifierRefreshSeconds(parseInt(response.formValues[3]));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
||||
import { MenuFormBuilder } from '../MenuFormBuilder';
|
||||
import { StructureVerifier } from '../Verifier/StructureVerifier';
|
||||
import { StructureStatistics } from '../Structure/StructureStatistics';
|
||||
import { InstanceStatistics } from './InstanceStatistics';
|
||||
import {EntityComponentTypes, TicksPerSecond, world} from '@minecraft/server';
|
||||
import { RENDER_MODE_LABELS, RENDER_MODE_ORDER } from '../Enums/RenderMode';
|
||||
import { RefreshRate } from '../Verifier/RefreshRate';
|
||||
|
||||
export class InstanceFormBuilder {
|
||||
static structureVerifier;
|
||||
@@ -43,9 +45,9 @@ export class InstanceFormBuilder {
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
if (this.structureVerifier)
|
||||
throw new Error('StructureVerifier is already running.');
|
||||
this.structureVerifier = new StructureVerifier(instance, { isEnabled: true, particleLifetime: 1*TicksPerSecond, isStandalone: true });
|
||||
this.structureVerifier = StructureVerifier.standalone(instance, { particleLifetime: 1*TicksPerSecond });
|
||||
const verification = await this.structureVerifier.verifyStructure(true);
|
||||
const statistics = new StructureStatistics(instance, verification);
|
||||
const statistics = new InstanceStatistics(instance, verification);
|
||||
const statsMessage = statistics.getMessage();
|
||||
this.structureVerifier = void 0;
|
||||
buildStatisticsForm.body(statsMessage);
|
||||
@@ -57,6 +59,24 @@ export class InstanceFormBuilder {
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
.toggle({ translate: 'construct.instance.option.validation' }, { defaultValue: instance.options.verifier.isEnabled, tooltip: { translate: 'construct.instance.option.validation.description' }})
|
||||
.slider({ translate: 'construct.instance.option.layer'}, 0, instance.getMaxLayer(), { defaultValue: instance.getLayer(), valueStep: 1, tooltip: { translate: 'construct.instance.option.layer.description' }})
|
||||
.dropdown(
|
||||
{ translate: 'construct.instance.option.rendermode' },
|
||||
RENDER_MODE_ORDER.map(mode => ({ translate: RENDER_MODE_LABELS[mode] })),
|
||||
{
|
||||
defaultValueIndex: Math.max(RENDER_MODE_ORDER.indexOf(instance.options.renderMode), 0),
|
||||
tooltip: { translate: 'construct.instance.option.rendermode.description' }
|
||||
}
|
||||
)
|
||||
.slider(
|
||||
{ translate: 'construct.instance.option.refreshrate' },
|
||||
RefreshRate.MIN_SECONDS,
|
||||
RefreshRate.MAX_SECONDS,
|
||||
{
|
||||
defaultValue: instance.options.verifier.refreshSeconds,
|
||||
valueStep: RefreshRate.SECONDS_STEP,
|
||||
tooltip: { translate: 'construct.instance.option.refreshrate.description' }
|
||||
}
|
||||
)
|
||||
.submitButton({ translate: 'construct.menu.submit' });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { world } from "@minecraft/server";
|
||||
import { Option } from "../Option";
|
||||
import { RenderMode } from "../Enums/RenderMode";
|
||||
import { RefreshRate } from "../Verifier/RefreshRate";
|
||||
|
||||
const VERIFIER_DEFAULTS = Object.freeze({
|
||||
isEnabled: true,
|
||||
trackPlayerDistance: 5,
|
||||
particleLifetime: 10,
|
||||
refreshSeconds: RefreshRate.DEFAULT_SECONDS
|
||||
});
|
||||
|
||||
export class InstanceOptions extends Option {
|
||||
#DP_NAMESPACE = "instanceOptions";
|
||||
@@ -10,25 +19,28 @@ export class InstanceOptions extends Option {
|
||||
dimensionId = void 0;
|
||||
worldLocation = new Vector();
|
||||
currentLayer = 0;
|
||||
verifier = {
|
||||
isEnabled: true,
|
||||
trackPlayerDistance: 5,
|
||||
particleLifetime: 10
|
||||
};
|
||||
verifier = { ...VERIFIER_DEFAULTS };
|
||||
renderMode = RenderMode.Default;
|
||||
|
||||
static getInstanceStructureId(instanceName) {
|
||||
const options = new InstanceOptions(instanceName, void 0);
|
||||
return options.structureId;
|
||||
}
|
||||
|
||||
constructor(instanceName, structureId) {
|
||||
constructor(instanceName, structureId, defaults = {}) {
|
||||
super();
|
||||
this.instanceName = instanceName;
|
||||
this.structureId = structureId;
|
||||
this.load();
|
||||
this.#applyDefaults(defaults);
|
||||
this.save();
|
||||
}
|
||||
|
||||
#applyDefaults({ renderMode }) {
|
||||
if (renderMode !== void 0)
|
||||
this.renderMode = renderMode;
|
||||
}
|
||||
|
||||
save() {
|
||||
this.saveToDP(this.#DP_NAMESPACE, this.instanceName, this);
|
||||
}
|
||||
@@ -36,6 +48,7 @@ export class InstanceOptions extends Option {
|
||||
load() {
|
||||
this.loadFromDP(this.#DP_NAMESPACE, this.instanceName);
|
||||
this.worldLocation = Vector.from(this.worldLocation);
|
||||
this.verifier = { ...VERIFIER_DEFAULTS, ...this.verifier };
|
||||
}
|
||||
|
||||
clear() {
|
||||
@@ -78,8 +91,18 @@ export class InstanceOptions extends Option {
|
||||
this.save();
|
||||
}
|
||||
|
||||
setVerifierRefreshSeconds(seconds) {
|
||||
this.verifier.refreshSeconds = RefreshRate.clampSeconds(seconds);
|
||||
this.save();
|
||||
}
|
||||
|
||||
setVerifierParticleLifetime(lifetime) {
|
||||
this.verifier.particleLifetime = lifetime;
|
||||
this.save();
|
||||
}
|
||||
|
||||
setRenderMode(mode) {
|
||||
this.renderMode = mode;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Vector } from '../../lib/Vector.js';
|
||||
import { BlockVerificationLevel } from '../Enums/BlockVerificationLevel.js';
|
||||
|
||||
export class InstanceStatistics {
|
||||
constructor(instance, verification) {
|
||||
this.instance = instance;
|
||||
this.statistics = verification.countByLevel();
|
||||
}
|
||||
|
||||
getNonAirBlocks() {
|
||||
const activeBounds = this.instance.getActiveBounds();
|
||||
return Vector.volume(activeBounds.min, activeBounds.max) - this.statistics[BlockVerificationLevel.Air];
|
||||
}
|
||||
|
||||
getStat(blockVerificationLevel) {
|
||||
return { num: this.statistics[blockVerificationLevel], percent: this.statistics[blockVerificationLevel] / (this.getNonAirBlocks()) * 100 };
|
||||
}
|
||||
|
||||
getSkipped() {
|
||||
return this.statistics[BlockVerificationLevel.Skipped] || 0;
|
||||
}
|
||||
|
||||
getMessage() {
|
||||
const message = { rawtext: [] };
|
||||
message.rawtext.push({ translate: 'construct.structure.statistics.header', with: [this.instance.getName()] });
|
||||
if (this.instance.hasLayerSelected())
|
||||
message.rawtext.push({ translate: 'construct.instance.materials.layer', with: [String(this.instance.getLayer())] });
|
||||
message.rawtext.push({ rawtext: [
|
||||
{ text: '\n' },
|
||||
{ translate: 'construct.structure.statistics.blocks', with: [String(this.getNonAirBlocks())] },
|
||||
{ text: '\n' }
|
||||
]});
|
||||
const skipped = this.getSkipped();
|
||||
if (skipped > 0)
|
||||
message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.skipped', with: [String(skipped)] }, { text: '\n' }] });
|
||||
message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.correct', with: [this.formatStat(this.getStat(BlockVerificationLevel.Match))] }, { text: '\n' }] });
|
||||
message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.stateincorrect', with: [this.formatStat(this.getStat(BlockVerificationLevel.TypeMatch))] }, { text: '\n' }] });
|
||||
message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.incorrect', with: [this.formatStat(this.getStat(BlockVerificationLevel.NoMatch))] }, { text: '\n' }] });
|
||||
message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.missing', with: [this.formatStat(this.getStat(BlockVerificationLevel.Missing))] }, { text: '\n' }] });
|
||||
return message;
|
||||
}
|
||||
|
||||
formatStat(stat) {
|
||||
return `${stat.num} (${stat.percent.toFixed(2)}%%)`;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { StructureOutliner } from "../Render/StructureOutliner";
|
||||
import { StructureOutliner } from "../Render/outline/StructureOutliner";
|
||||
import { StructureVerifier } from "../Verifier/StructureVerifier";
|
||||
import { Structure } from "../Structure/Structure";
|
||||
import { InstanceOptions } from "./InstanceOptions";
|
||||
import { world, system, TicksPerSecond } from "@minecraft/server";
|
||||
import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError";
|
||||
import { StructureMaterials } from "../Materials/StructureMaterials";
|
||||
import { VerificationRenderer } from "../Render/VerificationRenderer";
|
||||
import { structureCollection } from "../Structure/StructureCollection";
|
||||
import { BlockPreviewRenderer } from "../Render/preview/BlockPreviewRenderer";
|
||||
import { instanceCollection } from "../Instance/InstanceCollection";
|
||||
import { InstanceExistsError } from "../Errors/InstanceExistsError";
|
||||
|
||||
export class StructureInstance {
|
||||
@@ -15,13 +15,15 @@ export class StructureInstance {
|
||||
structure = void 0;
|
||||
outliner = void 0;
|
||||
verifier = void 0;
|
||||
verificationRenderer = void 0;
|
||||
previewRenderer = void 0;
|
||||
materials = void 0;
|
||||
flexMovingPlayerId = void 0;
|
||||
|
||||
constructor(instanceName, structureId) {
|
||||
#cachedDimension = void 0;
|
||||
|
||||
constructor(instanceName, structureId, defaults = {}) {
|
||||
this.structure = new Structure(structureId);
|
||||
this.options = new InstanceOptions(instanceName, structureId);
|
||||
this.options = new InstanceOptions(instanceName, structureId, defaults);
|
||||
this.refreshBox();
|
||||
this.subscribeToEvents();
|
||||
}
|
||||
@@ -33,7 +35,7 @@ export class StructureInstance {
|
||||
delete this.structure;
|
||||
delete this.outliner;
|
||||
delete this.verifier;
|
||||
delete this.verificationRenderer;
|
||||
delete this.previewRenderer;
|
||||
delete this.materials;
|
||||
}
|
||||
|
||||
@@ -43,14 +45,14 @@ export class StructureInstance {
|
||||
if (!this.outliner)
|
||||
this.outliner = new StructureOutliner(this);
|
||||
if (!this.verifier)
|
||||
this.verifier = new StructureVerifier(this, { isEnabled: this.options.verifier.isEnabled });
|
||||
if (!this.verificationRenderer)
|
||||
this.verificationRenderer = new VerificationRenderer(this);
|
||||
this.verifier = StructureVerifier.forInstance(this);
|
||||
if (!this.previewRenderer)
|
||||
this.previewRenderer = new BlockPreviewRenderer(this);
|
||||
if (!this.materials)
|
||||
this.materials = new StructureMaterials(this);
|
||||
this.outliner.refresh();
|
||||
this.verifier.refresh();
|
||||
this.verificationRenderer.refresh();
|
||||
this.verifier.refresh(true);
|
||||
this.previewRenderer.refresh(true);
|
||||
this.materials.refresh();
|
||||
}
|
||||
|
||||
@@ -71,7 +73,9 @@ export class StructureInstance {
|
||||
}
|
||||
|
||||
getDimension() {
|
||||
return this.options?.getDimension();
|
||||
if (!this.#cachedDimension)
|
||||
this.#cachedDimension = this.options?.getDimension();
|
||||
return this.#cachedDimension;
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
@@ -79,7 +83,7 @@ export class StructureInstance {
|
||||
}
|
||||
|
||||
getMaxLayer() {
|
||||
return this.structure.getHeight();
|
||||
return this.structure?.getHeight();
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
@@ -103,8 +107,8 @@ export class StructureInstance {
|
||||
const min = this.structure.getMin();
|
||||
const max = this.structure.getMax();
|
||||
return {
|
||||
min: new Vector(min.x, layer - 1, min.z),
|
||||
max: new Vector(max.x, layer, max.z)
|
||||
min: { x: min.x, y: layer - 1, z: min.z },
|
||||
max: { x: max.x, y: layer, z: max.z }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -197,7 +201,7 @@ export class StructureInstance {
|
||||
}
|
||||
|
||||
rename(newName) {
|
||||
if (structureCollection.has(newName))
|
||||
if (instanceCollection.has(newName))
|
||||
throw new InstanceExistsError(newName);
|
||||
this.options.rename(newName);
|
||||
}
|
||||
@@ -227,7 +231,7 @@ export class StructureInstance {
|
||||
setVerifierEnabled(enable) {
|
||||
this.options.setVerifierEnabled(enable);
|
||||
this.verifier.refresh();
|
||||
this.verificationRenderer.refresh();
|
||||
this.previewRenderer.refresh();
|
||||
}
|
||||
|
||||
setVerifierDistance(distance) {
|
||||
@@ -238,7 +242,22 @@ export class StructureInstance {
|
||||
} else {
|
||||
this.options.verifier.particleLifetime = 10;
|
||||
}
|
||||
this.verificationRenderer.refresh();
|
||||
this.previewRenderer.refresh();
|
||||
}
|
||||
|
||||
setVerifierRefreshSeconds(seconds) {
|
||||
this.options.setVerifierRefreshSeconds(seconds);
|
||||
this.verifier.refresh();
|
||||
this.previewRenderer.refresh();
|
||||
}
|
||||
|
||||
getRenderMode() {
|
||||
return this.options.renderMode;
|
||||
}
|
||||
|
||||
setRenderMode(mode) {
|
||||
this.options.setRenderMode(mode);
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
increaseLayer() {
|
||||
@@ -280,11 +299,19 @@ export class StructureInstance {
|
||||
}
|
||||
|
||||
toGlobalCoords(structureLocation) {
|
||||
return Vector.from(structureLocation).add(this.options.worldLocation);
|
||||
return {
|
||||
x: structureLocation.x + this.options.worldLocation.x,
|
||||
y: structureLocation.y + this.options.worldLocation.y,
|
||||
z: structureLocation.z + this.options.worldLocation.z
|
||||
};
|
||||
}
|
||||
|
||||
toStructureCoords(worldLocation) {
|
||||
return Vector.from(worldLocation).subtract(this.options.worldLocation);
|
||||
return {
|
||||
x: worldLocation.x - this.options.worldLocation.x,
|
||||
y: worldLocation.y - this.options.worldLocation.y,
|
||||
z: worldLocation.z - this.options.worldLocation.z
|
||||
};
|
||||
}
|
||||
|
||||
asPacket() {
|
||||
@@ -302,14 +329,15 @@ export class StructureInstance {
|
||||
isEnabled: this.options.verifier.isEnabled,
|
||||
trackPlayerDistance: this.options.verifier.trackPlayerDistance,
|
||||
particleLifetime: this.options.verifier.particleLifetime
|
||||
}
|
||||
},
|
||||
renderMode: this.options.renderMode
|
||||
};
|
||||
}
|
||||
|
||||
setOptions(newOptions) {
|
||||
const newVerifierOptions = newOptions.verifier;
|
||||
if (newOptions.name !== this.getName())
|
||||
structureCollection.rename(this.getName(), newOptions.name);
|
||||
instanceCollection.rename(this.getName(), newOptions.name);
|
||||
this.setStructure(newOptions.structureId);
|
||||
this.options.setEnabled(newOptions.isEnabled);
|
||||
this.options.move(newOptions.dimensionId, newOptions.location);
|
||||
@@ -317,6 +345,7 @@ export class StructureInstance {
|
||||
this.options.setVerifierEnabled(newVerifierOptions.isEnabled);
|
||||
this.options.setVerifierDistance(newVerifierOptions.trackPlayerDistance);
|
||||
this.options.setVerifierParticleLifetime(newVerifierOptions.particleLifetime);
|
||||
this.options.setRenderMode(newOptions.renderMode);
|
||||
this.options.save();
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MaterialGrabberFormBuilder } from './MaterialGrabberFormBuilder';
|
||||
import { forceShow } from '../../utils';
|
||||
import { Builders } from '../Builder/Builders';
|
||||
import { structureCollection } from '../Structure/StructureCollection';
|
||||
import { instanceCollection } from '../Instance/InstanceCollection';
|
||||
|
||||
export class MaterialGrabberForm {
|
||||
constructor(player) {
|
||||
@@ -11,10 +11,11 @@ export class MaterialGrabberForm {
|
||||
|
||||
show() {
|
||||
try {
|
||||
return forceShow(this.player, MaterialGrabberFormBuilder.buildInstanceSelector(this.player)).then((response) => {
|
||||
const instanceNames = instanceCollection.getInstanceNamesSortedByEnabled();
|
||||
return forceShow(this.player, MaterialGrabberFormBuilder.buildInstanceSelector(this.player, instanceNames)).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
const selectedInstanceName = structureCollection.getInstanceNames()[response.selection];
|
||||
const selectedInstanceName = instanceNames[response.selection];
|
||||
if (selectedInstanceName) {
|
||||
this.setActiveInstance(selectedInstanceName);
|
||||
this.player.sendMessage({ translate: 'construct.materials.grabber.menu.success', with: [selectedInstanceName] });
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ActionFormData } from "@minecraft/server-ui";
|
||||
import { MenuFormBuilder } from "../MenuFormBuilder";
|
||||
import { structureCollection } from "../Structure/StructureCollection";
|
||||
import { instanceCollection } from "../Instance/InstanceCollection";
|
||||
import { Builders } from "../Builder/Builders";
|
||||
|
||||
export class MaterialGrabberFormBuilder {
|
||||
static menuTitle = { rawtext: [MenuFormBuilder.menuTitle, { translate: 'construct.materials.grabber.menu.title' }] };
|
||||
|
||||
static buildInstanceSelector(player) {
|
||||
static buildInstanceSelector(player, instanceNames) {
|
||||
const allInstanceNameForm = new ActionFormData()
|
||||
.title(this.menuTitle);
|
||||
const currInstanceName = Builders.get(player.id).materialInstanceName;
|
||||
@@ -18,8 +18,8 @@ export class MaterialGrabberFormBuilder {
|
||||
body.rawtext.push({ text: '\n' });
|
||||
body.rawtext.push({ translate: 'construct.materials.grabber.menu.selectinstance' });
|
||||
allInstanceNameForm.body(body);
|
||||
structureCollection.getInstanceNames().forEach(instanceName => {
|
||||
allInstanceNameForm.button(`§2${instanceName}`);
|
||||
instanceNames.forEach(instanceName => {
|
||||
allInstanceNameForm.button(`${instanceCollection.get(instanceName).isEnabled() ? '§2' : '§c'}${instanceName}`);
|
||||
});
|
||||
return allInstanceNameForm;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError";
|
||||
|
||||
class StructureMaterials {
|
||||
instance;
|
||||
materials;
|
||||
#materials;
|
||||
|
||||
constructor(instance) {
|
||||
this.instance = instance;
|
||||
this.materials = {};
|
||||
this.#materials = {};
|
||||
}
|
||||
|
||||
refresh() {
|
||||
@@ -31,26 +31,26 @@ class StructureMaterials {
|
||||
}
|
||||
|
||||
get materials() {
|
||||
return this.materials;
|
||||
return this.#materials;
|
||||
}
|
||||
|
||||
get(itemType) {
|
||||
return this.materials[itemType];
|
||||
return this.#materials[itemType];
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return Object.keys(this.materials).length === 0;
|
||||
return Object.keys(this.#materials).length === 0;
|
||||
}
|
||||
|
||||
has(itemType) {
|
||||
return this.materials[itemType] !== undefined;
|
||||
return this.#materials[itemType] !== undefined;
|
||||
}
|
||||
|
||||
remove(itemType, amount) {
|
||||
if (!this.materials[itemType]) return;
|
||||
this.materials[itemType].count -= amount;
|
||||
if (this.materials[itemType].count <= 0)
|
||||
delete this.materials[itemType];
|
||||
if (!this.#materials[itemType]) return;
|
||||
this.#materials[itemType].count -= amount;
|
||||
if (this.#materials[itemType].count <= 0)
|
||||
delete this.#materials[itemType];
|
||||
}
|
||||
|
||||
*populateAll() {
|
||||
@@ -78,21 +78,21 @@ class StructureMaterials {
|
||||
}
|
||||
|
||||
countBlock(block) {
|
||||
const itemStack = block?.getItemStack();
|
||||
const itemStack = block?.permutation.getItemStack();
|
||||
const typeId = itemStack?.typeId;
|
||||
if (!typeId) return;
|
||||
if (!this.materials[typeId])
|
||||
this.materials[typeId] = { count: 0, stackSize: itemStack.maxAmount };
|
||||
this.materials[typeId].count++;
|
||||
if (!this.#materials[typeId])
|
||||
this.#materials[typeId] = { count: 0, stackSize: itemStack.maxAmount };
|
||||
this.#materials[typeId].count++;
|
||||
}
|
||||
|
||||
clear() {
|
||||
for (const key in this.materials)
|
||||
delete this.materials[key];
|
||||
for (const key in this.#materials)
|
||||
delete this.#materials[key];
|
||||
}
|
||||
|
||||
formatString(otherMaterials = void 0) {
|
||||
const materials = otherMaterials || this.materials;
|
||||
const materials = otherMaterials || this.#materials;
|
||||
let message = { rawtext: [] };
|
||||
const sortedTypes = Object.keys(materials).sort(
|
||||
(a, b) => materials[b].count - materials[a].count
|
||||
@@ -126,7 +126,7 @@ class StructureMaterials {
|
||||
}
|
||||
|
||||
getMaterialsDifference(container) {
|
||||
const missingMaterials = JSON.parse(JSON.stringify(this.materials));
|
||||
const missingMaterials = JSON.parse(JSON.stringify(this.#materials));
|
||||
for (let slotIndex = 0; slotIndex < container.size; slotIndex++) {
|
||||
const slot = container.getSlot(slotIndex);
|
||||
if (slot.hasItem()) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { forceShow } from '../utils';
|
||||
import { structureCollection } from './Structure/StructureCollection';
|
||||
import { instanceCollection } from './Instance/InstanceCollection';
|
||||
import { MenuFormBuilder } from './MenuFormBuilder';
|
||||
import { InstanceForm } from './Instance/InstanceForm';
|
||||
import { BuilderForm } from './Builder/BuilderForm';
|
||||
import { InstanceExistsError } from './Errors/InstanceExistsError';
|
||||
import { StructureNotFoundError } from './Errors/StructureNotFoundError';
|
||||
import { getDefaultRenderMode } from '../options/defaultRenderMode';
|
||||
|
||||
export class MenuForm {
|
||||
constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) {
|
||||
@@ -15,7 +16,7 @@ export class MenuForm {
|
||||
async show(jumpToInstance = false, instanceName = void 0) {
|
||||
if (jumpToInstance) {
|
||||
if (!instanceName)
|
||||
instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.getName();
|
||||
instanceName = instanceCollection.getInstanceAt(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.getName();
|
||||
if (instanceName) {
|
||||
new InstanceForm(this.player, instanceName);
|
||||
return;
|
||||
@@ -29,19 +30,20 @@ export class MenuForm {
|
||||
|
||||
async getInstanceNameFromForm() {
|
||||
try {
|
||||
return forceShow(this.player, MenuFormBuilder.buildAllInstanceName()).then((response) => {
|
||||
const instanceNames = instanceCollection.getInstanceNamesSortedByEnabled();
|
||||
return forceShow(this.player, MenuFormBuilder.buildAllInstanceName(instanceNames)).then((response) => {
|
||||
if (response.canceled)
|
||||
return void 0;
|
||||
let selection = response.selection;
|
||||
if (selection === 0) {
|
||||
new BuilderForm(this.player);
|
||||
return void 0;
|
||||
} else if (selection == structureCollection.getInstanceNames().length + 2) {
|
||||
} else if (selection == instanceNames.length + 2) {
|
||||
MenuFormBuilder.buildHowTo().show(this.player);
|
||||
return void 0;
|
||||
} else {
|
||||
selection--;
|
||||
const selectedInstanceName = structureCollection.getInstanceNames()[selection];
|
||||
const selectedInstanceName = instanceNames[selection];
|
||||
return selectedInstanceName || this.createNewInstance();
|
||||
}
|
||||
});
|
||||
@@ -65,7 +67,7 @@ export class MenuForm {
|
||||
if (!structureId)
|
||||
return void 0;
|
||||
try {
|
||||
structureCollection.add(instanceName, structureId);
|
||||
instanceCollection.add(instanceName, structureId, { renderMode: getDefaultRenderMode(this.player.id) });
|
||||
} catch (error) {
|
||||
if (error instanceof InstanceExistsError || error instanceof StructureNotFoundError) {
|
||||
error.sendTo(this.player);
|
||||
@@ -81,7 +83,9 @@ export class MenuForm {
|
||||
return MenuFormBuilder.buildAllStructures().show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return void 0;
|
||||
const selectedStructureId = structureCollection.getWorldStructureIds()[response.selection];
|
||||
const worldStructureIds = instanceCollection.getWorldStructureIds();
|
||||
worldStructureIds.sort((a, b) => a.localeCompare(b));
|
||||
const selectedStructureId = worldStructureIds[response.selection];
|
||||
return selectedStructureId || this.getOtherStructureId();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
||||
import { structureCollection } from './Structure/StructureCollection';
|
||||
import { instanceCollection } from './Instance/InstanceCollection';
|
||||
|
||||
export class MenuFormBuilder {
|
||||
static menuTitle = { translate: 'construct.mainmenu.title' };
|
||||
|
||||
static buildAllInstanceName() {
|
||||
static buildAllInstanceName(instanceNames) {
|
||||
const allInstanceNameForm = new ActionFormData()
|
||||
.title(this.menuTitle)
|
||||
.body({ translate: 'construct.mainmenu.selectinstance' });
|
||||
allInstanceNameForm.button({ translate: 'construct.mainmenu.settings' });
|
||||
structureCollection.getInstanceNames().forEach(instanceName => {
|
||||
allInstanceNameForm.button(`${structureCollection.get(instanceName).isEnabled() ? '§2' : '§c'}${instanceName}`);
|
||||
instanceNames.forEach(instanceName => {
|
||||
allInstanceNameForm.button(`${instanceCollection.get(instanceName).isEnabled() ? '§2' : '§c'}${instanceName}`);
|
||||
});
|
||||
allInstanceNameForm.button({ translate: 'construct.mainmenu.newinstance' });
|
||||
allInstanceNameForm.button({ translate: 'construct.mainmenu.howto' });
|
||||
@@ -28,7 +28,9 @@ export class MenuFormBuilder {
|
||||
const allStructuresForm = new ActionFormData()
|
||||
.title(this.menuTitle)
|
||||
.body({ translate: 'construct.mainmenu.selectstructure.header' });
|
||||
structureCollection.getWorldStructureIds().forEach(structureId => {
|
||||
const worldStructureIds = instanceCollection.getWorldStructureIds();
|
||||
worldStructureIds.sort((a, b) => a.localeCompare(b));
|
||||
worldStructureIds.forEach(structureId => {
|
||||
const structureName = structureId.replace('mystructure:', '');
|
||||
allStructuresForm.button(`§2${structureName}`);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { world, system, EntityComponentTypes, EntitySwingSource, EquipmentSlot, HeldItemOption, GameMode } from '@minecraft/server';
|
||||
import { MENU_ITEM } from '../consts';
|
||||
import { MenuForm } from './MenuForm';
|
||||
import { instanceCollection } from './Instance/InstanceCollection';
|
||||
import { Builders } from './Builder/Builders';
|
||||
import { StructurePickBlock } from "./Structure/StructurePickBlock";
|
||||
|
||||
class MenuItemInteractions {
|
||||
static onItemUse(event) {
|
||||
if (!event.source || !MenuItemInteractions.#isMenuItem(event.itemStack))
|
||||
return;
|
||||
event.cancel = true;
|
||||
const builder = Builders.get(event.source.id);
|
||||
system.run(() => {
|
||||
if (builder.isFlexibleInstanceMoving())
|
||||
return;
|
||||
MenuItemInteractions.#openMenu(event.source, event);
|
||||
});
|
||||
}
|
||||
|
||||
static onPlayerBreakBlock(event) {
|
||||
if (!event.player || !MenuItemInteractions.#isMenuItem(event.itemStack))
|
||||
return;
|
||||
event.cancel = true;
|
||||
}
|
||||
|
||||
static onPlayerStartBreakingBlock(event) {
|
||||
const player = event.player;
|
||||
if (!player || !MenuItemInteractions.#isMenuItem(event.heldItemStack))
|
||||
return;
|
||||
StructurePickBlock.perform(player);
|
||||
}
|
||||
|
||||
static onPlayerAttack(event) {
|
||||
if (!event.player || !MenuItemInteractions.#isMenuItem(event.heldItemStack))
|
||||
return;
|
||||
StructurePickBlock.perform(event.player);
|
||||
}
|
||||
|
||||
static #isMenuItem(itemStack) {
|
||||
return itemStack?.typeId === MENU_ITEM;
|
||||
}
|
||||
|
||||
static #openMenu(player, event = void 0) {
|
||||
const options = { jumpToInstance: true };
|
||||
if (event) {
|
||||
const instanceNames = instanceCollection.getInstanceNames();
|
||||
const instanceName = event.itemStack?.nameTag;
|
||||
if (instanceNames.includes(instanceName))
|
||||
options.instanceName = instanceName;
|
||||
}
|
||||
new MenuForm(player, options);
|
||||
}
|
||||
|
||||
static #getMainhandItemStack(player) {
|
||||
const equippableComponent = player.getComponent(EntityComponentTypes.Equippable);
|
||||
if (!equippableComponent)
|
||||
return;
|
||||
const mainhandSlot = equippableComponent.getEquipmentSlot(EquipmentSlot.Mainhand);
|
||||
if (!mainhandSlot)
|
||||
return;
|
||||
return mainhandSlot.getItem();
|
||||
}
|
||||
}
|
||||
|
||||
world.beforeEvents.itemUse.subscribe(MenuItemInteractions.onItemUse);
|
||||
world.afterEvents.playerSwingStart.subscribe(MenuItemInteractions.onPlayerAttack, { heldItemOption: HeldItemOption.AnyItem, swingSource: EntitySwingSource.Attack });
|
||||
world.afterEvents.playerStartBreakingBlock.subscribe(MenuItemInteractions.onPlayerStartBreakingBlock);
|
||||
world.beforeEvents.playerBreakBlock.subscribe(MenuItemInteractions.onPlayerBreakBlock);
|
||||
@@ -1,22 +1,18 @@
|
||||
import { structureCollection } from "./Structure/StructureCollection";
|
||||
import { instanceCollection } from "./Instance/InstanceCollection";
|
||||
|
||||
export class Raycaster {
|
||||
static STEP_SIZE = 0.2;
|
||||
|
||||
static getStructureBlocks(dimension, startLocation, direction, { maxDistance = 7, getFirst = true, collideWithWorldBlocks = true, useActiveLayer = true }) {
|
||||
// Can probably be optimized by the fact that we only need full blocks and aren't checking for partial blocks
|
||||
const blocks = [];
|
||||
let location = startLocation;
|
||||
let distance = 0;
|
||||
while (distance < maxDistance) {
|
||||
const structure = structureCollection.getStructure(dimension.id, location, { useActiveLayer });
|
||||
if (structure) {
|
||||
const block = structure.getBlock(structure.toStructureCoords(location));
|
||||
if (block?.type.id !== 'minecraft:air') {
|
||||
blocks.push({
|
||||
permutation: block,
|
||||
location: location
|
||||
});
|
||||
const instance = instanceCollection.getInstanceAt(dimension.id, location, { useActiveLayer });
|
||||
if (instance) {
|
||||
const block = instance.getBlock(instance.toStructureCoords(location));
|
||||
if (block?.typeId !== 'minecraft:air') {
|
||||
blocks.push({ block, location, instance});
|
||||
if (getFirst)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
|
||||
export const MaterialType = Object.freeze({
|
||||
OPAQUE: "opaque",
|
||||
BLEND: "blend"
|
||||
});
|
||||
|
||||
// An incorrect block is drawn slightly outside its cell so it wraps the real
|
||||
// block instead of z-fighting with it.
|
||||
const OVERLAY_SCALE = 1.01;
|
||||
|
||||
// A missing block is drawn slightly inside its cell so a run of them reads as one mass,
|
||||
// but when a block is placed there is no z-fighting. This inset is also the one thing
|
||||
// keeping neighbor culling from being exactly invisible: two missing blocks
|
||||
// side by side stop short of their shared boundary, so culling the faces they
|
||||
// present to each other opens a seam visible at a grazing angle. Setting this
|
||||
// to 1.00 makes every cull exact, but causes z-fighting for placed blocks.
|
||||
const MISSING_SCALE = 0.995;
|
||||
|
||||
// A missing block in a mode that draws no preview (the Classic look): a small see-through cube
|
||||
// floating in the middle of its cell.
|
||||
const MARKER_SCALE = 0.90;
|
||||
|
||||
const PARTICLE_STYLES = Object.freeze({
|
||||
[BlockVerificationLevel.NoMatch]: Object.freeze({
|
||||
color: { red: 1, green: 0, blue: 0, alpha: 0.2 }, scale: OVERLAY_SCALE, material: MaterialType.BLEND
|
||||
}),
|
||||
[BlockVerificationLevel.TypeMatch]: Object.freeze({
|
||||
color: { red: 1, green: 1, blue: 0, alpha: 0.2 }, scale: OVERLAY_SCALE, material: MaterialType.BLEND
|
||||
}),
|
||||
[BlockVerificationLevel.Missing]: Object.freeze({
|
||||
color: { red: 0.55, green: 0.8, blue: 1, alpha: 1 }, scale: MISSING_SCALE, material: MaterialType.OPAQUE
|
||||
})
|
||||
});
|
||||
|
||||
const MARKER_STYLE = Object.freeze({
|
||||
color: { red: 0, green: 0, blue: 1, alpha: 0.2 }, scale: MARKER_SCALE, material: MaterialType.BLEND
|
||||
});
|
||||
|
||||
const BOX_STYLES = Object.freeze({
|
||||
[BlockVerificationLevel.NoMatch]: Object.freeze({
|
||||
color: { red: 1, green: 0, blue: 0, alpha: 1 }, scale: OVERLAY_SCALE
|
||||
}),
|
||||
[BlockVerificationLevel.TypeMatch]: Object.freeze({
|
||||
color: { red: 1, green: 1, blue: 0, alpha: 1 }, scale: OVERLAY_SCALE
|
||||
}),
|
||||
[BlockVerificationLevel.Missing]: Object.freeze({
|
||||
color: { red: 0.3, green: 0.57, blue: 0.87, alpha: 1 }, scale: 1.00
|
||||
})
|
||||
});
|
||||
|
||||
// A face the pipeline couldn't resolve.
|
||||
export const UNRESOLVED_FACE_STYLE = Object.freeze({
|
||||
color: { red: 0.3, green: 0.57, blue: 0.87, alpha: 0.2 }, material: MaterialType.BLEND
|
||||
});
|
||||
|
||||
// Water is the one block whose texture is authored see-through - Java draws it
|
||||
// translucent and block/water_still carries an alpha of 180/255, which the
|
||||
// atlas keeps. The preview's usual material alpha-tests rather than blending,
|
||||
// which forced every partly-transparent texel solid.
|
||||
export const WATER_MATERIAL = MaterialType.BLEND;
|
||||
|
||||
// Undefined for levels that draw no particle at all.
|
||||
export function particleStyleOf(verificationLevel, showsBlockPreview) {
|
||||
if (verificationLevel === BlockVerificationLevel.Missing && !showsBlockPreview)
|
||||
return MARKER_STYLE;
|
||||
return PARTICLE_STYLES[verificationLevel];
|
||||
}
|
||||
|
||||
// Undefined for levels that get no box. That is what makes a cell going to
|
||||
// Match or Air a removal rather than a recolour.
|
||||
export function boxStyleOf(verificationLevel) {
|
||||
return BOX_STYLES[verificationLevel];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { blockKeySpecs, blockModels } from "../../../blockModels";
|
||||
|
||||
export class BlockModelKeyResolver {
|
||||
lookupFaceRefs(blockId, states) {
|
||||
const keySpec = blockKeySpecs[blockId];
|
||||
if (keySpec === void 0)
|
||||
return void 0;
|
||||
for (let shapeIndex = 0; shapeIndex < keySpec.props.length; shapeIndex++) {
|
||||
const key = this.buildStateKey(blockId, keySpec.props[shapeIndex], shapeIndex, states);
|
||||
if (key === void 0)
|
||||
continue;
|
||||
const faceReferences = blockModels[key];
|
||||
if (faceReferences !== void 0)
|
||||
return faceReferences;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
faceRefsForKey(key) {
|
||||
return blockModels[key];
|
||||
}
|
||||
|
||||
buildStateKey(blockId, shapeProperties, shapeIndex, states) {
|
||||
let key = `${blockId}[${shapeIndex}|`;
|
||||
for (let propertyIndex = 0; propertyIndex < shapeProperties.length; propertyIndex++) {
|
||||
const propertyName = shapeProperties[propertyIndex];
|
||||
const value = states[propertyName];
|
||||
if (value === void 0)
|
||||
return void 0;
|
||||
if (propertyIndex > 0)
|
||||
key += ",";
|
||||
key += typeof value === "boolean" ? (value ? 1 : 0) : value;
|
||||
}
|
||||
return `${key}]`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { FaceTable } from "./FaceTable";
|
||||
import { CubeFaceLibrary } from "./CubeFaceLibrary";
|
||||
import { BlockModelKeyResolver } from "./BlockModelKeyResolver";
|
||||
import { CellFlags } from "../../Verifier/CellFlags";
|
||||
|
||||
class BlockModelResolver {
|
||||
#waterBlockIds = new Set(["minecraft:water", "minecraft:flowing_water"]);
|
||||
#waterloggedWaterState = "minecraft:water[0|0]";
|
||||
|
||||
constructor({ keyResolver, faceReader, unknownFaces, sideMaskPacker }) {
|
||||
this.keyResolver = keyResolver;
|
||||
this.faceReader = faceReader;
|
||||
this.unknownFaces = unknownFaces;
|
||||
this.sideMaskPacker = sideMaskPacker;
|
||||
this.resolvedByBlock = new WeakMap();
|
||||
this.waterloggedFacesCache = void 0;
|
||||
this.overlaySideMasksCache = void 0;
|
||||
}
|
||||
|
||||
facesOf(block) {
|
||||
return this.resolve(block).faces;
|
||||
}
|
||||
|
||||
sideMasksOf(block) {
|
||||
return this.resolve(block).sideMasks;
|
||||
}
|
||||
|
||||
overlaySideMasks() {
|
||||
if (this.overlaySideMasksCache === void 0)
|
||||
this.overlaySideMasksCache = this.computeSideMasks(this.unknownFaces);
|
||||
return this.overlaySideMasksCache;
|
||||
}
|
||||
|
||||
waterloggedFaces() {
|
||||
if (this.waterloggedFacesCache === void 0) {
|
||||
const faceRefs = this.keyResolver.faceRefsForKey(this.#waterloggedWaterState) ?? [];
|
||||
this.waterloggedFacesCache = faceRefs.map(this.faceReader);
|
||||
}
|
||||
return this.waterloggedFacesCache;
|
||||
}
|
||||
|
||||
isWater(block) {
|
||||
return this.#waterBlockIds.has(this.blockIdOf(block));
|
||||
}
|
||||
|
||||
resolve(block) {
|
||||
const cached = this.resolvedByBlock.get(block);
|
||||
if (cached !== void 0)
|
||||
return cached;
|
||||
|
||||
const faces = this.lookupFaces(block);
|
||||
const resolved = {
|
||||
faces,
|
||||
sideMasks: this.computeSideMasks(faces)
|
||||
};
|
||||
|
||||
this.resolvedByBlock.set(block, resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
lookupFaces(block) {
|
||||
const blockId = this.blockIdOf(block);
|
||||
const states = block.states ?? block.getAllStates();
|
||||
const faceReferences = this.keyResolver.lookupFaceRefs(blockId, states);
|
||||
if (faceReferences === void 0)
|
||||
return this.unknownFaces;
|
||||
return faceReferences.map(this.faceReader);
|
||||
}
|
||||
|
||||
computeSideMasks(faces) {
|
||||
let markerMask = 0;
|
||||
let opaqueMask = 0;
|
||||
|
||||
for (const face of faces) {
|
||||
if (!face.covers)
|
||||
continue;
|
||||
if (face.missing)
|
||||
markerMask |= 1 << face.cull;
|
||||
else if (face.opaque)
|
||||
opaqueMask |= 1 << face.cull;
|
||||
}
|
||||
|
||||
return this.sideMaskPacker(markerMask, opaqueMask);
|
||||
}
|
||||
|
||||
blockIdOf(block) {
|
||||
return block.typeId ?? block.type.id;
|
||||
}
|
||||
}
|
||||
|
||||
export const blockModelResolver = new BlockModelResolver({
|
||||
keyResolver: new BlockModelKeyResolver(),
|
||||
faceReader: FaceTable.faceAt,
|
||||
unknownFaces: CubeFaceLibrary.createPlainUnknownCubeFaces(),
|
||||
sideMaskPacker: CellFlags.pack
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { whiteUvRect } from "../../../blockAtlas";
|
||||
|
||||
export class CubeFaceLibrary {
|
||||
static createPlainCubeFaces() {
|
||||
return [
|
||||
{ center: [8, 16, 8], width: 16, height: 16, facing: [0, -1, 0], roll: 180, tintindex: -1, uv: whiteUvRect, cull: 1, covers: true },
|
||||
{ center: [8, 0, 8], width: 16, height: 16, facing: [0, 1, 0], roll: 0, tintindex: -1, uv: whiteUvRect, cull: 0, covers: true },
|
||||
{ center: [8, 8, 0], width: 16, height: 16, facing: [0, 0, -1], roll: 0, tintindex: -1, uv: whiteUvRect, cull: 2, covers: true },
|
||||
{ center: [8, 8, 16], width: 16, height: 16, facing: [0, 0, 1], roll: 0, tintindex: -1, uv: whiteUvRect, cull: 3, covers: true },
|
||||
{ center: [16, 8, 8], width: 16, height: 16, facing: [1, 0, 0], roll: 0, tintindex: -1, uv: whiteUvRect, cull: 5, covers: true },
|
||||
{ center: [0, 8, 8], width: 16, height: 16, facing: [-1, 0, 0], roll: 0, tintindex: -1, uv: whiteUvRect, cull: 4, covers: true }
|
||||
];
|
||||
}
|
||||
|
||||
static createUnknownCubeFaces(baseFaces) {
|
||||
return baseFaces.map((face) => ({ ...face, missing: true }));
|
||||
}
|
||||
|
||||
static createPlainUnknownCubeFaces() {
|
||||
const plainFaces = CubeFaceLibrary.createPlainCubeFaces();
|
||||
return CubeFaceLibrary.createUnknownCubeFaces(plainFaces);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { blockFaceData, blockMissingFaces } from "../../../blockModels";
|
||||
|
||||
export class FaceTable {
|
||||
static FACE_CULL = 0;
|
||||
static FACE_FACING = 1;
|
||||
static FACE_CENTER = 4;
|
||||
static FACE_WIDTH = 7;
|
||||
static FACE_HEIGHT = 8;
|
||||
static FACE_ROLL = 9;
|
||||
static FACE_TINTINDEX = 10;
|
||||
static FACE_UV = 11;
|
||||
static FACE_ROW_WIDTH = 15;
|
||||
|
||||
static NO_CULL = -1;
|
||||
|
||||
// The cull column carries two extra bits above the direction: whether the face spans its whole side of the block, and whether it does so opaquely.
|
||||
static FACE_CULL_MASK = 0b111;
|
||||
static FACE_COVERS_SIDE = 0b1000;
|
||||
static FACE_OPAQUE_SIDE = 0b10000;
|
||||
|
||||
static #faceCount = blockFaceData.length / FaceTable.FACE_ROW_WIDTH;
|
||||
static #missingFaces = new Set(blockMissingFaces);
|
||||
static #materialized = new Array(FaceTable.#faceCount);
|
||||
|
||||
static faceAt(index) {
|
||||
const cachedFace = FaceTable.#materialized[index];
|
||||
if (cachedFace !== void 0)
|
||||
return cachedFace;
|
||||
|
||||
const face = FaceTable.readFaceRow(index * FaceTable.FACE_ROW_WIDTH);
|
||||
if (FaceTable.#missingFaces.has(index))
|
||||
face.missing = true;
|
||||
|
||||
FaceTable.#materialized[index] = face;
|
||||
return face;
|
||||
}
|
||||
|
||||
static readFaceRow(offset) {
|
||||
const face = {
|
||||
center: [
|
||||
blockFaceData[offset + FaceTable.FACE_CENTER],
|
||||
blockFaceData[offset + FaceTable.FACE_CENTER + 1],
|
||||
blockFaceData[offset + FaceTable.FACE_CENTER + 2]
|
||||
],
|
||||
width: blockFaceData[offset + FaceTable.FACE_WIDTH],
|
||||
height: blockFaceData[offset + FaceTable.FACE_HEIGHT],
|
||||
facing: [
|
||||
blockFaceData[offset + FaceTable.FACE_FACING],
|
||||
blockFaceData[offset + FaceTable.FACE_FACING + 1],
|
||||
blockFaceData[offset + FaceTable.FACE_FACING + 2]
|
||||
],
|
||||
roll: blockFaceData[offset + FaceTable.FACE_ROLL],
|
||||
tintindex: blockFaceData[offset + FaceTable.FACE_TINTINDEX],
|
||||
uv: {
|
||||
x: blockFaceData[offset + FaceTable.FACE_UV],
|
||||
y: blockFaceData[offset + FaceTable.FACE_UV + 1],
|
||||
w: blockFaceData[offset + FaceTable.FACE_UV + 2],
|
||||
h: blockFaceData[offset + FaceTable.FACE_UV + 3]
|
||||
}
|
||||
};
|
||||
|
||||
FaceTable.applyCullFlags(face, blockFaceData[offset + FaceTable.FACE_CULL]);
|
||||
return face;
|
||||
}
|
||||
|
||||
static applyCullFlags(face, cullData) {
|
||||
if (cullData === FaceTable.NO_CULL)
|
||||
return;
|
||||
|
||||
face.cull = cullData & FaceTable.FACE_CULL_MASK;
|
||||
if (cullData & FaceTable.FACE_COVERS_SIDE)
|
||||
face.covers = true;
|
||||
if (cullData & FaceTable.FACE_OPAQUE_SIDE)
|
||||
face.opaque = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Vector } from "../../../lib/Vector.js";
|
||||
|
||||
export class Cuboid {
|
||||
static #edges = [
|
||||
[0, 1], [0, 2], [0, 4], [1, 3],
|
||||
[1, 5], [2, 3], [2, 6], [3, 7],
|
||||
[4, 5], [4, 6], [5, 7], [6, 7]
|
||||
];
|
||||
static #maxSegmentsPerEdge = 16;
|
||||
|
||||
constructor(min, max) {
|
||||
this.min = Vector.from(min);
|
||||
this.max = Vector.from(max);
|
||||
this.corners = Cuboid.cornersOf(this.min, this.max);
|
||||
}
|
||||
|
||||
static cornersOf(min, max) {
|
||||
return [
|
||||
new Vector(min.x, min.y, min.z),
|
||||
new Vector(max.x, min.y, min.z),
|
||||
new Vector(min.x, max.y, min.z),
|
||||
new Vector(max.x, max.y, min.z),
|
||||
new Vector(min.x, min.y, max.z),
|
||||
new Vector(max.x, min.y, max.z),
|
||||
new Vector(min.x, max.y, max.z),
|
||||
new Vector(max.x, max.y, max.z)
|
||||
];
|
||||
}
|
||||
|
||||
static segmentCount(start, end) {
|
||||
return Math.min(Math.floor(end.distance(start)), Cuboid.#maxSegmentsPerEdge);
|
||||
}
|
||||
|
||||
static *edgePoints(corners) {
|
||||
for (const [startIndex, endIndex] of Cuboid.#edges) {
|
||||
const start = corners[startIndex];
|
||||
const end = corners[endIndex];
|
||||
const segments = Cuboid.segmentCount(start, end);
|
||||
for (let i = 1; i < segments; i++)
|
||||
yield start.lerp(end, i / segments);
|
||||
}
|
||||
}
|
||||
|
||||
static *edgeSegments(corners) {
|
||||
for (const [startIndex, endIndex] of Cuboid.#edges) {
|
||||
const start = corners[startIndex];
|
||||
const end = corners[endIndex];
|
||||
const segments = Cuboid.segmentCount(start, end);
|
||||
for (let i = 0; i < segments; i++)
|
||||
yield [start.lerp(end, i / segments), start.lerp(end, (i + 1) / segments)];
|
||||
}
|
||||
}
|
||||
|
||||
matches(min, max) {
|
||||
return this.min.x === min.x && this.min.y === min.y && this.min.z === min.z
|
||||
&& this.max.x === max.x && this.max.y === max.y && this.max.z === max.z;
|
||||
}
|
||||
|
||||
edgePoints() {
|
||||
return Cuboid.edgePoints(this.corners);
|
||||
}
|
||||
|
||||
edgeSegments() {
|
||||
return Cuboid.edgeSegments(this.corners);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { DebugLine, DebugSphere, debugDrawer } from "@minecraft/debug-utilities";
|
||||
import { OutlineRenderer } from "./OutlineRenderer.js";
|
||||
|
||||
export class DebugOutlineRenderer extends OutlineRenderer {
|
||||
CORNER_SPHERE_SCALE = 0.1;
|
||||
#shapes = [];
|
||||
#isDrawing = false;
|
||||
|
||||
start() {
|
||||
this.stop();
|
||||
this.#isDrawing = true;
|
||||
this.#draw();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.#isDrawing = false;
|
||||
for (const shape of this.#shapes)
|
||||
shape.remove();
|
||||
this.#shapes.length = 0;
|
||||
}
|
||||
|
||||
setBounds(dimension, min, max) {
|
||||
const changed = super.setBounds(dimension, min, max);
|
||||
if (changed && this.#isDrawing)
|
||||
this.start();
|
||||
return changed;
|
||||
}
|
||||
|
||||
#draw() {
|
||||
if (this.drawCorners) {
|
||||
for (const corner of this.corners) {
|
||||
const cornerSphereShape = this.#cornerSphere(corner);
|
||||
this.#add(cornerSphereShape, this.cornerColor);
|
||||
}
|
||||
}
|
||||
if (this.drawEdges) {
|
||||
let index = 0;
|
||||
for (const [start, end] of this.cuboid.edgeSegments()) {
|
||||
const edgeShape = new DebugLine(start, end);
|
||||
const color = this.edgeColors[index++ % this.edgeColors.length];
|
||||
this.#add(edgeShape, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#add(shape, color) {
|
||||
shape.color = color;
|
||||
this.#shapes.push(shape);
|
||||
debugDrawer.addShape(shape);
|
||||
}
|
||||
|
||||
#cornerSphere(corner) {
|
||||
const sphere = new DebugSphere({ dimension: this.dimension, x: corner.x, y: corner.y, z: corner.z });
|
||||
sphere.scale = this.CORNER_SPHERE_SCALE;
|
||||
return sphere;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { OutlineRenderer } from "./OutlineRenderer";
|
||||
import { DebugOutlineRenderer } from "./DebugOutlineRenderer";
|
||||
import { ParticleOutlineRenderer } from "./ParticleOutlineRenderer";
|
||||
|
||||
export class HybridOutlineRenderer extends OutlineRenderer {
|
||||
#edgeRenderer;
|
||||
#cornerRenderer;
|
||||
|
||||
constructor(dimension, min, max, particleTiming) {
|
||||
super(dimension, min, max);
|
||||
this.#edgeRenderer = new DebugOutlineRenderer(dimension, min, max);
|
||||
this.#edgeRenderer.drawCorners = false;
|
||||
this.#cornerRenderer = new ParticleOutlineRenderer(dimension, min, max, particleTiming);
|
||||
this.#cornerRenderer.drawEdges = false;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.#edgeRenderer.start();
|
||||
this.#cornerRenderer.start();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.#edgeRenderer.stop();
|
||||
this.#cornerRenderer.stop();
|
||||
}
|
||||
|
||||
setBounds(dimension, min, max) {
|
||||
const changed = super.setBounds(dimension, min, max);
|
||||
this.#edgeRenderer.setBounds(dimension, min, max);
|
||||
this.#cornerRenderer.setBounds(dimension, min, max);
|
||||
return changed;
|
||||
}
|
||||
|
||||
addStandaloneCorners(locations) {
|
||||
super.addStandaloneCorners(locations);
|
||||
this.#edgeRenderer.addStandaloneCorners(locations);
|
||||
this.#cornerRenderer.addStandaloneCorners(locations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Vector } from "../../../lib/Vector.js";
|
||||
import { Cuboid } from "./Cuboid.js";
|
||||
|
||||
export class OutlineRenderer {
|
||||
dimension;
|
||||
corners;
|
||||
cuboid;
|
||||
drawCorners = true;
|
||||
drawEdges = true;
|
||||
#standaloneCorners = [];
|
||||
|
||||
cornerColor = Object.freeze({ red: 1, green: 1, blue: 1, alpha: 1 });
|
||||
edgeColors = Object.freeze([
|
||||
Object.freeze({ red: 0.93333333, green: 0.77647059, blue: 0.13333333, alpha: 1 }),
|
||||
Object.freeze({ red: 0.09019608, green: 0.09019608, blue: 0.09019608, alpha: 1 })
|
||||
]);
|
||||
|
||||
constructor(dimension, min, max) {
|
||||
this.#assignBounds(dimension, min, max);
|
||||
}
|
||||
|
||||
setBounds(dimension, min, max) {
|
||||
if (dimension === this.dimension && this.cuboid.matches(min, max))
|
||||
return false;
|
||||
this.#assignBounds(dimension, min, max);
|
||||
return true;
|
||||
}
|
||||
|
||||
addStandaloneCorners(locations) {
|
||||
for (const location of locations)
|
||||
this.#standaloneCorners.push(Vector.from(location));
|
||||
this.#collectCorners();
|
||||
}
|
||||
|
||||
start() {
|
||||
throw new Error(`${this.constructor.name} must implement start().`);
|
||||
}
|
||||
|
||||
stop() {
|
||||
throw new Error(`${this.constructor.name} must implement stop().`);
|
||||
}
|
||||
|
||||
#assignBounds(dimension, min, max) {
|
||||
this.dimension = dimension;
|
||||
this.cuboid = new Cuboid(min, max);
|
||||
this.#collectCorners();
|
||||
}
|
||||
|
||||
#collectCorners() {
|
||||
this.corners = this.#standaloneCorners.length === 0
|
||||
? this.cuboid.corners
|
||||
: [...this.cuboid.corners, ...this.#standaloneCorners];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { MolangVariableMap, system, TicksPerSecond } from "@minecraft/server";
|
||||
import { OutlineRenderer } from "./OutlineRenderer.js";
|
||||
|
||||
const DEFAULT_TIMING = Object.freeze({ drawIntervalTicks: 10, lifetimeTicks: 20 });
|
||||
|
||||
export class ParticleOutlineRenderer extends OutlineRenderer {
|
||||
#outlineParticle = "construct:outline";
|
||||
|
||||
#drawIntervalTicks;
|
||||
#lifetimeSeconds;
|
||||
#runner;
|
||||
|
||||
constructor(dimension, min, max, timing) {
|
||||
super(dimension, min, max);
|
||||
const { drawIntervalTicks, lifetimeTicks } = { ...DEFAULT_TIMING, ...timing };
|
||||
this.#drawIntervalTicks = drawIntervalTicks;
|
||||
this.#lifetimeSeconds = lifetimeTicks / TicksPerSecond;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.#runner = system.runInterval(() => this.#draw(), this.#drawIntervalTicks);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.#runner)
|
||||
return;
|
||||
system.clearRun(this.#runner);
|
||||
this.#runner = void 0;
|
||||
}
|
||||
|
||||
#draw() {
|
||||
if (this.drawCorners) {
|
||||
for (const corner of this.corners)
|
||||
this.#spawn(corner, this.cornerColor);
|
||||
}
|
||||
if (this.drawEdges) {
|
||||
let index = 0;
|
||||
for (const point of this.cuboid.edgePoints()) {
|
||||
const color = this.edgeColors[index++ % this.edgeColors.length];
|
||||
this.#spawn(point, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spawn(location, color) {
|
||||
const molang = new MolangVariableMap();
|
||||
molang.setColorRGBA("dot_color", color);
|
||||
molang.setFloat("lifetime", this.#lifetimeSeconds);
|
||||
try {
|
||||
this.dimension.spawnParticle(this.#outlineParticle, location, molang);
|
||||
} catch {
|
||||
/* pass */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { StructureNotFoundError } from "../../Errors/StructureNotFoundError";
|
||||
import { Cuboid } from "./Cuboid";
|
||||
import { createOutlineRenderer } from "./createOutlineRenderer.js";
|
||||
|
||||
export class StructureOutliner {
|
||||
instance;
|
||||
#renderer;
|
||||
|
||||
constructor(instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.#renderer?.stop();
|
||||
this.#renderer = void 0;
|
||||
if (!this.instance.isEnabled())
|
||||
return;
|
||||
const view = this.#readView();
|
||||
if (!view)
|
||||
return;
|
||||
this.#renderer = this.#createRenderer(view);
|
||||
if (view.wholeStructureCorners)
|
||||
this.#renderer.addStandaloneCorners(view.wholeStructureCorners);
|
||||
this.#renderer.start();
|
||||
}
|
||||
|
||||
#readView() {
|
||||
try {
|
||||
const dimension = this.instance.getDimension();
|
||||
const bounds = this.instance.getBounds();
|
||||
const min = this.instance.toGlobalCoords(bounds.min);
|
||||
const max = this.instance.toGlobalCoords(bounds.max);
|
||||
if (!this.instance.hasLayerSelected())
|
||||
return { dimension, min, max };
|
||||
const layer = this.instance.getLayerBounds(this.instance.getLayer());
|
||||
return {
|
||||
dimension,
|
||||
min: this.instance.toGlobalCoords(layer.min),
|
||||
max: this.instance.toGlobalCoords(layer.max),
|
||||
wholeStructureCorners: Cuboid.cornersOf(min, max)
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof StructureNotFoundError)
|
||||
return void 0;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
#createRenderer(view) {
|
||||
return createOutlineRenderer(this.instance.getRenderMode(), view.dimension, view.min, view.max);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { renderProfileOf } from "../../Enums/RenderMode";
|
||||
import { HybridOutlineRenderer } from "./HybridOutlineRenderer.js";
|
||||
import { ParticleOutlineRenderer } from "./ParticleOutlineRenderer.js";
|
||||
|
||||
export function createOutlineRenderer(renderMode, dimension, min, max, particleTiming) {
|
||||
const profile = renderProfileOf(renderMode);
|
||||
const Renderer = profile.hybridOutline ? HybridOutlineRenderer : ParticleOutlineRenderer;
|
||||
return new Renderer(dimension, min, max, particleTiming);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { system } from "@minecraft/server";
|
||||
import { BlockVerificationLevel } from "../../Enums/BlockVerificationLevel";
|
||||
import { renderProfileOf } from "../../Enums/RenderMode";
|
||||
import { RefreshRate } from "../../Verifier/RefreshRate";
|
||||
import { Vector } from "../../../lib/Vector";
|
||||
import { DebugBoxLayer } from "./DebugBoxLayer";
|
||||
import { PreviewParticleLayer } from "./PreviewParticleLayer";
|
||||
import { PreviewSweep } from "./PreviewSweep";
|
||||
|
||||
const RENDER_INTERVAL_TICKS = 1;
|
||||
|
||||
export class BlockPreviewRenderer {
|
||||
instance;
|
||||
|
||||
#profile;
|
||||
#sweep = new PreviewSweep();
|
||||
#boxes = new DebugBoxLayer();
|
||||
#particles;
|
||||
#runner;
|
||||
|
||||
constructor(instance) {
|
||||
this.instance = instance;
|
||||
this.#applyRenderMode();
|
||||
}
|
||||
|
||||
refresh(isPriority = false) {
|
||||
this.#stop();
|
||||
this.#applyRenderMode();
|
||||
if (!this.instance.isEnabled() || !this.instance.options.verifier.isEnabled)
|
||||
return;
|
||||
if (isPriority)
|
||||
this.#sweep.arm();
|
||||
this.#runner = system.runInterval(() => this.#renderNextSlice(), RENDER_INTERVAL_TICKS);
|
||||
}
|
||||
|
||||
renderBlockAt(location) {
|
||||
if (!this.#runner)
|
||||
return;
|
||||
const frame = this.#openFrame();
|
||||
if (frame)
|
||||
this.#drawCell(frame, location);
|
||||
}
|
||||
|
||||
#applyRenderMode() {
|
||||
this.#profile = renderProfileOf(this.instance.options.renderMode);
|
||||
this.#particles = new PreviewParticleLayer(this.#profile.blockPreview);
|
||||
}
|
||||
|
||||
#stop() {
|
||||
this.#boxes.clear();
|
||||
if (!this.#runner)
|
||||
return;
|
||||
system.clearRun(this.#runner);
|
||||
this.#runner = void 0;
|
||||
this.#sweep.reset();
|
||||
}
|
||||
|
||||
#renderNextSlice() {
|
||||
const frame = this.#openFrame();
|
||||
if (!frame)
|
||||
return;
|
||||
const refreshSeconds = this.instance.options.verifier.refreshSeconds;
|
||||
for (const location of this.#sweep.locations(frame.bounds, frame.volume, refreshSeconds))
|
||||
this.#drawCell(frame, location);
|
||||
}
|
||||
|
||||
#openFrame() {
|
||||
const bounds = this.instance.getActiveBounds();
|
||||
const volume = Vector.volume(bounds.min, bounds.max);
|
||||
if (volume <= 0)
|
||||
return void 0;
|
||||
const grid = this.instance.verifier.getCompletedGrid();
|
||||
if (!grid?.matchesBounds(bounds))
|
||||
return void 0;
|
||||
this.#boxes.retarget(bounds);
|
||||
return {
|
||||
bounds,
|
||||
volume,
|
||||
grid,
|
||||
dimension: this.instance.getDimension(),
|
||||
lifetimeSeconds: RefreshRate.cycleSeconds(volume, this.instance.options.verifier.refreshSeconds)
|
||||
};
|
||||
}
|
||||
|
||||
#drawCell(frame, location) {
|
||||
const verificationLevel = frame.grid.get(location);
|
||||
const origin = this.instance.toGlobalCoords(location);
|
||||
if (this.#profile.debugMarkers) {
|
||||
const index = frame.grid.indexOf(location);
|
||||
this.#boxes.draw(index, frame.dimension, origin, verificationLevel);
|
||||
}
|
||||
if (!this.#profile.particleOverlays)
|
||||
return;
|
||||
if (verificationLevel === BlockVerificationLevel.Unknown || verificationLevel === BlockVerificationLevel.Air)
|
||||
return;
|
||||
this.#particles.draw({
|
||||
dimension: frame.dimension,
|
||||
origin,
|
||||
block: this.instance.getBlock(location),
|
||||
verificationLevel,
|
||||
lifetimeSeconds: frame.lifetimeSeconds,
|
||||
occlusionMask: frame.grid.occlusionMaskAt(location)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { DebugBox, debugDrawer } from "@minecraft/debug-utilities";
|
||||
import { boxStyleOf } from "../VerificationStyle";
|
||||
|
||||
export class DebugBoxLayer {
|
||||
#boxes = new Map();
|
||||
#bounds;
|
||||
|
||||
retarget(bounds) {
|
||||
if (this.#matchesBounds(bounds))
|
||||
return;
|
||||
this.clear();
|
||||
this.#bounds = {
|
||||
min: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z },
|
||||
max: { x: bounds.max.x, y: bounds.max.y, z: bounds.max.z }
|
||||
};
|
||||
}
|
||||
|
||||
draw(index, dimension, origin, verificationLevel) {
|
||||
if (index === -1)
|
||||
return;
|
||||
const style = boxStyleOf(verificationLevel);
|
||||
const existing = this.#boxes.get(index);
|
||||
if (!style)
|
||||
this.#hide(existing);
|
||||
else if (existing)
|
||||
this.#restyle(existing, style);
|
||||
else
|
||||
this.#boxes.set(index, this.#create(dimension, origin, style));
|
||||
}
|
||||
|
||||
clear() {
|
||||
for (const entry of this.#boxes.values())
|
||||
this.#hide(entry);
|
||||
this.#boxes.clear();
|
||||
this.#bounds = void 0;
|
||||
}
|
||||
|
||||
#matchesBounds(bounds) {
|
||||
return this.#bounds !== void 0
|
||||
&& this.#bounds.min.x === bounds.min.x && this.#bounds.max.x === bounds.max.x
|
||||
&& this.#bounds.min.y === bounds.min.y && this.#bounds.max.y === bounds.max.y
|
||||
&& this.#bounds.min.z === bounds.min.z && this.#bounds.max.z === bounds.max.z;
|
||||
}
|
||||
|
||||
#hide(entry) {
|
||||
if (!entry?.isVisible)
|
||||
return;
|
||||
entry.box.remove();
|
||||
entry.isVisible = false;
|
||||
}
|
||||
|
||||
#restyle(entry, style) {
|
||||
entry.box.color = style.color;
|
||||
entry.box.scale = style.scale;
|
||||
if (entry.isVisible)
|
||||
return;
|
||||
debugDrawer.addShape(entry.box);
|
||||
entry.isVisible = true;
|
||||
}
|
||||
|
||||
#create(dimension, origin, style) {
|
||||
const box = new DebugBox({
|
||||
dimension,
|
||||
x: origin.x + 0.5,
|
||||
y: origin.y + 0.5,
|
||||
z: origin.z + 0.5
|
||||
});
|
||||
box.color = style.color;
|
||||
box.scale = style.scale;
|
||||
debugDrawer.addShape(box);
|
||||
return { box, isVisible: true };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { DebugBox, debugDrawer } from "@minecraft/debug-utilities";
|
||||
import { boxStyleOf } from "../VerificationStyle";
|
||||
|
||||
export function drawExpiringDebugBox(dimension, origin, verificationLevel, lifetimeSeconds) {
|
||||
const style = boxStyleOf(verificationLevel);
|
||||
if (!style)
|
||||
return;
|
||||
const box = new DebugBox({
|
||||
dimension,
|
||||
x: origin.x + 0.5,
|
||||
y: origin.y + 0.5,
|
||||
z: origin.z + 0.5
|
||||
});
|
||||
box.color = style.color;
|
||||
box.scale = style.scale;
|
||||
box.timeLeft = lifetimeSeconds;
|
||||
debugDrawer.addShape(box);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { MolangVariableMap } from "@minecraft/server";
|
||||
import { DEBUG_CONFIG } from "../../../consts";
|
||||
|
||||
const BLOCK_CENTER = 0.5;
|
||||
const PIXELS_PER_BLOCK = 16;
|
||||
|
||||
export class FaceParticleSpawner {
|
||||
#molang = new MolangVariableMap();
|
||||
#location = { x: 0, y: 0, z: 0 };
|
||||
#normal = { x: 0, y: 0, z: 0 };
|
||||
#size = { x: 0, y: 0, z: 0 };
|
||||
#uv = { x: 0, y: 0, z: 0 };
|
||||
#uvSize = { x: 0, y: 0, z: 0 };
|
||||
|
||||
#dimension;
|
||||
#origin;
|
||||
#color;
|
||||
|
||||
startBlock(dimension, origin, lifetimeSeconds) {
|
||||
this.#dimension = dimension;
|
||||
this.#origin = origin;
|
||||
this.#color = void 0;
|
||||
this.#molang.setFloat("lifetime", lifetimeSeconds);
|
||||
}
|
||||
|
||||
spawnFace(face, color, material, scale) {
|
||||
this.#setColor(color);
|
||||
this.#setGeometry(face, scale);
|
||||
this.#setUv(face);
|
||||
try {
|
||||
this.#dimension.spawnParticle(`construct:block_face_${material}`, this.#location, this.#molang);
|
||||
} catch {
|
||||
/* pass */
|
||||
}
|
||||
}
|
||||
|
||||
#setColor(color) {
|
||||
if (color === this.#color)
|
||||
return;
|
||||
this.#molang.setColorRGBA("face_color", color);
|
||||
this.#color = color;
|
||||
}
|
||||
|
||||
#setGeometry(face, scale) {
|
||||
this.#location.x = this.#origin.x + BLOCK_CENTER + (face.center[0] / PIXELS_PER_BLOCK - BLOCK_CENTER) * scale;
|
||||
this.#location.y = this.#origin.y + BLOCK_CENTER + (face.center[1] / PIXELS_PER_BLOCK - BLOCK_CENTER) * scale;
|
||||
this.#location.z = this.#origin.z + BLOCK_CENTER + (face.center[2] / PIXELS_PER_BLOCK - BLOCK_CENTER) * scale;
|
||||
|
||||
this.#normal.x = face.facing[0];
|
||||
this.#normal.y = face.facing[1];
|
||||
this.#normal.z = face.facing[2];
|
||||
|
||||
this.#size.x = (face.width / PIXELS_PER_BLOCK) * 0.5 * scale;
|
||||
this.#size.y = (face.height / PIXELS_PER_BLOCK) * 0.5 * scale;
|
||||
this.#size.z = face.roll;
|
||||
|
||||
this.#molang.setVector3("normal", this.#normal);
|
||||
this.#molang.setVector3("size", this.#size);
|
||||
}
|
||||
|
||||
#setUv(face) {
|
||||
this.#uv.x = DEBUG_CONFIG.enable ? DEBUG_CONFIG.render_all_textures_as.u : face.uv.x;
|
||||
this.#uv.y = DEBUG_CONFIG.enable ? DEBUG_CONFIG.render_all_textures_as.v : face.uv.y;
|
||||
this.#uvSize.x = face.uv.w;
|
||||
this.#uvSize.y = face.uv.h;
|
||||
this.#molang.setVector3("uv", this.#uv);
|
||||
this.#molang.setVector3("uv_size", this.#uvSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { BlockVerificationLevel } from "../../Enums/BlockVerificationLevel";
|
||||
import { CellFlags } from "../../Verifier/CellFlags";
|
||||
import { CubeFaceLibrary } from "../model/CubeFaceLibrary";
|
||||
import { blockModelResolver } from "../model/BlockModelResolver";
|
||||
import { UNRESOLVED_FACE_STYLE, WATER_MATERIAL, particleStyleOf } from "../VerificationStyle";
|
||||
import { FaceParticleSpawner } from "./FaceParticleSpawner";
|
||||
|
||||
export class PreviewParticleLayer {
|
||||
#showsBlockPreview;
|
||||
#spawner = new FaceParticleSpawner();
|
||||
|
||||
constructor(showsBlockPreview) {
|
||||
this.#showsBlockPreview = showsBlockPreview;
|
||||
}
|
||||
|
||||
draw(cell) {
|
||||
const style = particleStyleOf(cell.verificationLevel, this.#showsBlockPreview);
|
||||
if (!style || !cell.block)
|
||||
return;
|
||||
const culls = this.#cullMasks(cell);
|
||||
this.#spawner.startBlock(cell.dimension, cell.origin, cell.lifetimeSeconds);
|
||||
for (const layer of this.#faceLayers(cell, style.material))
|
||||
this.#drawLayer(cell, layer, style, culls);
|
||||
}
|
||||
|
||||
#cullMasks(cell) {
|
||||
if (this.#isMarkerCube(cell.verificationLevel))
|
||||
return { opaque: 0, marker: 0 };
|
||||
const opaque = CellFlags.opaqueMask(cell.occlusionMask);
|
||||
return { opaque, marker: opaque | CellFlags.markerMask(cell.occlusionMask) };
|
||||
}
|
||||
|
||||
#faceLayers(cell, material) {
|
||||
if (!this.#showsBlockPreview || cell.verificationLevel !== BlockVerificationLevel.Missing)
|
||||
return [{ faces: CubeFaceLibrary.createPlainCubeFaces(), material, isMarker: true }];
|
||||
const layers = [{
|
||||
faces: blockModelResolver.facesOf(cell.block),
|
||||
material: blockModelResolver.isWater(cell.block) ? WATER_MATERIAL : material
|
||||
}];
|
||||
if (cell.block.isWaterlogged)
|
||||
layers.push({ faces: blockModelResolver.waterloggedFaces(), material: WATER_MATERIAL });
|
||||
return layers;
|
||||
}
|
||||
|
||||
#drawLayer(cell, layer, style, culls) {
|
||||
const layerCull = layer.isMarker ? culls.marker : culls.opaque;
|
||||
for (const face of layer.faces) {
|
||||
const unresolved = face.missing === true;
|
||||
if (this.#isCulled(face, unresolved ? culls.marker : layerCull))
|
||||
continue;
|
||||
try {
|
||||
if (unresolved)
|
||||
this.#spawner.spawnFace(face, UNRESOLVED_FACE_STYLE.color, UNRESOLVED_FACE_STYLE.material, style.scale);
|
||||
else
|
||||
this.#spawner.spawnFace(face, style.color, layer.material, style.scale);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to render face for block ${cell.block.typeId} at ${JSON.stringify(cell.origin)} with verification level ${cell.verificationLevel}:`, error, error.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#isCulled(face, cullMask) {
|
||||
return face.cull !== void 0 && ((cullMask >> face.cull) & 1) === 1;
|
||||
}
|
||||
|
||||
#isMarkerCube(verificationLevel) {
|
||||
return verificationLevel === BlockVerificationLevel.Missing && !this.#showsBlockPreview;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { BlockBudget } from "../../Verifier/BlockBudget";
|
||||
import { PriorityPass } from "../../Verifier/PriorityPass";
|
||||
import { RefreshRate } from "../../Verifier/RefreshRate";
|
||||
|
||||
export class PreviewSweep {
|
||||
#cursor = 0;
|
||||
#budget = new BlockBudget();
|
||||
#priorityPass = new PriorityPass();
|
||||
|
||||
arm() {
|
||||
this.#priorityPass.arm();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#cursor = 0;
|
||||
this.#budget.clear();
|
||||
}
|
||||
|
||||
*locations(bounds, volume, refreshSeconds) {
|
||||
this.#budget.credit(this.#blocksPerTick(volume, refreshSeconds));
|
||||
if (this.#budget.isExhausted())
|
||||
return;
|
||||
if (this.#cursor >= volume)
|
||||
this.#cursor = 0;
|
||||
while (this.#cursor < volume && !this.#budget.isExhausted()) {
|
||||
yield locationAt(bounds, this.#cursor);
|
||||
this.#budget.spend(1);
|
||||
this.#cursor++;
|
||||
}
|
||||
if (this.#cursor >= volume) {
|
||||
this.#cursor = 0;
|
||||
this.#priorityPass.disarm();
|
||||
}
|
||||
}
|
||||
|
||||
#blocksPerTick(volume, refreshSeconds) {
|
||||
if (this.#priorityPass.isArmed())
|
||||
return RefreshRate.priorityBlocksPerTick();
|
||||
return RefreshRate.blocksPerTick(volume, refreshSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
function locationAt(bounds, index) {
|
||||
const width = bounds.max.x - bounds.min.x;
|
||||
const depth = bounds.max.z - bounds.min.z;
|
||||
const layerArea = width * depth;
|
||||
const y = bounds.min.y + Math.floor(index / layerArea);
|
||||
const withinLayer = index % layerArea;
|
||||
if (bounds.max.x < bounds.max.z) {
|
||||
return {
|
||||
x: bounds.min.x + (withinLayer % width),
|
||||
y,
|
||||
z: bounds.min.z + Math.floor(withinLayer / width)
|
||||
};
|
||||
}
|
||||
return {
|
||||
x: bounds.min.x + Math.floor(withinLayer / depth),
|
||||
y,
|
||||
z: bounds.min.z + (withinLayer % depth)
|
||||
};
|
||||
}
|
||||
@@ -2,9 +2,25 @@ import { world } from "@minecraft/server";
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { StructureNotFoundError } from "../Errors/StructureNotFoundError";
|
||||
|
||||
const NOT_LOADED = 0;
|
||||
const NO_BLOCK = 1;
|
||||
|
||||
const SLOT_WIDTHS = [
|
||||
{ maxSlot: 0xFF, ArrayType: Uint8Array },
|
||||
{ maxSlot: 0xFFFF, ArrayType: Uint16Array },
|
||||
{ maxSlot: 0xFFFFFFFF, ArrayType: Uint32Array },
|
||||
];
|
||||
|
||||
export class Structure {
|
||||
structureId;
|
||||
#structure;
|
||||
#size;
|
||||
|
||||
#paletteSlots;
|
||||
#widthIndex = 0;
|
||||
|
||||
#palette = [void 0, void 0];
|
||||
#slotsByKey = new Map();
|
||||
|
||||
constructor(structureId) {
|
||||
this.structureId = structureId;
|
||||
@@ -12,27 +28,74 @@ export class Structure {
|
||||
if (!this.#structure)
|
||||
throw new StructureNotFoundError(structureId);
|
||||
this.#structure.saveToWorld();
|
||||
this.#size = { ...this.#structure.size };
|
||||
this.#paletteSlots = new SLOT_WIDTHS[0].ArrayType(this.#size.x * this.#size.y * this.#size.z);
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.#structure.size.y;
|
||||
return this.#size.y;
|
||||
}
|
||||
|
||||
getMin() {
|
||||
return new Vector(0, 0, 0);
|
||||
return { x: 0, y: 0, z: 0 };
|
||||
}
|
||||
|
||||
getMax() {
|
||||
return Vector.from(this.#structure.size);
|
||||
return this.#size;
|
||||
}
|
||||
|
||||
getBlock(structureLocation) {
|
||||
const index = this.#toIndex(structureLocation);
|
||||
if (index === void 0)
|
||||
return this.#palette[this.#loadSlot(structureLocation)];
|
||||
const slot = this.#paletteSlots[index];
|
||||
if (slot !== NOT_LOADED)
|
||||
return this.#palette[slot];
|
||||
const loadedSlot = this.#loadSlot(structureLocation);
|
||||
this.#paletteSlots[index] = loadedSlot;
|
||||
return this.#palette[loadedSlot];
|
||||
}
|
||||
|
||||
#toIndex(structureLocation) {
|
||||
const x = Math.floor(structureLocation.x);
|
||||
const y = Math.floor(structureLocation.y);
|
||||
const z = Math.floor(structureLocation.z);
|
||||
if (x < 0 || y < 0 || z < 0 || x >= this.#size.x || y >= this.#size.y || z >= this.#size.z)
|
||||
return void 0;
|
||||
return (y * this.#size.z + z) * this.#size.x + x;
|
||||
}
|
||||
|
||||
#loadSlot(structureLocation) {
|
||||
const blockPermutation = this.#structure.getBlockPermutation(structureLocation);
|
||||
if (!blockPermutation)
|
||||
return void 0;
|
||||
blockPermutation.location = structureLocation;
|
||||
blockPermutation.isWaterlogged = this.#structure.getIsWaterlogged(blockPermutation.location);
|
||||
return blockPermutation;
|
||||
return NO_BLOCK;
|
||||
return this.#intern(blockPermutation, this.#structure.getIsWaterlogged(structureLocation));
|
||||
}
|
||||
|
||||
#intern(blockPermutation, isWaterlogged) {
|
||||
const typeId = blockPermutation.type.id;
|
||||
const states = blockPermutation.getAllStates();
|
||||
const key = `${typeId}|${isWaterlogged ? 1 : 0}|${JSON.stringify(states)}`;
|
||||
const interned = this.#slotsByKey.get(key);
|
||||
if (interned !== void 0)
|
||||
return interned;
|
||||
const slot = this.#palette.length;
|
||||
this.#palette.push({
|
||||
permutation: blockPermutation,
|
||||
typeId,
|
||||
states,
|
||||
hasStates: Object.keys(states).length > 0,
|
||||
isWaterlogged
|
||||
});
|
||||
this.#slotsByKey.set(key, slot);
|
||||
if (slot > SLOT_WIDTHS[this.#widthIndex].maxSlot)
|
||||
this.#widenPaletteSlots();
|
||||
return slot;
|
||||
}
|
||||
|
||||
#widenPaletteSlots() {
|
||||
this.#widthIndex++;
|
||||
this.#paletteSlots = new SLOT_WIDTHS[this.#widthIndex].ArrayType(this.#paletteSlots);
|
||||
}
|
||||
|
||||
*getBlocks(locations) {
|
||||
@@ -42,23 +105,23 @@ export class Structure {
|
||||
}
|
||||
|
||||
*getLayerBlocks(layer) {
|
||||
for (let x = 0; x < this.#structure.size.x; x++) {
|
||||
for (let z = 0; z < this.#structure.size.z; z++) {
|
||||
for (let x = 0; x < this.#size.x; x++) {
|
||||
for (let z = 0; z < this.#size.z; z++) {
|
||||
yield this.getBlock({ x, y: layer, z });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*getAllBlocks() {
|
||||
for (let y = 0; y < this.#structure.size.y; y++) {
|
||||
for (let y = 0; y < this.#size.y; y++) {
|
||||
yield * this.getLayerBlocks(y);
|
||||
}
|
||||
}
|
||||
|
||||
getLayerLocations(layer) {
|
||||
const locations = new Set();
|
||||
for (let x = 0; x < this.#structure.size.x; x++) {
|
||||
for (let z = 0; z < this.#structure.size.z; z++) {
|
||||
for (let x = 0; x < this.#size.x; x++) {
|
||||
for (let z = 0; z < this.#size.z; z++) {
|
||||
locations.add(new Vector(x, layer, z));
|
||||
}
|
||||
}
|
||||
@@ -67,7 +130,7 @@ export class Structure {
|
||||
|
||||
getAllLocations() {
|
||||
const locations = new Set();
|
||||
for (let y = 0; y < this.#structure.size.y; y++) {
|
||||
for (let y = 0; y < this.#size.y; y++) {
|
||||
const layerLocations = this.getLayerLocations(y);
|
||||
for (const location of layerLocations) {
|
||||
locations.add(location);
|
||||
@@ -76,4 +139,3 @@ export class Structure {
|
||||
return locations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { BlockPermutation } from "@minecraft/server";
|
||||
import { bannedToValidBlockMap, resetToBlockStates, blockIdToItemStackMap } from "../../options/easyPlaceConversions";
|
||||
|
||||
export class StructureBlockConverter {
|
||||
static sanitizeToPermutation(structureBlock) {
|
||||
const permutation = StructureBlockConverter.fromBannedBlockToValidPermutation(structureBlock);
|
||||
return StructureBlockConverter.toDefaultState(permutation);
|
||||
}
|
||||
|
||||
static sanitizeToPlaceableItemStack(structureBlock) {
|
||||
const permutation = StructureBlockConverter.sanitizeToPermutation(structureBlock);
|
||||
return StructureBlockConverter.toPlaceableItemStack(permutation);
|
||||
}
|
||||
|
||||
static fromBannedBlockToValidPermutation(structureBlock) {
|
||||
const blockId = structureBlock.typeId.replace('minecraft:', '');
|
||||
if (Object.keys(bannedToValidBlockMap).includes(blockId))
|
||||
return BlockPermutation.resolve(bannedToValidBlockMap[blockId], structureBlock.states);
|
||||
if (blockId === "bubble_column" && structureBlock.isWaterlogged)
|
||||
return BlockPermutation.resolve('minecraft:water');
|
||||
return structureBlock.permutation;
|
||||
}
|
||||
|
||||
static toDefaultState(permutation) {
|
||||
const newStates = {};
|
||||
for (const [stateKey, stateValue] of Object.entries(permutation.getAllStates())) {
|
||||
if (resetToBlockStates[stateKey] !== void 0 && stateValue !== resetToBlockStates[stateKey])
|
||||
newStates[stateKey] = resetToBlockStates[stateKey];
|
||||
else
|
||||
newStates[stateKey] = stateValue;
|
||||
}
|
||||
return BlockPermutation.resolve(permutation.type.id, newStates);
|
||||
}
|
||||
|
||||
static toPlaceableItemStack(permutation) {
|
||||
const blockId = permutation.type.id.replace('minecraft:', '');
|
||||
const newItemId = blockIdToItemStackMap[blockId];
|
||||
return newItemId ? new ItemStack(newItemId) : permutation.getItemStack();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { EntityComponentTypes, EquipmentSlot, GameMode } from "@minecraft/server";
|
||||
import { fetchMatchingItemSlotIndex } from "../../utils";
|
||||
import { Raycaster } from "../Raycaster";
|
||||
import { StructureBlockConverter } from "./StructureBlockConverter";
|
||||
|
||||
export class StructurePickBlock {
|
||||
static #HOTBAR_ITEM_SLOTS = 9;
|
||||
|
||||
static perform(player) {
|
||||
const placeableItemStack = StructurePickBlock.#getItemStackFromTargetedStructure(player);
|
||||
if (!placeableItemStack)
|
||||
return;
|
||||
const itemSlotIndexToUse = fetchMatchingItemSlotIndex(player, placeableItemStack?.typeId);
|
||||
const equippableComponent = player.getComponent(EntityComponentTypes.Equippable);
|
||||
const mainHandSlot = equippableComponent?.getEquipmentSlot(EquipmentSlot.Mainhand);
|
||||
if (itemSlotIndexToUse)
|
||||
StructurePickBlock.#swapItemIntoHotbar(player, itemSlotIndexToUse, mainHandSlot);
|
||||
else if (player.getGameMode() === GameMode.Creative)
|
||||
StructurePickBlock.#pickBlockCreative(player, mainHandSlot, placeableItemStack);
|
||||
}
|
||||
|
||||
static #getItemStackFromTargetedStructure(player) {
|
||||
const hit = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true, useActiveLayer: true });
|
||||
if (!hit?.block)
|
||||
return void 0;
|
||||
return StructureBlockConverter.sanitizeToPlaceableItemStack(hit?.block);
|
||||
}
|
||||
|
||||
static #swapItemIntoHotbar(player, sourceSlotIndex, hotbarReplacementSlot) {
|
||||
const inventoryContainer = player.getComponent(EntityComponentTypes.Inventory)?.container;
|
||||
if (sourceSlotIndex < StructurePickBlock.#HOTBAR_ITEM_SLOTS) {
|
||||
player.selectedSlotIndex = sourceSlotIndex;
|
||||
return;
|
||||
}
|
||||
const emptyHotbarSlotIndex = StructurePickBlock.#getFirstEmptyHotbarSlotIndex(inventoryContainer);
|
||||
const sourceSlot = inventoryContainer.getSlot(sourceSlotIndex);
|
||||
if (emptyHotbarSlotIndex !== void 0) {
|
||||
const itemToMove = sourceSlot.getItem();
|
||||
sourceSlot.setItem(void 0);
|
||||
const emptyHotbarSlot = inventoryContainer.getSlot(emptyHotbarSlotIndex);
|
||||
emptyHotbarSlot.setItem(itemToMove);
|
||||
player.selectedSlotIndex = emptyHotbarSlotIndex;
|
||||
} else {
|
||||
StructurePickBlock.#swapSlots(sourceSlot, hotbarReplacementSlot)
|
||||
}
|
||||
}
|
||||
|
||||
static #pickBlockCreative(player, mainHandSlot, placeableItemStack) {
|
||||
const inventoryContainer = player.getComponent(EntityComponentTypes.Inventory)?.container;
|
||||
const firstEmptySlotNum = inventoryContainer.firstEmptySlot();
|
||||
if (firstEmptySlotNum === void 0) {
|
||||
player.onScreenDisplay.setActionBar({ translate: 'construct.pickblock.inventoryfull' });
|
||||
return;
|
||||
}
|
||||
const emptySlot = inventoryContainer?.getSlot(firstEmptySlotNum);
|
||||
emptySlot.setItem(placeableItemStack);
|
||||
StructurePickBlock.#swapItemIntoHotbar(player, firstEmptySlotNum, mainHandSlot);
|
||||
}
|
||||
|
||||
static #getFirstEmptyHotbarSlotIndex(inventoryContainer) {
|
||||
for (let index = 0; index < StructurePickBlock.#HOTBAR_ITEM_SLOTS; index++) {
|
||||
if (!inventoryContainer.getItem(index))
|
||||
return index;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
static #swapSlots(slotOne, slotTwo) {
|
||||
const slotOneItem = slotOne.getItem();
|
||||
const slotTwoItem = slotTwo.getItem();
|
||||
slotOne.setItem(slotTwoItem);
|
||||
slotTwo.setItem(slotOneItem);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export class BlockBudget {
|
||||
#remaining = 0;
|
||||
|
||||
credit(blocks) {
|
||||
this.#remaining += blocks;
|
||||
}
|
||||
|
||||
spend(blocks) {
|
||||
this.#remaining -= blocks;
|
||||
}
|
||||
|
||||
isExhausted() {
|
||||
return this.#remaining <= 0;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#remaining = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export class CellFlags {
|
||||
static #sideMask = 0b111111;
|
||||
static #opaqueShift = 6;
|
||||
static NONE = 0;
|
||||
|
||||
static pack(markerMask, opaqueMask) {
|
||||
return (markerMask & CellFlags.#sideMask) | ((opaqueMask & CellFlags.#sideMask) << CellFlags.#opaqueShift);
|
||||
}
|
||||
|
||||
static markerMask(packed) {
|
||||
return packed & CellFlags.#sideMask;
|
||||
}
|
||||
|
||||
static opaqueMask(packed) {
|
||||
return (packed >> CellFlags.#opaqueShift) & CellFlags.#sideMask;
|
||||
}
|
||||
|
||||
static hasMarker(packed, side) {
|
||||
return ((packed >> side) & 1) === 1;
|
||||
}
|
||||
|
||||
static hasOpaque(packed, side) {
|
||||
return ((packed >> (side + CellFlags.#opaqueShift)) & 1) === 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
import { blockModelResolver } from "../Render/model/BlockModelResolver";
|
||||
import { CellFlags } from "./CellFlags";
|
||||
|
||||
export class CellShape {
|
||||
#instance;
|
||||
#showsBlockPreview;
|
||||
|
||||
constructor(instance, showsBlockPreview) {
|
||||
this.#instance = instance;
|
||||
this.#showsBlockPreview = showsBlockPreview;
|
||||
}
|
||||
|
||||
flagsFor(location, verificationLevel) {
|
||||
switch (verificationLevel) {
|
||||
case BlockVerificationLevel.NoMatch:
|
||||
case BlockVerificationLevel.TypeMatch:
|
||||
return blockModelResolver.overlaySideMasks();
|
||||
case BlockVerificationLevel.Match:
|
||||
return this.#structureBlockFlags(location);
|
||||
case BlockVerificationLevel.Missing:
|
||||
return this.#showsBlockPreview ? this.#structureBlockFlags(location) : CellFlags.NONE;
|
||||
default:
|
||||
return CellFlags.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
#structureBlockFlags(location) {
|
||||
const structureBlock = this.#instance.getBlock(location);
|
||||
if (structureBlock === void 0)
|
||||
return CellFlags.NONE;
|
||||
return blockModelResolver.sideMasksOf(structureBlock);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
import { CellShape } from "./CellShape";
|
||||
|
||||
export class CellVerifier {
|
||||
#instance;
|
||||
#shape;
|
||||
|
||||
constructor(instance, showsBlockPreview) {
|
||||
this.#instance = instance;
|
||||
this.#shape = new CellShape(instance, showsBlockPreview);
|
||||
}
|
||||
|
||||
verify(location) {
|
||||
const verificationLevel = this.#levelAt(location);
|
||||
return {
|
||||
verificationLevel,
|
||||
flags: this.#shape.flagsFor(location, verificationLevel)
|
||||
};
|
||||
}
|
||||
|
||||
#levelAt(location) {
|
||||
const globalLocation = this.#instance.toGlobalCoords(location);
|
||||
const worldBlock = this.#instance.getDimension()?.getBlock(globalLocation);
|
||||
if (!worldBlock)
|
||||
return BlockVerificationLevel.Skipped;
|
||||
return this.#compare(worldBlock, this.#instance.getBlock(location));
|
||||
}
|
||||
|
||||
#compare(worldBlock, structureBlock) {
|
||||
if (!structureBlock)
|
||||
return BlockVerificationLevel.Air;
|
||||
if (structureBlock.typeId === "minecraft:air")
|
||||
return worldBlock.isAir ? BlockVerificationLevel.Air : BlockVerificationLevel.NoMatch;
|
||||
if (worldBlock.isAir)
|
||||
return BlockVerificationLevel.Missing;
|
||||
if (worldBlock.typeId !== structureBlock.typeId)
|
||||
return BlockVerificationLevel.NoMatch;
|
||||
if (worldBlock.isWaterlogged !== structureBlock.isWaterlogged)
|
||||
return BlockVerificationLevel.TypeMatch;
|
||||
if (!structureBlock.hasStates)
|
||||
return BlockVerificationLevel.Match;
|
||||
if (worldBlock.permutation.matches(structureBlock.typeId, structureBlock.states))
|
||||
return BlockVerificationLevel.Match;
|
||||
return BlockVerificationLevel.TypeMatch;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { Side } from "../Enums/Side";
|
||||
import { instanceCollection } from "../Instance/InstanceCollection";
|
||||
|
||||
export class ChangedCellPatcher {
|
||||
#patchedOffsets = Object.freeze([Vector.zero, ...Side.OFFSETS]);
|
||||
|
||||
patchAround(dimensionId, worldLocation) {
|
||||
for (const offset of this.#patchedOffsets)
|
||||
this.#patchCell(dimensionId, Vector.add(worldLocation, offset));
|
||||
}
|
||||
|
||||
#patchCell(dimensionId, worldLocation) {
|
||||
for (const instance of instanceCollection.getInstancesAt(dimensionId, worldLocation)) {
|
||||
if (!instance.verifier || !instance.previewRenderer)
|
||||
continue;
|
||||
const location = instance.toStructureCoords(worldLocation);
|
||||
instance.verifier.patchCell(location);
|
||||
instance.previewRenderer.renderBlockAt(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
export class ChunkGrid {
|
||||
#chunkSize = 16;
|
||||
#chunkMask = this.#chunkSize - 1;
|
||||
#chunkShift = 4;
|
||||
#chunkKeyStride = 4194304;
|
||||
|
||||
#origin;
|
||||
|
||||
constructor(origin) {
|
||||
this.#origin = origin;
|
||||
}
|
||||
|
||||
keyAt(x, z) {
|
||||
const chunkX = (x + this.#origin.x) >> this.#chunkShift;
|
||||
const chunkZ = (z + this.#origin.z) >> this.#chunkShift;
|
||||
return chunkX * this.#chunkKeyStride + chunkZ;
|
||||
}
|
||||
|
||||
endOfSpanX(x) {
|
||||
return x + this.#chunkSize - ((x + this.#origin.x) & this.#chunkMask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { VerificationGrid } from "./VerificationGrid";
|
||||
|
||||
export class GridBuffers {
|
||||
#completed;
|
||||
#filling;
|
||||
|
||||
completed() {
|
||||
return this.#completed;
|
||||
}
|
||||
|
||||
filling() {
|
||||
return this.#filling;
|
||||
}
|
||||
|
||||
beginPass(bounds) {
|
||||
this.#filling = this.#recycled(this.#filling, bounds);
|
||||
return this.#filling;
|
||||
}
|
||||
|
||||
commit() {
|
||||
const finished = this.#filling;
|
||||
this.#filling = this.#completed;
|
||||
this.#completed = finished;
|
||||
}
|
||||
|
||||
#recycled(grid, bounds) {
|
||||
if (!grid?.matchesBounds(bounds))
|
||||
return new VerificationGrid(bounds);
|
||||
grid.clear();
|
||||
return grid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Armed means the drawn picture does not yet describe the current bounds, so
|
||||
// the next pass should run flat out. Reading and clearing are separate because
|
||||
// each holder clears at a different point: the verifier only when a pass
|
||||
// commits, the render cursor only when a lap ends.
|
||||
export class PriorityPass {
|
||||
#isArmed = false;
|
||||
|
||||
arm() {
|
||||
this.#isArmed = true;
|
||||
}
|
||||
|
||||
isArmed() {
|
||||
return this.#isArmed;
|
||||
}
|
||||
|
||||
disarm() {
|
||||
this.#isArmed = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { TicksPerSecond } from "@minecraft/server";
|
||||
|
||||
export class RefreshRate {
|
||||
static MAX_BLOCKS_PER_TICK = 30;
|
||||
|
||||
static MIN_SECONDS = 1;
|
||||
static MAX_SECONDS = 60;
|
||||
static SECONDS_STEP = 1;
|
||||
static DEFAULT_SECONDS = 15;
|
||||
|
||||
static clampSeconds(seconds) {
|
||||
return Math.min(RefreshRate.MAX_SECONDS, Math.max(RefreshRate.MIN_SECONDS, seconds));
|
||||
}
|
||||
|
||||
static priorityBlocksPerTick() {
|
||||
return RefreshRate.MAX_BLOCKS_PER_TICK;
|
||||
}
|
||||
|
||||
static blocksPerTick(volume, refreshSeconds) {
|
||||
if (volume <= 0)
|
||||
return 0;
|
||||
const seconds = Math.max(refreshSeconds, RefreshRate.MIN_SECONDS);
|
||||
return Math.min(volume / (seconds * TicksPerSecond), RefreshRate.MAX_BLOCKS_PER_TICK);
|
||||
}
|
||||
|
||||
static cycleSeconds(volume, refreshSeconds) {
|
||||
const rate = RefreshRate.blocksPerTick(volume, refreshSeconds);
|
||||
if (rate <= 0)
|
||||
return 0;
|
||||
return volume / (rate * TicksPerSecond);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export class SkippedChunkTracker {
|
||||
#unloadedChunks = new Set();
|
||||
#outOfBoundsChunks = new Set();
|
||||
|
||||
startLayer() {
|
||||
this.#outOfBoundsChunks.clear();
|
||||
}
|
||||
|
||||
isSkipped(chunkKey) {
|
||||
return this.#unloadedChunks.has(chunkKey) || this.#outOfBoundsChunks.has(chunkKey);
|
||||
}
|
||||
|
||||
trackError(error, chunkKey) {
|
||||
if (error?.name === 'LocationInUnloadedChunkError') {
|
||||
this.#unloadedChunks.add(chunkKey);
|
||||
return true;
|
||||
}
|
||||
if (error?.name === 'LocationOutOfWorldBoundariesError') {
|
||||
this.#outOfBoundsChunks.add(chunkKey);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,144 +1,147 @@
|
||||
import { BlockVerifier } from "./BlockVerifier";
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
import { BlockVerificationLevelRender } from "../Render/BlockVerificationLevelRender";
|
||||
import { system, TicksPerSecond } from "@minecraft/server";
|
||||
import { system } from "@minecraft/server";
|
||||
import { Vector } from "../../lib/Vector";
|
||||
|
||||
const MIN_TRACK_PLAYER_DISTANCE = 0;
|
||||
const MAX_TRACK_PLAYER_DISTANCE = 7;
|
||||
const MIN_LIFETIME = 8;
|
||||
import { CellVerifier } from "./CellVerifier";
|
||||
import { GridBuffers } from "./GridBuffers";
|
||||
import { PriorityPass } from "./PriorityPass";
|
||||
import { RefreshRate } from "./RefreshRate";
|
||||
import { DebugBoxSweepObserver, SilentSweepObserver } from "./SweepObserver";
|
||||
import { VerificationRun } from "./VerificationRun";
|
||||
import { VerificationSweep } from "./VerificationSweep";
|
||||
import { InstanceVerifierSettings, StandaloneVerifierSettings } from "./VerifierSettings";
|
||||
|
||||
export class StructureVerifier {
|
||||
instance;
|
||||
particleLifetime;
|
||||
#instance;
|
||||
#settings;
|
||||
#buffers = new GridBuffers();
|
||||
#priorityPass = new PriorityPass();
|
||||
#run;
|
||||
#loop;
|
||||
#isPassPending = false;
|
||||
|
||||
locationsToVerify;
|
||||
blockVerificationLevels;
|
||||
isLocationPopulationComplete;
|
||||
isVerificationComplete;
|
||||
shouldStartNextVerification;
|
||||
lastCompleteVerificationLevels;
|
||||
|
||||
#runner;
|
||||
#verifyJob;
|
||||
#populateJob = {};
|
||||
|
||||
constructor(instance, { isEnabled = false, trackPlayerDistance = 0, particleLifetime = 10, isStandalone = false } = {}) {
|
||||
this.instance = instance;
|
||||
this.particleLifetime = Math.max(particleLifetime, MIN_LIFETIME);
|
||||
if (isStandalone) {
|
||||
this.isStandalone = isStandalone;
|
||||
this.enabled = isEnabled;
|
||||
this.trackPlayerDistance = trackPlayerDistance;
|
||||
} else {
|
||||
this.instance.options.setVerifierEnabled(isEnabled);
|
||||
this.instance.options.setVerifierDistance(trackPlayerDistance);
|
||||
}
|
||||
this.locationsToVerify = new Set();
|
||||
static forInstance(instance) {
|
||||
return new StructureVerifier(instance, new InstanceVerifierSettings(instance));
|
||||
}
|
||||
|
||||
startContinuousVerification() {
|
||||
this.shouldStartNextVerification = true;
|
||||
this.#runner = system.runInterval(() => {
|
||||
if (this.shouldStartNextVerification)
|
||||
this.verifyStructure();
|
||||
});
|
||||
static standalone(instance, options) {
|
||||
return new StructureVerifier(instance, new StandaloneVerifierSettings(options));
|
||||
}
|
||||
|
||||
stopContinuousVerification() {
|
||||
if (!this.#runner)
|
||||
return;
|
||||
system.clearRun(this.#runner);
|
||||
this.#runner = void 0;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.stopContinuousVerification();
|
||||
if (!this.instance.isEnabled())
|
||||
return;
|
||||
this.startContinuousVerification();
|
||||
constructor(instance, settings) {
|
||||
this.#instance = instance;
|
||||
this.#settings = settings;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
if (this.isStandalone)
|
||||
return this.enabled;
|
||||
return this.instance.options.verifier.isEnabled;
|
||||
return this.#settings.isEnabled();
|
||||
}
|
||||
|
||||
getTrackPlayerDistance() {
|
||||
let distance;
|
||||
if (this.isStandalone)
|
||||
distance = this.trackPlayerDistance;
|
||||
else
|
||||
distance = this.instance.options.verifier.trackPlayerDistance
|
||||
return Math.min(MAX_TRACK_PLAYER_DISTANCE, Math.max(MIN_TRACK_PLAYER_DISTANCE, distance));
|
||||
getCompletedGrid() {
|
||||
return this.#buffers.completed();
|
||||
}
|
||||
|
||||
refresh(isPriority = false) {
|
||||
this.#stopLoop();
|
||||
this.#cancelRun();
|
||||
if (isPriority)
|
||||
this.#priorityPass.arm();
|
||||
if (this.#instance.isEnabled())
|
||||
this.#startLoop();
|
||||
}
|
||||
|
||||
async verifyStructure(shouldRender = false) {
|
||||
if (!this.isEnabled())
|
||||
return void 0;
|
||||
const bounds = this.#instance.getActiveBounds();
|
||||
const volume = Vector.volume(bounds.min, bounds.max);
|
||||
if (volume <= 0)
|
||||
return this.#buffers.completed();
|
||||
this.#cancelRun();
|
||||
const run = this.#beginRun(bounds, volume, shouldRender);
|
||||
this.#finishRun(run, await run.start());
|
||||
return this.#buffers.completed();
|
||||
}
|
||||
|
||||
patchCell(location) {
|
||||
const completed = this.#buffers.completed();
|
||||
if (!completed)
|
||||
return;
|
||||
this.initVerification();
|
||||
return new Promise(async (resolve) => {
|
||||
if (this.#verifyJob)
|
||||
system.clearJob(this.#verifyJob);
|
||||
this.#verifyJob = system.runJob(this.verifyBlocks(shouldRender));
|
||||
const checker = system.runInterval(() => {
|
||||
if (this.isVerificationComplete) {
|
||||
system.clearRun(checker);
|
||||
this.lastCompleteVerificationLevels = JSON.parse(JSON.stringify(this.blockVerificationLevels));
|
||||
this.shouldStartNextVerification = true;
|
||||
resolve(this.blockVerificationLevels);
|
||||
}
|
||||
}, 1);
|
||||
const cell = this.#tryVerifyCell(location);
|
||||
if (!cell)
|
||||
return;
|
||||
completed.setCell(location, cell.verificationLevel, cell.flags);
|
||||
const filling = this.#buffers.filling()
|
||||
filling?.setCell(location, cell.verificationLevel, cell.flags);
|
||||
}
|
||||
|
||||
#tryVerifyCell(location) {
|
||||
try {
|
||||
return this.#newCellVerifier().verify(location);
|
||||
} catch {
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
#startLoop() {
|
||||
this.#isPassPending = true;
|
||||
this.#loop = system.runInterval(() => {
|
||||
if (this.#isPassPending)
|
||||
this.verifyStructure();
|
||||
});
|
||||
}
|
||||
|
||||
initVerification() {
|
||||
this.shouldStartNextVerification = false;
|
||||
this.locationsToVerify.clear();
|
||||
this.blockVerificationLevels = { correctlyAir: 0 };
|
||||
this.isLocationPopulationComplete = false;
|
||||
this.isVerificationComplete = false;
|
||||
}
|
||||
|
||||
*verifyBlocks(shouldRender) {
|
||||
const bounds = this.instance.getActiveBounds();
|
||||
for (let y = bounds.min.y; y < bounds.max.y; y++) {
|
||||
for (let z = bounds.min.z; z < bounds.max.z; z++) {
|
||||
for (let x = bounds.min.x; x < bounds.max.x; x++) {
|
||||
const location = new Vector(x, y, z);
|
||||
this.verifyBlock(location, shouldRender);
|
||||
yield void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.isVerificationComplete = true;
|
||||
#stopLoop() {
|
||||
if (!this.#loop)
|
||||
return;
|
||||
system.clearRun(this.#loop);
|
||||
this.#loop = void 0;
|
||||
}
|
||||
|
||||
verifyBlock(location, shouldRender) {
|
||||
const verificationLevel = this.getVerificationLevel(location);
|
||||
if (verificationLevel === BlockVerificationLevel.Air) {
|
||||
this.blockVerificationLevels.correctlyAir++;
|
||||
} else {
|
||||
this.blockVerificationLevels[JSON.stringify(location)] = verificationLevel;
|
||||
if (shouldRender) {
|
||||
const dimensionLocation = { dimension: this.instance.getDimension(), location: this.instance.toGlobalCoords(location) };
|
||||
new BlockVerificationLevelRender(dimensionLocation, verificationLevel, this.particleLifetime/TicksPerSecond);
|
||||
}
|
||||
}
|
||||
#beginRun(bounds, volume, shouldRender) {
|
||||
this.#isPassPending = false;
|
||||
this.#run = new VerificationRun(
|
||||
this.#newSweep(bounds, shouldRender),
|
||||
this.#blocksPerTick(volume)
|
||||
);
|
||||
return this.#run;
|
||||
}
|
||||
|
||||
getVerificationLevel(location) {
|
||||
const worldBlock = this.instance.getDimension()?.getBlock(this.instance.toGlobalCoords(location));
|
||||
if (!worldBlock)
|
||||
return BlockVerificationLevel.Skipped;
|
||||
const blockVerifier = new BlockVerifier(worldBlock, this.instance);
|
||||
return blockVerifier.verify();
|
||||
#blocksPerTick(volume) {
|
||||
if (this.#priorityPass.isArmed())
|
||||
return RefreshRate.priorityBlocksPerTick();
|
||||
return this.#settings.blocksPerTick(volume);
|
||||
}
|
||||
|
||||
getLastVerificationLevels() {
|
||||
if (!this.lastCompleteVerificationLevels)
|
||||
return {};
|
||||
return this.lastCompleteVerificationLevels;
|
||||
#finishRun(run, didComplete) {
|
||||
if (this.#run === run)
|
||||
this.#run = void 0;
|
||||
if (!didComplete)
|
||||
return;
|
||||
this.#priorityPass.disarm();
|
||||
this.#buffers.commit();
|
||||
this.#isPassPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
#cancelRun() {
|
||||
this.#run?.cancel();
|
||||
}
|
||||
|
||||
#newSweep(bounds, shouldRender) {
|
||||
return new VerificationSweep({
|
||||
bounds,
|
||||
grid: this.#buffers.beginPass(bounds),
|
||||
origin: this.#instance.toGlobalCoords({ x: 0, y: 0, z: 0 }),
|
||||
cellVerifier: this.#newCellVerifier(),
|
||||
observer: this.#newObserver(shouldRender)
|
||||
});
|
||||
}
|
||||
|
||||
#newCellVerifier() {
|
||||
return new CellVerifier(this.#instance, this.#settings.showsBlockPreview());
|
||||
}
|
||||
|
||||
#newObserver(shouldRender) {
|
||||
if (!shouldRender)
|
||||
return new SilentSweepObserver();
|
||||
return new DebugBoxSweepObserver(this.#instance, this.#settings.particleLifetimeSeconds());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
import { drawExpiringDebugBox } from "../Render/preview/ExpiringDebugBox";
|
||||
|
||||
export class SilentSweepObserver {
|
||||
onCellVerified() {}
|
||||
}
|
||||
|
||||
export class DebugBoxSweepObserver {
|
||||
#instance;
|
||||
#lifetimeSeconds;
|
||||
|
||||
constructor(instance, lifetimeSeconds) {
|
||||
this.#instance = instance;
|
||||
this.#lifetimeSeconds = lifetimeSeconds;
|
||||
}
|
||||
|
||||
onCellVerified(location, verificationLevel) {
|
||||
if (verificationLevel === BlockVerificationLevel.Air)
|
||||
return;
|
||||
drawExpiringDebugBox(
|
||||
this.#instance.getDimension(),
|
||||
this.#instance.toGlobalCoords(location),
|
||||
verificationLevel,
|
||||
this.#lifetimeSeconds
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel.js";
|
||||
import { Side } from "../Enums/Side.js";
|
||||
import { CellFlags } from "./CellFlags.js";
|
||||
|
||||
const LEVEL_COUNT = Object.keys(BlockVerificationLevel).length;
|
||||
|
||||
export class VerificationGrid {
|
||||
#min;
|
||||
#sizeX;
|
||||
#sizeY;
|
||||
#sizeZ;
|
||||
#levels;
|
||||
#flags;
|
||||
|
||||
constructor(bounds) {
|
||||
this.#min = { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z };
|
||||
this.#sizeX = Math.max(bounds.max.x - bounds.min.x, 0);
|
||||
this.#sizeY = Math.max(bounds.max.y - bounds.min.y, 0);
|
||||
this.#sizeZ = Math.max(bounds.max.z - bounds.min.z, 0);
|
||||
this.#levels = new Uint8Array(this.#sizeX * this.#sizeY * this.#sizeZ);
|
||||
this.#flags = new Uint16Array(this.#levels.length);
|
||||
}
|
||||
|
||||
matchesBounds(bounds) {
|
||||
return this.#min.x === bounds.min.x && this.#min.y === bounds.min.y && this.#min.z === bounds.min.z
|
||||
&& this.#sizeX === bounds.max.x - bounds.min.x
|
||||
&& this.#sizeY === bounds.max.y - bounds.min.y
|
||||
&& this.#sizeZ === bounds.max.z - bounds.min.z;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#levels.fill(BlockVerificationLevel.Unknown);
|
||||
this.#flags.fill(CellFlags.NONE);
|
||||
}
|
||||
|
||||
setCell(location, verificationLevel, flags) {
|
||||
const index = this.indexOf(location);
|
||||
if (index === -1)
|
||||
return;
|
||||
this.#levels[index] = verificationLevel;
|
||||
this.#flags[index] = flags;
|
||||
}
|
||||
|
||||
setLevel(location, verificationLevel) {
|
||||
const index = this.indexOf(location);
|
||||
if (index === -1)
|
||||
return;
|
||||
this.#levels[index] = verificationLevel;
|
||||
}
|
||||
|
||||
get(location) {
|
||||
const index = this.indexOf(location);
|
||||
if (index === -1)
|
||||
return BlockVerificationLevel.Unknown;
|
||||
return this.#levels[index];
|
||||
}
|
||||
|
||||
occlusionMaskAt(location) {
|
||||
let markerMask = CellFlags.NONE;
|
||||
let opaqueMask = CellFlags.NONE;
|
||||
for (let side = 0; side < Side.COUNT; side++) {
|
||||
const neighborFlags = this.#neighborFlags(location, side);
|
||||
const facingSide = Side.opposite(side);
|
||||
if (CellFlags.hasMarker(neighborFlags, facingSide))
|
||||
markerMask |= 1 << side;
|
||||
if (CellFlags.hasOpaque(neighborFlags, facingSide))
|
||||
opaqueMask |= 1 << side;
|
||||
}
|
||||
return CellFlags.pack(markerMask, opaqueMask);
|
||||
}
|
||||
|
||||
#neighborFlags(location, side) {
|
||||
const offset = Side.OFFSETS[side];
|
||||
const index = this.#indexOfCoords(
|
||||
location.x + offset.x, location.y + offset.y, location.z + offset.z
|
||||
);
|
||||
if (index === -1)
|
||||
return CellFlags.NONE;
|
||||
return this.#flags[index];
|
||||
}
|
||||
|
||||
countByLevel() {
|
||||
const counts = new Uint32Array(LEVEL_COUNT);
|
||||
for (let index = 0; index < this.#levels.length; index++)
|
||||
counts[this.#levels[index]]++;
|
||||
return counts;
|
||||
}
|
||||
|
||||
indexOf(location) {
|
||||
return this.#indexOfCoords(location.x, location.y, location.z);
|
||||
}
|
||||
|
||||
#indexOfCoords(x, y, z) {
|
||||
const localX = x - this.#min.x;
|
||||
const localY = y - this.#min.y;
|
||||
const localZ = z - this.#min.z;
|
||||
if (localX < 0 || localY < 0 || localZ < 0
|
||||
|| localX >= this.#sizeX || localY >= this.#sizeY || localZ >= this.#sizeZ)
|
||||
return -1;
|
||||
return (localY * this.#sizeZ + localZ) * this.#sizeX + localX;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { system } from "@minecraft/server";
|
||||
import { BlockBudget } from "./BlockBudget";
|
||||
|
||||
export class VerificationRun {
|
||||
#sweep;
|
||||
#blocksPerTick;
|
||||
#budget = new BlockBudget();
|
||||
#steps;
|
||||
#runner;
|
||||
#settle;
|
||||
#completion;
|
||||
|
||||
constructor(sweep, blocksPerTick) {
|
||||
this.#sweep = sweep;
|
||||
this.#blocksPerTick = blocksPerTick;
|
||||
this.#completion = new Promise((resolve) => { this.#settle = resolve; });
|
||||
}
|
||||
|
||||
start() {
|
||||
this.#steps = this.#sweep.run();
|
||||
this.#runner = system.runInterval(() => this.#advance());
|
||||
return this.#completion;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.#settleWith(false);
|
||||
}
|
||||
|
||||
#advance() {
|
||||
this.#budget.credit(this.#blocksPerTick);
|
||||
try {
|
||||
while (!this.#budget.isExhausted()) {
|
||||
if (!this.#advanceOneStep())
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
this.#settleWith(false);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
#advanceOneStep() {
|
||||
const step = this.#steps.next();
|
||||
if (step.done) {
|
||||
this.#settleWith(true);
|
||||
return false;
|
||||
}
|
||||
this.#budget.spend(step.value);
|
||||
return true;
|
||||
}
|
||||
|
||||
#settleWith(didComplete) {
|
||||
if (!this.#settle)
|
||||
return;
|
||||
const settle = this.#settle;
|
||||
this.#settle = void 0;
|
||||
this.#stop();
|
||||
settle(didComplete);
|
||||
}
|
||||
|
||||
#stop() {
|
||||
if (this.#runner !== void 0)
|
||||
system.clearRun(this.#runner);
|
||||
this.#runner = void 0;
|
||||
this.#steps = void 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { ChunkGrid } from "./ChunkGrid";
|
||||
import { SkippedChunkTracker } from "./SkippedChunkTracker";
|
||||
|
||||
export class VerificationSweep {
|
||||
#bounds;
|
||||
#grid;
|
||||
#cellVerifier;
|
||||
#observer;
|
||||
#chunks;
|
||||
#skippedChunks = new SkippedChunkTracker();
|
||||
#location = new Vector();
|
||||
|
||||
constructor({ bounds, grid, cellVerifier, observer, origin }) {
|
||||
this.#bounds = bounds;
|
||||
this.#grid = grid;
|
||||
this.#cellVerifier = cellVerifier;
|
||||
this.#observer = observer;
|
||||
this.#chunks = new ChunkGrid(origin);
|
||||
}
|
||||
|
||||
*run() {
|
||||
for (let y = this.#bounds.min.y; y < this.#bounds.max.y; y++) {
|
||||
this.#skippedChunks.startLayer();
|
||||
for (let z = this.#bounds.min.z; z < this.#bounds.max.z; z++)
|
||||
yield* this.#sweepRow(y, z);
|
||||
}
|
||||
}
|
||||
|
||||
*#sweepRow(y, z) {
|
||||
for (let x = this.#bounds.min.x; x < this.#bounds.max.x;) {
|
||||
const endX = this.#sweepChunkSpan(x, y, z);
|
||||
yield endX - x;
|
||||
x = endX;
|
||||
}
|
||||
}
|
||||
|
||||
#sweepChunkSpan(startX, y, z) {
|
||||
const chunkKey = this.#chunks.keyAt(startX, z);
|
||||
const endX = Math.min(this.#chunks.endOfSpanX(startX), this.#bounds.max.x);
|
||||
if (this.#skippedChunks.isSkipped(chunkKey))
|
||||
this.#markSkipped(startX, endX, y, z);
|
||||
else
|
||||
this.#verifySpan(chunkKey, startX, endX, y, z);
|
||||
return endX;
|
||||
}
|
||||
|
||||
#verifySpan(chunkKey, startX, endX, y, z) {
|
||||
let x = startX;
|
||||
try {
|
||||
for (; x < endX; x++)
|
||||
this.#verifyCell(this.#location.set(x, y, z));
|
||||
} catch (error) {
|
||||
if (!this.#skippedChunks.trackError(error, chunkKey))
|
||||
throw error;
|
||||
this.#markSkipped(x, endX, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
#verifyCell(location) {
|
||||
const { verificationLevel, flags } = this.#cellVerifier.verify(location);
|
||||
this.#grid.setCell(location, verificationLevel, flags);
|
||||
this.#observer.onCellVerified(location, verificationLevel);
|
||||
}
|
||||
|
||||
#markSkipped(startX, endX, y, z) {
|
||||
for (let x = startX; x < endX; x++)
|
||||
this.#grid.setLevel(this.#location.set(x, y, z), BlockVerificationLevel.Skipped);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { TicksPerSecond } from "@minecraft/server";
|
||||
import { renderProfileOf } from "../Enums/RenderMode";
|
||||
import { RefreshRate } from "./RefreshRate";
|
||||
|
||||
const DEFAULT_PARTICLE_LIFETIME_TICKS = 10;
|
||||
const DEFAULT_BLOCKS_PER_TICK = 10;
|
||||
|
||||
class ParticleLifetime {
|
||||
static MIN_TICKS = 8;
|
||||
|
||||
static toSeconds(ticks) {
|
||||
return Math.max(ticks, ParticleLifetime.MIN_TICKS) / TicksPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
export class InstanceVerifierSettings {
|
||||
#instance;
|
||||
|
||||
constructor(instance) {
|
||||
this.#instance = instance;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.#verifierOptions().isEnabled;
|
||||
}
|
||||
|
||||
blocksPerTick(volume) {
|
||||
return RefreshRate.blocksPerTick(volume, this.#verifierOptions().refreshSeconds);
|
||||
}
|
||||
|
||||
showsBlockPreview() {
|
||||
return renderProfileOf(this.#instance.options.renderMode).blockPreview;
|
||||
}
|
||||
|
||||
particleLifetimeSeconds() {
|
||||
return ParticleLifetime.toSeconds(this.#verifierOptions().particleLifetime);
|
||||
}
|
||||
|
||||
#verifierOptions() {
|
||||
return this.#instance.options.verifier;
|
||||
}
|
||||
}
|
||||
|
||||
export class StandaloneVerifierSettings {
|
||||
#blocksPerTick;
|
||||
#particleLifetimeTicks;
|
||||
|
||||
constructor({ blocksPerTick = DEFAULT_BLOCKS_PER_TICK, particleLifetime = DEFAULT_PARTICLE_LIFETIME_TICKS } = {}) {
|
||||
this.#blocksPerTick = blocksPerTick;
|
||||
this.#particleLifetimeTicks = particleLifetime;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
blocksPerTick() {
|
||||
return this.#blocksPerTick;
|
||||
}
|
||||
|
||||
showsBlockPreview() {
|
||||
return true;
|
||||
}
|
||||
|
||||
particleLifetimeSeconds() {
|
||||
return ParticleLifetime.toSeconds(this.#particleLifetimeTicks);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
import { InstanceExistsError } from '../classes/Errors/InstanceExistsError';
|
||||
import { StructureNotFoundError } from '../classes/Errors/StructureNotFoundError';
|
||||
import { getDefaultRenderMode } from '../options/defaultRenderMode';
|
||||
|
||||
export class CreateCommand extends Command {
|
||||
constructor() {
|
||||
@@ -34,7 +35,8 @@ export class CreateCommand extends Command {
|
||||
}
|
||||
|
||||
addStructure(origin, instanceName, structureId) {
|
||||
structureCollection.add(instanceName, structureId);
|
||||
const playerId = origin.getType() === 'Player' ? origin.getSource().id : void 0;
|
||||
instanceCollection.add(instanceName, structureId, { renderMode: getDefaultRenderMode(playerId) });
|
||||
origin.sendMessage({ translate: 'construct.commands.create.success', with: [instanceName, structureId] });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { CustomCommandParamType, CustomCommandStatus, CommandPermissionLevel, system } from '@minecraft/server';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class DeleteCommand extends Command {
|
||||
constructor() {
|
||||
@@ -16,9 +16,9 @@ export class DeleteCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
system.run(() => {
|
||||
structureCollection.delete(instanceName);
|
||||
instanceCollection.delete(instanceName);
|
||||
origin.sendMessage({ translate: 'construct.commands.delete.success', with: [instanceName] });
|
||||
});
|
||||
return { status: CustomCommandStatus.Success };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { CustomCommandParamType, CustomCommandStatus, CommandPermissionLevel, system } from '@minecraft/server';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class EnableCommand extends Command {
|
||||
constructor() {
|
||||
@@ -17,7 +17,7 @@ export class EnableCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName, state) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
if (state && !instance.hasLocation()) {
|
||||
origin.sendMessage({ translate: 'construct.commands.error.noLocation', with: [instanceName] });
|
||||
return void 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
import { Vector } from '../lib/Vector';
|
||||
|
||||
export class InstanceInfoCommand extends Command {
|
||||
@@ -17,7 +17,7 @@ export class InstanceInfoCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
const message = { rawtext: [
|
||||
this.getHeaderText(instance),
|
||||
{ text: '\n' },
|
||||
@@ -29,6 +29,8 @@ export class InstanceInfoCommand extends Command {
|
||||
{ text: '\n' },
|
||||
this.getLayerText(instance),
|
||||
{ text: '\n' },
|
||||
this.getRenderModeText(instance),
|
||||
{ text: '\n' },
|
||||
this.getVerifierText(instance),
|
||||
{ text: '\n' },
|
||||
this.getSizeText(instance)
|
||||
@@ -49,7 +51,7 @@ export class InstanceInfoCommand extends Command {
|
||||
if (!instance.hasLocation())
|
||||
return { translate: 'construct.commands.instanceinfo.noLocation' };
|
||||
const { dimensionId, location } = instance.getLocation();
|
||||
return { translate: 'construct.commands.instanceinfo.location', with: [location.toString(), dimensionId.replace('minecraft:', '')] };
|
||||
return { translate: 'construct.commands.instanceinfo.location', with: [Vector.from(location).toString(), dimensionId.replace('minecraft:', '')] };
|
||||
}
|
||||
|
||||
getEnabledText(instance) {
|
||||
@@ -60,6 +62,10 @@ export class InstanceInfoCommand extends Command {
|
||||
return { translate: 'construct.commands.instanceinfo.layer', with: [String(instance.getLayer()), String(instance.getMaxLayer())] };
|
||||
}
|
||||
|
||||
getRenderModeText(instance) {
|
||||
return { translate: 'construct.commands.instanceinfo.renderMode', with: [String(instance.getRenderMode())] };
|
||||
}
|
||||
|
||||
getVerifierText(instance) {
|
||||
const verifier = instance.options.verifier;
|
||||
return { translate: 'construct.commands.instanceinfo.verifier', with: [String(verifier.isEnabled)] };
|
||||
@@ -67,7 +73,7 @@ export class InstanceInfoCommand extends Command {
|
||||
|
||||
getSizeText(instance) {
|
||||
const bounds = instance.getBounds();
|
||||
return { translate: 'construct.commands.instanceinfo.size', with: [bounds.max.toString(), Vector.volume(bounds.min, bounds.max).toString()] };
|
||||
return { translate: 'construct.commands.instanceinfo.size', with: [Vector.from(bounds.max).toString(), Vector.volume(bounds.min, bounds.max).toString()] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandPermissionLevel, CustomCommandStatus, system } from '@minecraft/server';
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class InstancesCommand extends Command {
|
||||
constructor() {
|
||||
@@ -13,7 +13,7 @@ export class InstancesCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin) {
|
||||
const names = structureCollection.getInstanceNames();
|
||||
const names = instanceCollection.getInstanceNames();
|
||||
if (names.length === 0)
|
||||
return { status: CustomCommandStatus.Success, message: 'construct.commands.instances.empty' };
|
||||
const rawtext = [
|
||||
@@ -21,7 +21,7 @@ export class InstancesCommand extends Command {
|
||||
{ text: '\n' }
|
||||
];
|
||||
for (const name of names) {
|
||||
const instance = structureCollection.get(name);
|
||||
const instance = instanceCollection.get(name);
|
||||
const status = this.formatStatus(instance);
|
||||
rawtext.push({
|
||||
translate: 'construct.commands.instances.row',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class LayerCommand extends Command {
|
||||
constructor() {
|
||||
@@ -17,7 +17,7 @@ export class LayerCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName, layer) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
const max = instance.getMaxLayer();
|
||||
if (layer < 0 || layer > max) {
|
||||
origin.sendMessage({ translate: 'construct.commands.layer.outOfBounds', with: [String(layer), instanceName, String(max)] });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, EntityComponentTypes, system } from '@minecraft/server';
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
import { PlayerCommandOrigin } from '../classes/Commands/PlayerCommandOrigin';
|
||||
import { NotAPlayerError } from '../classes/Errors/NotAPlayerError';
|
||||
|
||||
@@ -21,7 +21,7 @@ export class MaterialsCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName, missing) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
const onlyMissing = missing === true;
|
||||
if (onlyMissing)
|
||||
this.assertIsPlayer(origin);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system, world } from '@minecraft/server';
|
||||
import { Vector } from '../lib/Vector';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
import { PlayerCommandOrigin } from '../classes/Commands/PlayerCommandOrigin';
|
||||
|
||||
export class MoveCommand extends Command {
|
||||
@@ -13,7 +13,7 @@ export class MoveCommand extends Command {
|
||||
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||
],
|
||||
optionalParameters: [
|
||||
{ name: 'dimensionId', type: CustomCommandParamType.Enum }, // Enum defined in PlaceCommand.js
|
||||
{ name: 'dimensionId', type: CustomCommandParamType.Enum }, // Enum defined in PlaceCommand
|
||||
{ name: 'location', type: CustomCommandParamType.Location }
|
||||
],
|
||||
permissionLevel: CommandPermissionLevel.Any,
|
||||
@@ -22,7 +22,7 @@ export class MoveCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName, dimensionId, location) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
if (dimensionId === void 0 || location === void 0) {
|
||||
if (!(origin instanceof PlayerCommandOrigin))
|
||||
return { status: CustomCommandStatus.Failure, message: 'construct.commands.move.locationRequired' };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class NextLayerCommand extends Command {
|
||||
constructor() {
|
||||
@@ -16,7 +16,7 @@ export class NextLayerCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
instance.increaseLayer();
|
||||
origin.sendMessage({ translate: 'construct.commands.nextlayer.success', with: [instanceName, String(instance.getLayer())] });
|
||||
return { status: CustomCommandStatus.Success };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, DimensionTypes, system, world } from '@minecraft/server';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
import { InstanceExistsError } from '../classes/Errors/InstanceExistsError';
|
||||
import { Vector } from '../lib/Vector';
|
||||
|
||||
@@ -21,7 +21,7 @@ export class PlaceCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName, dimensionId, location) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
const flooredLocation = Vector.from(location).floor();
|
||||
this.assertDimensionExists(dimensionId);
|
||||
system.run(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class PrevLayerCommand extends Command {
|
||||
constructor() {
|
||||
@@ -16,7 +16,7 @@ export class PrevLayerCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
instance.decreaseLayer();
|
||||
origin.sendMessage({ translate: 'construct.commands.prevlayer.success', with: [instanceName, String(instance.getLayer())] });
|
||||
return { status: CustomCommandStatus.Success };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class RenameCommand extends Command {
|
||||
constructor() {
|
||||
@@ -17,12 +17,12 @@ export class RenameCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName, newName) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
if (structureCollection.has(newName)) {
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
if (instanceCollection.has(newName)) {
|
||||
origin.sendMessage({ translate: 'construct.error.instanceExists', with: [newName] });
|
||||
return void 0;
|
||||
}
|
||||
structureCollection.rename(instanceName, newName);
|
||||
instanceCollection.rename(instanceName, newName);
|
||||
origin.sendMessage({ translate: 'construct.commands.rename.success', with: [instanceName, newName] });
|
||||
return { status: CustomCommandStatus.Success };
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system, TicksPerSecond } from '@minecraft/server';
|
||||
import { InstanceFormBuilder } from '../classes/Instance/InstanceFormBuilder';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
import { StructureVerifier } from '../classes/Verifier/StructureVerifier';
|
||||
import { StructureStatistics } from '../classes/Structure/StructureStatistics';
|
||||
import { InstanceStatistics } from '../classes/Instance/InstanceStatistics';
|
||||
|
||||
export class StatsCommand extends Command {
|
||||
constructor() {
|
||||
@@ -19,7 +19,7 @@ export class StatsCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
if (this.structureVerifier)
|
||||
return { status: CustomCommandStatus.Failure, error: 'construct.commands.stats.alreadyRunning' };
|
||||
system.run(async () => {
|
||||
@@ -29,10 +29,9 @@ export class StatsCommand extends Command {
|
||||
}
|
||||
|
||||
async getStatsMessage(instance) {
|
||||
const verifierOptions = { isEnabled: true, particleLifetime: 1*TicksPerSecond, isStandalone: true };
|
||||
this.structureVerifier = new StructureVerifier(instance, verifierOptions);
|
||||
this.structureVerifier = StructureVerifier.standalone(instance, { particleLifetime: 1*TicksPerSecond });
|
||||
const verification = await this.structureVerifier.verifyStructure(true);
|
||||
const statistics = new StructureStatistics(instance, verification);
|
||||
const statistics = new InstanceStatistics(instance, verification);
|
||||
const statsMessage = statistics.getMessage();
|
||||
this.structureVerifier = void 0;
|
||||
return statsMessage;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Command } from '../classes/Commands/Command';
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, EntityComponentTypes, EquipmentSlot, system } from '@minecraft/server';
|
||||
import { PlayerCommandOrigin } from '../classes/Commands/PlayerCommandOrigin';
|
||||
import { MENU_ITEM } from '../consts';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class TagCommand extends Command {
|
||||
constructor() {
|
||||
@@ -19,7 +19,7 @@ export class TagCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
const player = origin.getSource();
|
||||
const equipment = player.getComponent(EntityComponentTypes.Equippable);
|
||||
const itemStack = equipment?.getEquipment(EquipmentSlot.Mainhand);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||
import { Command } from '../classes/Commands/Command';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
export class VerifierCommand extends Command {
|
||||
constructor() {
|
||||
@@ -17,7 +17,7 @@ export class VerifierCommand extends Command {
|
||||
}
|
||||
|
||||
run(origin, instanceName, state) {
|
||||
const instance = structureCollection.get(instanceName);
|
||||
const instance = instanceCollection.get(instanceName);
|
||||
if (state)
|
||||
instance.setVerifierEnabled(true);
|
||||
else
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
export const PACK_IDENTIFIER = 'construct';
|
||||
export const MENU_ITEM = 'construct:menu';
|
||||
|
||||
export const DEBUG_CONFIG = {
|
||||
enable: false,
|
||||
render_all_textures_as: { u: 736, v: 368 },
|
||||
};
|
||||
|
||||
+165
-77
@@ -1,78 +1,166 @@
|
||||
/**
|
||||
* Unknown author, with additions.
|
||||
*/
|
||||
const isVec3Symbol = Symbol("isVec3");
|
||||
export function Vector(x = 0, y = 0, z = 0) {
|
||||
if (new.target) {
|
||||
this.x = Number(x);
|
||||
this.y = Number(y);
|
||||
this.z = Number(z);
|
||||
} else {return { x: Number(x), y: Number(y), z: Number(z), __proto__: Vector.prototype };}
|
||||
}
|
||||
Vector.magnitude = function magnitude(vec) { return Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z); }
|
||||
Vector.normalize = function normalize(vec) { const l = Vector.magnitude(vec); return { x: vec.x / l, y: vec.y / l, z: vec.z / l, __proto__: Vector.prototype }; }
|
||||
Vector.cross = function crossProduct(a, b) { return { x: a.y * b.z - a.z * b.y, y: a.x * b.z - a.z * b.x, z: a.x * b.y - a.y * b.x, __proto__: Vector.prototype }; }
|
||||
Vector.dot = function dot(a, b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
|
||||
Vector.angleBetween = function angleBetween(a, b) { return Math.acos(Vector.dot(a, b) / (Vector.magnitude(a) * Vector.magnitude(b))); }
|
||||
Vector.subtract = function subtract(a, b) { return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z, __proto__: Vector.prototype } };
|
||||
Vector.add = function add(a, b) { return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z, __proto__: Vector.prototype } };
|
||||
Vector.multiply = function multiply(vec, num) {
|
||||
if (typeof num == "number") return { x: vec.x * num, y: vec.y * num, z: vec.z * num, __proto__: Vector.prototype };
|
||||
return { x: vec.x * num.x, y: vec.y * num.y, z: vec.z * num.z, __proto__: Vector.prototype };
|
||||
}
|
||||
Vector.isVec3 = function isVec3(vec) { return vec[isVec3Symbol] === true; }
|
||||
Vector.floor = function floor(vec) { return { x: Math.floor(vec.x), y: Math.floor(vec.y), z: Math.floor(vec.z), __proto__: Vector.prototype }; }
|
||||
Vector.volume = function volume(a, b) {
|
||||
const [min, max] = Vector.sort(a, b);
|
||||
return (max.x - min.x) * (max.y - min.y) * (max.z - min.z);
|
||||
}
|
||||
Vector.projection = function projection(a, b) { return Vector.multiply(b, Vector.dot(a, b) / ((b.x * b.x + b.y * b.y + b.z * b.z) ** 2)); }
|
||||
Vector.rejection = function rejection(a, b) { return Vector.subtract(a, Vector.projection(a, b)); }
|
||||
Vector.reflect = function reflect(v, n) { return Vector.subtract(v, Vector.multiply(n, 2 * Vector.dot(v, n))); }
|
||||
Vector.lerp = function lerp(a, b, t) { return Vector.multiply(a, 1 - t).add(Vector.multiply(b, t)); }
|
||||
Vector.distance = function distance(a, b) { return Vector.magnitude(Vector.subtract(a, b)); }
|
||||
Vector.intersect = function intersect(maxA, minA, maxB, minB) {
|
||||
return (maxA.x >= minB.x && minA.x <= maxB.x) &&
|
||||
(maxA.y >= minB.y && minA.y <= maxB.y) &&
|
||||
(maxA.z >= minB.z && minA.z <= maxB.z);
|
||||
}
|
||||
Vector.from = function from(object) {
|
||||
if (Vector.isVec3(object)) return object;
|
||||
if (Array.isArray(object)) return new Vector(object[0], object[1], object[2]);
|
||||
const { x = 0, y = 0, z = 0 } = object ?? {};
|
||||
return { x: Number(x), y: Number(y), z: Number(z), __proto__: Vector.prototype };
|
||||
}
|
||||
Vector.sort = function sort(vec1, vec2) {
|
||||
const [x1, x2] = vec1.x < vec2.x ? [vec1.x, vec2.x] : [vec2.x, vec1.x];
|
||||
const [y1, y2] = vec1.y < vec2.y ? [vec1.y, vec2.y] : [vec2.y, vec1.y];
|
||||
const [z1, z2] = vec1.z < vec2.z ? [vec1.z, vec2.z] : [vec2.z, vec1.z];
|
||||
return [{ x: x1, y: y1, z: z1, __proto__: Vector.prototype }, { x: x2, y: y2, z: z2, __proto__: Vector.prototype }];
|
||||
}
|
||||
Vector.up = { x: 0, y: 1, z: 0, __proto__: Vector.prototype };
|
||||
Vector.down = { x: 0, y: -1, z: 0, __proto__: Vector.prototype };
|
||||
Vector.right = { x: 1, y: 0, z: 0, __proto__: Vector.prototype };
|
||||
Vector.left = { x: -1, y: 0, z: 0, __proto__: Vector.prototype };
|
||||
Vector.forward = { x: 0, y: 0, z: 1, __proto__: Vector.prototype };
|
||||
Vector.backward = { x: 0, y: 0, z: -1, __proto__: Vector.prototype };
|
||||
Vector.zero = { x: 0, y: 0, z: 0, __proto__: Vector.prototype };
|
||||
Vector.prototype = {
|
||||
distance(vec) { return Vector.distance(this, vec); },
|
||||
lerp(vec, t) { return Vector.lerp(this, vec, t); },
|
||||
projection(vec) { return Vector.projection(this, vec); },
|
||||
reflect(vec) { return Vector.reflect(this, vec); },
|
||||
rejection(vec) { return Vector.rejection(this, vec); },
|
||||
cross(vec) { return Vector.cross(this, vec); },
|
||||
dot(vec) { return Vector.dot(this, vec); },
|
||||
floor() { return Vector.floor(this); },
|
||||
volume(vec) { return Vector.volume(this, vec); },
|
||||
add(vec) { return Vector.add(this, vec); },
|
||||
subtract(vec) { return Vector.subtract(this, vec); },
|
||||
multiply(num) { return Vector.multiply(this, num); },
|
||||
get length() { return Vector.magnitude(this); },
|
||||
get normalized() { return Vector.normalize(this); },
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
[isVec3Symbol]: true,
|
||||
export class Vector {
|
||||
constructor(x = 0, y = 0, z = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
static magnitude(vec) {
|
||||
return Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
|
||||
}
|
||||
|
||||
static normalize(vec) {
|
||||
const l = Vector.magnitude(vec); return new Vector(vec.x / l, vec.y / l, vec.z / l);
|
||||
}
|
||||
|
||||
static cross(a, b) {
|
||||
return new Vector(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
|
||||
}
|
||||
|
||||
static dot(a, b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
static angleBetween(a, b) {
|
||||
return Math.acos(Vector.dot(a, b) / (Vector.magnitude(a) * Vector.magnitude(b)));
|
||||
}
|
||||
|
||||
static subtract(a, b) {
|
||||
return new Vector(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
}
|
||||
|
||||
static add(a, b) {
|
||||
return new Vector(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
}
|
||||
|
||||
static floor(vec) {
|
||||
return new Vector(Math.floor(vec.x), Math.floor(vec.y), Math.floor(vec.z));
|
||||
}
|
||||
|
||||
static multiply(vec, num) {
|
||||
if (typeof num === "number")
|
||||
return new Vector(vec.x * num, vec.y * num, vec.z * num);
|
||||
return new Vector(vec.x * num.x, vec.y * num.y, vec.z * num.z);
|
||||
}
|
||||
|
||||
static volume(a, b) {
|
||||
return Math.abs((a.x - b.x) * (a.y - b.y) * (a.z - b.z));
|
||||
}
|
||||
|
||||
static projection(a, b) {
|
||||
const scale = Vector.dot(a, b) / (b.x * b.x + b.y * b.y + b.z * b.z);
|
||||
return new Vector(b.x * scale, b.y * scale, b.z * scale);
|
||||
}
|
||||
|
||||
static rejection(a, b) {
|
||||
const scale = Vector.dot(a, b) / (b.x * b.x + b.y * b.y + b.z * b.z);
|
||||
return new Vector(a.x - b.x * scale, a.y - b.y * scale, a.z - b.z * scale);
|
||||
}
|
||||
|
||||
static reflect(v, n) {
|
||||
const scale = 2 * Vector.dot(v, n);
|
||||
return new Vector(v.x - n.x * scale, v.y - n.y * scale, v.z - n.z * scale);
|
||||
}
|
||||
|
||||
static lerp(a, b, t) {
|
||||
return new Vector(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t);
|
||||
}
|
||||
|
||||
static distance(a, b) {
|
||||
const dx = a.x - b.x;
|
||||
const dy = a.y - b.y;
|
||||
const dz = a.z - b.z;
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
static intersect(maxA, minA, maxB, minB) {
|
||||
return (maxA.x >= minB.x && minA.x <= maxB.x) &&
|
||||
(maxA.y >= minB.y && minA.y <= maxB.y) &&
|
||||
(maxA.z >= minB.z && minA.z <= maxB.z);
|
||||
}
|
||||
|
||||
static from(object) {
|
||||
if (object instanceof Vector)
|
||||
return object;
|
||||
if (Array.isArray(object))
|
||||
return new Vector(object[0], object[1], object[2]);
|
||||
const { x = 0, y = 0, z = 0 } = object ?? {};
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
|
||||
static sort(vec1, vec2) {
|
||||
const [x1, x2] = vec1.x < vec2.x ? [vec1.x, vec2.x] : [vec2.x, vec1.x];
|
||||
const [y1, y2] = vec1.y < vec2.y ? [vec1.y, vec2.y] : [vec2.y, vec1.y];
|
||||
const [z1, z2] = vec1.z < vec2.z ? [vec1.z, vec2.z] : [vec2.z, vec1.z];
|
||||
return [new Vector(x1, y1, z1), new Vector(x2, y2, z2)];
|
||||
}
|
||||
|
||||
distance(vec) { return Vector.distance(this, vec); }
|
||||
lerp(vec, t) { return Vector.lerp(this, vec, t); }
|
||||
projection(vec) { return Vector.projection(this, vec); }
|
||||
reflect(vec) { return Vector.reflect(this, vec); }
|
||||
rejection(vec) { return Vector.rejection(this, vec); }
|
||||
cross(vec) { return Vector.cross(this, vec); }
|
||||
dot(vec) { return Vector.dot(this, vec); }
|
||||
floor() { return Vector.floor(this); }
|
||||
volume(vec) { return Vector.volume(this, vec); }
|
||||
add(vec) { return Vector.add(this, vec); }
|
||||
subtract(vec) { return Vector.subtract(this, vec); }
|
||||
multiply(num) { return Vector.multiply(this, num); }
|
||||
|
||||
set(x, y, z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
setFrom(vec) {
|
||||
this.x = vec.x;
|
||||
this.y = vec.y;
|
||||
this.z = vec.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
addInPlace(vec) {
|
||||
this.x += vec.x;
|
||||
this.y += vec.y;
|
||||
this.z += vec.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
subtractInPlace(vec) {
|
||||
this.x -= vec.x;
|
||||
this.y -= vec.y;
|
||||
this.z -= vec.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
floorInPlace() {
|
||||
this.x = Math.floor(this.x);
|
||||
this.y = Math.floor(this.y);
|
||||
this.z = Math.floor(this.z);
|
||||
return this;
|
||||
}
|
||||
|
||||
multiplyInPlace(num) {
|
||||
if (typeof num === "number") {
|
||||
this.x *= num; this.y *= num; this.z *= num;
|
||||
return this;
|
||||
}
|
||||
this.x *= num.x; this.y *= num.y; this.z *= num.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
get length() { return Vector.magnitude(this); }
|
||||
get normalized() { return Vector.normalize(this); }
|
||||
toString() { return `<${this.x}, ${this.y}, ${this.z}>`; }
|
||||
}
|
||||
}
|
||||
|
||||
Vector.up = Object.freeze(new Vector(0, 1, 0));
|
||||
Vector.down = Object.freeze(new Vector(0, -1, 0));
|
||||
Vector.north = Object.freeze(new Vector(0, 0, -1));
|
||||
Vector.south = Object.freeze(new Vector(0, 0, 1));
|
||||
Vector.east = Object.freeze(new Vector(1, 0, 0));
|
||||
Vector.west = Object.freeze(new Vector(-1, 0, 0));
|
||||
Vector.zero = Object.freeze(new Vector(0, 0, 0));
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// Setup
|
||||
import './classes/Builder/Builders';
|
||||
import './classes/BlockChangeWatcher';
|
||||
|
||||
// Options
|
||||
import './options/easyPlace';
|
||||
import './options/fastEasyPlace';
|
||||
import './options/materialGrabber';
|
||||
import './options/defaultRenderMode';
|
||||
|
||||
// Menu item handler
|
||||
import './classes/MenuItemHandler';
|
||||
// Interaction Handling
|
||||
import './classes/MenuItemInteractions';
|
||||
|
||||
// Commands
|
||||
import './commands/ConstructCommand';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { BuilderChoiceOption } from '../classes/Builder/BuilderChoiceOption';
|
||||
import { RENDER_MODE_LABELS, RENDER_MODE_ORDER, RenderMode } from '../classes/Enums/RenderMode';
|
||||
|
||||
const builderOption = new BuilderChoiceOption({
|
||||
identifier: 'defaultRenderMode',
|
||||
displayName: { translate: 'construct.option.defaultrendermode.name' },
|
||||
description: { translate: 'construct.option.defaultrendermode.description' },
|
||||
choices: RENDER_MODE_ORDER,
|
||||
choiceLabels: RENDER_MODE_ORDER.map(mode => ({ translate: RENDER_MODE_LABELS[mode] })),
|
||||
defaultValue: RenderMode.Default
|
||||
});
|
||||
|
||||
export function getDefaultRenderMode(playerId) {
|
||||
if (!playerId)
|
||||
return RenderMode.Default;
|
||||
return builderOption.getValue(playerId);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BuilderOption } from '../classes/Builder/BuilderOption';
|
||||
import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, ItemStack, system, world } from '@minecraft/server';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { StructureBlockConverter } from '../classes/Structure/StructureBlockConverter';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, blockIdToItemStackMap } from './easyPlaceConversions';
|
||||
import { fetchMatchingItemSlot, placeBlock } from '../utils';
|
||||
import { Builders } from '../classes/Builder/Builders';
|
||||
@@ -49,7 +50,7 @@ world.beforeEvents.playerPlaceBlock.subscribe(onPlayerPlaceBlock);
|
||||
function onPlayerPlaceBlock(event) {
|
||||
const { player, block } = event;
|
||||
if (!player || !block || !builderOption.isEnabled(player.id) || !isHoldingActionItem(player)) return;
|
||||
const structureBlock = structureCollection.fetchStructureBlock(block.dimension.id, block.location);
|
||||
const structureBlock = instanceCollection.fetchStructureBlock(block.dimension.id, block.location);
|
||||
if (!structureBlock)
|
||||
return;
|
||||
tryPlaceBlock(event, player, block, structureBlock);
|
||||
@@ -65,13 +66,17 @@ function isHoldingActionItem(player) {
|
||||
function tryPlaceBlock(event, player, block, structureBlock) {
|
||||
if (shouldPreventAction(player, structureBlock))
|
||||
return preventAction(event, player);
|
||||
structureBlock = tryConvertBannedToValidBlock(structureBlock);
|
||||
if (player.getGameMode() === GameMode.Creative) {
|
||||
const gameMode = player.getGameMode();
|
||||
let permutation = StructureBlockConverter.fromBannedBlockToValidPermutation(structureBlock);
|
||||
if (gameMode === GameMode.Survival)
|
||||
permutation = StructureBlockConverter.toDefaultState(permutation);
|
||||
if (!isSupported(block, permutation))
|
||||
return preventAction(event, player);
|
||||
if (gameMode === GameMode.Creative) {
|
||||
event.cancel = true;
|
||||
placeBlock(player, block, structureBlock);
|
||||
} else if (player.getGameMode() === GameMode.Survival) {
|
||||
structureBlock = tryConvertToDefaultState(structureBlock);
|
||||
tryPlaceBlockSurvival(event, player, block, structureBlock);
|
||||
placeBlock(player, block, permutation);
|
||||
} else if (gameMode === GameMode.Survival) {
|
||||
tryPlaceBlockSurvival(event, player, block, permutation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +92,7 @@ function preventAction(event, player) {
|
||||
}
|
||||
|
||||
function isBannedBlock(player, structureBlock) {
|
||||
const blockId = structureBlock.type.id.replace('minecraft:', '');
|
||||
const blockId = structureBlock.typeId.replace('minecraft:', '');
|
||||
if (bannedBlocks.includes(blockId))
|
||||
return true;
|
||||
if (bannedDimensionBlocks[player.dimension.id.replace('minecraft:', '')]?.includes(blockId))
|
||||
@@ -95,46 +100,24 @@ function isBannedBlock(player, structureBlock) {
|
||||
const allowedStates = whitelistedBlockStates[blockId];
|
||||
if (allowedStates) {
|
||||
for (const [stateKey, stateValue] of Object.entries(allowedStates)) {
|
||||
if (structureBlock.getState(stateKey) !== stateValue)
|
||||
if (structureBlock.states[stateKey] !== stateValue)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryConvertBannedToValidBlock(structureBlock) {
|
||||
const blockId = structureBlock.type.id.replace('minecraft:', '');
|
||||
if (Object.keys(bannedToValidBlockMap).includes(blockId))
|
||||
return BlockPermutation.resolve(bannedToValidBlockMap[blockId], structureBlock.getAllStates());
|
||||
if (blockId === "bubble_column" && structureBlock.isWaterlogged)
|
||||
return BlockPermutation.resolve('minecraft:water');
|
||||
return structureBlock;
|
||||
function isSupported(worldBlock, permutationToPlace) {
|
||||
return worldBlock.canPlace(permutationToPlace);
|
||||
}
|
||||
|
||||
function tryConvertToDefaultState(structureBlock) {
|
||||
const newStates = {};
|
||||
for (const [stateKey, stateValue] of Object.entries(structureBlock.getAllStates())) {
|
||||
if (resetToBlockStates[stateKey] !== void 0 && stateValue !== resetToBlockStates[stateKey])
|
||||
newStates[stateKey] = resetToBlockStates[stateKey];
|
||||
else
|
||||
newStates[stateKey] = stateValue;
|
||||
}
|
||||
return BlockPermutation.resolve(structureBlock.type.id, newStates);
|
||||
}
|
||||
|
||||
function tryPlaceBlockSurvival(event, player, block, structureBlock) {
|
||||
const placeableItemStack = getPlaceableItemStack(structureBlock);
|
||||
function tryPlaceBlockSurvival(event, player, block, permutation) {
|
||||
const placeableItemStack = StructureBlockConverter.toPlaceableItemStack(permutation);
|
||||
const itemSlotToUse = fetchMatchingItemSlot(player, placeableItemStack?.typeId);
|
||||
if (itemSlotToUse) {
|
||||
event.cancel = true;
|
||||
placeBlock(player, block, structureBlock, itemSlotToUse);
|
||||
placeBlock(player, block, permutation, itemSlotToUse);
|
||||
} else {
|
||||
preventAction(event, player);
|
||||
}
|
||||
}
|
||||
|
||||
function getPlaceableItemStack(structureBlock) {
|
||||
const blockId = structureBlock.type.id.replace('minecraft:', '');
|
||||
const newItemId = blockIdToItemStackMap[blockId];
|
||||
return newItemId ? new ItemStack(newItemId) : structureBlock.getItemStack();
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { placeBlock, fetchMatchingItemSlot } from '../utils';
|
||||
import { Raycaster } from '../classes/Raycaster';
|
||||
import { Builders } from '../classes/Builder/Builders';
|
||||
import { Vector } from '../lib/Vector';
|
||||
import { StructureBlockConverter } from '../classes/Structure/StructureBlockConverter';
|
||||
|
||||
const locationsPlacedLastTick = new Set();
|
||||
const ACTION_ITEM = 'construct:easy_place';
|
||||
@@ -78,14 +79,15 @@ function onPlayerInteractWithBlock(event) {
|
||||
const { player, block, isFirstEvent } = event;
|
||||
if (!player || !isFirstEvent || !block || !builderOption.isEnabled(player.id) || !isHoldingActionItem(player)) return;
|
||||
preventAction(event, player);
|
||||
processEasyPlace(player);
|
||||
}
|
||||
|
||||
function processEasyPlace(player) {
|
||||
if (!player || !isHoldingActionItem(player)) return;
|
||||
const structureBlock = Raycaster.getTargetedStructureBlock(player, { isFirst: true });
|
||||
if (!structureBlock)
|
||||
const hit = Raycaster.getTargetedStructureBlock(player, { isFirst: true });
|
||||
if (!hit)
|
||||
return;
|
||||
const worldBlock = player.dimension.getBlock(structureBlock.location);
|
||||
const worldBlock = player.dimension.getBlock(hit.location);
|
||||
if (!worldBlock)
|
||||
return;
|
||||
if (locationsPlacedLastTick.has(JSON.stringify(worldBlock.location)))
|
||||
@@ -94,7 +96,7 @@ function processEasyPlace(player) {
|
||||
system.runTimeout(() => {
|
||||
locationsPlacedLastTick.delete(JSON.stringify(worldBlock.location));
|
||||
}, 2);
|
||||
tryPlaceBlock(player, worldBlock, structureBlock.permutation);
|
||||
tryPlaceBlock(player, worldBlock, hit.block);
|
||||
}
|
||||
|
||||
function preventAction(event, player) {
|
||||
@@ -116,13 +118,18 @@ function isHoldingActionItem(player) {
|
||||
}
|
||||
|
||||
function tryPlaceBlock(player, worldBlock, structureBlock) {
|
||||
if (isBannedBlock(player, structureBlock) || !locationIsPlaceable(player, worldBlock)) return;
|
||||
structureBlock = tryConvertBannedToValidBlock(structureBlock);
|
||||
if (player.getGameMode() === GameMode.Creative) {
|
||||
placeBlock(player, worldBlock, structureBlock);
|
||||
} else if (player.getGameMode() === GameMode.Survival) {
|
||||
structureBlock = tryConvertToDefaultState(structureBlock);
|
||||
tryPlaceBlockSurvival(player, worldBlock, structureBlock);
|
||||
if (isBannedBlock(player, structureBlock) || !locationIsPlaceable(player, worldBlock))
|
||||
return;
|
||||
const gameMode = player.getGameMode();
|
||||
let permutation = StructureBlockConverter.fromBannedBlockToValidPermutation(structureBlock);
|
||||
if (gameMode === GameMode.Survival)
|
||||
permutation = StructureBlockConverter.toDefaultState(permutation);
|
||||
if (!isSupported(worldBlock, permutation))
|
||||
return;
|
||||
if (gameMode === GameMode.Creative) {
|
||||
placeBlock(player, worldBlock, permutation);
|
||||
} else if (gameMode === GameMode.Survival) {
|
||||
tryPlaceBlockSurvival(player, worldBlock, permutation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,10 +137,10 @@ function locationIsPlaceable(player, worldBlock) {
|
||||
return (worldBlock.isAir || worldBlock.isLiquid) && !isBlockInsidePlayer(player, worldBlock);
|
||||
}
|
||||
|
||||
function isBannedBlock(player, structureBlock) {
|
||||
export function isBannedBlock(player, structureBlock) {
|
||||
if (!structureBlock)
|
||||
return true;
|
||||
const blockId = structureBlock.type.id.replace('minecraft:', '');
|
||||
const blockId = structureBlock.typeId.replace('minecraft:', '');
|
||||
if (bannedBlocks.includes(blockId))
|
||||
return true;
|
||||
if (bannedDimensionBlocks[player.dimension.id.replace('minecraft:', '')]?.includes(blockId))
|
||||
@@ -141,44 +148,22 @@ function isBannedBlock(player, structureBlock) {
|
||||
const allowedStates = whitelistedBlockStates[blockId];
|
||||
if (allowedStates) {
|
||||
for (const [stateKey, stateValue] of Object.entries(allowedStates)) {
|
||||
if (structureBlock.getState(stateKey) !== stateValue)
|
||||
if (structureBlock.states[stateKey] !== stateValue)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryConvertBannedToValidBlock(structureBlock) {
|
||||
const blockId = structureBlock.type.id.replace('minecraft:', '');
|
||||
if (Object.keys(bannedToValidBlockMap).includes(blockId))
|
||||
return BlockPermutation.resolve(bannedToValidBlockMap[blockId], structureBlock.getAllStates());
|
||||
if (blockId === "bubble_column" && structureBlock.isWaterlogged)
|
||||
return BlockPermutation.resolve('minecraft:water');
|
||||
return structureBlock;
|
||||
function isSupported(worldBlock, permutationToPlace) {
|
||||
return worldBlock.canPlace(permutationToPlace);
|
||||
}
|
||||
|
||||
function tryConvertToDefaultState(structureBlock) {
|
||||
const newStates = {};
|
||||
for (const [stateKey, stateValue] of Object.entries(structureBlock.getAllStates())) {
|
||||
if (resetToBlockStates[stateKey] !== void 0 && stateValue !== resetToBlockStates[stateKey])
|
||||
newStates[stateKey] = resetToBlockStates[stateKey];
|
||||
else
|
||||
newStates[stateKey] = stateValue;
|
||||
}
|
||||
return BlockPermutation.resolve(structureBlock.type.id, newStates);
|
||||
}
|
||||
|
||||
function tryPlaceBlockSurvival(player, block, structureBlock) {
|
||||
const placeableItemStack = getPlaceableItemStack(structureBlock);
|
||||
function tryPlaceBlockSurvival(player, block, permutation) {
|
||||
const placeableItemStack = StructureBlockConverter.toPlaceableItemStack(permutation);
|
||||
const itemSlotToUse = fetchMatchingItemSlot(player, placeableItemStack?.typeId);
|
||||
if (itemSlotToUse)
|
||||
placeBlock(player, block, structureBlock, itemSlotToUse);
|
||||
}
|
||||
|
||||
function getPlaceableItemStack(structureBlock) {
|
||||
const blockId = structureBlock.type.id.replace('minecraft:', '');
|
||||
const newItemId = blockIdToItemStackMap[blockId];
|
||||
return newItemId ? new ItemStack(newItemId) : structureBlock.getItemStack();
|
||||
placeBlock(player, block, permutation, itemSlotToUse);
|
||||
}
|
||||
|
||||
function isBlockInsidePlayer(player, worldBlock) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BuilderOption } from '../classes/Builder/BuilderOption';
|
||||
import { EntityComponentTypes, ItemStack, Player, world, system, EquipmentSlot } from '@minecraft/server';
|
||||
import { MaterialGrabberForm } from '../classes/Materials/MaterialGrabberForm';
|
||||
import { Builders } from '../classes/Builder/Builders';
|
||||
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||
import { instanceCollection } from '../classes/Instance/InstanceCollection';
|
||||
|
||||
const builderOption = new BuilderOption({
|
||||
identifier: 'materialGrabber',
|
||||
@@ -57,7 +57,7 @@ function onPlayerInteract(event) {
|
||||
const player = event.player;
|
||||
const target = event.block || event.target;
|
||||
const focusedInstanceName = Builders.get(player.id).materialInstanceName;
|
||||
if (!focusedInstanceName || !structureCollection.has(focusedInstanceName))
|
||||
if (!focusedInstanceName || !instanceCollection.has(focusedInstanceName))
|
||||
openInstanceSelectionForm(player, event);
|
||||
else
|
||||
tryGrabMaterials(player, target, focusedInstanceName, event);
|
||||
@@ -86,7 +86,7 @@ function tryGrabMaterials(player, target, focusedInstanceName, event) {
|
||||
}
|
||||
|
||||
function getActiveMaterials(focusedInstanceName) {
|
||||
const instance = structureCollection.get(focusedInstanceName);
|
||||
const instance = instanceCollection.get(focusedInstanceName);
|
||||
return instance?.getActiveMaterials();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { system, EntityComponentTypes, LiquidType, ItemStack } from '@minecraft/
|
||||
import { FormCancelationReason } from '@minecraft/server-ui';
|
||||
import { specialItemPlacementConversions } from './options/easyPlaceConversions';
|
||||
import { blocks, block_sounds } from './blocks';
|
||||
import { blockPlacementSignal } from './classes/BlockPlacementSignal';
|
||||
|
||||
export async function forceShow(player, form, timeout = Infinity) {
|
||||
const startTick = system.currentTick;
|
||||
@@ -32,6 +33,23 @@ export function fetchMatchingItemSlot(entity, itemToMatchId) {
|
||||
return smallestItemSlot;
|
||||
}
|
||||
|
||||
export function fetchMatchingItemSlotIndex(entity, itemToMatchId) {
|
||||
if (!itemToMatchId)
|
||||
return void 0;
|
||||
const inventory = entity.getComponent(EntityComponentTypes.Inventory)?.container;
|
||||
if (!inventory)
|
||||
return void 0;
|
||||
let smallestItemSlotIndex;
|
||||
for (let index = 0; index < inventory.size; index++) {
|
||||
const itemSlot = inventory.getSlot(index);
|
||||
if (itemSlot.hasItem() && itemSlot?.typeId === itemToMatchId) {
|
||||
if (smallestItemSlotIndex === void 0 || itemSlot.amount < inventory.getSlot(smallestItemSlotIndex).amount)
|
||||
smallestItemSlotIndex = index;
|
||||
}
|
||||
}
|
||||
return smallestItemSlotIndex;
|
||||
}
|
||||
|
||||
export function placeBlock(player, placedBlock, blockToPlace, itemSlotToConsume = void 0) {
|
||||
system.run(() => {
|
||||
if (itemSlotToConsume)
|
||||
@@ -39,6 +57,9 @@ export function placeBlock(player, placedBlock, blockToPlace, itemSlotToConsume
|
||||
placedBlock.setPermutation(blockToPlace);
|
||||
handleWaterlogging(placedBlock, blockToPlace);
|
||||
playBlockPlacementSound(player, placedBlock, blockToPlace);
|
||||
const dimensionId = placedBlock.dimension.id;
|
||||
const { x, y, z } = placedBlock.location;
|
||||
system.run(() => blockPlacementSignal.emit(dimensionId, { x, y, z }));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Construct [RP] v1.1.0",
|
||||
"name": "Construct [RP] v1.2.0",
|
||||
"description": "Survival building addon by §aForestOfLight§r.",
|
||||
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4",
|
||||
"version": [1, 1, 0],
|
||||
"min_engine_version": [1, 26, 30]
|
||||
"version": [1, 2, 0],
|
||||
"min_engine_version": [1, 26, 40]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
@@ -17,7 +17,7 @@
|
||||
"dependencies": [
|
||||
{
|
||||
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
||||
"version": [1, 1, 0]
|
||||
"version": [1, 2, 0]
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "construct:block_face_blend",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_blend",
|
||||
"texture": "textures/particle/vanilla_block_atlas"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_instant": {
|
||||
"num_particles": 1
|
||||
},
|
||||
"minecraft:emitter_lifetime_once": {
|
||||
"active_time": 1
|
||||
},
|
||||
"minecraft:emitter_shape_point": {},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": "variable.lifetime"
|
||||
},
|
||||
"minecraft:particle_motion_parametric": {
|
||||
"relative_position": [ 0, 0, 0 ],
|
||||
"rotation": "variable.size.z"
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [
|
||||
"variable.size.x",
|
||||
"variable.size.y"
|
||||
],
|
||||
"facing_camera_mode": "direction_z",
|
||||
"direction": {
|
||||
"mode": "custom",
|
||||
"custom_direction": [
|
||||
"variable.normal.x",
|
||||
"variable.normal.y",
|
||||
"variable.normal.z"
|
||||
]
|
||||
},
|
||||
"uv": {
|
||||
"texture_width": 1024,
|
||||
"texture_height": 1024,
|
||||
"uv": [
|
||||
"variable.uv.x",
|
||||
"variable.uv.y"
|
||||
],
|
||||
"uv_size": [
|
||||
"variable.uv_size.x",
|
||||
"variable.uv_size.y"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_appearance_tinting": {
|
||||
"color": [
|
||||
"variable.face_color.r",
|
||||
"variable.face_color.g",
|
||||
"variable.face_color.b",
|
||||
"variable.face_color.a"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "construct:block_face_opaque",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_opaque",
|
||||
"texture": "textures/particle/vanilla_block_atlas"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_instant": {
|
||||
"num_particles": 1
|
||||
},
|
||||
"minecraft:emitter_lifetime_once": {
|
||||
"active_time": 1
|
||||
},
|
||||
"minecraft:emitter_shape_point": {},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": "variable.lifetime"
|
||||
},
|
||||
"minecraft:particle_motion_parametric": {
|
||||
"relative_position": [ 0, 0, 0 ],
|
||||
"rotation": "variable.size.z"
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [
|
||||
"variable.size.x",
|
||||
"variable.size.y"
|
||||
],
|
||||
"facing_camera_mode": "direction_z",
|
||||
"direction": {
|
||||
"mode": "custom",
|
||||
"custom_direction": [
|
||||
"variable.normal.x",
|
||||
"variable.normal.y",
|
||||
"variable.normal.z"
|
||||
]
|
||||
},
|
||||
"uv": {
|
||||
"texture_width": 1024,
|
||||
"texture_height": 1024,
|
||||
"uv": [
|
||||
"variable.uv.x",
|
||||
"variable.uv.y"
|
||||
],
|
||||
"uv_size": [
|
||||
"variable.uv_size.x",
|
||||
"variable.uv_size.y"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_appearance_tinting": {
|
||||
"color": [
|
||||
"variable.face_color.r",
|
||||
"variable.face_color.g",
|
||||
"variable.face_color.b",
|
||||
"variable.face_color.a"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,14 @@ construct.instance.option.validation=Block Validation
|
||||
construct.instance.option.validation.description=Shows missing and incorrect block overlay.
|
||||
construct.instance.option.layer=Layer
|
||||
construct.instance.option.layer.description=Changes the active layer. Use 0 for all layers.
|
||||
construct.instance.option.rendermode=Render Mode
|
||||
construct.instance.option.rendermode.description=How the instance is drawn. Performance mode is great for FPS.
|
||||
construct.instance.option.rendermode.default=Default
|
||||
construct.instance.option.rendermode.performance=Performance
|
||||
construct.instance.option.rendermode.previewonly=Hologram Only
|
||||
construct.instance.option.rendermode.classic=Classic
|
||||
construct.instance.option.refreshrate=Refresh Rate (seconds)
|
||||
construct.instance.option.refreshrate.description=How long a full rescan takes. This controls how quickly changes not caused directly by the player are noticed. Low values can be laggy.
|
||||
construct.instance.materials.noinventory=§cNo player inventory found.
|
||||
construct.instance.materials.missing.header=§cMaterials Missing From Inventory:
|
||||
construct.instance.materials.missing.button=Show Missing Materials
|
||||
@@ -78,13 +86,17 @@ construct.mainmenu.howto.remove.body=§7- Use the §f/structure delete§7 comman
|
||||
## Structure Block Info Display
|
||||
construct.blockinfo.header=Structure:
|
||||
construct.blockinfo.none=§7None
|
||||
construct.blockinfo.nosupply= §c[No Supply] ## When the targeted block is not in the player's inventory
|
||||
construct.blockinfo.nosupply=§c[No Supply] ## When the targeted block is not in the player's inventory
|
||||
construct.blockinfo.noeasyplace=§e[Cannot be Easy Placed] ## When the targeted block cannot be easyPlaced
|
||||
construct.blockinfo.unknown=§7Unknown
|
||||
construct.blockinfo.waterlogged=§7isWaterlogged: §3true
|
||||
|
||||
construct.pickblock.inventoryfull=§cCannot pick structure block: inventory is full.
|
||||
|
||||
## Builder Options
|
||||
construct.option.enabled= is now enabled!
|
||||
construct.option.disabled= is now disabled.
|
||||
construct.option.setto= is now set to §f
|
||||
construct.option.easyplace.name=Easy Place
|
||||
construct.option.easyplace.description=Always place the correct structure block.
|
||||
construct.option.easyplace.howto=Hold the Easy Place item in your offhand and place blocks in an instance to have them be corrected automatically.
|
||||
@@ -98,6 +110,8 @@ construct.option.materialgrabber.howto=Interact with inventories using the Mater
|
||||
construct.option.materialgrabber.grabbed.zero=§7Grabbed 0 items.
|
||||
construct.option.materialgrabber.grabbed.one=§aGrabbed 1 item.
|
||||
construct.option.materialgrabber.grabbed.many=§aGrabbed %s items. ## Insert string: number of items transferred to the player
|
||||
construct.option.defaultrendermode.name=Default Render Mode
|
||||
construct.option.defaultrendermode.description=The render mode given to instances you create.
|
||||
|
||||
## Commands
|
||||
construct.commands.construct=Gives you the Construct item. Use it to open the Construct menu.
|
||||
@@ -149,11 +163,11 @@ construct.commands.layer.outOfBounds=§cLayer %1 is out of bounds for instance "
|
||||
|
||||
## construct:nextlayer
|
||||
construct.commands.nextlayer=Step a Construct instance one layer up.
|
||||
construct.commands.nextlayer.success=§aInstance "%1" advanced to layer %2. ## Insert strings: instance name, layer number
|
||||
construct.commands.nextlayer.success=§aInstance "%1" increased to layer %2. ## Insert strings: instance name, layer number
|
||||
|
||||
## construct:prevlayer
|
||||
construct.commands.prevlayer=Step a Construct instance one layer down.
|
||||
construct.commands.prevlayer.success=§aInstance "%1" stepped back to layer %2. ## Insert strings: instance name, layer number
|
||||
construct.commands.prevlayer.success=§aInstance "%1" decreased to layer %2. ## Insert strings: instance name, layer number
|
||||
|
||||
## construct:verifier
|
||||
construct.commands.verifier=Toggle block validation overlay for a Construct instance.
|
||||
@@ -173,6 +187,7 @@ construct.commands.instanceinfo.location=§7Location:§r %1 in %2 ## Insert stri
|
||||
construct.commands.instanceinfo.noLocation=§7Location:§r (none) ## Used when the instance has no saved location
|
||||
construct.commands.instanceinfo.enabled=§7Enabled:§r %1 ## Insert string: true/false indicating whether the instance is enabled
|
||||
construct.commands.instanceinfo.layer=§7Layer:§r %1 / %2 ## Insert strings: current layer, max layer
|
||||
construct.commands.instanceinfo.renderMode=§7Render Mode:§r %1 ## Insert string: render mode
|
||||
construct.commands.instanceinfo.verifier=§7Verifier running:§r %1 ## Insert string: true/false indicating whether the verifier is running
|
||||
construct.commands.instanceinfo.size=§7Size:§r %1 (%2 blocks) ## Insert strings: size (x, y, z), total block volume count
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 555 KiB |
Reference in New Issue
Block a user