Jumping and 1.14
This should mark 2.0.6
This commit is contained in:
parent
64318c7a20
commit
dcdf9a880e
7 changed files with 200 additions and 20 deletions
10
README.md
10
README.md
|
@ -1,3 +1,11 @@
|
||||||
# Giants
|
# Giants
|
||||||
|
|
||||||
This plugin adds naturally spawning Giants with AI to your Minecraft server.
|
This plugin adds naturally spawning Giants with AI to your Minecraft server.
|
||||||
|
|
||||||
|
### Requested features
|
||||||
|
- Texture variation \
|
||||||
|
I think this can be done, but would require a texture pack
|
||||||
|
- Boss bar \
|
||||||
|
I don't want to get buried in that, so this would require an API, which, for performance reasons, should not be mandatory
|
||||||
|
- Hitboxes \
|
||||||
|
I don't know if this is still a major issue, but a person reported that the hitbox doesn't cover the whole body
|
17
pom.xml
17
pom.xml
|
@ -24,8 +24,23 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.spigotmc</groupId>
|
<groupId>org.spigotmc</groupId>
|
||||||
<artifactId>spigot-api</artifactId>
|
<artifactId>spigot-api</artifactId>
|
||||||
<version>1.21.1-R0.1-SNAPSHOT</version>
|
<version>1.14.4-R0.1-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
|
<!-- fix vulnerabilities complaints -->
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -20,14 +20,16 @@ public class Configuration {
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
|
||||||
boolean ai;
|
boolean ai;
|
||||||
boolean attack;
|
|
||||||
|
|
||||||
double chance;
|
|
||||||
double attackDamage;
|
double attackDamage;
|
||||||
int attackDelay;
|
int attackDelay;
|
||||||
|
|
||||||
Vector attackReach;
|
Vector attackReach;
|
||||||
|
int jumpMode;
|
||||||
|
int jumpCondition;
|
||||||
|
int jumpDelay;
|
||||||
|
double jumpHeight = -1;
|
||||||
|
|
||||||
|
double chance;
|
||||||
List<String> worldBlacklist;
|
List<String> worldBlacklist;
|
||||||
Set<PotionEffect> effects = new HashSet<>();
|
Set<PotionEffect> effects = new HashSet<>();
|
||||||
Set<Drop> drops = new HashSet<>();
|
Set<Drop> drops = new HashSet<>();
|
||||||
|
@ -41,15 +43,28 @@ public class Configuration {
|
||||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
|
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||||
|
|
||||||
ai = config.getBoolean("ai");
|
ai = config.getBoolean("ai");
|
||||||
attack = config.getBoolean("attack");
|
|
||||||
|
|
||||||
chance = config.getDouble("chance");
|
chance = config.getDouble("chance");
|
||||||
attackDamage = config.getDouble("attackDamage");
|
attackDamage = config.getDouble("attackDamage");
|
||||||
attackDelay = config.getInt("attackDelay");
|
attackDelay = config.getInt("attackDelay");
|
||||||
|
jumpMode = config.getInt("jumpMode");
|
||||||
|
jumpCondition = config.getInt("jumpCondition");
|
||||||
|
jumpDelay = config.getInt("jumpDelay");
|
||||||
|
|
||||||
|
if (jumpMode != 0) {
|
||||||
|
jumpHeight = config.getDouble("jumpHeight", -1);
|
||||||
|
if (jumpHeight == -1) {
|
||||||
|
jumpHeight = defaultJumpHeight();
|
||||||
|
}
|
||||||
|
logger.info("Jumping is experimental.");
|
||||||
|
logger.info("Jump mode: " + jumpMode);
|
||||||
|
logger.info("Jump condition: " + jumpCondition);
|
||||||
|
logger.info("Jump height: " + jumpHeight);
|
||||||
|
}
|
||||||
|
|
||||||
double _attackReach = config.getDouble("attackReach");
|
double _attackReach = config.getDouble("attackReach");
|
||||||
double _expandUp = config.getDouble("expandUp");
|
double _attackVerticalReach = config.getDouble("attackVerticalReach");
|
||||||
attackReach = new Vector(_attackReach, _expandUp, _attackReach);
|
attackReach = new Vector(_attackReach, _attackVerticalReach, _attackReach);
|
||||||
|
|
||||||
worldBlacklist = config.getStringList("blacklist");
|
worldBlacklist = config.getStringList("blacklist");
|
||||||
|
|
||||||
|
@ -99,4 +114,17 @@ public class Configuration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double defaultJumpHeight() {
|
||||||
|
switch (jumpMode) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
return 0.42;
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
return 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package eu.m724.giants;
|
package eu.m724.giants;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.entity.*;
|
import org.bukkit.entity.*;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.entity.EntityDeathEvent;
|
import org.bukkit.event.entity.EntityDeathEvent;
|
||||||
import org.bukkit.event.entity.EntitySpawnEvent;
|
import org.bukkit.event.entity.EntitySpawnEvent;
|
||||||
|
import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
|
||||||
import org.bukkit.event.world.ChunkLoadEvent;
|
import org.bukkit.event.world.ChunkLoadEvent;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.persistence.PersistentDataType;
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
@ -14,6 +16,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.bukkit.potion.PotionEffect;
|
import org.bukkit.potion.PotionEffect;
|
||||||
import org.bukkit.potion.PotionEffectType;
|
import org.bukkit.potion.PotionEffectType;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
@ -32,6 +35,8 @@ public class GiantProcessor implements Listener {
|
||||||
|
|
||||||
// private final Set<Giant> trackedGiants = new HashSet<>();
|
// private final Set<Giant> trackedGiants = new HashSet<>();
|
||||||
private final Set<Husk> trackedHusks = new HashSet<>();
|
private final Set<Husk> trackedHusks = new HashSet<>();
|
||||||
|
private final Map<Entity, Location> giantLocationMap = new HashMap<>();
|
||||||
|
private final Map<Entity, Long> giantLastJump = new HashMap<>();
|
||||||
|
|
||||||
private final ThreadLocalRandom random = ThreadLocalRandom.current();
|
private final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||||
private final NamespacedKey huskKey;
|
private final NamespacedKey huskKey;
|
||||||
|
@ -50,7 +55,7 @@ public class GiantProcessor implements Listener {
|
||||||
public void run() {
|
public void run() {
|
||||||
for (Husk husk : Set.copyOf(trackedHusks)) {
|
for (Husk husk : Set.copyOf(trackedHusks)) {
|
||||||
if (husk.isValid()) {
|
if (husk.isValid()) {
|
||||||
Location location = husk.getLocation();
|
Location huskLocation = husk.getLocation();
|
||||||
Entity giant = husk.getVehicle();
|
Entity giant = husk.getVehicle();
|
||||||
|
|
||||||
if (giant instanceof Giant) {
|
if (giant instanceof Giant) {
|
||||||
|
@ -59,7 +64,23 @@ public class GiantProcessor implements Listener {
|
||||||
giant.getBoundingBox().expand(configuration.attackReach),
|
giant.getBoundingBox().expand(configuration.attackReach),
|
||||||
e -> (e instanceof Player && !e.isInvulnerable())
|
e -> (e instanceof Player && !e.isInvulnerable())
|
||||||
).forEach(p -> ((Player) p).damage(configuration.attackDamage, giant));
|
).forEach(p -> ((Player) p).damage(configuration.attackDamage, giant));
|
||||||
giant.setRotation(location.getYaw(), location.getPitch());
|
giant.setRotation(huskLocation.getYaw(), huskLocation.getPitch());
|
||||||
|
|
||||||
|
// jumping
|
||||||
|
if (configuration.jumpMode != 0) {
|
||||||
|
// tracking location is only required for jumping
|
||||||
|
Location prevLocation = giantLocationMap.get(giant);
|
||||||
|
Location location = giant.getLocation();
|
||||||
|
if (prevLocation == null) {
|
||||||
|
prevLocation = location;
|
||||||
|
}
|
||||||
|
giantLocationMap.put(giant, location);
|
||||||
|
|
||||||
|
LivingEntity target = husk.getTarget();
|
||||||
|
if (target != null) {
|
||||||
|
processJump(giant, prevLocation, location, target.getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no vehicle means the giant doesn't exist anymore and the husk should also not exist
|
// no vehicle means the giant doesn't exist anymore and the husk should also not exist
|
||||||
|
@ -80,6 +101,41 @@ public class GiantProcessor implements Listener {
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processJump(Entity giant, Location prevLocation, Location location, Location targetLocation) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - giantLastJump.getOrDefault(giant, 0L) < configuration.jumpDelay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (giant.isOnGround()) {
|
||||||
|
giantLastJump.put(giant, now);
|
||||||
|
if (configuration.jumpCondition == 0) {
|
||||||
|
if (targetLocation.subtract(location).getY() > 0) {
|
||||||
|
jump(giant);
|
||||||
|
}
|
||||||
|
} else if (configuration.jumpCondition == 1) {
|
||||||
|
Location delta = prevLocation.subtract(location);
|
||||||
|
if (targetLocation.subtract(location).getY() > 0 && (delta.getX() == 0 || delta.getZ() == 0)) {
|
||||||
|
jump(giant);
|
||||||
|
}
|
||||||
|
} else if (configuration.jumpCondition == 2) {
|
||||||
|
Location delta = prevLocation.subtract(location);
|
||||||
|
if (delta.getX() == 0 || delta.getZ() == 0) {
|
||||||
|
jump(giant);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // I could probably simplify that code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void jump(Entity giant) {
|
||||||
|
if (configuration.jumpMode == 1) {
|
||||||
|
giant.setVelocity(new Vector(0, configuration.jumpHeight, 0));
|
||||||
|
} else if (configuration.jumpMode == 2) {
|
||||||
|
giant.teleport(giant.getLocation().add(0, configuration.jumpHeight, 0));
|
||||||
|
}
|
||||||
|
} // TODO those should be moved
|
||||||
|
|
||||||
public LivingEntity spawnGiant(Location pos) {
|
public LivingEntity spawnGiant(Location pos) {
|
||||||
LivingEntity entity = (LivingEntity) pos.getWorld().spawnEntity(pos, EntityType.GIANT);
|
LivingEntity entity = (LivingEntity) pos.getWorld().spawnEntity(pos, EntityType.GIANT);
|
||||||
|
|
||||||
|
@ -92,6 +148,7 @@ public class GiantProcessor implements Listener {
|
||||||
passenger.getPersistentDataContainer().set(huskKey, PersistentDataType.INTEGER, VERSION);
|
passenger.getPersistentDataContainer().set(huskKey, PersistentDataType.INTEGER, VERSION);
|
||||||
|
|
||||||
entity.addPassenger(passenger);
|
entity.addPassenger(passenger);
|
||||||
|
|
||||||
trackedHusks.add((Husk) passenger);
|
trackedHusks.add((Husk) passenger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +166,7 @@ public class GiantProcessor implements Listener {
|
||||||
logger.fine("Chunk loaded: " + event.getChunk().getX() + " " + event.getChunk().getZ());
|
logger.fine("Chunk loaded: " + event.getChunk().getX() + " " + event.getChunk().getZ());
|
||||||
|
|
||||||
Husk[] husks = Arrays.stream(entities)
|
Husk[] husks = Arrays.stream(entities)
|
||||||
.filter(entity -> entity instanceof Husk && entity.getPersistentDataContainer().has(huskKey))
|
.filter(entity -> entity instanceof Husk && entity.getPersistentDataContainer().has(huskKey, PersistentDataType.INTEGER))
|
||||||
.map(Husk.class::cast)
|
.map(Husk.class::cast)
|
||||||
.toArray(Husk[]::new);
|
.toArray(Husk[]::new);
|
||||||
|
|
||||||
|
@ -167,7 +224,7 @@ public class GiantProcessor implements Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Entity passenger : entity.getPassengers()) {
|
for (Entity passenger : entity.getPassengers()) {
|
||||||
if (passenger.getPersistentDataContainer().has(huskKey)) {
|
if (passenger.getPersistentDataContainer().has(huskKey, PersistentDataType.INTEGER)) {
|
||||||
((LivingEntity) passenger).setHealth(0);
|
((LivingEntity) passenger).setHealth(0);
|
||||||
logger.fine("Killed a Husk");
|
logger.fine("Killed a Husk");
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -16,9 +18,11 @@ import java.util.Map;
|
||||||
|
|
||||||
public class GiantsCommand implements CommandExecutor {
|
public class GiantsCommand implements CommandExecutor {
|
||||||
private final GiantsPlugin plugin;
|
private final GiantsPlugin plugin;
|
||||||
|
private final Configuration configuration;
|
||||||
|
|
||||||
public GiantsCommand(GiantsPlugin plugin) {
|
public GiantsCommand(GiantsPlugin plugin, Configuration configuration) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -57,22 +61,73 @@ public class GiantsCommand implements CommandExecutor {
|
||||||
list.add(map);
|
list.add(map);
|
||||||
|
|
||||||
YamlConfiguration yamlConfiguration = new YamlConfiguration();
|
YamlConfiguration yamlConfiguration = new YamlConfiguration();
|
||||||
yamlConfiguration.set("v", list);
|
|
||||||
|
|
||||||
yamlConfiguration.setInlineComments("v", List.of("Copy the below content to your config.yml"));
|
try {
|
||||||
|
Method method = yamlConfiguration.getClass().getMethod("setInlineComments", String.class, List.class);
|
||||||
|
|
||||||
|
yamlConfiguration.set("v", list);
|
||||||
|
method.invoke(yamlConfiguration, "v", List.of("Copy the below content to your config.yml"));
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
yamlConfiguration.set("v", null); // two latter exceptions happen after setting
|
||||||
|
yamlConfiguration.set("copy_everything_below_to_config_yml", list);
|
||||||
|
}
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
String name = "item-" + now + ".yml";
|
String name = "item-" + now + ".yml";
|
||||||
|
File file = new File(plugin.getDataFolder(), name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
yamlConfiguration.save(new File(plugin.getDataFolder(), name));
|
yamlConfiguration.save(file);
|
||||||
sender.sendMessage("Saved to plugins/Giants/" + name + ". To add it as a drop, see instructions in the file.");
|
sender.sendMessage("Saved to plugins/Giants/" + name + ". To add it as a drop, see instructions in the file.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
|
||||||
sender.sendMessage("Error saving file. See console for details.");
|
sender.sendMessage("Error saving file. See console for details.");
|
||||||
|
throw new RuntimeException("Error saving file to " + file.getName(), e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage("Only players can use this command.");
|
sender.sendMessage("Only players can use this command.");
|
||||||
}
|
}
|
||||||
|
} else if (action.equals("jm")) { // TODO remove
|
||||||
|
if (args.length > 1) {
|
||||||
|
int mode = Integer.parseInt(args[1]);
|
||||||
|
configuration.jumpMode = mode;
|
||||||
|
|
||||||
|
sender.sendMessage("Jump mode set to " + mode);
|
||||||
|
sender.sendMessage("This command doesn't check if it's valid, an invalid value turns off jumping.");
|
||||||
|
|
||||||
|
if (configuration.jumpHeight == -1) {
|
||||||
|
configuration.jumpHeight = configuration.defaultJumpHeight();
|
||||||
|
sender.sendMessage("Jump height set to " + configuration.jumpHeight + ". Modify it with /giants jumpheight");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.sendMessage("Jump mode: " + configuration.jumpMode);
|
||||||
|
}
|
||||||
|
} else if (action.equals("jh")) {
|
||||||
|
if (args.length > 1) {
|
||||||
|
double height = Double.parseDouble(args[1]);
|
||||||
|
configuration.jumpHeight = height;
|
||||||
|
|
||||||
|
sender.sendMessage("Jump height set to " + height);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage("Jump height: " + configuration.jumpHeight);
|
||||||
|
}
|
||||||
|
} else if (action.equals("jc")) {
|
||||||
|
if (args.length > 1) {
|
||||||
|
int condition = Integer.parseInt(args[1]);
|
||||||
|
configuration.jumpCondition = condition;
|
||||||
|
|
||||||
|
sender.sendMessage("Jump condition set to " + condition);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage("Jump condition: " + configuration.jumpCondition);
|
||||||
|
}
|
||||||
|
} else if (action.equals("jd")) {
|
||||||
|
if (args.length > 1) {
|
||||||
|
int delay = Integer.parseInt(args[1]);
|
||||||
|
configuration.jumpDelay = delay;
|
||||||
|
|
||||||
|
sender.sendMessage("Jump delay set to " + delay);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage("Jump delay: " + configuration.jumpDelay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor {
|
||||||
|
|
||||||
configuration.load();
|
configuration.load();
|
||||||
|
|
||||||
getCommand("giants").setExecutor(new GiantsCommand(this));
|
getCommand("giants").setExecutor(new GiantsCommand(this, configuration));
|
||||||
|
|
||||||
giantProcessor.start();
|
giantProcessor.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,23 @@
|
||||||
# If disabled, the giant will not move or attack
|
# If disabled, the giant will not move or attack
|
||||||
ai: true
|
ai: true
|
||||||
|
|
||||||
|
# Makes giants jump. Very experimental.
|
||||||
|
# I prefer velocity mode
|
||||||
|
# 0 - disabled
|
||||||
|
# 1 - velocity mode
|
||||||
|
# 2 - tp mode
|
||||||
|
jumpMode: 0
|
||||||
|
# 0 - when target is higher
|
||||||
|
# 1 - when target is higher and giant is stuck
|
||||||
|
# 2 - when giant is stuck
|
||||||
|
jumpCondition: 1
|
||||||
|
# Delay before jumping again, counted from hitting the ground. In milliseconds
|
||||||
|
jumpDelay: 200
|
||||||
|
# If velocity mode, y blocks/tick (default 0.42)
|
||||||
|
# If tp mode, +y to teleport (default 1.2)
|
||||||
|
# -1: auto
|
||||||
|
jumpHeight: -1
|
||||||
|
|
||||||
# In hearts, 0.5 is half a heart
|
# In hearts, 0.5 is half a heart
|
||||||
attackDamage: 1.0
|
attackDamage: 1.0
|
||||||
|
|
||||||
|
@ -15,8 +32,8 @@ attackDelay: 20
|
||||||
# There's no wall check yet, so it will hit through walls
|
# There's no wall check yet, so it will hit through walls
|
||||||
attackReach: 2
|
attackReach: 2
|
||||||
|
|
||||||
# The value above does not modify vertical reach, this value extends it upwards
|
# Vertical reach
|
||||||
expandUp: 0
|
attackVerticalReach: 1
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue