Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.24.0</build.version>
<build.version>1.25.0</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,12 +527,18 @@ private void processNextBlock(Cancellable e, Island i, Player player, Block bloc
* @param nextBlock - next block object containing entity info
*/
private void handleEntitySpawn(Cancellable e, Island i, Player player, Block block, OneBlockObject nextBlock) {
if (!(e instanceof EntitySpawnEvent)) {
boolean cancelled = !(e instanceof EntitySpawnEvent);
if (cancelled) {
e.setCancelled(true);
}
spawnEntity(nextBlock, block);
// Cancelling BlockBreakEvent at HIGHEST priority leaves the client with a
// mispredicted "block gone" state; resync the actual block to clear it.
if (cancelled && player != null) {
player.sendBlockChange(block.getLocation(), block.getBlockData());
}
Bukkit.getPluginManager().callEvent(new MagicBlockEntityEvent(i,
player == null ? null : player.getUniqueId(),
player == null ? null : player.getUniqueId(),
block, nextBlock.getEntityType()));
}

Expand Down Expand Up @@ -886,7 +892,14 @@ private void fillChest(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Color color = addon.getSettings().resolveChestColor(rarity);
Object particleData = Particle.DUST.equals(particle) ? new Particle.DustOptions(color, 1) : null;
Object particleData = null;
if (Particle.DUST.equals(particle)) {
particleData = new Particle.DustOptions(color, 1);
} else if (!Void.class.equals(particle.getDataType())) {
// Particle requires typed data we can't provide — skip rather than crash
addon.logWarning("Chest particle " + particle.name() + " requires extra data and cannot be used. Use DUST or a void-data particle.");
return;
}
block.getWorld().spawnParticle(particle, block.getLocation().add(new Vector(0.5, 1.0, 0.5)), 50, 0.5,
0, 0.5, 1, particleData);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package world.bentobox.aoneblock.oneblocks.customblock;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import org.bukkit.Material;
Expand All @@ -26,10 +25,29 @@ public static Optional<CraftEngineCustomBlock> fromId(String id) {
return Optional.empty();
}

/**
* Checks whether {@code id} is a syntactically valid namespaced key
* ({@code namespace:key} with both parts non-blank).
*/
static boolean isValidNamespacedKey(String id) {
int colon = id.indexOf(':');
if (colon <= 0 || colon == id.length() - 1) {
return false;
}
String namespace = id.substring(0, colon);
String key = id.substring(colon + 1);
return !namespace.isBlank() && !key.isBlank();
}

public static Optional<CraftEngineCustomBlock> fromMap(Map<?, ?> map) {
return Optional
.ofNullable(Objects.toString(map.get("id"), null))
.flatMap(CraftEngineCustomBlock::fromId);
Object raw = map.get("id");
if (!(raw instanceof String id)) {
return Optional.empty();
}
if (id.isBlank() || !isValidNamespacedKey(id)) {
return Optional.empty();
}
return Optional.of(new CraftEngineCustomBlock(id));
Comment thread
tastybento marked this conversation as resolved.
}

@Override
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/phases/8500_plenty.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
DIRT_PATH: 1
LIGHT_GRAY_TERRACOTTA: 1
SUSPICIOUS_SAND: 1
custom-blocks:
- type: block
data: 'minecraft:bee_nest[honey_level=0,facing=north]{bees:[{entity_data:{id:"minecraft:bee"},min_ticks_in_hive:600,ticks_in_hive:0},{entity_data:{id:"minecraft:bee"},min_ticks_in_hive:600,ticks_in_hive:0},{entity_data:{id:"minecraft:bee"},min_ticks_in_hive:600,ticks_in_hive:0}]}'
probability: 1
mobs:
COW: 1
DONKEY: 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package world.bentobox.aoneblock.oneblocks.customblock;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.LinkedHashMap;
Expand Down Expand Up @@ -27,4 +29,122 @@

assertTrue(result.isEmpty(), "Should return empty when 'id' is missing");
}

@Test
void fromMapReturnsEmptyWhenIdIsNull() {

Check warning on line 34 in src/test/java/world/bentobox/aoneblock/oneblocks/customblock/CraftEngineCustomBlockTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace these 5 tests with a single Parameterized one.

See more on https://sonarcloud.io/project/issues?id=BentoBoxWorld_AOneBlock&issues=AZ3r270BNwJKsMcWfCTZ&open=AZ3r270BNwJKsMcWfCTZ&pullRequest=515
Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "craftengine");
map.put("id", null);

var result = CraftEngineCustomBlock.fromMap(map);

assertTrue(result.isEmpty(), "Should return empty when 'id' is null");
}

@Test
void fromMapReturnsEmptyWhenIdIsBlank() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "craftengine");
map.put("id", " ");

var result = CraftEngineCustomBlock.fromMap(map);

assertTrue(result.isEmpty(), "Should return empty when 'id' is blank");
}

@Test
void fromMapReturnsEmptyWhenIdIsNonStringValue() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "craftengine");
map.put("id", 42); // integer, not a String

var result = CraftEngineCustomBlock.fromMap(map);

assertTrue(result.isEmpty(), "Should return empty when 'id' is not a String");
}

@Test
void fromMapReturnsEmptyWhenIdHasNoColon() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "craftengine");
map.put("id", "nocolon");

var result = CraftEngineCustomBlock.fromMap(map);

assertTrue(result.isEmpty(), "Should return empty when 'id' has no colon");
}

@Test
void fromMapReturnsEmptyWhenIdHasEmptyNamespace() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "craftengine");
map.put("id", ":key");

var result = CraftEngineCustomBlock.fromMap(map);

assertTrue(result.isEmpty(), "Should return empty when namespace part is empty");
}

@Test
void fromMapReturnsEmptyWhenIdHasEmptyKey() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "craftengine");
map.put("id", "namespace:");

var result = CraftEngineCustomBlock.fromMap(map);

assertTrue(result.isEmpty(), "Should return empty when key part is empty");
}

/**
* {@code fromMap} must succeed even when CraftEngine has not yet loaded its
* block registry (i.e. without calling {@code CraftEngineHook.exists}).
* This prevents false "Bad custom block" errors during the initial server
* start-up phase that occurs before CraftEngine fires its reload event.
*/
@Test
void fromMapReturnsPresentWhenIdProvided() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "craftengine");
map.put("id", "oneblock:common_loot_block");
map.put("probability", 300);

var result = CraftEngineCustomBlock.fromMap(map);

assertTrue(result.isPresent(), "Should return a block when 'id' is present, regardless of CraftEngine load state");
assertInstanceOf(CraftEngineCustomBlock.class, result.get());
}

// --- isValidNamespacedKey helper tests ---

@Test
void isValidNamespacedKeyReturnsTrueForValidKey() {
assertTrue(CraftEngineCustomBlock.isValidNamespacedKey("ns:key"));
assertTrue(CraftEngineCustomBlock.isValidNamespacedKey("oneblock:common_loot_block"));
}

@Test
void isValidNamespacedKeyReturnsFalseForMissingColon() {
assertFalse(CraftEngineCustomBlock.isValidNamespacedKey("nocolon"));
}

@Test
void isValidNamespacedKeyReturnsFalseForLeadingColon() {
assertFalse(CraftEngineCustomBlock.isValidNamespacedKey(":key"));
}

@Test
void isValidNamespacedKeyReturnsFalseForTrailingColon() {
assertFalse(CraftEngineCustomBlock.isValidNamespacedKey("ns:"));
}

@Test
void isValidNamespacedKeyReturnsFalseForBlankNamespace() {
assertFalse(CraftEngineCustomBlock.isValidNamespacedKey(" :key"));
}

@Test
void isValidNamespacedKeyReturnsFalseForBlankKey() {
assertFalse(CraftEngineCustomBlock.isValidNamespacedKey("ns: "));
}
}
Loading