mirror of
https://github.com/zDevelopers/ImageOnMap.git
synced 2024-11-25 11:35:35 +01:00
New Sign-based Prompt GUI API.
* NEW: Added new Callback class. * NEW: Added ReflectionUtils module. * NEW: Added new PromptGui class. * NEW: MapDetailGui: Implement renaming using new prompt API. * BUG: ExplorerGUI: Fix a crash when setting null data. * BUG: InventoryGUI: Fix titles longer than 32 characters.
This commit is contained in:
parent
48dbdbd9d4
commit
96093898c8
134
src/main/java/fr/moribus/imageonmap/ReflectionUtils.java
Normal file
134
src/main/java/fr/moribus/imageonmap/ReflectionUtils.java
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Moribus
|
||||
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fr.moribus.imageonmap;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
abstract public class ReflectionUtils
|
||||
{
|
||||
static public String getBukkitPackageVersion()
|
||||
{
|
||||
return getBukkitPackageName().substring("org.bukkit.craftbukkit.".length());
|
||||
}
|
||||
|
||||
static public String getBukkitPackageName()
|
||||
{
|
||||
return Bukkit.getServer().getClass().getPackage().getName();
|
||||
}
|
||||
|
||||
static public String getMinecraftPackageName()
|
||||
{
|
||||
return "net.minecraft.server." + getBukkitPackageVersion();
|
||||
}
|
||||
|
||||
static public Class getBukkitClassByName(String name) throws ClassNotFoundException
|
||||
{
|
||||
return Class.forName(getBukkitPackageName() + "." + name);
|
||||
}
|
||||
|
||||
static public Class getMinecraftClassByName(String name) throws ClassNotFoundException
|
||||
{
|
||||
return Class.forName(getMinecraftPackageName() + "." + name);
|
||||
}
|
||||
|
||||
static public Object getFieldValue(Class hClass, Object instance, String name)
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException
|
||||
{
|
||||
return getField(hClass, name).get(instance);
|
||||
}
|
||||
|
||||
static public Object getFieldValue(Object instance, String name)
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException
|
||||
{
|
||||
return getFieldValue(instance.getClass(), instance, name);
|
||||
}
|
||||
|
||||
static public Field getField(Class klass, String name) throws NoSuchFieldException
|
||||
{
|
||||
Field field = klass.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
|
||||
static public Field getField(Class klass, Class type) throws NoSuchFieldException
|
||||
{
|
||||
for(Field field : klass.getDeclaredFields())
|
||||
{
|
||||
if(field.getType().equals(type))
|
||||
{
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException("Class " + klass.getName() + " does not define any field of type " + type.getName());
|
||||
}
|
||||
|
||||
static public void setFieldValue(Object instance, String name, Object value)
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException
|
||||
{
|
||||
setFieldValue(instance.getClass(), instance, name, value);
|
||||
}
|
||||
|
||||
static public void setFieldValue(Class hClass, Object instance, String name, Object value)
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException
|
||||
{
|
||||
getField(hClass, name).set(instance, value);
|
||||
}
|
||||
|
||||
static public Object call(Class hClass, String name, Object ... parameters)
|
||||
throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
|
||||
{
|
||||
return call(hClass, null, name, parameters);
|
||||
}
|
||||
|
||||
static public Object call(Object instance, String name, Object ... parameters)
|
||||
throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
|
||||
{
|
||||
return call(instance.getClass(), instance, name, parameters);
|
||||
}
|
||||
|
||||
static public Object call(Class hClass, Object instance, String name, Object ... parameters)
|
||||
throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
|
||||
{
|
||||
Method method = hClass.getMethod(name, getTypes(parameters));
|
||||
return method.invoke(instance, parameters);
|
||||
}
|
||||
|
||||
static public Object instanciate(Class hClass, Object ... parameters)
|
||||
throws NoSuchMethodException, InstantiationException,
|
||||
IllegalAccessException, IllegalArgumentException, InvocationTargetException
|
||||
{
|
||||
Constructor constructor = hClass.getConstructor(getTypes(parameters));
|
||||
return constructor.newInstance(parameters);
|
||||
}
|
||||
|
||||
static public Class[] getTypes(Object[] objects)
|
||||
{
|
||||
Class[] types = new Class[objects.length];
|
||||
for(int i = 0; i < objects.length; i++)
|
||||
{
|
||||
types[i] = objects[i].getClass();
|
||||
}
|
||||
return types;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Moribus
|
||||
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package fr.moribus.imageonmap.guiproko.core;
|
||||
|
||||
public interface Callback<T>
|
||||
{
|
||||
public void call(T parameter);
|
||||
}
|
@ -98,10 +98,11 @@ abstract public class ExplorerGui<T> extends ActionGui
|
||||
protected void setData(T[] data, int dataWidth)
|
||||
{
|
||||
this.data = data;
|
||||
int dataLength = data == null ? 0 : data.length;
|
||||
if(dataWidth > 0)
|
||||
setDataShape(dataWidth, (int) Math.ceil((double) data.length / (double) dataWidth));
|
||||
setDataShape(dataWidth, (int) Math.ceil((double) dataLength / (double) dataWidth));
|
||||
else
|
||||
setDataShape(0, data.length);
|
||||
setDataShape(0, dataLength);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,6 +35,7 @@ abstract public class InventoryGui extends Gui
|
||||
static protected final int INVENTORY_ROW_SIZE = 9;
|
||||
static protected final int MAX_INVENTORY_COLUMN_SIZE = 6;
|
||||
static protected final int MAX_INVENTORY_SIZE = INVENTORY_ROW_SIZE * MAX_INVENTORY_COLUMN_SIZE;
|
||||
static protected final int MAX_TITLE_LENGTH = 32;
|
||||
|
||||
public InventoryGui()
|
||||
{
|
||||
@ -197,7 +198,14 @@ abstract public class InventoryGui extends Gui
|
||||
* It will be applied on the next GUI update.
|
||||
* @param title The new title of the inventory
|
||||
*/
|
||||
protected void setTitle(String title){this.title = title;}
|
||||
protected void setTitle(String title)
|
||||
{
|
||||
if(title != null && title.length() > MAX_TITLE_LENGTH)
|
||||
{
|
||||
title = title.substring(0, MAX_TITLE_LENGTH - 4) + "...";
|
||||
}
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/** @return The underlying inventory, or null if the Gui has not been opened yet. */
|
||||
public Inventory getInventory() { return inventory; }
|
||||
|
257
src/main/java/fr/moribus/imageonmap/guiproko/core/PromptGui.java
Normal file
257
src/main/java/fr/moribus/imageonmap/guiproko/core/PromptGui.java
Normal file
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Moribus
|
||||
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fr.moribus.imageonmap.guiproko.core;
|
||||
|
||||
import fr.moribus.imageonmap.ImageOnMap;
|
||||
import fr.moribus.imageonmap.PluginLogger;
|
||||
import fr.moribus.imageonmap.ReflectionUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
|
||||
public class PromptGui extends Gui
|
||||
{
|
||||
static private final int SIGN_LINES_COUNT = 4;
|
||||
static private final int SIGN_COLUMNS_COUNT = 15;
|
||||
|
||||
static private boolean isInitialized = false;
|
||||
|
||||
/* ===== Reflection to Sign API ===== */
|
||||
static private Field fieldSign = null;//CraftSign.sign
|
||||
static private Field fieldIsEditable = null;//TileEntitySign.isEditable
|
||||
static private Method methodGetHandle = null;//CraftPlayer.getHandle()
|
||||
static private Method methodOpenSign = null;//EntityHuman.openSign()
|
||||
static private Field fieldSignEditor = null;//TileEntitySign.k{EntityHuman}
|
||||
static private Class classTileEntitySign = null;//CraftBlock.class
|
||||
|
||||
static public boolean isAvailable()
|
||||
{
|
||||
if(!isInitialized) init();
|
||||
return fieldSign != null;
|
||||
}
|
||||
|
||||
static public void prompt(Player owner, Callback<String> callback)
|
||||
{
|
||||
prompt(owner, callback, "");
|
||||
}
|
||||
|
||||
static public void prompt(Player owner, Callback<String> callback, String contents)
|
||||
{
|
||||
Gui.open(owner, new PromptGui(callback, contents));
|
||||
}
|
||||
|
||||
static private void init()
|
||||
{
|
||||
isInitialized = true;
|
||||
|
||||
try
|
||||
{
|
||||
Class CraftSign = ReflectionUtils.getBukkitClassByName("block.CraftSign");
|
||||
classTileEntitySign = ReflectionUtils.getMinecraftClassByName("TileEntitySign");
|
||||
Class CraftPlayer = ReflectionUtils.getBukkitClassByName("entity.CraftPlayer");
|
||||
Class EntityHuman = ReflectionUtils.getMinecraftClassByName("EntityHuman");
|
||||
|
||||
|
||||
fieldSign = ReflectionUtils.getField(CraftSign, "sign");
|
||||
fieldIsEditable = ReflectionUtils.getField(classTileEntitySign, "isEditable");
|
||||
methodGetHandle = CraftPlayer.getDeclaredMethod("getHandle");
|
||||
methodOpenSign = EntityHuman.getDeclaredMethod("openSign", classTileEntitySign);
|
||||
fieldSignEditor = ReflectionUtils.getField(classTileEntitySign, EntityHuman);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLogger.error("Unable to initialize Sign Prompt API", ex);
|
||||
fieldSign = null;
|
||||
}
|
||||
}
|
||||
|
||||
private final Callback<String> callback;
|
||||
private Location signLocation;
|
||||
private String contents;
|
||||
|
||||
public PromptGui(Callback<String> callback, String contents)
|
||||
{
|
||||
this(callback);
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
public PromptGui(Callback<String> callback)
|
||||
{
|
||||
super();
|
||||
registerListener(PromptGuiListener.class);
|
||||
if(!isAvailable()) throw new IllegalStateException("Sign-based prompt GUI are not available");
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void open(final Player player)
|
||||
{
|
||||
super.open(player);
|
||||
|
||||
signLocation = findAvailableLocation(player);
|
||||
Block block = player.getWorld().getBlockAt(signLocation);
|
||||
block.setType(Material.SIGN_POST, false);
|
||||
final Sign sign = (Sign) block.getState();
|
||||
setSignContents(sign, contents);
|
||||
sign.update();
|
||||
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(ImageOnMap.getPlugin(), new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
Object signTE = fieldSign.get(sign);
|
||||
Object playerEntity = methodGetHandle.invoke(player);
|
||||
methodOpenSign.invoke(playerEntity, signTE);
|
||||
}
|
||||
catch(Throwable ex)
|
||||
{
|
||||
PluginLogger.error("Error while opening Sign prompt", ex);
|
||||
}
|
||||
}
|
||||
}, 3);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
Block block = getPlayer().getWorld().getBlockAt(signLocation);
|
||||
block.setType(Material.AIR);
|
||||
super.close();
|
||||
}
|
||||
|
||||
private void validate(String[] lines)
|
||||
{
|
||||
callback.call(getSignContents(lines));
|
||||
this.close();
|
||||
}
|
||||
|
||||
static private String getSignContents(String[] lines)
|
||||
{
|
||||
String content = lines[0].trim();
|
||||
|
||||
for(int i = 1; i < lines.length; i++)
|
||||
{
|
||||
if(lines[i] == null || lines[i].isEmpty()) continue;
|
||||
content += " " + lines[i].trim();
|
||||
}
|
||||
return content.trim();
|
||||
}
|
||||
|
||||
static private void setSignContents(Sign sign, String content)
|
||||
{
|
||||
String[] lines = new String[SIGN_LINES_COUNT + 1];
|
||||
String curLine;
|
||||
int curLineIndex = 0, spacePos;
|
||||
|
||||
if(content != null)
|
||||
{
|
||||
lines[0] = content;
|
||||
while(curLineIndex < SIGN_LINES_COUNT)
|
||||
{
|
||||
curLine = lines[curLineIndex];
|
||||
if(curLine.length() <= SIGN_COLUMNS_COUNT)
|
||||
break;
|
||||
|
||||
spacePos = curLine.lastIndexOf(' ', SIGN_COLUMNS_COUNT);
|
||||
if(spacePos < 0) break;
|
||||
lines[curLineIndex + 1] = curLine.substring(spacePos + 1);
|
||||
lines[curLineIndex] = curLine.substring(0, spacePos);
|
||||
curLineIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = SIGN_LINES_COUNT; i --> 0;)
|
||||
{
|
||||
sign.setLine(i, lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static private Location findAvailableLocation(Player player)
|
||||
{
|
||||
World world = player.getWorld();
|
||||
Chunk playerChunk = player.getLocation().getChunk();
|
||||
Chunk firstChunk = world.getChunkAt(playerChunk.getX() - 1, playerChunk.getZ() - 1);
|
||||
Location firstLoc = firstChunk.getBlock(0, 255, 0).getLocation();
|
||||
Location loc;
|
||||
|
||||
for(int i = 48; i --> 0;)
|
||||
{
|
||||
for(int j = 0; j --> -10;)
|
||||
{
|
||||
for(int k = 48; k --> 0;)
|
||||
{
|
||||
loc = firstLoc.add(i, j, k);
|
||||
if(hasSpace(world, loc))
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static private boolean hasSpace(World world, Location loc)
|
||||
{
|
||||
if(!Material.AIR.equals(world.getBlockAt(loc).getType()))
|
||||
return false;
|
||||
|
||||
for(int i = 1; i --> -1;)
|
||||
{
|
||||
for(int j = 1; j --> -1;)
|
||||
{
|
||||
for(int k = 1; k --> -1;)
|
||||
{
|
||||
if(!Material.AIR.equals(world.getBlockAt(
|
||||
loc.getBlockX() + i, loc.getBlockY() + j, loc.getBlockZ() + k)
|
||||
.getType()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static private final class PromptGuiListener implements Listener
|
||||
{
|
||||
@EventHandler
|
||||
public void onSignChange(SignChangeEvent event)
|
||||
{
|
||||
PromptGui gui = Gui.getOpenGui(event.getPlayer(), PromptGui.class);
|
||||
if(gui == null) return;
|
||||
gui.validate(event.getLines());
|
||||
}
|
||||
}
|
||||
}
|
@ -68,6 +68,25 @@ public class MapDetailGui extends ExplorerGui<Void>
|
||||
else return super.getEmptyViewItem();
|
||||
}
|
||||
|
||||
@GuiAction
|
||||
private void rename()
|
||||
{
|
||||
PromptGui.prompt(getPlayer(), new Callback<String>()
|
||||
{
|
||||
@Override
|
||||
public void call(String newName)
|
||||
{
|
||||
if(newName == null || newName.isEmpty())
|
||||
{
|
||||
getPlayer().sendMessage(ChatColor.RED + "Map names can't be empty.");
|
||||
return;
|
||||
}
|
||||
map.rename(newName);
|
||||
getPlayer().sendMessage(ChatColor.GRAY + "Map successfuly renamed.");
|
||||
}
|
||||
}, map.getName());
|
||||
}
|
||||
|
||||
@GuiAction
|
||||
private void delete()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user