11package org .milkteamc .autotreechop .utils ;
22
33import com .cryptomorin .xseries .XMaterial ;
4+ import java .util .Set ;
45import org .bukkit .Bukkit ;
56import org .bukkit .Location ;
67import org .bukkit .Material ;
8+ import org .bukkit .World ;
79import org .bukkit .block .Block ;
810import org .bukkit .block .BlockFace ;
911import org .bukkit .entity .Player ;
@@ -33,7 +35,8 @@ public static void scheduleReplant(
3335 LandsHook landsHook ,
3436 ResidenceHook residenceHook ,
3537 GriefPreventionHook griefPreventionHook ,
36- WorldGuardHook worldGuardHook ) {
38+ WorldGuardHook worldGuardHook ,
39+ Set <Location > choppedLogs ) {
3740
3841 if (!config .isAutoReplantEnabled ()) {
3942 return ;
@@ -45,7 +48,7 @@ public static void scheduleReplant(
4548 }
4649
4750 Location originalLocation = brokenLogBlock .getLocation ().clone ();
48- boolean needs2x2 = requires2x2Formation (originalLogType );
51+ boolean needs2x2 = isLikely2x2Tree (originalLogType , originalLocation , choppedLogs );
4952
5053 Runnable replantTask = () -> {
5154 if (needs2x2 ) {
@@ -110,11 +113,67 @@ public static void scheduleReplant(
110113 }
111114
112115 /**
113- * Returns true for tree types that require a 2x2 sapling formation to grow.
116+ * Determines whether the chopped tree should be replanted as a 2x2 sapling
117+ * formation.
118+ *
119+ * <p>Dark Oak and Pale Oak are always 2x2. Spruce and Jungle are 2x2 only when
120+ * the base of the chopped tree contained four logs arranged in a 2x2 square —
121+ * detected by scanning the chopped-log set for a matching pattern at the Y
122+ * level of the lowest broken log. All other tree types are always single.
114123 */
115- private static boolean requires2x2Formation (Material logType ) {
124+ private static boolean isLikely2x2Tree (Material logType , Location lowestLogLocation , Set <Location > choppedLogs ) {
125+
116126 XMaterial xMat = XMaterial .matchXMaterial (logType );
117- return xMat == XMaterial .DARK_OAK_LOG || xMat == XMaterial .PALE_OAK_LOG ;
127+
128+ // Dark Oak and Pale Oak are always planted as 2x2
129+ if (xMat == XMaterial .DARK_OAK_LOG || xMat == XMaterial .PALE_OAK_LOG ) {
130+ return true ;
131+ }
132+
133+ // Only Spruce and Jungle can be big (2x2) trees — everything else is always single
134+ if (xMat != XMaterial .SPRUCE_LOG && xMat != XMaterial .JUNGLE_LOG ) {
135+ return false ;
136+ }
137+
138+ // Detect 2x2 by checking whether four logs of this type form a square at
139+ // the base Y level among the actually-chopped blocks.
140+ int baseY = lowestLogLocation .getBlockY ();
141+ int baseX = lowestLogLocation .getBlockX ();
142+ int baseZ = lowestLogLocation .getBlockZ ();
143+ World world = lowestLogLocation .getWorld ();
144+
145+ // Try all four possible 2x2 anchors that include the base-log position as a corner
146+ int [][] candidateAnchors = {{0 , 0 }, {-1 , 0 }, {0 , -1 }, {-1 , -1 }};
147+ for (int [] ao : candidateAnchors ) {
148+ int ax = baseX + ao [0 ];
149+ int az = baseZ + ao [1 ];
150+ boolean all4Present = true ;
151+ for (int [] offset : FORMATION_2X2 ) {
152+ if (!containsBlockLocation (choppedLogs , world , ax + offset [0 ], baseY , az + offset [1 ])) {
153+ all4Present = false ;
154+ break ;
155+ }
156+ }
157+ if (all4Present ) {
158+ return true ;
159+ }
160+ }
161+
162+ return false ;
163+ }
164+
165+ /**
166+ * Returns {@code true} if {@code locations} contains a block-coordinate match
167+ * for the given world and integer coordinates. Uses integer comparison to avoid
168+ * floating-point or yaw/pitch equality issues.
169+ */
170+ private static boolean containsBlockLocation (Set <Location > locations , World world , int x , int y , int z ) {
171+ for (Location loc : locations ) {
172+ if (loc .getWorld () == world && loc .getBlockX () == x && loc .getBlockY () == y && loc .getBlockZ () == z ) {
173+ return true ;
174+ }
175+ }
176+ return false ;
118177 }
119178
120179 /**
0 commit comments