1+ package net .countercraft .movecraft .compat .v1_21_10 ;
2+
3+ import ca .spottedleaf .moonrise .common .util .WorldUtil ;
4+ import net .countercraft .movecraft .MovecraftLocation ;
5+ import net .countercraft .movecraft .MovecraftRotation ;
6+ import net .countercraft .movecraft .WorldHandler ;
7+ import net .countercraft .movecraft .craft .Craft ;
8+ import net .countercraft .movecraft .util .CollectionUtils ;
9+ import net .countercraft .movecraft .util .MathUtils ;
10+ import net .countercraft .movecraft .util .UnsafeUtils ;
11+ import net .minecraft .core .BlockPos ;
12+ import net .minecraft .server .level .ServerLevel ;
13+ import net .minecraft .world .level .Level ;
14+ import net .minecraft .world .level .block .Block ;
15+ import net .minecraft .world .level .block .Blocks ;
16+ import net .minecraft .world .level .block .Rotation ;
17+ import net .minecraft .world .level .block .entity .BlockEntity ;
18+ import net .minecraft .world .level .block .piston .PistonBaseBlock ;
19+ import net .minecraft .world .level .block .piston .PistonMovingBlockEntity ;
20+ import net .minecraft .world .level .block .state .BlockState ;
21+ import net .minecraft .world .level .chunk .LevelChunk ;
22+ import net .minecraft .world .level .chunk .LevelChunkSection ;
23+ import net .minecraft .world .ticks .LevelChunkTicks ;
24+ import net .minecraft .world .ticks .ScheduledTick ;
25+ import org .bukkit .Bukkit ;
26+ import org .bukkit .Location ;
27+ import org .bukkit .block .data .BlockData ;
28+ import org .bukkit .craftbukkit .CraftWorld ;
29+ import org .bukkit .craftbukkit .block .data .CraftBlockData ;
30+ import org .jetbrains .annotations .NotNull ;
31+ import org .jetbrains .annotations .Nullable ;
32+
33+ import java .util .ArrayList ;
34+ import java .util .Collection ;
35+ import java .util .HashMap ;
36+ import java .util .List ;
37+ import java .util .Map ;
38+ import java .util .function .Predicate ;
39+
40+ @ SuppressWarnings ("unused" )
41+ public class IWorldHandler extends WorldHandler {
42+ private static final Rotation ROTATION [];
43+
44+ static {
45+ ROTATION = new Rotation [3 ];
46+ ROTATION [MovecraftRotation .NONE .ordinal ()] = Rotation .NONE ;
47+ ROTATION [MovecraftRotation .CLOCKWISE .ordinal ()] = Rotation .CLOCKWISE_90 ;
48+ ROTATION [MovecraftRotation .ANTICLOCKWISE .ordinal ()] = Rotation .COUNTERCLOCKWISE_90 ;
49+ }
50+
51+ private final NextTickProvider tickProvider = new NextTickProvider ();
52+
53+ public IWorldHandler () {
54+ String version = Bukkit .getServer ().getMinecraftVersion ();
55+ if (!version .equals ("1.21.10" ))
56+ throw new IllegalStateException ("Movecraft is not compatible with this version of Minecraft: " + version );
57+ }
58+
59+ @ Override
60+ public void rotateCraft (@ NotNull Craft craft , @ NotNull MovecraftLocation originPoint , @ NotNull MovecraftRotation rotation ) {
61+ //*******************************************
62+ //* Step one: Convert to Positions *
63+ //*******************************************
64+ HashMap <BlockPos , BlockPos > rotatedPositions = new HashMap <>();
65+ MovecraftRotation counterRotation = rotation == MovecraftRotation .CLOCKWISE ? MovecraftRotation .ANTICLOCKWISE : MovecraftRotation .CLOCKWISE ;
66+ for (MovecraftLocation newLocation : craft .getHitBox ()) {
67+ rotatedPositions .put (locationToPosition (MathUtils .rotateVec (counterRotation , newLocation .subtract (originPoint )).add (originPoint )), locationToPosition (newLocation ));
68+ }
69+ //*******************************************
70+ //* Step two: Get the tiles *
71+ //*******************************************
72+ ServerLevel nativeWorld = ((CraftWorld ) craft .getWorld ()).getHandle ();
73+ List <TileHolder > tiles = new ArrayList <>();
74+ List <TickHolder > ticks = new ArrayList <>();
75+ //get the tiles
76+ for (BlockPos position : rotatedPositions .keySet ()) {
77+ //BlockEntity tile = nativeWorld.removeBlockEntity(position);
78+ BlockEntity tile = removeBlockEntity (nativeWorld , position );
79+ if (tile != null )
80+ tiles .add (new TileHolder (tile , position ));
81+
82+ //get the nextTick to move with the tile
83+ ScheduledTick tickHere = tickProvider .getNextTick (nativeWorld , position );
84+ if (tickHere != null ) {
85+ ((LevelChunkTicks ) nativeWorld .getChunkAt (position ).getBlockTicks ()).removeIf (
86+ (Predicate <ScheduledTick >) scheduledTick -> scheduledTick .equals (tickHere ));
87+ ticks .add (new TickHolder (tickHere , position ));
88+ }
89+ }
90+
91+ //*******************************************
92+ //* Step three: Translate all the blocks *
93+ //*******************************************
94+ // blockedByWater=false means an ocean-going vessel
95+ //TODO: Simplify
96+ //TODO: go by chunks
97+ //TODO: Don't move unnecessary blocks
98+ //get the blocks and rotate them
99+ HashMap <BlockPos , BlockState > blockData = new HashMap <>();
100+ for (BlockPos position : rotatedPositions .keySet ()) {
101+ blockData .put (position , nativeWorld .getBlockState (position ).rotate (ROTATION [rotation .ordinal ()]));
102+ }
103+ //create the new block
104+ for (Map .Entry <BlockPos , BlockState > entry : blockData .entrySet ()) {
105+ setBlockFast (nativeWorld , rotatedPositions .get (entry .getKey ()), entry .getValue ());
106+ }
107+
108+
109+ //*******************************************
110+ //* Step four: replace all the tiles *
111+ //*******************************************
112+ //TODO: go by chunks
113+ for (TileHolder tileHolder : tiles )
114+ moveBlockEntity (nativeWorld , rotatedPositions .get (tileHolder .getTilePosition ()), tileHolder .getTile ());
115+ for (TickHolder tickHolder : ticks ) {
116+ final long currentTime = nativeWorld .serverLevelData .getGameTime ();
117+ nativeWorld .getBlockTicks ().schedule (new ScheduledTick <>(
118+ (Block ) tickHolder .getTick ().type (),
119+ rotatedPositions .get (tickHolder .getTick ().pos ()),
120+ tickHolder .getTick ().triggerTick () - currentTime ,
121+ tickHolder .getTick ().priority (),
122+ tickHolder .getTick ().subTickOrder ()));
123+ }
124+
125+ //*******************************************
126+ //* Step five: Destroy the leftovers *
127+ //*******************************************
128+ //TODO: add support for pass-through
129+ Collection <BlockPos > deletePositions = CollectionUtils .filter (rotatedPositions .keySet (), rotatedPositions .values ());
130+ for (BlockPos position : deletePositions ) {
131+ setBlockFast (nativeWorld , position , Blocks .AIR .defaultBlockState ());
132+ }
133+ }
134+
135+ @ Override
136+ public void translateCraft (@ NotNull Craft craft , @ NotNull MovecraftLocation displacement , @ NotNull org .bukkit .World world ) {
137+ //TODO: Add support for rotations
138+ //A craftTranslateCommand should only occur if the craft is moving to a valid position
139+ //*******************************************
140+ //* Step one: Convert to Positions *
141+ //*******************************************
142+ BlockPos translateVector = locationToPosition (displacement );
143+ List <BlockPos > positions = new ArrayList <>(craft .getHitBox ().size ());
144+ craft .getHitBox ().forEach ((movecraftLocation ) -> positions .add (locationToPosition ((movecraftLocation )).subtract (translateVector )));
145+ ServerLevel oldNativeWorld = ((CraftWorld ) craft .getWorld ()).getHandle ();
146+ ServerLevel nativeWorld = ((CraftWorld ) world ).getHandle ();
147+ //*******************************************
148+ //* Step two: Get the tiles *
149+ //*******************************************
150+ List <TileHolder > tiles = new ArrayList <>();
151+ List <TickHolder > ticks = new ArrayList <>();
152+ //get the tiles
153+ for (int i = 0 , positionsSize = positions .size (); i < positionsSize ; i ++) {
154+ BlockPos position = positions .get (i );
155+ if (oldNativeWorld .getBlockState (position ) == Blocks .AIR .defaultBlockState ())
156+ continue ;
157+ //BlockEntity tile = nativeWorld.removeBlockEntity(position);
158+ BlockEntity tile = removeBlockEntity (oldNativeWorld , position );
159+ if (tile != null )
160+ tiles .add (new TileHolder (tile ,position ));
161+
162+ //get the nextTick to move with the tile
163+ ScheduledTick tickHere = tickProvider .getNextTick (nativeWorld , position );
164+ if (tickHere != null ) {
165+ ((LevelChunkTicks ) nativeWorld .getChunkAt (position ).getBlockTicks ()).removeIf (
166+ (Predicate <ScheduledTick >) scheduledTick -> scheduledTick .equals (tickHere ));
167+ ticks .add (new TickHolder (tickHere , position ));
168+ }
169+ }
170+ //*******************************************
171+ //* Step three: Translate all the blocks *
172+ //*******************************************
173+ // blockedByWater=false means an ocean-going vessel
174+ //TODO: Simplify
175+ //TODO: go by chunks
176+ //TODO: Don't move unnecessary blocks
177+ //get the blocks and translate the positions
178+ List <BlockState > blockData = new ArrayList <>();
179+ List <BlockPos > newPositions = new ArrayList <>();
180+ for (int i = 0 , positionsSize = positions .size (); i < positionsSize ; i ++) {
181+ BlockPos position = positions .get (i );
182+ blockData .add (oldNativeWorld .getBlockState (position ));
183+ newPositions .add (position .offset (translateVector ));
184+ }
185+ //create the new block
186+ for (int i = 0 , positionSize = newPositions .size (); i < positionSize ; i ++) {
187+ setBlockFast (nativeWorld , newPositions .get (i ), blockData .get (i ));
188+ }
189+ //*******************************************
190+ //* Step four: replace all the tiles *
191+ //*******************************************
192+ //TODO: go by chunks
193+ for (TileHolder tileHolder : tiles )
194+ moveBlockEntity (nativeWorld , tileHolder .getTilePosition ().offset (translateVector ), tileHolder .getTile ());
195+ for (TickHolder tickHolder : ticks ) {
196+ final long currentTime = nativeWorld .getGameTime ();
197+ nativeWorld .getBlockTicks ().schedule (new ScheduledTick <>((Block ) tickHolder .getTick ().type (), tickHolder .getTickPosition ().offset (translateVector ), tickHolder .getTick ().triggerTick () - currentTime , tickHolder .getTick ().priority (), tickHolder .getTick ().subTickOrder ()));
198+ }
199+ //*******************************************
200+ //* Step five: Destroy the leftovers *
201+ //*******************************************
202+ List <BlockPos > deletePositions = positions ;
203+ if (oldNativeWorld == nativeWorld )
204+ deletePositions = CollectionUtils .filter (positions , newPositions );
205+ for (int i = 0 , deletePositionsSize = deletePositions .size (); i < deletePositionsSize ; i ++) {
206+ BlockPos position = deletePositions .get (i );
207+ setBlockFast (oldNativeWorld , position , Blocks .AIR .defaultBlockState ());
208+ }
209+ }
210+
211+ @ Nullable
212+ private BlockEntity removeBlockEntity (@ NotNull Level world , @ NotNull BlockPos position ) {
213+ BlockEntity testEntity = world .getChunkAt (position ).getBlockEntity (position );
214+ //Prevents moving pistons by locking up by forcing their movement to finish
215+ if (testEntity instanceof PistonMovingBlockEntity )
216+ {
217+ BlockState oldState ;
218+ if (((PistonMovingBlockEntity ) testEntity ).isSourcePiston () && testEntity .getBlockState ().getBlock () instanceof PistonBaseBlock ) {
219+ if (((PistonMovingBlockEntity ) testEntity ).getMovedState ().is (Blocks .PISTON ))
220+ oldState = Blocks .PISTON .defaultBlockState ()
221+ .setValue (PistonBaseBlock .FACING , ((PistonMovingBlockEntity ) testEntity ).getMovedState ().getValue (PistonBaseBlock .FACING ));
222+ else
223+ oldState = Blocks .STICKY_PISTON .defaultBlockState ()
224+ .setValue (PistonBaseBlock .FACING , ((PistonMovingBlockEntity ) testEntity ).getMovedState ().getValue (PistonBaseBlock .FACING ));
225+ } else
226+ oldState = ((PistonMovingBlockEntity ) testEntity ).getMovedState ();
227+ ((PistonMovingBlockEntity ) testEntity ).finalTick ();
228+ setBlockFast (world , position , oldState );
229+ return world .getBlockEntity (position );
230+ }
231+ return world .getChunkAt (position ).blockEntities .remove (position );
232+ }
233+
234+ @ NotNull
235+ private BlockPos locationToPosition (@ NotNull MovecraftLocation loc ) {
236+ return new BlockPos (loc .getX (), loc .getY (), loc .getZ ());
237+ }
238+
239+ private void setBlockFast (@ NotNull Level world , @ NotNull BlockPos position , @ NotNull BlockState data ) {
240+ LevelChunk chunk = world .getChunkAt (position );
241+ int chunkSection = (position .getY () >> 4 ) - WorldUtil .getMinSection (world );
242+ LevelChunkSection section = chunk .getSections ()[chunkSection ];
243+ if (section == null ) {
244+ // Put a GLASS block to initialize the section. It will be replaced next with the real block.
245+ chunk .setBlockState (position , Blocks .GLASS .defaultBlockState (), 0 );
246+ section = chunk .getSections ()[chunkSection ];
247+ }
248+ if (section .getBlockState (position .getX () & 15 , position .getY () & 15 , position .getZ () & 15 ).equals (data )) {
249+ //Block is already of correct type and data, don't overwrite
250+ return ;
251+ }
252+ section .setBlockState (position .getX () & 15 , position .getY () & 15 , position .getZ () & 15 , data );
253+ world .sendBlockUpdated (position , data , data , 3 );
254+ world .getLightEngine ().checkBlock (position ); // boolean corresponds to if chunk section empty
255+ chunk .markUnsaved ();
256+ }
257+
258+ @ Override
259+ public void setBlockFast (@ NotNull Location location , @ NotNull BlockData data ) {
260+ setBlockFast (location , MovecraftRotation .NONE , data );
261+ }
262+
263+ @ Override
264+ public void setBlockFast (@ NotNull Location location , @ NotNull MovecraftRotation rotation , @ NotNull BlockData data ) {
265+ BlockState blockData ;
266+ if (data instanceof CraftBlockData ) {
267+ blockData = ((CraftBlockData ) data ).getState ();
268+ }
269+ else {
270+ blockData = (BlockState ) data ;
271+ }
272+ blockData = blockData .rotate (ROTATION [rotation .ordinal ()]);
273+ Level world = ((CraftWorld ) (location .getWorld ())).getHandle ();
274+ BlockPos BlockPos = locationToPosition (MathUtils .bukkit2MovecraftLoc (location ));
275+ setBlockFast (world , BlockPos , blockData );
276+ }
277+
278+ private void moveBlockEntity (@ NotNull Level nativeWorld , @ NotNull BlockPos newPosition , @ NotNull BlockEntity tile ) {
279+ LevelChunk chunk = nativeWorld .getChunkAt (newPosition );
280+ try {
281+ var positionField = BlockEntity .class .getDeclaredField ("o" ); // o is obfuscated worldPosition
282+ UnsafeUtils .setField (positionField , tile , newPosition );
283+ }
284+ catch (NoSuchFieldException e ) {
285+ e .printStackTrace ();
286+ }
287+ tile .setLevel (nativeWorld );
288+ tile .clearRemoved ();
289+ if (nativeWorld .captureBlockStates ) {
290+ nativeWorld .capturedTileEntities .put (newPosition , tile );
291+ return ;
292+ }
293+ chunk .setBlockEntity (tile );
294+ chunk .blockEntities .put (newPosition , tile );
295+ }
296+
297+ private static class TileHolder {
298+ @ NotNull
299+ private final BlockEntity tile ;
300+ @ NotNull
301+ private final BlockPos tilePosition ;
302+
303+ public TileHolder (@ NotNull BlockEntity tile , @ NotNull BlockPos tilePosition ) {
304+ this .tile = tile ;
305+ this .tilePosition = tilePosition ;
306+ }
307+
308+
309+ @ NotNull
310+ public BlockEntity getTile () {
311+ return tile ;
312+ }
313+
314+ @ NotNull
315+ public BlockPos getTilePosition () {
316+ return tilePosition ;
317+ }
318+ }
319+
320+ private static class TickHolder {
321+ @ NotNull
322+ private final ScheduledTick tick ;
323+ @ NotNull
324+ private final BlockPos tickPosition ;
325+
326+ public TickHolder (@ NotNull ScheduledTick tick , @ NotNull BlockPos tilePosition ) {
327+ this .tick = tick ;
328+ this .tickPosition = tilePosition ;
329+ }
330+
331+
332+ @ NotNull
333+ public ScheduledTick getTick () {
334+ return tick ;
335+ }
336+
337+ @ NotNull
338+ public BlockPos getTickPosition () {
339+ return tickPosition ;
340+ }
341+ }
342+ }
0 commit comments