11package wearblackallday .dimthread .mixin ;
22
3+ import net .minecraft .block .BlockState ;
34import net .minecraft .entity .Entity ;
4- import net .minecraft .entity .FallingBlockEntity ;
5+ import net .minecraft .entity .EntityDimensions ;
6+ import net .minecraft .entity .EntityPose ;
7+ import net .minecraft .nbt .NbtCompound ;
8+ import net .minecraft .server .MinecraftServer ;
59import net .minecraft .server .world .ServerWorld ;
10+ import net .minecraft .state .property .Properties ;
11+ import net .minecraft .util .math .BlockPos ;
12+ import net .minecraft .util .math .Direction ;
13+ import net .minecraft .util .math .Vec3d ;
14+ import net .minecraft .world .BlockLocating ;
15+ import net .minecraft .world .Heightmap ;
16+ import net .minecraft .world .TeleportTarget ;
17+ import net .minecraft .world .World ;
18+ import net .minecraft .world .border .WorldBorder ;
19+ import net .minecraft .world .dimension .AreaHelper ;
20+ import net .minecraft .world .dimension .DimensionType ;
21+ import org .jetbrains .annotations .NotNull ;
22+ import org .jetbrains .annotations .Nullable ;
23+ import org .slf4j .Logger ;
624import org .spongepowered .asm .mixin .Final ;
725import org .spongepowered .asm .mixin .Mixin ;
826import org .spongepowered .asm .mixin .Shadow ;
927import org .spongepowered .asm .mixin .injection .At ;
1028import org .spongepowered .asm .mixin .injection .Inject ;
29+ import org .spongepowered .asm .mixin .injection .Redirect ;
1130import org .spongepowered .asm .mixin .injection .callback .CallbackInfo ;
1231import org .spongepowered .asm .mixin .injection .callback .CallbackInfoReturnable ;
1332import wearblackallday .dimthread .DimThread ;
33+ import wearblackallday .dimthread .util .UncompletedTeleportTarget ;
1434
15- import org . slf4j . LoggerFactory ;
35+ import java . util . Optional ;
1636
1737@ Mixin (Entity .class )
18- public abstract class EntityMixin implements Cloneable {
38+ public abstract class EntityMixin {
1939
20- public boolean isCloned = false ;
21-
22- @ Shadow @ Final
23- abstract void setRemoved (Entity .RemovalReason reason );
40+ private NbtCompound nbtCachedForMoveToWorld ;
2441
42+ private UncompletedTeleportTarget uncompletedTeleportTargetForMoveToWorld ;
2543 @ Shadow
26- abstract void removeFromDimension ();
44+ protected abstract void removeFromDimension ();
45+
46+
47+ @ Shadow public abstract World getWorld ();
48+
49+ @ Shadow public abstract NbtCompound writeNbt (NbtCompound nbt );
50+
51+ @ Shadow public abstract @ Nullable Entity moveToWorld (ServerWorld destination );
52+
53+ @ Shadow private int netherPortalCooldown ;
54+
55+ @ Shadow protected BlockPos lastNetherPortalPosition ;
56+
57+ @ Shadow public abstract boolean isRemoved ();
58+
59+ @ Shadow @ Nullable public abstract MinecraftServer getServer ();
60+
61+ @ Shadow public World world ;
62+
63+ @ Shadow public abstract double getX ();
64+
65+ @ Shadow public abstract double getY ();
66+
67+ @ Shadow public abstract double getZ ();
68+
69+ @ Shadow protected abstract Optional <BlockLocating .Rectangle > getPortalRect (ServerWorld destWorld , BlockPos destPos , boolean destIsNether , WorldBorder worldBorder );
70+
71+ @ Shadow protected abstract Vec3d positionInPortal (Direction .Axis portalAxis , BlockLocating .Rectangle portalRect );
72+
73+ @ Shadow public abstract EntityDimensions getDimensions (EntityPose pose );
74+
75+ @ Shadow public abstract EntityPose getPose ();
76+
77+ @ Shadow public abstract Vec3d getVelocity ();
78+
79+ @ Shadow public abstract float getYaw ();
80+
81+ @ Shadow public abstract float getPitch ();
82+
83+ @ Shadow protected abstract @ Nullable TeleportTarget getTeleportTarget (ServerWorld destination );
84+
85+ @ Shadow protected abstract void unsetRemoved ();
86+
87+ @ Shadow public abstract void readNbt (NbtCompound nbt );
88+
89+ @ Shadow @ Final private static Logger LOGGER ;
2790
2891 /**
2992 * Schedules moving entities between dimensions to the server thread. Once all
@@ -35,40 +98,113 @@ public abstract class EntityMixin implements Cloneable {
3598 * another thread will cause a deadlock in the server chunk manager.
3699 */
37100 @ Inject (method = "moveToWorld" , at = @ At ("HEAD" ), cancellable = true )
38- public void moveToWorld (ServerWorld destination , CallbackInfoReturnable <Entity > ci ) {
101+ public void onMoveToWorld (ServerWorld destination , CallbackInfoReturnable <Entity > ci ) {
39102 if (!DimThread .MANAGER .isActive (destination .getServer ()))
40103 return ;
41104
42105 if (DimThread .owns (Thread .currentThread ())) {
43- Entity snapshot = null ;
44- try {
45- snapshot = (Entity ) (this .clone ());
46- } catch (CloneNotSupportedException e ) {
47- throw new RuntimeException (e );
48- }
49- final Entity finalSnapshot = snapshot ;
106+ nbtCachedForMoveToWorld = writeNbt (new NbtCompound ());
107+ uncompletedTeleportTargetForMoveToWorld = createTeleportTargetUncompleted (destination );
50108 destination .getServer ().execute (
51- () -> finalSnapshot .moveToWorld (destination )
109+ () -> {
110+ Entity entity = this .moveToWorld (destination );
111+ if (entity == null ) {
112+ this .unsetRemoved ();
113+ nbtCachedForMoveToWorld .putInt ("PortalCooldown" , this .netherPortalCooldown );
114+ this .readNbt (nbtCachedForMoveToWorld );
115+ this .uncompletedTeleportTargetForMoveToWorld = null ;
116+ this .nbtCachedForMoveToWorld = null ;
117+ this .world .spawnEntity ((Entity ) (Object ) this );// if the teleporting failed, we need to add it back to the world
118+ LOGGER .debug ("Failed to teleport {}, return it to its previous world" , this );
119+ }
120+ }
52121 );
53122 this .removeFromDimension ();
54123 ci .setReturnValue (null );
55124 }
56125 }
57126
58127 /**
59- * @author xiaoyu2006
60- * @reason If this is a cloned entity, it should not execute removeFromDimension.
128+ * Perform deep copy instead of clone() to fix the bug that handed item disappear while teleporting
129+ */
130+ @ Redirect (method = "moveToWorld" , at = @ At (value = "INVOKE" , target = "Lnet/minecraft/entity/Entity;copyFrom(Lnet/minecraft/entity/Entity;)V" ))
131+ private void onMoveToWorldCopyFrom (Entity instance , Entity original ) {
132+ if (DimThread .MANAGER .isActive (getServer ())) {
133+ NbtCompound nbtCompound = ((EntityMixin ) (Object ) original ).nbtCachedForMoveToWorld ;
134+ nbtCompound .remove ("Dimension" );
135+ instance .readNbt (nbtCompound );
136+ ((EntityMixin ) (Object ) instance ).netherPortalCooldown = ((EntityMixin ) (Object ) original ).netherPortalCooldown ;
137+ ((EntityMixin ) (Object ) instance ).lastNetherPortalPosition = ((EntityMixin ) (Object ) original ).lastNetherPortalPosition ;
138+ } else {
139+ instance .copyFrom (original );
140+ }
141+ }
142+
143+ /**
144+ * We have to use the data we cached when moveToWorld is called
145+ * It's getting modified later
61146 */
147+ @ Redirect (method = "moveToWorld" , at = @ At (value = "INVOKE" , target = "Lnet/minecraft/entity/Entity;getTeleportTarget(Lnet/minecraft/server/world/ServerWorld;)Lnet/minecraft/world/TeleportTarget;" ))
148+ private TeleportTarget onMoveToWorldGetTeleportTarget (@ NotNull Entity instance , ServerWorld destination ) {
149+ EntityMixin ins = ((EntityMixin ) (Object ) instance );
150+ if (DimThread .MANAGER .isActive (getServer ())) {
151+ return ins .uncompletedTeleportTargetForMoveToWorld == null ? null : ins .uncompletedTeleportTargetForMoveToWorld .complete (destination );
152+ } else {
153+ return ins .getTeleportTarget (destination );
154+ }
155+ }
156+
62157 @ Inject (method = "removeFromDimension" , at = @ At ("HEAD" ), cancellable = true )
63- public void clonedDoNotRemove (CallbackInfo ci ) {
64- if ( this . isCloned ) {
158+ private void onRemoveFromDimension (CallbackInfo ci ) {
159+ if ( isRemoved () ) {
65160 ci .cancel ();
66161 }
67162 }
68163
69- protected Object clone () throws CloneNotSupportedException {
70- EntityMixin cloned = (EntityMixin ) super .clone ();
71- cloned .isCloned = true ;
72- return cloned ;
164+ /**
165+ * We check this because when we call this method in getServer().execute(), the entity has already been removed
166+ */
167+ @ Redirect (method = "moveToWorld" , at = @ At (value = "INVOKE" , target = "Lnet/minecraft/entity/Entity;isRemoved()Z" ))
168+ private boolean onMoveToWorldIsRemoved (Entity instance ) {
169+ return instance .isRemoved () && ((EntityMixin ) (Object ) instance ).nbtCachedForMoveToWorld == null ;
170+ }
171+
172+ /**
173+ * take a snapshot of some values so that codes modified it later doesn't affect teleporting
174+ */
175+ private UncompletedTeleportTarget createTeleportTargetUncompleted (ServerWorld dest ) {
176+ boolean isEndReturnPortal = world .getRegistryKey () == World .END && dest .getRegistryKey () == World .OVERWORLD ;
177+ boolean isEndPortal = dest .getRegistryKey () == World .END ;
178+ Vec3d velocity = getVelocity ();
179+ float yaw = getYaw (), pitch = getPitch ();
180+ if (!isEndPortal && !isEndReturnPortal ) {
181+ boolean isNetherPortal = dest .getRegistryKey () == World .NETHER ;
182+ boolean isNetherReturnPortal = world .getRegistryKey () == World .NETHER ;
183+ if (!isNetherPortal && !isNetherReturnPortal ) {
184+ return dest1 -> null ;
185+ } else {
186+ WorldBorder border = dest .getWorldBorder ();
187+ double scale = DimensionType .getCoordinateScaleFactor (world .getDimension (), dest .getDimension ());
188+ BlockPos target = border .clamp (getX () * scale , getY (), getZ () * scale );
189+ BlockState portalState = world .getBlockState (lastNetherPortalPosition );
190+ Direction .Axis axis ;
191+ Vec3d vec3d ;
192+ EntityDimensions dimensions = getDimensions (getPose ());
193+ if (portalState .contains (Properties .HORIZONTAL_AXIS )) {
194+ axis = portalState .get (Properties .HORIZONTAL_AXIS );
195+ BlockLocating .Rectangle rectangle = BlockLocating .getLargestRectangle (this .lastNetherPortalPosition , axis , 21 , Direction .Axis .Y , 21 , (blockPos ) -> this .world .getBlockState (blockPos ) == portalState );
196+ vec3d = this .positionInPortal (axis , rectangle );
197+ } else {
198+ axis = Direction .Axis .X ;
199+ vec3d = new Vec3d (0.5 , 0.0 , 0.0 );
200+ }
201+ return dest1 -> getPortalRect (dest1 , target , isNetherPortal , border ).map ((rect ) -> AreaHelper .getNetherTeleportTarget (dest1 , rect , axis , vec3d , dimensions , velocity , yaw , pitch )).orElse (null );
202+ }
203+ } else {
204+ return dest1 -> {
205+ BlockPos target = isEndPortal ? ServerWorld .END_SPAWN_POS : dest1 .getTopPosition (Heightmap .Type .MOTION_BLOCKING_NO_LEAVES , dest1 .getSpawnPos ());
206+ return new TeleportTarget (new Vec3d (target .getX () + 0.5 , target .getY (), target .getZ () + 0.5 ), velocity , yaw , pitch );
207+ };
208+ }
73209 }
74210}
0 commit comments