diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/ModBlockStates.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/ModBlockStates.java new file mode 100644 index 0000000..3261e42 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/ModBlockStates.java @@ -0,0 +1,68 @@ +package de.jaschastarke.minecraft.limitedcreative; + +import de.jaschastarke.bukkit.lib.CoreModule; +import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockListener; +import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockStateConfig; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; +import de.jaschastarke.minecraft.limitedcreative.blockstate.PlayerListener; +import de.jaschastarke.modularize.IModule; +import de.jaschastarke.modularize.ModuleEntry; +import de.jaschastarke.modularize.ModuleEntry.ModuleState; + +public class ModBlockStates extends CoreModule { + private BlockStateConfig config; + private FeatureBlockItemSpawn blockDrops; + private DBQueries queries; + + public ModBlockStates(LimitedCreative plugin) { + super(plugin); + } + @Override + public String getName() { + return "BlockState"; + } + + @Override + public void initialize(ModuleEntry entry) { + super.initialize(entry); + + blockDrops = plugin.getModule(FeatureBlockItemSpawn.class); + if (blockDrops == null) + blockDrops = plugin.addModule(new FeatureBlockItemSpawn(plugin)).getModule(); + + config = new BlockStateConfig(this, entry); + plugin.getPluginConfig().registerSection(config); + + listeners.addListener(new BlockListener(this)); + listeners.addListener(new PlayerListener(this)); + } + @Override + public void onEnable() { + try { + queries = new DBQueries(getPlugin().getDatabaseConnection()); + queries.initTable(); + } catch (Exception e) { + e.printStackTrace(); + getLog().warn(plugin.getLocale().trans("block_state.error.sql_connection_failed", getName())); + entry.initialState = ModuleState.NOT_INITIALIZED; + return; + } + super.onEnable(); + + getLog().info(plugin.getLocale().trans("basic.loaded.module")); + } + @Override + public void onDisable() { + super.onDisable(); + } + + public BlockStateConfig getConfig() { + return config; + } + public FeatureBlockItemSpawn getBlockSpawn() { + return blockDrops; + } + public DBQueries getQueries() { + return queries; + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockListener.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockListener.java new file mode 100644 index 0000000..2963ec3 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockListener.java @@ -0,0 +1,70 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate; + +import java.sql.SQLException; +import java.util.Date; + +import org.bukkit.GameMode; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; + +import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; + +public class BlockListener implements Listener { + private ModBlockStates mod; + public BlockListener(ModBlockStates mod) { + this.mod = mod; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockBreak(BlockBreakEvent event) { + if (event.isCancelled()) + return; + + try { + BlockState s = mod.getQueries().find(event.getBlock().getLocation()); + if (s != null) { + if (mod.isDebug()) + mod.getLog().debug("Breaking bad, err.. block: " + s.toString()); + + if (s.getGameMode() == GameMode.CREATIVE && event.getPlayer().getGameMode() != GameMode.CREATIVE) { + if (mod.isDebug()) + mod.getLog().debug("... was placed by creative. Drop prevented"); + mod.getBlockSpawn().block(event.getBlock(), event.getPlayer()); + } + + mod.getQueries().delete(s); + } + } catch (SQLException e) { + mod.getLog().warn("DB-Error while onBlockBreak: "+e.getMessage()); + event.setCancelled(true); + } + } + @EventHandler(priority = EventPriority.MONITOR) + public void onBlockPlace(BlockPlaceEvent event) { + if (event.isCancelled()) + return; + + try { + BlockState s = mod.getQueries().find(event.getBlock().getLocation()); + if (s != null) { + // This shouldn't happen + if (mod.isDebug()) + mod.getLog().debug("Replacing current BlockState: " + s.toString()); + } else { + s = new BlockState(); + s.setLocation(event.getBlock().getLocation()); + } + s.setPlayer(event.getPlayer()); + s.setDate(new Date()); + if (mod.isDebug()) + mod.getLog().debug("Saving BlockState: " + s.toString()); + mod.getQueries().insert(s); + } catch (SQLException e) { + mod.getLog().warn("DB-Error while onBlockPlace: "+e.getMessage()); + event.setCancelled(true); + } + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockState.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockState.java new file mode 100644 index 0000000..46160ce --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockState.java @@ -0,0 +1,114 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate; + +import java.util.Date; + +import javax.persistence.Column; +//import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +//import javax.persistence.IdClass; +import javax.persistence.Table; + +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import com.avaje.ebean.validation.NotNull; + +/** + * @TODO: A Database Table-Generation based on Annotations (much work). all those Annotations here have not effect yet + */ +@Entity +@Table(name = "block_state") +//@IdClass(BlockLocation.class) +public class BlockState { + public static enum Source { + SEED, // There is no way to determine this source, but lets be prepared for miracles ;) + PLAYER, + EDIT, // WorldEdit or MCEdit or such, we also can't determine that. But I keep believing in miracles + UNKNOWN + } + + private Location location; + + @Column(name = "gm") + private GameMode gameMode; + + @Column(name = "player") + private String playerName; + + @NotNull + @Column(name = "cdate") + private Date date; + + @NotNull + private Source source = Source.UNKNOWN; + + + public Location getLocation() { + return location; + } + + public void setLocation(Location loc) { + location = loc; + } + + public GameMode getGameMode() { + return gameMode; + } + + public void setGameMode(GameMode gm) { + this.gameMode = gm; + } + + public String getPlayerName() { + return playerName; + } + + public void setPlayerName(String s) { + playerName = s; + } + + public OfflinePlayer getPlayer() { + OfflinePlayer p = Bukkit.getPlayerExact(playerName); + if (p == null) + p = Bukkit.getOfflinePlayer(playerName); + return p; + } + + public void setPlayer(OfflinePlayer player) { + setSource(Source.PLAYER); + this.playerName = player.getName(); + if (player instanceof Player) { + setGameMode(((Player) player).getGameMode()); + } + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public Source getSource() { + return source; + } + + public void setSource(Source source) { + if (source != Source.PLAYER) + setPlayer(null); + this.source = source; + } + + @Override + public String toString() { + //return blockLocation.toString() + " by " + + return location.toString() + " by " + + (source == Source.PLAYER ? playerName : (source.toString() + (playerName != null ? "(" + playerName + ")" : ""))) + + (gameMode != null ? "" : (" in GM: " + gameMode)) + + " at " + date.toString(); + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateConfig.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateConfig.java new file mode 100644 index 0000000..97f9ca8 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateConfig.java @@ -0,0 +1,134 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; + +import de.jaschastarke.bukkit.lib.configuration.Configuration; +import de.jaschastarke.configuration.IConfigurationNode; +import de.jaschastarke.configuration.IConfigurationSubGroup; +import de.jaschastarke.configuration.InvalidValueException; +import de.jaschastarke.configuration.annotations.IsConfigurationNode; +import de.jaschastarke.maven.ArchiveDocComments; +import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; +import de.jaschastarke.modularize.IModule; +import de.jaschastarke.modularize.ModuleEntry; +import de.jaschastarke.modularize.ModuleEntry.ModuleState; + +/** + * BlockState-Feature + * + * http://dev.bukkit.org/server-mods/limited-creative/pages/features/blockstate/ + */ +@ArchiveDocComments +public class BlockStateConfig extends Configuration implements IConfigurationSubGroup { + protected ModBlockStates mod; + protected ModuleEntry entry; + + public BlockStateConfig(ModBlockStates mod, ModuleEntry modEntry) { + this.mod = mod; + entry = modEntry; + } + + @Override + public void setValue(IConfigurationNode node, Object pValue) throws InvalidValueException { + if (node.getName().equals("tool")) + setTool(pValue); + else + super.setValue(node, pValue); + if (node.getName().equals("enabled")) { + if (getEnabled()) { + if (entry.initialState != ModuleState.NOT_INITIALIZED) + entry.enable(); + } else { + entry.disable(); + } + } + } + + @Override + public void setValues(ConfigurationSection sect) { + super.setValues(sect); + if (entry.initialState != ModuleState.NOT_INITIALIZED) + entry.initialState = getEnabled() ? ModuleState.ENABLED : ModuleState.DISABLED; + } + @Override + public String getName() { + return "blockstate"; + } + @Override + public int getOrder() { + return 700; + } + + /** + * BlockStateEnabled + * + * This experimental Feature stores the GameMode a Block was created in, and prevents drops if a Block was created + * in creative mode. + * + * Due to the Experimental state this Feature isn't enabled by default. It uses the Database-credentials from + * bukkit.yml (http://wiki.bukkit.org/Bukkit.yml#database) in the server-directory. + * + * default: false + */ + @IsConfigurationNode(order = 100) + public boolean getEnabled() { + return config.getBoolean("enabled", true); + } + + /** + * BlockStateTool + * + * The id or technical name (http://tinyurl.com/bukkit-material) of an item that displays information about the + * right-clicked block. + * + * default: WOOD_PICKAXE + */ + @IsConfigurationNode(order = 200) + public Material getTool() { + if (config.isString("tool")) { + Material v = Material.getMaterial(config.getString("tool")); + if (v != null) + return v; + } else if (config.isInt("tool")) { + Material v = Material.getMaterial(config.getInt("tool")); + if (v != null) + return v; + } else { + Object v = config.get("tool", Material.WOOD_PICKAXE); + if (v instanceof Material) + return (Material) v; + } + mod.getLog().warn("Unknown BlockStateTool: " + config.get("tool")); + return Material.WOOD_PICKAXE; + } + + protected void setTool(Object val) throws InvalidValueException { + String v = (String) val; + Material m = null; + try { + int i = Integer.parseInt(v); + if (i > 0) + m = Material.getMaterial(i); + } catch (NumberFormatException e) { + m = null; + } + if (m == null) + m = Material.getMaterial(v); + if (m == null) + throw new InvalidValueException("Material '" + v + "' not found"); + else + config.set("tool", m); + } + + + @Override + public Object getValue(final IConfigurationNode node) { + Object val = super.getValue(node); + if (node.getName().equals("tool") && val != null) { + return val.toString(); + } else { + return val; + } + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStatePermissions.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStatePermissions.java new file mode 100644 index 0000000..4a1f5f6 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStatePermissions.java @@ -0,0 +1,43 @@ +/* + * Limited Creative - (Bukkit Plugin) + * Copyright (C) 2012 jascha@ja-s.de + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.jaschastarke.minecraft.limitedcreative.blockstate; + +import org.bukkit.permissions.PermissionDefault; + +import de.jaschastarke.maven.ArchiveDocComments; +import de.jaschastarke.minecraft.lib.permissions.BasicPermission; +import de.jaschastarke.minecraft.lib.permissions.IAbstractPermission; +import de.jaschastarke.minecraft.lib.permissions.IPermission; +import de.jaschastarke.minecraft.lib.permissions.IPermissionContainer; +import de.jaschastarke.minecraft.lib.permissions.SimplePermissionContainerNode; +import de.jaschastarke.minecraft.limitedcreative.Permissions; + +@ArchiveDocComments +public class BlockStatePermissions extends SimplePermissionContainerNode { + public BlockStatePermissions(IAbstractPermission parent, String name) { + super(parent, name); + } + + + public static final IPermissionContainer PARENT = new BlockStatePermissions(Permissions.CONTAINER, "blockstate"); + + /** + * Grants ability to use the configured tool to get info about placed blocks. + */ + public static final IPermission TOOL = new BasicPermission(PARENT, "tool", PermissionDefault.OP); +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBQueries.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBQueries.java new file mode 100644 index 0000000..394647c --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBQueries.java @@ -0,0 +1,153 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.bukkit.GameMode; +import org.bukkit.Location; + +import de.jaschastarke.database.Type; +import de.jaschastarke.database.db.Database; +import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState.Source; + +public class DBQueries { + private Database db; + public DBQueries(Database db) { + this.db = db; + } + + private PreparedStatement find = null; + public BlockState find(Location loc) throws SQLException { + if (find == null) { + find = db.prepare("SELECT * FROM lc_block_state WHERE x = ? AND y = ? AND z = ? AND world = ?"); + } + find.setInt(1, loc.getBlockX()); + find.setInt(2, loc.getBlockY()); + find.setInt(3, loc.getBlockZ()); + find.setString(4, loc.getWorld().getUID().toString()); + ResultSet rs = find.executeQuery(); + if (rs.next()) { + BlockState bs = new BlockState(); + bs.setLocation(loc); + bs.setDate(rs.getDate("cdate")); + bs.setGameMode(getGameMode(rs)); + bs.setPlayerName(rs.getString("player")); + bs.setSource(getSource(rs)); + return bs; + } + return null; + } + + private PreparedStatement delete = null; + public boolean delete(BlockState s) throws SQLException { + if (delete == null) { + delete = db.prepare("DELETE FROM lc_block_state WHERE x = ? AND y = ? AND z = ? AND world = ?"); + } + delete.setInt(1, s.getLocation().getBlockX()); + delete.setInt(2, s.getLocation().getBlockY()); + delete.setInt(3, s.getLocation().getBlockZ()); + delete.setString(4, s.getLocation().getWorld().getUID().toString()); + return delete.executeUpdate() > 0; + } + + private PreparedStatement insert = null; + public boolean insert(BlockState s) throws SQLException { + if (insert == null) { + insert = db.prepare("INSERT INTO lc_block_state (x, y, z, world, gm, player, cdate, source)"+ + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + } + insert.setInt(1, s.getLocation().getBlockX()); + insert.setInt(2, s.getLocation().getBlockY()); + insert.setInt(3, s.getLocation().getBlockZ()); + insert.setString(4, s.getLocation().getWorld().getUID().toString()); + if (db.getType() == Type.MySQL) + insert.setString(5, s.getGameMode().name()); + else + insert.setInt(5, s.getGameMode().getValue()); + insert.setString(6, s.getPlayerName()); + insert.setTimestamp(7, new java.sql.Timestamp(s.getDate().getTime())); + if (db.getType() == Type.MySQL) + insert.setString(8, s.getSource().name()); + else + insert.setInt(8, s.getSource().ordinal()); + return insert.executeUpdate() > 0; + } + + private GameMode getGameMode(ResultSet rs) { + try { + switch (db.getType()) { + case SQLite: + return GameMode.getByValue(rs.getInt("gm")); + case MySQL: + return GameMode.valueOf(rs.getString("gm")); + default: + throw new RuntimeException("Unsupported Database-Type."); + } + } catch (SQLException e) { + db.getLogger().warn("Couldn't get GameMode from result-set: "+e.getMessage()); + return GameMode.SURVIVAL; + } + } + + private Source getSource(ResultSet rs) { + try { + switch (db.getType()) { + case SQLite: + return Source.values()[rs.getInt("source")]; + case MySQL: + return Source.valueOf(rs.getString("source")); + default: + throw new RuntimeException("Unsupported Database-Type."); + } + } catch (Exception e) { + db.getLogger().warn("Couldn't get Source from result-set: "+e.getMessage()); + return Source.UNKNOWN; + } + } + + public void initTable() throws SQLException { + switch (db.getType()) { + case SQLite: + if (!db.getDDL().tableExists("lc_block_state")) { + db.execute( + "CREATE TABLE lc_block_state ("+ + "x integer,"+ + "y integer,"+ + "z integer,"+ + "world varchar(40),"+ + "gm integer,"+ + "player varchar(255),"+ + "cdate timestamp not null,"+ + "source integer not null,"+ + "primary key (x, y, z, world),"+ + "constraint ck_lc_block_state_gm check (gm in (0,1,2)),"+ + "constraint ck_lc_block_state_source check (source in (0,1,2,3))"+ + ")" + ); + db.getLogger().info("Created SQLite-Table: lc_block_state"); + } + break; + case MySQL: + if (!db.getDDL().tableExists("lc_block_state")) { + db.execute( + "CREATE TABLE IF NOT EXISTS lc_block_state ("+ + "x INT NOT NULL,"+ + "y INT NOT NULL,"+ + "z INT NOT NULL,"+ + "world VARCHAR(40) NOT NULL,"+ + "gm ENUM('CREATIVE', 'SURVIVAL', 'ADVENTURE'),"+ + "player VARCHAR(255),"+ + "cdate TIMESTAMP NOT NULL,"+ + "source ENUM('SEED','PLAYER','EDIT','UNKNOWN'),"+ + "PRIMARY KEY (x, y, z, world)"+ + ")" + ); + db.getLogger().info("Created MySQL-Table: lc_block_state"); + } + break; + default: + throw new RuntimeException("Currently only SQLite is supported."); + } + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/PlayerListener.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/PlayerListener.java new file mode 100644 index 0000000..40fae9c --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/PlayerListener.java @@ -0,0 +1,64 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate; + +import java.sql.SQLException; + +import org.bukkit.ChatColor; +import org.bukkit.block.Block; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; + +import de.jaschastarke.bukkit.lib.chat.ChatFormattings; +import de.jaschastarke.bukkit.lib.chat.InGameFormatter; +import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; +import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState.Source; + +public class PlayerListener implements Listener { + private ModBlockStates mod; + public PlayerListener(ModBlockStates mod) { + this.mod = mod; + } + + @EventHandler(priority = EventPriority.HIGH) + public void onInteract(PlayerInteractEvent event) { + if (event.isCancelled()) + return; + if (event.getAction() == Action.RIGHT_CLICK_BLOCK && mod.getPlugin().getPermManager().hasPermission(event.getPlayer(), BlockStatePermissions.TOOL)) { + Block b = event.getClickedBlock(); + if (b != null && event.getPlayer().getItemInHand().getType().equals(mod.getConfig().getTool())) { + try { + BlockState s = mod.getQueries().find(b.getLocation()); + InGameFormatter f = new InGameFormatter(mod.getPlugin().getLang()); + String ret = null; + if (s == null || s.getSource() == Source.UNKNOWN) { + ret = f.formatString(ChatFormattings.ERROR, f.getString("block_state.tool_info.unknown", b.getType().toString())); + } else { + String k = "block_state.tool_info." + s.getSource().name().toLowerCase(); + String gm = s.getGameMode().toString().toLowerCase(); + switch (s.getGameMode()) { + case CREATIVE: + gm = ChatColor.GOLD + gm + ChatColor.RESET; + case SURVIVAL: + gm = ChatColor.GREEN + gm + ChatColor.RESET; + case ADVENTURE: + gm = ChatColor.DARK_GREEN + gm + ChatColor.RESET; + default: + break; + } + + ret = f.formatString(ChatFormattings.INFO, f.getString(k, b.getType().toString(), + s.getPlayerName(), + gm, + s.getDate())); + } + if (ret != null) + event.getPlayer().sendMessage(ret); + } catch (SQLException e) { + mod.getLog().warn("DB-Error while onPlayerInteract: "+e.getMessage()); + } + } + } + } +} diff --git a/src/main/resources/lang/messages.properties b/src/main/resources/lang/messages.properties index c6e77c9..4010789 100644 --- a/src/main/resources/lang/messages.properties +++ b/src/main/resources/lang/messages.properties @@ -49,3 +49,4 @@ block_state.tool_info.seed: This {0}-Block is generated by the god who created t block_state.tool_info.player: This {0}-Block was created by ''{1}'' in {2}-mode on {3} block_state.tool_info.edit: This {0}-Block was modified by the tool ''{1}'' or such at {3} block_state.tool_info.unknown: The origin of this {0}-Block is unknown +block_state.error.sql_connection_failed: Failed to connect to Database. Check bukkit.yml \ No newline at end of file