Merge pull request #96 from D3v1s0m/2.X
End Crystal, display name property (to remove), small fix, Added SQLite Storage
This commit is contained in:
commit
0873ddab45
16 changed files with 659 additions and 10 deletions
46
api/src/main/java/lol/pyr/znpcsplus/util/Vector3i.java
Normal file
46
api/src/main/java/lol/pyr/znpcsplus/util/Vector3i.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
package lol.pyr.znpcsplus.util;
|
||||
|
||||
public class Vector3i {
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int z;
|
||||
|
||||
public Vector3i(int x, int y, int z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public int getZ() {
|
||||
return this.z;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.x + "," + this.y + "," + this.z;
|
||||
}
|
||||
|
||||
public String toPrettyString() {
|
||||
return "(" + this.x + ", " + this.y + ", " + this.z + ")";
|
||||
}
|
||||
|
||||
public static Vector3i fromString(String s) {
|
||||
String[] split = s.split(",");
|
||||
if (split.length < 3) {
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
return new Vector3i(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
|
||||
} catch (NumberFormatException var3) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -251,6 +251,7 @@ public class ZNpcsPlus extends JavaPlugin {
|
|||
manager.registerParser(Color.class, new ColorParser(incorrectUsageMessage));
|
||||
manager.registerParser(Vector3f.class, new Vector3fParser(incorrectUsageMessage));
|
||||
manager.registerParser(String.class, new StringParser(incorrectUsageMessage));
|
||||
manager.registerParser(Vector3i.class, new Vector3iParser(incorrectUsageMessage));
|
||||
|
||||
// TODO: Need to find a better way to do this
|
||||
registerEnumParser(manager, NpcPose.class, incorrectUsageMessage);
|
||||
|
|
|
@ -19,6 +19,9 @@ import net.kyori.adventure.text.Component;
|
|||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Color;
|
||||
import com.github.retrooper.packetevents.protocol.item.ItemStack;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -117,9 +120,20 @@ public class PropertySetCommand implements CommandHandler {
|
|||
value = context.parse(type);
|
||||
valueName = value == null ? "NONE" : ((NpcEntryImpl) value).getId();
|
||||
}
|
||||
else {
|
||||
else if (type == Vector3i.class) {
|
||||
value = context.parse(type);
|
||||
valueName = String.valueOf(value);
|
||||
valueName = value == null ? "NONE" : ((Vector3i) value).toPrettyString();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
value = context.parse(type);
|
||||
valueName = String.valueOf(value);
|
||||
} catch (NullPointerException e) {
|
||||
context.send(Component.text("An error occurred while trying to parse the value. Please report this to the plugin author.",
|
||||
NamedTextColor.RED));
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
npc.UNSAFE_setProperty(property, value);
|
||||
|
@ -144,6 +158,17 @@ public class PropertySetCommand implements CommandHandler {
|
|||
context.suggestEnum(Arrays.stream(SpellType.values()).filter(spellType -> spellType.ordinal() <= 3).toArray(SpellType[]::new)) :
|
||||
context.suggestEnum(SpellType.values());
|
||||
|
||||
if (type == Vector3i.class) {
|
||||
if (context.getSender() instanceof Player) {
|
||||
Player player = (Player) context.getSender();
|
||||
Block targetBlock = player.getTargetBlock(Collections.singleton(Material.AIR), 5);
|
||||
if (targetBlock.getType().equals(Material.AIR)) return Collections.emptyList();
|
||||
return context.suggestLiteral(
|
||||
targetBlock.getX() + "",
|
||||
targetBlock.getX() + " " + targetBlock.getY(),
|
||||
targetBlock.getX() + " " + targetBlock.getY() + " " + targetBlock.getZ());
|
||||
}
|
||||
}
|
||||
// Suggest enum values directly
|
||||
if (type.isEnum()) {
|
||||
return context.suggestEnum((Enum<?>[]) type.getEnumConstants());
|
||||
|
@ -154,6 +179,25 @@ public class PropertySetCommand implements CommandHandler {
|
|||
// TODO: suggest block with nbt like minecraft setblock command
|
||||
return context.suggestionParse(2, String.class).equals("block") ? context.suggestStream(StateTypes.values().stream().map(StateType::getName)) : Collections.emptyList();
|
||||
}
|
||||
if (type == Vector3i.class) {
|
||||
if (context.getSender() instanceof Player) {
|
||||
Player player = (Player) context.getSender();
|
||||
Block targetBlock = player.getTargetBlock(Collections.singleton(Material.AIR), 5);
|
||||
if (targetBlock.getType().equals(Material.AIR)) return Collections.emptyList();
|
||||
return context.suggestLiteral(
|
||||
targetBlock.getY() + "",
|
||||
targetBlock.getY() + " " + targetBlock.getZ());
|
||||
}
|
||||
}
|
||||
} else if (context.argSize() == 5) {
|
||||
if (type == Vector3i.class) {
|
||||
if (context.getSender() instanceof Player) {
|
||||
Player player = (Player) context.getSender();
|
||||
Block targetBlock = player.getTargetBlock(Collections.singleton(Material.AIR), 5);
|
||||
if (targetBlock.getType().equals(Material.AIR)) return Collections.emptyList();
|
||||
return context.suggestLiteral(targetBlock.getZ() + "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
|
|
|
@ -28,7 +28,7 @@ public interface MainConfig {
|
|||
boolean debugEnabled();
|
||||
|
||||
@ConfKey("storage-type")
|
||||
@ConfComments("The storage type to use. Available storage types: YAML")
|
||||
@ConfComments("The storage type to use. Available storage types: YAML, SQLITE")
|
||||
@DefaultString("YAML")
|
||||
NpcStorageType storageType();
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
|
|||
registerSerializer(new Vector3fPropertySerializer());
|
||||
registerSerializer(new BlockStatePropertySerializer());
|
||||
registerSerializer(new LookTypeSerializer());
|
||||
registerSerializer(new GenericSerializer<>(Vector3i::toString, Vector3i::fromString, Vector3i.class));
|
||||
|
||||
registerEnumSerializer(NpcPose.class);
|
||||
registerEnumSerializer(DyeColor.class);
|
||||
|
@ -119,6 +120,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
|
|||
register(new EquipmentProperty(packetFactory, "offhand", EquipmentSlot.OFF_HAND));
|
||||
|
||||
register(new NameProperty(legacyNames, optionalComponents));
|
||||
register(new DummyProperty<>("display_name", String.class));
|
||||
register(new DinnerboneProperty(legacyNames, optionalComponents));
|
||||
|
||||
register(new DummyProperty<>("look", LookType.FIXED));
|
||||
|
@ -251,6 +253,17 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
|
|||
register(new BitsetProperty("is_rearing", horseIndex, horseEating << 1, false, legacyBooleans));
|
||||
register(new BitsetProperty("has_mouth_open", horseIndex, horseEating << 2, false, legacyBooleans));
|
||||
|
||||
// End Crystal
|
||||
if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) {
|
||||
int endCrystalIndex;
|
||||
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) endCrystalIndex = 8;
|
||||
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) endCrystalIndex = 7;
|
||||
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) endCrystalIndex = 6;
|
||||
else endCrystalIndex = 5;
|
||||
register(new OptionalBlockPosProperty("beam_target", null, endCrystalIndex++));
|
||||
register(new BooleanProperty("show_base", endCrystalIndex, true, false));
|
||||
}
|
||||
|
||||
// Horse
|
||||
if (ver.isNewerThanOrEquals(ServerVersion.V_1_8) && ver.isOlderThan(ServerVersion.V_1_9)) {
|
||||
register(new EncodedByteProperty<>("horse_type", HorseType.HORSE, 19, obj -> (byte) obj.ordinal()));
|
||||
|
@ -367,7 +380,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
|
|||
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) witherIndex = 11;
|
||||
else witherIndex = 17;
|
||||
witherIndex += 3; // skip the first 3 indexes, will be used for the other properties later
|
||||
register(new IntegerProperty("invulnerable_time", witherIndex++, 0, false));
|
||||
register(new IntegerProperty("invulnerable_time", witherIndex, 0, false));
|
||||
|
||||
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_9)) return;
|
||||
// Shulker
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package lol.pyr.znpcsplus.entity.properties;
|
||||
|
||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
|
||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
|
||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
|
||||
import lol.pyr.znpcsplus.entity.PacketEntity;
|
||||
import lol.pyr.znpcsplus.util.Vector3i;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class OptionalBlockPosProperty extends EntityPropertyImpl<Vector3i> {
|
||||
private final int index;
|
||||
|
||||
public OptionalBlockPosProperty(String name, Vector3i defaultValue, int index) {
|
||||
super(name, defaultValue, Vector3i.class);
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
|
||||
Vector3i value = entity.getProperty(this);
|
||||
if (value == null) properties.put(index, new EntityData(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION, Optional.empty()));
|
||||
else properties.put(index, new EntityData(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION,
|
||||
Optional.of(new com.github.retrooper.packetevents.util.Vector3i(value.getX(), value.getY(), value.getZ()))));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package lol.pyr.znpcsplus.entity.serializers;
|
||||
|
||||
import lol.pyr.znpcsplus.entity.PropertySerializer;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class GenericSerializer<T> implements PropertySerializer<T> {
|
||||
private final Function<T, String> encoder;
|
||||
private final Function<String, T> decoder;
|
||||
private final Class<T> typeClass;
|
||||
|
||||
public GenericSerializer(Function<T, String> encoder, Function<String, T> decoder, Class<T> typeClass) {
|
||||
this.encoder = encoder;
|
||||
this.decoder = decoder;
|
||||
this.typeClass = typeClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialize(T property) {
|
||||
return encoder.apply(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(String property) {
|
||||
return decoder.apply(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getTypeClass() {
|
||||
return typeClass;
|
||||
}
|
||||
}
|
|
@ -10,15 +10,17 @@ import lol.pyr.znpcsplus.interaction.ActionRegistry;
|
|||
import lol.pyr.znpcsplus.packets.PacketFactory;
|
||||
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
|
||||
import lol.pyr.znpcsplus.storage.NpcStorage;
|
||||
import lol.pyr.znpcsplus.storage.NpcStorageType;
|
||||
import lol.pyr.znpcsplus.util.NpcLocation;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NpcRegistryImpl implements NpcRegistry {
|
||||
private final NpcStorage storage;
|
||||
private NpcStorage storage;
|
||||
private final PacketFactory packetFactory;
|
||||
private final ConfigManager configManager;
|
||||
private final LegacyComponentSerializer textSerializer;
|
||||
|
@ -32,6 +34,10 @@ public class NpcRegistryImpl implements NpcRegistry {
|
|||
this.textSerializer = textSerializer;
|
||||
this.propertyRegistry = propertyRegistry;
|
||||
storage = configManager.getConfig().storageType().create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
|
||||
if (storage == null) {
|
||||
Bukkit.getLogger().warning("Failed to initialize storage, falling back to YAML");
|
||||
storage = NpcStorageType.YAML.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
|
||||
}
|
||||
this.packetFactory = packetFactory;
|
||||
this.configManager = configManager;
|
||||
|
||||
|
@ -57,7 +63,9 @@ public class NpcRegistryImpl implements NpcRegistry {
|
|||
}
|
||||
|
||||
private void unregisterAll() {
|
||||
for (NpcEntryImpl entry : npcList) entry.getNpc().delete();
|
||||
for (NpcEntryImpl entry : getAll()) {
|
||||
if (entry.isSave()) entry.getNpc().delete();
|
||||
}
|
||||
npcList.clear();
|
||||
npcIdLookupMap.clear();
|
||||
npcUuidLookupMap.clear();
|
||||
|
|
|
@ -115,7 +115,7 @@ public class NpcTypeImpl implements NpcType {
|
|||
public NpcTypeImpl build() {
|
||||
ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion();
|
||||
addProperties("fire", "invisible", "silent", "look", "look_distance", "view_distance",
|
||||
"potion_color", "potion_ambient");
|
||||
"potion_color", "potion_ambient", "display_name");
|
||||
if (!type.equals(EntityTypes.PLAYER)) addProperties("dinnerbone");
|
||||
// TODO: make this look nicer after completing the rest of the properties
|
||||
if (version.isNewerThanOrEquals(ServerVersion.V_1_9)) addProperties("glow");
|
||||
|
|
|
@ -73,6 +73,10 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
|
|||
.setHologramOffset(-0.275)
|
||||
.addProperties("creeper_state", "creeper_charged"));
|
||||
|
||||
register(builder(p, "end_crystal", EntityTypes.END_CRYSTAL)
|
||||
.setHologramOffset(0.025)
|
||||
.addProperties("beam_target", "show_base"));
|
||||
|
||||
register(builder(p, "ender_dragon", EntityTypes.ENDER_DRAGON)
|
||||
.setHologramOffset(6.0245));
|
||||
|
||||
|
@ -206,8 +210,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
|
|||
.addEquipmentProperties());
|
||||
|
||||
register(builder(p, "evoker", EntityTypes.EVOKER)
|
||||
.setHologramOffset(-0.025)
|
||||
.addProperties("evoker_spell"));
|
||||
.setHologramOffset(-0.025));
|
||||
|
||||
register(builder(p, "llama", EntityTypes.LLAMA)
|
||||
.setHologramOffset(-0.105)
|
||||
|
|
|
@ -14,6 +14,6 @@ public class StringParser extends ParserType<String> {
|
|||
|
||||
@Override
|
||||
public String parse(Deque<String> deque) throws CommandExecutionException {
|
||||
return deque.pollFirst();
|
||||
return String.join(" ", deque);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package lol.pyr.znpcsplus.parsers;
|
||||
|
||||
import lol.pyr.director.adventure.command.CommandContext;
|
||||
import lol.pyr.director.adventure.parse.ParserType;
|
||||
import lol.pyr.director.common.command.CommandExecutionException;
|
||||
import lol.pyr.director.common.message.Message;
|
||||
import lol.pyr.znpcsplus.util.Vector3i;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
public class Vector3iParser extends ParserType<Vector3i> {
|
||||
public Vector3iParser(Message<CommandContext> message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3i parse(Deque<String> deque) throws CommandExecutionException {
|
||||
if (deque.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new Vector3i(
|
||||
Integer.parseInt(deque.pop()),
|
||||
Integer.parseInt(deque.pop()),
|
||||
Integer.parseInt(deque.pop()));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new CommandExecutionException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
|
|||
import lol.pyr.znpcsplus.interaction.ActionRegistry;
|
||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
|
||||
import lol.pyr.znpcsplus.packets.PacketFactory;
|
||||
import lol.pyr.znpcsplus.storage.sqlite.SQLiteStorage;
|
||||
import lol.pyr.znpcsplus.storage.yaml.YamlStorage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
|
@ -17,6 +18,17 @@ public enum NpcStorageType {
|
|||
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistry actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
|
||||
return new YamlStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "data"));
|
||||
}
|
||||
},
|
||||
SQLITE {
|
||||
@Override
|
||||
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistry actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
|
||||
try {
|
||||
return new SQLiteStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "znpcsplus.sqlite"));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public abstract NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistry actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package lol.pyr.znpcsplus.storage.database;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public abstract class Database {
|
||||
protected final Logger logger;
|
||||
protected Connection connection;
|
||||
public Database(Logger logger){
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public abstract Connection getSQLConnection();
|
||||
|
||||
public abstract void load();
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package lol.pyr.znpcsplus.storage.sqlite;
|
||||
|
||||
import lol.pyr.znpcsplus.storage.database.Database;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class SQLite extends Database{
|
||||
private final File dbFile;
|
||||
public SQLite(File file, Logger logger){
|
||||
super(logger);
|
||||
dbFile = file;
|
||||
}
|
||||
|
||||
public Connection getSQLConnection() {
|
||||
if (!dbFile.exists()){
|
||||
try {
|
||||
dbFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "File write error: "+dbFile.getName());
|
||||
}
|
||||
}
|
||||
try {
|
||||
if(connection!=null&&!connection.isClosed()){
|
||||
return connection;
|
||||
}
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||
return connection;
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE,"SQLite exception on initialize", ex);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
logger.log(Level.SEVERE, "SQLite JDBC library not found", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
connection = getSQLConnection();
|
||||
}
|
||||
|
||||
public boolean tableExists(String tableName) {
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
s.executeQuery("SELECT * FROM " + tableName + ";");
|
||||
s.close();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean columnExists(String tableName, String columnName) {
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
s.executeQuery("SELECT " + columnName + " FROM " + tableName + ";");
|
||||
s.close();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addColumn(String tableName, String columnName, String type) {
|
||||
if (columnExists(tableName, columnName)) return false;
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
s.executeQuery("ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + type + ";");
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public ResultSet executeQuery(String query) {
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
ResultSet rs = s.executeQuery(query);
|
||||
s.close();
|
||||
return rs;
|
||||
} catch (SQLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int executeUpdate(String query) {
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
int rowCount = s.executeUpdate(query);
|
||||
s.close();
|
||||
return rowCount;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
package lol.pyr.znpcsplus.storage.sqlite;
|
||||
|
||||
import lol.pyr.znpcsplus.api.entity.EntityProperty;
|
||||
import lol.pyr.znpcsplus.config.ConfigManager;
|
||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
|
||||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
|
||||
import lol.pyr.znpcsplus.entity.PropertySerializer;
|
||||
import lol.pyr.znpcsplus.hologram.HologramImpl;
|
||||
import lol.pyr.znpcsplus.interaction.ActionRegistry;
|
||||
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
|
||||
import lol.pyr.znpcsplus.npc.NpcImpl;
|
||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
|
||||
import lol.pyr.znpcsplus.packets.PacketFactory;
|
||||
import lol.pyr.znpcsplus.storage.NpcStorage;
|
||||
import lol.pyr.znpcsplus.util.NpcLocation;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SQLiteStorage implements NpcStorage {
|
||||
private final static Logger logger = Logger.getLogger("SQLiteStorage");
|
||||
|
||||
private final PacketFactory packetFactory;
|
||||
private final ConfigManager configManager;
|
||||
private final ActionRegistry actionRegistry;
|
||||
private final NpcTypeRegistryImpl typeRegistry;
|
||||
private final EntityPropertyRegistryImpl propertyRegistry;
|
||||
private final LegacyComponentSerializer textSerializer;
|
||||
private final SQLite database;
|
||||
|
||||
private final String TABLE_NPCS;
|
||||
private final String TABLE_NPCS_PROPERTIES;
|
||||
private final String TABLE_NPCS_HOLOGRAMS;
|
||||
private final String TABLE_NPCS_ACTIONS;
|
||||
|
||||
public SQLiteStorage(PacketFactory packetFactory, ConfigManager configManager, ActionRegistry actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, File file) {
|
||||
this.packetFactory = packetFactory;
|
||||
this.configManager = configManager;
|
||||
this.actionRegistry = actionRegistry;
|
||||
this.typeRegistry = typeRegistry;
|
||||
this.propertyRegistry = propertyRegistry;
|
||||
this.textSerializer = textSerializer;
|
||||
this.database = new SQLite(file, logger);
|
||||
database.load();
|
||||
if (database.getSQLConnection() == null) {
|
||||
throw new RuntimeException("Failed to initialize SQLite Storage.");
|
||||
}
|
||||
TABLE_NPCS = "npcs";
|
||||
TABLE_NPCS_PROPERTIES = "npcs_properties";
|
||||
TABLE_NPCS_HOLOGRAMS = "npcs_holograms";
|
||||
TABLE_NPCS_ACTIONS = "npcs_actions";
|
||||
validateTables();
|
||||
}
|
||||
|
||||
private void validateTables() {
|
||||
if (!database.tableExists(TABLE_NPCS)) {
|
||||
logger.info("Creating table " + TABLE_NPCS + "...");
|
||||
createNpcsTable();
|
||||
}
|
||||
if (!database.tableExists(TABLE_NPCS_PROPERTIES)) {
|
||||
logger.info("Creating table " + TABLE_NPCS_PROPERTIES + "...");
|
||||
createNpcsPropertiesTable();
|
||||
}
|
||||
if (!database.tableExists(TABLE_NPCS_HOLOGRAMS)) {
|
||||
logger.info("Creating table " + TABLE_NPCS_HOLOGRAMS + "...");
|
||||
createNpcsHologramsTable();
|
||||
}
|
||||
if (!database.tableExists(TABLE_NPCS_ACTIONS)) {
|
||||
logger.info("Creating table " + TABLE_NPCS_ACTIONS + "...");
|
||||
createNpcsActionsTable();
|
||||
}
|
||||
updateTables();
|
||||
}
|
||||
|
||||
private void createNpcsTable() {
|
||||
if (database.executeUpdate("CREATE TABLE " + TABLE_NPCS + " " +
|
||||
"(id TEXT PRIMARY KEY, isProcessed BOOLEAN, allowCommands BOOLEAN, enabled BOOLEAN, " +
|
||||
"uuid TEXT, world TEXT, x REAL, y REAL, z REAL, yaw REAL, pitch REAL, type TEXT, hologramOffset REAL, hologramRefreshDelay INTEGER)") != -1) {
|
||||
logger.info("Table " + TABLE_NPCS + " created.");
|
||||
} else {
|
||||
logger.severe("Failed to create table " + TABLE_NPCS + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private void createNpcsPropertiesTable() {
|
||||
if (database.executeUpdate("CREATE TABLE " + TABLE_NPCS_PROPERTIES + " " +
|
||||
"(npc_id TEXT, property TEXT, value TEXT, PRIMARY KEY (npc_id, property))") != -1) {
|
||||
logger.info("Table " + TABLE_NPCS_PROPERTIES + " created.");
|
||||
} else {
|
||||
logger.severe("Failed to create table " + TABLE_NPCS_PROPERTIES + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private void createNpcsHologramsTable() {
|
||||
if (database.executeUpdate("CREATE TABLE " + TABLE_NPCS_HOLOGRAMS + " " +
|
||||
"(npc_id TEXT, line INTEGER, text TEXT, PRIMARY KEY (npc_id, line))") != -1) {
|
||||
logger.info("Table " + TABLE_NPCS_HOLOGRAMS + " created.");
|
||||
} else {
|
||||
logger.severe("Failed to create table " + TABLE_NPCS_HOLOGRAMS + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private void createNpcsActionsTable() {
|
||||
if (database.executeUpdate("CREATE TABLE " + TABLE_NPCS_ACTIONS + " " +
|
||||
"(npc_id TEXT, action_id INTEGER, action_data TEXT, PRIMARY KEY (npc_id, action_id))") != -1) {
|
||||
logger.info("Table " + TABLE_NPCS_ACTIONS + " created.");
|
||||
} else {
|
||||
logger.severe("Failed to create table " + TABLE_NPCS_ACTIONS + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTables() {
|
||||
// Any table updates go here
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<NpcEntryImpl> loadNpcs() {
|
||||
Map<String, NpcEntryImpl> npcMap = new HashMap<>();
|
||||
try {
|
||||
PreparedStatement st = database.getSQLConnection().prepareStatement("SELECT * FROM " + TABLE_NPCS);
|
||||
ResultSet rs = st.executeQuery();
|
||||
while (rs.next()) {
|
||||
NpcImpl npc = new NpcImpl(UUID.fromString(rs.getString("uuid")), propertyRegistry, configManager, packetFactory, textSerializer,
|
||||
rs.getString("world"), typeRegistry.getByName(rs.getString("type")),
|
||||
new NpcLocation(rs.getDouble("x"), rs.getDouble("y"), rs.getDouble("z"), rs.getFloat("yaw"), rs.getFloat("pitch")));
|
||||
|
||||
if (!rs.getBoolean("enabled")) npc.setEnabled(false);
|
||||
|
||||
npc.getHologram().setOffset(rs.getDouble("hologramOffset"));
|
||||
if (rs.getBigDecimal("hologramRefreshDelay") != null) npc.getHologram().setRefreshDelay(rs.getBigDecimal("hologramRefreshDelay").longValue());
|
||||
|
||||
NpcEntryImpl entry = new NpcEntryImpl(rs.getString("id"), npc);
|
||||
entry.setProcessed(rs.getBoolean("isProcessed"));
|
||||
entry.setAllowCommandModification(rs.getBoolean("allowCommands"));
|
||||
entry.setSave(true);
|
||||
npcMap.put(rs.getString("id"), entry);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
PreparedStatement st = database.getSQLConnection().prepareStatement("SELECT * FROM " + TABLE_NPCS_PROPERTIES);
|
||||
ResultSet rs = st.executeQuery();
|
||||
while (rs.next()) {
|
||||
NpcEntryImpl entry = npcMap.get(rs.getString("npc_id"));
|
||||
String key = rs.getString("property");
|
||||
if (entry != null) {
|
||||
EntityPropertyImpl<?> property = propertyRegistry.getByName(key);
|
||||
if (property == null) {
|
||||
logger.warning("Unknown property '" + key + "' for npc '" + rs.getString("npc_id") + "'. skipping ...");
|
||||
continue;
|
||||
}
|
||||
PropertySerializer<?> serializer = propertyRegistry.getSerializer(property.getType());
|
||||
if (serializer == null) {
|
||||
logger.warning("Unknown serializer for property '" + key + "' for npc '" + rs.getString("npc_id") + "'. skipping ...");
|
||||
continue;
|
||||
}
|
||||
Object value = serializer.deserialize(rs.getString("value"));
|
||||
if (value == null) {
|
||||
logger.warning("Failed to deserialize property '" + key + "' for npc '" + rs.getString("npc_id") + "'. Resetting to default ...");
|
||||
value = property.getDefaultValue();
|
||||
}
|
||||
entry.getNpc().UNSAFE_setProperty(property, value);
|
||||
npcMap.put(rs.getString("npc_id"), entry);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
PreparedStatement st = database.getSQLConnection().prepareStatement("SELECT * FROM " + TABLE_NPCS_HOLOGRAMS + " ORDER BY line ASC");
|
||||
ResultSet rs = st.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
NpcEntryImpl entry = npcMap.get(rs.getString("npc_id"));
|
||||
if (entry != null) {
|
||||
entry.getNpc().getHologram().insertLine(rs.getInt("line"), rs.getString("text"));
|
||||
}
|
||||
npcMap.put(rs.getString("npc_id"), entry);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
PreparedStatement st = database.getSQLConnection().prepareStatement("SELECT * FROM " + TABLE_NPCS_ACTIONS + " ORDER BY action_id ASC");
|
||||
ResultSet rs = st.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
NpcEntryImpl entry = npcMap.get(rs.getString("npc_id"));
|
||||
if (entry != null) {
|
||||
entry.getNpc().addAction(actionRegistry.deserialize(rs.getString("action_data")));
|
||||
}
|
||||
npcMap.put(rs.getString("npc_id"), entry);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return npcMap.values().stream().filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNpcs(Collection<NpcEntryImpl> npcs) {
|
||||
long start = System.currentTimeMillis();
|
||||
for (NpcEntryImpl entry : npcs) try {
|
||||
PreparedStatement ps;
|
||||
ps = database.getSQLConnection().prepareStatement("REPLACE INTO " + TABLE_NPCS + " (id, isProcessed, allowCommands, enabled, uuid, world, x, y, z, yaw, pitch, type, hologramOffset, hologramRefreshDelay) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.setBoolean(2, entry.isProcessed());
|
||||
ps.setBoolean(3, entry.isAllowCommandModification());
|
||||
NpcImpl npc = entry.getNpc();
|
||||
ps.setBoolean(4, npc.isEnabled());
|
||||
ps.setString(5, npc.getUuid().toString());
|
||||
ps.setString(6, npc.getWorldName());
|
||||
ps.setDouble(7, npc.getLocation().getX());
|
||||
ps.setDouble(8, npc.getLocation().getY());
|
||||
ps.setDouble(9, npc.getLocation().getZ());
|
||||
ps.setFloat(10, npc.getLocation().getYaw());
|
||||
ps.setFloat(11, npc.getLocation().getPitch());
|
||||
ps.setString(12, npc.getType().getName());
|
||||
HologramImpl hologram = npc.getHologram();
|
||||
ps.setDouble(13, hologram.getOffset());
|
||||
if(hologram.getRefreshDelay() != -1) ps.setBigDecimal(14, new BigDecimal(hologram.getRefreshDelay()));
|
||||
ps.executeUpdate();
|
||||
|
||||
ps = database.getSQLConnection().prepareStatement("DELETE FROM " + TABLE_NPCS_PROPERTIES + " WHERE npc_id = ?");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.executeUpdate();
|
||||
|
||||
for (EntityProperty<?> property : npc.getAllProperties()) try {
|
||||
PropertySerializer<?> serializer = propertyRegistry.getSerializer(((EntityPropertyImpl<?>) property).getType());
|
||||
if (serializer == null) {
|
||||
logger.warning("Unknown serializer for property '" + property.getName() + "' for npc '" + entry.getId() + "'. skipping ...");
|
||||
continue;
|
||||
}
|
||||
ps = database.getSQLConnection().prepareStatement("REPLACE INTO " + TABLE_NPCS_PROPERTIES + " (npc_id, property, value) VALUES(?,?,?)");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.setString(2, property.getName());
|
||||
ps.setString(3, serializer.UNSAFE_serialize(npc.getProperty(property)));
|
||||
ps.executeUpdate();
|
||||
} catch (Exception exception) {
|
||||
logger.severe("Failed to serialize property " + property.getName() + " for npc with id " + entry.getId());
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
ps = database.getSQLConnection().prepareStatement("DELETE FROM " + TABLE_NPCS_HOLOGRAMS + " WHERE npc_id = ? AND line > ?");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.setInt(2, hologram.getLines().size() - 1);
|
||||
ps.executeUpdate();
|
||||
|
||||
for (int i = 0; i < hologram.getLines().size(); i++) {
|
||||
ps = database.getSQLConnection().prepareStatement("REPLACE INTO " + TABLE_NPCS_HOLOGRAMS + " (npc_id, line, text) VALUES(?,?,?)");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.setInt(2, i);
|
||||
ps.setString(3, hologram.getLine(i));
|
||||
ps.executeUpdate();
|
||||
}
|
||||
|
||||
ps = database.getSQLConnection().prepareStatement("DELETE FROM " + TABLE_NPCS_ACTIONS + " WHERE npc_id = ? AND action_id > ?");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.setInt(2, npc.getActions().size() - 1);
|
||||
ps.executeUpdate();
|
||||
|
||||
for (int i = 0; i < npc.getActions().size(); i++) {
|
||||
ps = database.getSQLConnection().prepareStatement("REPLACE INTO " + TABLE_NPCS_ACTIONS + " (npc_id, action_id, action_data) VALUES(?,?,?)");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.setInt(2, i);
|
||||
String action = actionRegistry.serialize(npc.getActions().get(i));
|
||||
if (action == null) continue;
|
||||
ps.setString(3, action);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
logger.severe("Failed to save npc with id " + entry.getId());
|
||||
exception.printStackTrace();
|
||||
}
|
||||
if (configManager.getConfig().debugEnabled()) {
|
||||
logger.info("Saved " + npcs.size() + " npcs in " + (System.currentTimeMillis() - start) + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteNpc(NpcEntryImpl entry) {
|
||||
try {
|
||||
PreparedStatement ps;
|
||||
ps = database.getSQLConnection().prepareStatement("DELETE FROM " + TABLE_NPCS + " WHERE id = ?");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.executeUpdate();
|
||||
|
||||
ps = database.getSQLConnection().prepareStatement("DELETE FROM " + TABLE_NPCS_PROPERTIES + " WHERE npc_id = ?");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.executeUpdate();
|
||||
|
||||
ps = database.getSQLConnection().prepareStatement("DELETE FROM " + TABLE_NPCS_HOLOGRAMS + " WHERE npc_id = ?");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.executeUpdate();
|
||||
|
||||
ps = database.getSQLConnection().prepareStatement("DELETE FROM " + TABLE_NPCS_ACTIONS + " WHERE npc_id = ?");
|
||||
ps.setString(1, entry.getId());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException exception) {
|
||||
logger.severe("Failed to delete npc with id " + entry.getId());
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue