@@ -353,30 +353,87 @@ private String stripPrefixIfPresent(String n) {
353353 }
354354
355355 /**
356- * Find an online player matching the provided name in either: - exact form
357- * (case-insensitive), OR - prefixed variant, OR - prefix-stripped comparison
358- * (online ".name" matches input "name")
356+ * Find an online player matching the provided name in either:
357+ * <ul>
358+ * <li>Exact form (case-insensitive)</li>
359+ * <li>Prefixed variant (case-insensitive)</li>
360+ * <li>Prefix-stripped comparison (e.g. ".DarkAshley" matches "darkashley")</li>
361+ * </ul>
362+ *
363+ * <p>
364+ * IMPORTANT: If both a Java and Bedrock player effectively match the same
365+ * unprefixed input (e.g. "Name" and ".Name" are both online), this method will
366+ * deterministically prefer the Java player to avoid incorrectly forcing Bedrock
367+ * resolution due to non-deterministic iteration order.
368+ * </p>
369+ *
370+ * @param name incoming player name
371+ * @return matching online {@link Player} or null
359372 */
360373 private Player findOnlineByNameOrStripped (String name ) {
361- if (name == null || name .isEmpty ())
374+ if (name == null || name .isEmpty ()) {
362375 return null ;
376+ }
363377
364378 final String lower = name .toLowerCase (Locale .ROOT );
365379 final String prefixed = buildPrefixedVariant (name );
366380
381+ // 1) Exact match first (deterministic preference)
367382 for (Player p : Bukkit .getOnlinePlayers ()) {
368383 final String pn = p .getName ();
369- if (pn .equalsIgnoreCase (name ))
384+ if (pn != null && pn .equalsIgnoreCase (name )) {
370385 return p ;
386+ }
387+ }
371388
372- if (prefixed != null && pn .equalsIgnoreCase (prefixed ))
373- return p ;
389+ // 2) Prefixed variant match next (if incoming was unprefixed)
390+ if (prefixed != null ) {
391+ for (Player p : Bukkit .getOnlinePlayers ()) {
392+ final String pn = p .getName ();
393+ if (pn != null && pn .equalsIgnoreCase (prefixed )) {
394+ return p ;
395+ }
396+ }
397+ }
374398
375- // ".DarkAshley" should match "darkashley"
376- if (stripPrefixIfPresent (pn ).toLowerCase (Locale .ROOT ).equals (lower ))
377- return p ;
399+ // 3) Prefix-stripped match last
400+ // If multiple players match after stripping, prefer Java over Bedrock.
401+ Player bedrockCandidate = null ;
402+ Player javaCandidate = null ;
403+
404+ for (Player p : Bukkit .getOnlinePlayers ()) {
405+ final String pn = p .getName ();
406+ if (pn == null ) {
407+ continue ;
408+ }
409+
410+ final String strippedLower = stripPrefixIfPresent (pn ).toLowerCase (Locale .ROOT );
411+ if (!strippedLower .equals (lower )) {
412+ continue ;
413+ }
414+
415+ // Decide preference using UUID-based bedrock detection (authoritative online)
416+ boolean isBedrock = false ;
417+ try {
418+ isBedrock = bedrockDetect .isBedrock (p .getUniqueId ());
419+ } catch (Throwable ignored ) {
420+ // If detection fails, treat as unknown; prefer as java to avoid
421+ // incorrectly forcing bedrock.
422+ isBedrock = false ;
423+ }
424+
425+ if (isBedrock ) {
426+ if (bedrockCandidate == null ) {
427+ bedrockCandidate = p ;
428+ }
429+ } else {
430+ // Java (or unknown) wins immediately
431+ javaCandidate = p ;
432+ break ;
433+ }
378434 }
379- return null ;
435+
436+ return (javaCandidate != null ) ? javaCandidate : bedrockCandidate ;
380437 }
381438
382439 public static final class Result {
0 commit comments