@@ -19,6 +19,7 @@ import {
1919 Block ,
2020 BlockStates ,
2121 ItemStack ,
22+ LiquidType ,
2223 world ,
2324 system
2425} from "@minecraft/server" ;
@@ -41,43 +42,43 @@ const DEBUG_STICK_ID = "vyt:debug_stick";
4142// Some event listeners. Listens for entityHitBkock
4243// and itemUseOn events, which triggers an action onto
4344// the debug stick
44- world . afterEvents . entityHitBlock . subscribe ( ( ev ) => {
45+ world . afterEvents . entityHitBlock . subscribe ( safeCallWrapper ( ( ev ) => {
4546 if ( ev . damagingEntity . typeId != "minecraft:player" )
4647 return ;
47-
4848 const player = getPlayerByID ( ev . damagingEntity . id ) ;
49-
49+ if ( ! player )
50+ return ;
5051 if ( ! isHoldingDebugStick ( player ) )
5152 return ;
53+ changeSelectedProperty ( player , ev . hitBlock ) ;
54+ } ) ) ;
5255
53- safeCall ( changeSelectedProperty , player , ev . hitBlock ) ;
54- } ) ;
5556
56- world . beforeEvents . itemUseOn . subscribe ( ( ev ) => {
57+ world . beforeEvents . itemUseOn . subscribe ( safeCallWrapper ( ( ev ) => {
5758 if ( ev . source . typeId != "minecraft:player" )
5859 return ;
5960 if ( ev . itemStack ?. typeId != DEBUG_STICK_ID )
6061 return ;
61-
6262 ev . cancel = true ;
63-
6463 const player = getPlayerByID ( ev . source . id ) ;
65-
64+ if ( ! player )
65+ return ;
6666 if ( player . isSneaking )
67- safeCall ( displayBlockInfo , player , ev . block ) ;
67+ displayBlockInfo ( player , ev . block ) ;
6868 else
69- safeCall ( updateBlockProperty , player , ev . block ) ;
70- } ) ;
69+ updateBlockProperty ( player , ev . block ) ;
70+ } ) ) ;
71+
7172
7273// Players should not be able to break blocks using
7374// the debug stick in survival
7475//
7576// TODO: explore other alternatives
76- world . beforeEvents . playerBreakBlock . subscribe ( ( ev ) => {
77+ world . beforeEvents . playerBreakBlock . subscribe ( safeCallWrapper ( ( ev ) => {
7778 if ( ev . itemStack ?. typeId != DEBUG_STICK_ID )
7879 return ;
7980 ev . cancel = true ;
80- } )
81+ } ) ) ;
8182
8283
8384/*============================================================================*\
@@ -90,37 +91,22 @@ world.beforeEvents.playerBreakBlock.subscribe((ev) => {
9091 * @param block
9192 */
9293function changeSelectedProperty ( player : Player , block : Block ) {
93- const permutation = block . permutation ;
94- const states = permutation . getAllStates ( ) ;
94+ const states = getBlockStates ( block ) ;
9595 const names = Object . keys ( states ) ;
96-
97- if ( ! names . length /* && !block.type.canBeWaterlogged*/ )
96+ if ( ! names . length )
9897 return message ( `${ block . typeId } has no properties` , player ) ;
99-
10098 let prop = getCurrentProperty ( player , block . typeId ) ;
10199 let val : BlockStateValue ;
102-
103100 // Increment for the next property
104101 prop = names [ names . indexOf ( prop ) + 1 ] ;
105102 val = states [ prop ] ;
106-
107103 // We're probably at the end of the property names
108- // list, check if the 'waterlogged' property is
109- // available, or just go back at the start of the list
104+ // list, cycle back from the start.
110105 if ( ! prop ) {
111- /*if (block.type.canBeWaterlogged) {
112- prop = "waterlogged";
113- val = block.isWaterlogged;
114- }
115- else {*/
116- prop = names [ 0 ] ;
117- val = states [ prop ] ;
118- /*}*/
106+ prop = names [ 0 ] ;
107+ val = states [ prop ] ;
119108 }
120-
121- // Update the player's selection
122109 setCurrentProperty ( player , block . typeId , prop ) ;
123-
124110 message ( `selected "${ prop } " (${ val } )` , player ) ;
125111}
126112
@@ -130,48 +116,22 @@ function changeSelectedProperty(player: Player, block: Block) {
130116 * @param block
131117 */
132118function updateBlockProperty ( player : Player , block : Block ) {
133- const permutation = block . permutation ;
134- const states = permutation . getAllStates ( ) ;
119+ const states = getBlockStates ( block ) ;
135120 const names = Object . keys ( states ) ;
136-
137- if ( ! names . length /* && !block.type.canBeWaterlogged*/ )
121+ if ( ! names . length )
138122 return message ( `${ block . typeId } has no properties` , player ) ;
139-
140123 let prop = getCurrentProperty ( player , block . typeId ) ;
141124 let val : BlockStateValue ;
142-
143125 // Ensure that the recorded block property selection
144126 // is available on the block
145- /*if (prop == "waterlogged" ? !block.type.canBeWaterlogged : !names.includes(prop))*/
146127 if ( ! names . includes ( prop ) )
147128 prop = names [ 0 ] ;
148-
149- /*if (!prop && block.type.canBeWaterlogged)
150- prop = "waterlogged";*/
151-
152- // Update the property value
153- /*if (prop == "waterlogged") {
154- val = !block.isWaterlogged;
155- system.run(() => {
156- block.setWaterlogged(val as boolean);
157- });
158- }
159-
160- else {*/
161- const valids = BlockStates . get ( prop ) . validValues ;
162- val = valids [ valids . indexOf ( states [ prop ] ) + 1 ] ;
163-
164- if ( typeof val === "undefined" )
165- val = valids [ 0 ] ;
166-
167- system . run ( ( ) => {
168- block . setPermutation ( permutation . withState ( prop as keyof BlockStateSuperset , val ) ) ;
169- } ) ;
170- /*}*/
171-
172- // Avoid some edge cases bugs
129+ const valids = getStateValidValues ( prop ) ;
130+ val = valids [ valids . indexOf ( states [ prop ] ) + 1 ] ;
131+ if ( typeof val === "undefined" )
132+ val = valids [ 0 ] ;
133+ setBlockState ( block , prop , val ) ;
173134 setCurrentProperty ( player , block . typeId , prop ) ;
174-
175135 message ( `"${ prop } " to ${ val } ` , player ) ;
176136}
177137
@@ -182,38 +142,24 @@ function updateBlockProperty(player: Player, block: Block) {
182142 */
183143function displayBlockInfo ( player : Player , block : Block ) {
184144 let info = "§l§b" + block . typeId + "§r" ;
185-
186- // The block's coordinates
145+ // Basic info
187146 info += "\n§4" + block . x + " §a" + block . y + " §9" + block . z ;
188-
189- // Block's matter state
190147 info += "\n§7matter state§8: §e" ;
191- if ( block . isLiquid ) info += "liquid" ;
148+ if ( block . isLiquid ) info += "liquid" ;
192149 else if ( block . isAir ) info += "gas" ;
193- else info += "solid" ;
194-
195- // Whether the block is impassable
150+ else info += "solid" ;
196151 info += "\n§7hard block§8: " + ( block . isSolid ? "§ayes" : "§cno" ) ;
197-
198- // The block's emitted/recieved redstone power
199152 info += "\n§7redstone power§8: §c" + ( block . getRedstonePower ( ) ?? 0 ) ;
200-
201153 // The block states
202- Object . entries ( block . permutation . getAllStates ( ) ) . forEach ( ( [ k , v ] ) => {
154+ Object . entries ( getBlockStates ( block ) ) . forEach ( ( [ k , v ] ) => {
203155 info += "\n§o§7" + k + "§r§8: " ;
204156 if ( typeof v == "string" ) info += "§e" ;
205157 if ( typeof v == "number" ) info += "§3" ;
206158 if ( typeof v == "boolean" ) info += "§6" ;
207159 info += v ;
208160 } ) ;
209-
210- // Waterlog property if available
211- /*if (block.type.canBeWaterlogged)
212- info += "\n§o§7waterlogged§r§8: §6" + block.isWaterlogged;*/
213-
214161 // Additional block tags
215162 block . getTags ( ) . forEach ( v => info += "\n§d#" + v ) ;
216-
217163 message ( info , player ) ;
218164}
219165
@@ -271,6 +217,54 @@ function getPlayerByID(id: string): Player | undefined {
271217 . find ( v => v . id == id ) ;
272218}
273219
220+ /**
221+ * Returns all the block states of a block.
222+ * @param block The block.
223+ * @returns Record<string, BlockStateValue>
224+ */
225+ function getBlockStates ( block : Block ) : Record < string , BlockStateValue > {
226+ const states = block . permutation . getAllStates ( ) || { } ;
227+ if ( block . canContainLiquid ( LiquidType . Water ) )
228+ states [ "waterlogged" ] = block . isWaterlogged ;
229+ return states ;
230+ }
231+
232+ /**
233+ * Get the valid values of a block state.
234+ * @param state The block state.
235+ * @returns BlockStateValue[]
236+ */
237+ function getStateValidValues ( state : string ) : BlockStateValue [ ] {
238+ if ( state == "waterlogged" )
239+ return [ false , true ] ;
240+ return BlockStates . get ( state ) . validValues ;
241+ }
242+
243+ /**
244+ * Set a block's state.
245+ * @param block The block to modify.
246+ * @param state The state property to set.
247+ * @param val The value to set.
248+ * @returns Promise
249+ */
250+ function setBlockState ( block : Block , state : string , val : BlockStateValue ) : Promise < undefined > {
251+ return new Promise < undefined > ( ( res , rej ) => {
252+ system . run ( ( ) => {
253+ try {
254+ if ( state == "waterlogged" )
255+ block . setWaterlogged ( val as boolean ) ;
256+ else
257+ block . setPermutation ( block . permutation . withState (
258+ state as keyof BlockStateSuperset , val ) ) ;
259+ res ( undefined ) ;
260+ }
261+ catch ( e ) {
262+ rej ( e ) ;
263+ }
264+ } ) ;
265+ } ) ;
266+ }
267+
274268/**
275269 * Get the currently selected property for a block given the
276270 * interacting player
@@ -306,7 +300,7 @@ function setCurrentProperty(player: Player, block: string, val: string): void {
306300function safeCall < A extends any [ ] , R > (
307301 func : ( ...args : A ) => R ,
308302 ...args : A
309- ) : R {
303+ ) : R | undefined {
310304
311305 try {
312306 return func . apply ( { } , args ) ;
@@ -319,9 +313,22 @@ function safeCall<A extends any[], R>(
319313
320314 msg += e ;
321315
322- if ( e ?. stack )
316+ if ( e instanceof Error && e ?. stack )
323317 msg += `\n${ e . stack } ` ;
324318
325319 console . error ( msg ) ;
326320 }
327321}
322+
323+ /**
324+ * Safe call wrapper function.
325+ * @param func The function to wrap
326+ * @returns A function
327+ */
328+ function safeCallWrapper < A extends any [ ] , R > (
329+ func : ( ...args : A ) => R
330+ ) : ( ( ...args : A ) => R | undefined ) {
331+ return function ( ...args : A ) : R | undefined {
332+ return safeCall ( func , ...args ) ;
333+ }
334+ }
0 commit comments