Added automatic spawn location selection and /debugspawn.
parent
643e8e0f62
commit
82b2ac79bd
|
@ -5,6 +5,7 @@ import java.io.IOException;
|
|||
import java.sql.SQLException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import me.jamestmartin.wasteland.commands.CommandDebugSpawn;
|
||||
import me.jamestmartin.wasteland.commands.CommandDebugSpawnWeights;
|
||||
import me.jamestmartin.wasteland.commands.CommandOfficial;
|
||||
import me.jamestmartin.wasteland.commands.CommandRank;
|
||||
|
@ -92,6 +93,7 @@ public class Wasteland extends JavaPlugin {
|
|||
this.getCommand("official").setExecutor(new CommandOfficial());
|
||||
|
||||
// debug commands
|
||||
this.getCommand("debugspawn").setExecutor(new CommandDebugSpawn());
|
||||
this.getCommand("debugspawnweights").setExecutor(new CommandDebugSpawnWeights());
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package me.jamestmartin.wasteland.commands;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
|
||||
public class CommandDebugSpawn implements CommandExecutor {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("You must be a player to use this command.");
|
||||
return false;
|
||||
}
|
||||
Player player = (Player) sender;
|
||||
|
||||
if (args.length > 1) {
|
||||
sender.sendMessage("Too many arguments.");
|
||||
return false;
|
||||
}
|
||||
|
||||
final int attempts;
|
||||
if (args.length == 0) {
|
||||
attempts = 1;
|
||||
} else {
|
||||
attempts = Integer.parseUnsignedInt(args[0]);
|
||||
}
|
||||
|
||||
Random rand = new Random();
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
int successfulSpawns = 0;
|
||||
for (int attempt = 0; attempt < attempts; attempt++) {
|
||||
Optional<LivingEntity> tryMonster = Wasteland.getInstance().getSpawner().trySpawn(rand, player.getLocation());
|
||||
if (tryMonster.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LivingEntity monster = tryMonster.get();
|
||||
Location loc = monster.getLocation();
|
||||
sender.sendMessage("Spawned a " + monster.getType() + " at location " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ".");
|
||||
successfulSpawns++;
|
||||
}
|
||||
|
||||
sender.sendMessage("Spawned " + successfulSpawns + " monsters.");
|
||||
|
||||
sender.sendMessage("Took " + (System.currentTimeMillis() - time) + " milliseconds.");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,24 @@
|
|||
package me.jamestmartin.wasteland.spawns;
|
||||
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
public enum MonsterType {
|
||||
/** A creeper or charged creeper, as appropriate. */
|
||||
CREEPER,
|
||||
CREEPER(EntityType.CREEPER),
|
||||
/** A skeleton, wither skeleton, stray, etc., as appropriate. */
|
||||
SKELETON,
|
||||
SKELETON(EntityType.SKELETON),
|
||||
/** A spider, cave spider, or spider jockey, as appropriate. */
|
||||
SPIDER,
|
||||
SPIDER(EntityType.SPIDER),
|
||||
/** A zombie, husk, pig zombie, etc., as appropriate. */
|
||||
ZOMBIE,
|
||||
ZOMBIE(EntityType.ZOMBIE);
|
||||
|
||||
private final EntityType defaultVariant;
|
||||
|
||||
private MonsterType(final EntityType defaultVariant) {
|
||||
this.defaultVariant = defaultVariant;
|
||||
}
|
||||
|
||||
public EntityType getDefaultVariant() {
|
||||
return defaultVariant;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import java.util.Collection;
|
|||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
@ -15,6 +18,7 @@ import org.bukkit.entity.Monster;
|
|||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.config.MonsterSpawnConfig;
|
||||
import me.jamestmartin.wasteland.util.Pair;
|
||||
import me.jamestmartin.wasteland.world.MoonPhase;
|
||||
|
||||
public class WastelandSpawner {
|
||||
|
@ -177,4 +181,58 @@ public class WastelandSpawner {
|
|||
public HashMap<MonsterType, Float> calculateSpawnProbabilities(Block block) {
|
||||
return calculateSpawnProbabilities(block.getLocation());
|
||||
}
|
||||
|
||||
public static Location spawnBiasedRandomLocation(Random rand, Location center, int minRadius, int maxRadius) {
|
||||
// An angle on the xy plane, from 0 to 1.
|
||||
double xzAngle = 2 * Math.PI * rand.nextDouble();
|
||||
// An angle on the plane specified by the xz angle and the y axis, from -0.5 to 0.5.
|
||||
// We have a bias towards y-levels closer to the player;
|
||||
// the angle 0 is the xy line, so the bell curve is centered on the player's y level.
|
||||
double xzyAngle = Math.PI * rand.nextGaussian() / 3;
|
||||
|
||||
// Prefer to spawn closer to the player, using half a bell curve.
|
||||
double magnitude = (maxRadius - minRadius) * Math.abs(rand.nextGaussian() / 3) + minRadius;
|
||||
|
||||
double offX = magnitude * Math.cos(xzAngle) * Math.cos(xzyAngle);
|
||||
double offZ = magnitude * Math.sin(xzAngle) * Math.cos(xzyAngle);
|
||||
double offY = magnitude * Math.sin(xzyAngle);
|
||||
|
||||
return new Location(center.getWorld(), center.getX() + offX, center.getY() + offY, center.getZ() + offZ);
|
||||
}
|
||||
|
||||
public static Optional<Pair<MonsterType, Double>> chooseWeightedRandomMonster(Random rand, Map<MonsterType, Float> weights) {
|
||||
double overallSpawnProbability = weights.values().stream().reduce(0.0f, (x, y) -> x + y);
|
||||
if (rand.nextDouble() >= overallSpawnProbability) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
double whichMonster = rand.nextDouble() * overallSpawnProbability;
|
||||
for (Entry<MonsterType, Float> monster : weights.entrySet()) {
|
||||
double successMargin = monster.getValue() - whichMonster;
|
||||
if (successMargin > 0) {
|
||||
return Optional.of(new Pair<>(monster.getKey(), successMargin));
|
||||
}
|
||||
whichMonster -= monster.getValue();
|
||||
}
|
||||
|
||||
// This should only be able to happen due to rounding error.
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<Pair<MonsterType, Double>> pickRandomMonster(Random rand, Block block) {
|
||||
Map<MonsterType, Float> weights = calculateSpawnProbabilities(block);
|
||||
return chooseWeightedRandomMonster(rand, weights);
|
||||
}
|
||||
|
||||
public Optional<LivingEntity> trySpawn(Random rand, Location center) {
|
||||
Location location = spawnBiasedRandomLocation(rand, center, 20, 54);
|
||||
return pickRandomMonster(rand, location.getBlock()).map(mm -> {
|
||||
MonsterType type = mm.x;
|
||||
return (LivingEntity) location.getWorld().spawnEntity(location, type.getDefaultVariant());
|
||||
});
|
||||
}
|
||||
|
||||
public Optional<LivingEntity> trySpawn(Location center) {
|
||||
return trySpawn(new Random(), center);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package me.jamestmartin.wasteland.util;
|
||||
|
||||
public class Pair<A, B> {
|
||||
public final A x;
|
||||
public final B y;
|
||||
|
||||
public Pair(A x, B y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
|
@ -32,6 +32,11 @@ commands:
|
|||
permission-message: You are not a staff member with an officer rank.
|
||||
|
||||
# debug commands
|
||||
debugspawn:
|
||||
description: Spawn a random monster.
|
||||
usage: "Usage: /<command> [<attempts>]"
|
||||
permission: wasteland.debug.spawns.spawn
|
||||
permission-message: You do not have permission to spawn debug monsters.
|
||||
debugspawnweights:
|
||||
description: View the monster spawn weights at the block you are looking at.
|
||||
usage: "Usage: /<command>"
|
||||
|
@ -82,7 +87,11 @@ permissions:
|
|||
description: Commands for debugging the spawning system.
|
||||
default: false
|
||||
children:
|
||||
wasteland.debug.spawns.spawn: true
|
||||
wasteland.debug.spawns.weights: true
|
||||
wasteland.debug.spawns.spawn:
|
||||
description: Allows you to randomly spawn monsters using a command.
|
||||
default: false
|
||||
wasteland.debug.spawns.weights:
|
||||
description: Allows you to see the spawn weights by mob type at a block.
|
||||
default: false
|
||||
|
|
Loading…
Reference in New Issue