Skip to content

Commit

Permalink
"spawnable" improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
mcmonkey4eva committed Jan 1, 2022
1 parent e514609 commit 5c709aa
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 8 deletions.
Expand Up @@ -2,6 +2,7 @@

import com.denizenscript.denizen.events.BukkitScriptEvent;
import com.denizenscript.denizen.nms.NMSHandler;
import com.denizenscript.denizen.utilities.blocks.SpawnableHelper;
import com.denizenscript.denizen.utilities.depends.Depends;
import com.denizenscript.denizen.utilities.flags.LocationFlagSearchHelper;
import com.denizenscript.denizencore.objects.ObjectTag;
Expand Down Expand Up @@ -214,16 +215,17 @@ static <T extends AreaContainmentObject> void registerTags(Class<T> type, Objec
// @description
// Returns each LocationTag within the area that is safe for players or similar entities to spawn in.
// Optionally, specify a material matcher to only return locations with that block type.
// Uses the same spawnable check as <@link tag LocationTag.is_spawnable>
// -->
processor.registerTag(ListTag.class, "spawnable_blocks", (attribute, area) -> {
NMSHandler.getChunkHelper().changeChunkServerThread(area.getWorld().getWorld());
try {
if (attribute.hasParam()) {
String matcher = attribute.getParam();
Predicate<Location> predicate = (l) -> isSpawnable(l) && BukkitScriptEvent.tryMaterial(l.getBlock().getRelative(0, -1, 0).getType(), matcher);
Predicate<Location> predicate = (l) -> SpawnableHelper.isSpawnable(l) && BukkitScriptEvent.tryMaterial(l.getBlock().getRelative(0, -1, 0).getType(), matcher);
return area.getBlocks(predicate);
}
return area.getBlocks(AreaContainmentObject::isSpawnable);
return area.getBlocks(SpawnableHelper::isSpawnable);
}
finally {
NMSHandler.getChunkHelper().restoreServerThread(area.getWorld().getWorld());
Expand Down Expand Up @@ -314,10 +316,4 @@ static <T extends AreaContainmentObject> void registerTags(Class<T> type, Objec
return (T) area.withWorld(world);
});
}

static boolean isSpawnable(Location loc) {
return loc.getBlock().getType().isAir()
&& (loc.clone().add(0, 1, 0).getBlock().getType().isAir()
&& (loc.clone().add(0, -1, 0)).getBlock().getType().isSolid());
}
}
Expand Up @@ -8,6 +8,7 @@
import com.denizenscript.denizen.objects.properties.material.MaterialPersistent;
import com.denizenscript.denizen.scripts.commands.world.SwitchCommand;
import com.denizenscript.denizen.utilities.AdvancedTextImpl;
import com.denizenscript.denizen.utilities.blocks.SpawnableHelper;
import com.denizenscript.denizen.utilities.flags.DataPersistenceFlagTracker;
import com.denizenscript.denizen.utilities.flags.LocationFlagSearchHelper;
import com.denizenscript.denizen.utilities.world.PathFinder;
Expand Down Expand Up @@ -2522,6 +2523,49 @@ else if (yaw < 315) {
return found;
});

// <--[tag]
// @attribute <LocationTag.find_spawnable_blocks_within[<#.#>]>
// @returns ListTag(LocationTag)
// @group finding
// @description
// Returns a list of blocks within a radius, that are safe for spawning, with the same logic as <@link tag LocationTag.is_spawnable>.
// Note: current implementation measures the center of nearby block's distance from the exact given location.
// Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).
// -->
tagProcessor.registerTag(ListTag.class, "find_spawnable_blocks_within", (attribute, object) -> {
if (!attribute.hasParam()) {
return null;
}
double radius = attribute.getDoubleParam();
ListTag found = new ListTag();
int max = Settings.blockTagsMaxBlocks();
int index = 0;
Location tstart = object.getBlockLocation();
double tstartY = tstart.getY();
int radiusInt = (int) radius;
fullloop:
for (int y = -radiusInt; y <= radiusInt; y++) {
double newY = y + tstartY;
if (!Utilities.isLocationYSafe(newY, object.getWorld())) {
continue;
}
for (int x = -radiusInt; x <= radiusInt; x++) {
for (int z = -radiusInt; z <= radiusInt; z++) {
index++;
if (index > max) {
break fullloop;
}
Location loc = tstart.clone().add(x + 0.5, y + 0.5, z + 0.5);
if (Utilities.checkLocation(object, loc, radius) && SpawnableHelper.isSpawnable(loc)) {
found.addObject(new LocationTag(loc.add(0, -0.5, 0)));
}
}
}
}
found.objectForms.sort((loc1, loc2) -> object.compare((LocationTag) loc1, (LocationTag) loc2));
return found;
});

// <--[tag]
// @attribute <LocationTag.find_players_within[<#.#>]>
// @returns ListTag(PlayerTag)
Expand Down Expand Up @@ -3940,6 +3984,22 @@ else if (material.hasModernData() && material.getModernData() instanceof org.buk
}
return output;
});

// <--[tag]
// @attribute <LocationTag.is_spawnable>
// @returns ElementTag(Boolean)
// @group world
// @description
// Returns whether the location is safe to spawn at, for a player or player-like entity.
// Specifically this verifies that:
// - The block above this location is air.
// - The block at this location is non-solid.
// - The block below this location is solid.
// - All relevant blocks are not dangerous (like fire, lava, etc.), or unstable/small/awkward (like fences, doors, etc.) or otherwise likely to go wrong (like pressure plates).
// -->
tagProcessor.registerTag(ElementTag.class, "is_spawnable", (attribute, object) -> {
return new ElementTag(SpawnableHelper.isSpawnable(object));
});
}

public static ObjectTagProcessor<LocationTag> tagProcessor = new ObjectTagProcessor<>();
Expand Down
Expand Up @@ -9,6 +9,7 @@
import com.denizenscript.denizen.npc.traits.TriggerTrait;
import com.denizenscript.denizen.tags.BukkitTagContext;
import com.denizenscript.denizen.utilities.blocks.MaterialCompat;
import com.denizenscript.denizencore.events.ScriptEvent;
import com.denizenscript.denizencore.objects.core.ScriptTag;
import com.denizenscript.denizencore.scripts.ScriptEntry;
import com.denizenscript.denizencore.tags.TagManager;
Expand Down Expand Up @@ -474,4 +475,15 @@ public static boolean isLocationYSafe(double y, World world) {
}
return y >= world.getMinHeight() && y <= world.getMaxHeight();
}

public static ArrayList<Material> allMaterialsThatMatch(String matcherText) {
ScriptEvent.MatchHelper matcher = ScriptEvent.createMatcher(matcherText);
ArrayList<Material> mats = new ArrayList<>();
for (Material material : Material.values()) {
if (matcher.doesMatch(material.name()) && !material.name().startsWith("LEGACY_")) {
mats.add(material);
}
}
return mats;
}
}
@@ -0,0 +1,53 @@
package com.denizenscript.denizen.utilities.blocks;

import com.denizenscript.denizen.utilities.Utilities;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;

import java.util.HashSet;

public class SpawnableHelper {

/**
* Materials that would be a problem to spawn in/on.
*/
public static HashSet<Material> DANGEROUS_MATERIALS = new HashSet<>(Utilities.allMaterialsThatMatch(
"fire|cactus|water|lava|magma_block|*cauldron|*campfire|*portal" // Core problems
+ "|cobweb|ladder|*fence|*door|end_rod|iron_bars|chain|*wall|*_pane" // Unstable
+ "*egg|*plate|tripwire|*piston")); // Could hurt

/**
* Returns true if the location would likely be safe to spawn a player at (based on material solidity at, below, and above the location).
*/
public static boolean isSpawnable(Location loc) {
World w = loc.getWorld();
if (w == null) {
return false;
}
int x = loc.getBlockX();
int y = loc.getBlockY();
int z = loc.getBlockZ();
if (y - 1 <= w.getMinHeight() || y + 1 >= w.getMaxHeight()) {
return false;
}
if (!w.getBlockAt(x, y + 1, z).getType().isAir()) {
return false;
}
Material self = w.getBlockAt(x, y, z).getType();
if (self.isSolid()) {
return false;
}
if (DANGEROUS_MATERIALS.contains(self)) {
return false;
}
Material below = w.getBlockAt(x, y - 1, z).getType();
if (!below.isSolid()) {
return false;
}
if (DANGEROUS_MATERIALS.contains(below)) {
return false;
}
return true;
}
}

0 comments on commit 5c709aa

Please sign in to comment.