diff --git a/README.md b/README.md
index ac48308..f4feca1 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,11 @@
# Giants
-This plugin adds naturally spawning Giants with AI to your Minecraft server.
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 884a170..258f8e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,8 +24,23 @@
org.spigotmc
spigot-api
- 1.21.1-R0.1-SNAPSHOT
+ 1.14.4-R0.1-SNAPSHOT
provided
+
+
+
+ org.yaml
+ snakeyaml
+
+
+ com.google.guava
+ guava
+
+
+ com.google.code.gson
+ gson
+
+
diff --git a/src/main/java/eu/m724/giants/Configuration.java b/src/main/java/eu/m724/giants/Configuration.java
index de52b6f..1b34d40 100644
--- a/src/main/java/eu/m724/giants/Configuration.java
+++ b/src/main/java/eu/m724/giants/Configuration.java
@@ -20,14 +20,16 @@ public class Configuration {
private final Logger logger;
boolean ai;
- boolean attack;
- double chance;
double attackDamage;
int attackDelay;
-
Vector attackReach;
+ int jumpMode;
+ int jumpCondition;
+ int jumpDelay;
+ double jumpHeight = -1;
+ double chance;
List worldBlacklist;
Set effects = new HashSet<>();
Set drops = new HashSet<>();
@@ -41,15 +43,28 @@ public class Configuration {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
ai = config.getBoolean("ai");
- attack = config.getBoolean("attack");
chance = config.getDouble("chance");
attackDamage = config.getDouble("attackDamage");
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 _expandUp = config.getDouble("expandUp");
- attackReach = new Vector(_attackReach, _expandUp, _attackReach);
+ double _attackVerticalReach = config.getDouble("attackVerticalReach");
+ attackReach = new Vector(_attackReach, _attackVerticalReach, _attackReach);
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;
+ }
}
diff --git a/src/main/java/eu/m724/giants/GiantProcessor.java b/src/main/java/eu/m724/giants/GiantProcessor.java
index 038fb3c..7aff656 100644
--- a/src/main/java/eu/m724/giants/GiantProcessor.java
+++ b/src/main/java/eu/m724/giants/GiantProcessor.java
@@ -1,12 +1,14 @@
package eu.m724.giants;
import org.bukkit.Location;
+import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
+import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
@@ -14,6 +16,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.util.Vector;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
@@ -32,6 +35,8 @@ public class GiantProcessor implements Listener {
// private final Set trackedGiants = new HashSet<>();
private final Set trackedHusks = new HashSet<>();
+ private final Map giantLocationMap = new HashMap<>();
+ private final Map giantLastJump = new HashMap<>();
private final ThreadLocalRandom random = ThreadLocalRandom.current();
private final NamespacedKey huskKey;
@@ -50,7 +55,7 @@ public class GiantProcessor implements Listener {
public void run() {
for (Husk husk : Set.copyOf(trackedHusks)) {
if (husk.isValid()) {
- Location location = husk.getLocation();
+ Location huskLocation = husk.getLocation();
Entity giant = husk.getVehicle();
if (giant instanceof Giant) {
@@ -59,7 +64,23 @@ public class GiantProcessor implements Listener {
giant.getBoundingBox().expand(configuration.attackReach),
e -> (e instanceof Player && !e.isInvulnerable())
).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 {
// 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);
}
+ 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) {
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);
entity.addPassenger(passenger);
+
trackedHusks.add((Husk) passenger);
}
@@ -109,7 +166,7 @@ public class GiantProcessor implements Listener {
logger.fine("Chunk loaded: " + event.getChunk().getX() + " " + event.getChunk().getZ());
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)
.toArray(Husk[]::new);
@@ -167,7 +224,7 @@ public class GiantProcessor implements Listener {
}
for (Entity passenger : entity.getPassengers()) {
- if (passenger.getPersistentDataContainer().has(huskKey)) {
+ if (passenger.getPersistentDataContainer().has(huskKey, PersistentDataType.INTEGER)) {
((LivingEntity) passenger).setHealth(0);
logger.fine("Killed a Husk");
}
diff --git a/src/main/java/eu/m724/giants/GiantsCommand.java b/src/main/java/eu/m724/giants/GiantsCommand.java
index 717c9df..a8337ef 100644
--- a/src/main/java/eu/m724/giants/GiantsCommand.java
+++ b/src/main/java/eu/m724/giants/GiantsCommand.java
@@ -9,6 +9,8 @@ import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -16,9 +18,11 @@ import java.util.Map;
public class GiantsCommand implements CommandExecutor {
private final GiantsPlugin plugin;
+ private final Configuration configuration;
- public GiantsCommand(GiantsPlugin plugin) {
+ public GiantsCommand(GiantsPlugin plugin, Configuration configuration) {
this.plugin = plugin;
+ this.configuration = configuration;
}
@Override
@@ -57,22 +61,73 @@ public class GiantsCommand implements CommandExecutor {
list.add(map);
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();
String name = "item-" + now + ".yml";
+ File file = new File(plugin.getDataFolder(), name);
+
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.");
} catch (IOException e) {
- e.printStackTrace();
sender.sendMessage("Error saving file. See console for details.");
+ throw new RuntimeException("Error saving file to " + file.getName(), e);
}
} else {
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;
diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java
index 711a22f..9823dad 100644
--- a/src/main/java/eu/m724/giants/GiantsPlugin.java
+++ b/src/main/java/eu/m724/giants/GiantsPlugin.java
@@ -22,7 +22,7 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor {
configuration.load();
- getCommand("giants").setExecutor(new GiantsCommand(this));
+ getCommand("giants").setExecutor(new GiantsCommand(this, configuration));
giantProcessor.start();
}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index fa9ca98..964dab4 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -4,6 +4,23 @@
# If disabled, the giant will not move or attack
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
attackDamage: 1.0
@@ -15,8 +32,8 @@ attackDelay: 20
# There's no wall check yet, so it will hit through walls
attackReach: 2
-# The value above does not modify vertical reach, this value extends it upwards
-expandUp: 0
+# Vertical reach
+attackVerticalReach: 1
###