Added events for components and implemented 'buildclientconfiguration'-event in ClientConfigurationComponent.

This commit is contained in:
FrozenCow 2011-05-19 20:36:56 +02:00
parent 38c8254707
commit e57301b14e
13 changed files with 358 additions and 129 deletions

View File

@ -1,8 +1,43 @@
# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/plugins/dynmap/
components:
- class: org.dynmap.TestComponent
stuff: "this is some stuff"
- class: org.dynmap.ClientConfigurationComponent
- class: org.dynmap.ClientComponent
type: chat
- class: org.dynmap.ClientComponent
type: chatballoon
focuschatballoons: false
- class: org.dynmap.ClientComponent
type: chatbox
showplayerfaces: true
messagettl: 5
- class: org.dynmap.ClientComponent
type: playermarkers
showplayerfaces: true
showplayerhealth: false
#- class: org.dynmap.ClientComponent
# type: digitalclock
- class: org.dynmap.ClientComponent
type: timeofdayclock
showdigitalclock: true
#showweather: true
#- class: org.dynmap.ClientComponent
# type: regions
# name: WorldGuard
# useworldpath: true
# filename: regions.yml
# basenode: regions
# use3dregions: true
# infowindow: '<div class="infowindow"><span style="font-size:120%;">%regionname% - %priority% (%parent%)</span><br /> Owners <span style="font-weight:bold;">%playerowners% %groupowners%</span><br />Members <span style="font-weight:bold;">%playermembers% %groupmembers%</span><br />Flags<br /><span style="font-weight:bold;">%flags%</span></div>'
# regionstyle:
# strokeColor: "#FF0000"
# strokeOpacity: 0.8
# strokeWeight: 3
# fillColor: "#FF0000"
# fillOpacity: 0.35
#- class: org.dynmap.TestComponent
# stuff: "This is some configuration-value"
# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false)
display-whitelist: false
@ -49,16 +84,46 @@ timeslicerender: true
# Period between tile renders for timesliced fullrender, in seconds
timesliceinterval: 0.5
# Interval the browser should poll for updates.
updaterate: 2000
allowchat: true
allowwebchat: true
webchat-interval: 5
# Set to true to enable HeroChat support
enableherochat: false
# Control which HeroChat channel messages from web are directed to
herochatwebchannel: Global
# Control which channels are monitored and reported to the web
herochatchannels:
- Global
#- Trade
#- Haggle
showplayerfacesinmenu: true
joinmessage: "%playername% joined"
quitmessage: "%playername% quit"
spammessage: "You may only chat once every %interval% seconds."
defaultzoom: 0
defaultworld: world
# The maptypes Dynmap will use to render.
worlds:
- name: world
title: "World"
maps:
- class: org.dynmap.flat.FlatMap
name: flat
title: "Flat"
prefix: flat
colorscheme: default
- class: org.dynmap.kzedmap.KzedMap
renderers:
- class: org.dynmap.kzedmap.DefaultTileRenderer
name: surface
title: "Surface"
prefix: t
maximumheight: 127
colorscheme: default
@ -73,128 +138,27 @@ worlds:
# - 66 # Highlight minecart track
# highlight: 56 # For highlighting a single block-type.
- class: org.dynmap.kzedmap.CaveTileRenderer
name: cave
title: "Cave"
prefix: ct
maximumheight: 127
- name: nether
title: "Nether"
maps:
- class: org.dynmap.flat.FlatMap
name: flat
title: "Flat"
prefix: flat
colorscheme: default
- class: org.dynmap.kzedmap.KzedMap
renderers:
- class: org.dynmap.kzedmap.DefaultTileRenderer
name: nether
title: "Surface"
prefix: nt
maximumheight: 127
colorscheme: default
web:
# Handles the clientside updates differently only enable if using jsonfile
jsonfile: false
# Interval the browser should poll for updates.
updaterate: 2000
allowchat: true
allowwebchat: true
webchat-interval: 5
# Set to true to enable HeroChat support
enableherochat: false
# Control which HeroChat channel messages from web are directed to
herochatwebchannel: Global
# Control which channels are monitored and reported to the web
herochatchannels:
- Global
#- Trade
#- Haggle
showplayerfacesinmenu: true
joinmessage: "%playername% joined"
quitmessage: "%playername% quit"
spammessage: "You may only chat once every %interval% seconds."
components:
- type: chat
- type: chatballoon
focuschatballoons: false
- type: chatbox
showplayerfaces: true
messagettl: 5
- type: playermarkers
showplayerfaces: true
showplayerhealth: false
#- type: digitalclock
- type: timeofdayclock
showdigitalclock: true
#showweather: true
#- type: regions
# name: WorldGuard
# useworldpath: true
# filename: regions.yml
# basenode: regions
# use3dregions: true
# infowindow: '<div class="infowindow"><span style="font-size:120%;">%regionname% - %priority% (%parent%)</span><br /> Owners <span style="font-weight:bold;">%playerowners% %groupowners%</span><br />Members <span style="font-weight:bold;">%playermembers% %groupmembers%</span><br />Flags<br /><span style="font-weight:bold;">%flags%</span></div>'
# regionstyle:
# strokeColor: "#FF0000"
# strokeOpacity: 0.8
# strokeWeight: 3
# fillColor: "#FF0000"
# fillOpacity: 0.35
defaultzoom: 0
defaultworld: world
worlds:
- title: World
name: world
center:
x: 0
y: 64
z: 0
maps:
- type: FlatMapType
title: Flat
name: flat
prefix: flat
- type: KzedMapType
title: Surface
name: surface
prefix: t
#- type: KzedMapType
# title: Highlighted Map
# name: highlight
# prefix: ht
- type: KzedMapType
title: Cave
name: cave
prefix: ct
- title: Nether
name: nether
center:
x: 0
y: 64
z: 0
maps:
- type: FlatMapType
title: Flat
name: flat
prefix: flat
- type: KzedMapType
title: Surface
name: nether
prefix: nt
# Example:
#- title: Other World # With what name the world is displayed.
# name: world_other # The actual name of the world (equal to your directory-name).
# maps:
# - type: KzedMapType # The type (or perspective) of the map. At the moment, there are no others than KzedMapType.
# title: Surface # The name of the map that will be displayed.
# name: surface # The actual name of the map (should be unique for this world).
# prefix: t # The prefix of the tile-files that are generated.
# icon: images/block_other.png # Sets a custom icon for the map. (optional)
# - type: KzedMapType
# title: Cave
# name: cave
# prefix: ct
# Enables debugging.
#debuggers:
# - class: org.dynmap.debug.LogDebugger

View File

@ -0,0 +1,50 @@
package org.dynmap;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.s;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
public class ClientComponent extends Component {
public ClientComponent(final DynmapPlugin plugin, final ConfigurationNode configuration) {
super(plugin, configuration);
plugin.events.addListener("buildclientconfiguration", new Event.Listener<JSONObject>() {
@Override
public void triggered(JSONObject t) {
JSONObject o = convertMap(configuration);
o.remove("class");
a(t, "components", o);
}
});
}
JSONObject convertMap(Map<String, ?> m) {
JSONObject o = new JSONObject();
for(Map.Entry<String, ?> entry : m.entrySet()) {
s(o, entry.getKey(), convert(entry.getValue()));
}
return o;
}
JSONArray convertList(List<?> l) {
JSONArray o = new JSONArray();
for(Object entry : l) {
o.add(convert(entry));
}
return o;
}
Object convert(Object o) {
if (o instanceof Map<?, ?>) {
return convertMap((Map<String, ?>)o);
} else if (o instanceof List<?>) {
return convertList((List<?>)o);
}
return o;
}
}

View File

@ -0,0 +1,48 @@
package org.dynmap;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.l;
import static org.dynmap.JSONUtils.s;
import org.dynmap.Event.Listener;
import org.json.simple.JSONObject;
public class ClientConfigurationComponent extends Component {
public ClientConfigurationComponent(final DynmapPlugin plugin, ConfigurationNode configuration) {
super(plugin, configuration);
plugin.events.<JSONObject>addListener("buildclientconfiguration", new Listener<JSONObject>() {
@Override
public void triggered(JSONObject t) {
ConfigurationNode c = plugin.configuration;
s(t, "jsonfile", c.getBoolean("jsonfile", false));
s(t, "updaterate", c.getFloat("updaterate", 1.0f));
s(t, "allowchat", c.getBoolean("allowchat", true));
s(t, "allowwebchat", c.getBoolean("allowwebchat", true));
s(t, "webchat-interval", c.getFloat("webchat-interval", 5.0f));
s(t, "enableherochat", c.getBoolean("enableherochat", false));
s(t, "herochatwebchannel", c.getString("herochatwebchannel", "Global"));
s(t, "herochatchannels", l(c.getStrings("herochatchannels", null)));
s(t, "showplayerfacesinmenu", c.getBoolean("showplayerfacesinmenu", true));
s(t, "joinmessage", c.getString("joinmessage", "%playername% joined"));
s(t, "quitmessage", c.getString("joinmessage", "%playername% quit"));
s(t, "spammessage", c.getString("joinmessage", "You may only chat once every %interval% seconds."));
for(ConfigurationNode wn : plugin.configuration.getNodes("worlds")) {
DynmapWorld world = plugin.mapManager.getWorld(wn.getString("name"));
JSONObject wo = new JSONObject();
s(wo, "name", wn.getString("name"));
s(wo, "title", wn.getString("title"));
s(wo, "center/x", wn.getFloat("center/x", 0.0f));
s(wo, "center/y", wn.getFloat("center/y", 0.0f));
s(wo, "center/z", wn.getFloat("center/z", 0.0f));
a(t, "worlds", wo);
for(MapType mt : world.maps) {
mt.buildClientConfiguration(wo);
}
}
}
});
}
}

View File

@ -20,8 +20,6 @@ import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.Event.Priority;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockListener;
import org.bukkit.event.block.BlockPlaceEvent;
@ -45,8 +43,8 @@ import org.dynmap.web.Json;
import org.dynmap.web.handlers.ClientConfigurationHandler;
import org.dynmap.web.handlers.ClientUpdateHandler;
import org.dynmap.web.handlers.FilesystemHandler;
import org.dynmap.web.handlers.SendMessageHandler;
import org.dynmap.web.handlers.RegionHandler;
import org.dynmap.web.handlers.SendMessageHandler;
public class DynmapPlugin extends JavaPlugin {
public HttpServer webServer = null;
@ -57,6 +55,7 @@ public class DynmapPlugin extends JavaPlugin {
public PermissionProvider permissions;
public HeroChatHandler hchand;
public ComponentManager componentManager = new ComponentManager();
public Events events = new Events();
public Timer timer;
@ -70,7 +69,7 @@ public class DynmapPlugin extends JavaPlugin {
public HttpServer getWebServer() {
return webServer;
}
public void onEnable() {
permissions = NijikokunPermissions.create(getServer(), "dynmap");
if (permissions == null)
@ -83,7 +82,7 @@ public class DynmapPlugin extends JavaPlugin {
configuration = new ConfigurationNode(bukkitConfiguration);
loadDebuggers();
// Load components.
for(Component component : configuration.<Component>createInstances("components", new Class<?>[] { DynmapPlugin.class }, new Object[] { this })) {
componentManager.add(component);
@ -147,7 +146,7 @@ public class DynmapPlugin extends JavaPlugin {
webServer.handlers.put("/", new FilesystemHandler(getFile(configuration.getString("webpath", "web"))));
webServer.handlers.put("/tiles/", new FilesystemHandler(tilesDirectory));
webServer.handlers.put("/up/", new ClientUpdateHandler(mapManager, playerList, getServer(), configuration.getBoolean("health-in-json", false)));
webServer.handlers.put("/up/configuration", new ClientConfigurationHandler(configuration.getNode("web")));
webServer.handlers.put("/up/configuration", new ClientConfigurationHandler(this, configuration.getNode("web")));
/* See if regions configuration branch is present */
for(ConfigurationNode type : configuration.getNodes("web/components")) {
if(type.getString("type").equalsIgnoreCase("regions")) {
@ -217,9 +216,9 @@ public class DynmapPlugin extends JavaPlugin {
}
};
if (isTrigger("blockplaced"))
pm.registerEvent(Event.Type.BLOCK_PLACE, renderTrigger, Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_PLACE, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
if (isTrigger("blockbreak"))
pm.registerEvent(Event.Type.BLOCK_BREAK, renderTrigger, Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_BREAK, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
}
{
PlayerListener renderTrigger = new PlayerListener() {
@ -234,9 +233,9 @@ public class DynmapPlugin extends JavaPlugin {
}
};
if (isTrigger("playerjoin"))
pm.registerEvent(Event.Type.PLAYER_JOIN, renderTrigger, Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.PLAYER_JOIN, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
if (isTrigger("playermove"))
pm.registerEvent(Event.Type.PLAYER_MOVE, renderTrigger, Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.PLAYER_MOVE, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
}
{
WorldListener renderTrigger = new WorldListener() {
@ -255,7 +254,7 @@ public class DynmapPlugin extends JavaPlugin {
*/
};
if (isTrigger("chunkloaded"))
pm.registerEvent(Event.Type.CHUNK_LOAD, renderTrigger, Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.CHUNK_LOAD, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
//if (isTrigger("chunkgenerated")) pm.registerEvent(Event.Type.CHUNK_GENERATED, renderTrigger, Priority.Monitor, this);
}
@ -264,10 +263,10 @@ public class DynmapPlugin extends JavaPlugin {
// To handle webchat.
PlayerListener playerListener = new DynmapPlayerChatListener(this);
//getServer().getPluginManager().registerEvent(Event.Type.PLAYER_COMMAND, playerListener, Priority.Normal, this);
pm.registerEvent(Event.Type.PLAYER_CHAT, playerListener, Priority.Monitor, this);
pm.registerEvent(Event.Type.PLAYER_LOGIN, playerListener, Priority.Monitor, this);
pm.registerEvent(Event.Type.PLAYER_JOIN, playerListener, Priority.Monitor, this);
pm.registerEvent(Event.Type.PLAYER_QUIT, playerListener, Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.PLAYER_CHAT, playerListener, org.bukkit.event.Event.Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.PLAYER_LOGIN, playerListener, org.bukkit.event.Event.Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.PLAYER_JOIN, playerListener, org.bukkit.event.Event.Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.PLAYER_QUIT, playerListener, org.bukkit.event.Event.Priority.Monitor, this);
}
// To link configuration to real loaded worlds.
@ -277,7 +276,7 @@ public class DynmapPlugin extends JavaPlugin {
mm.activateWorld(event.getWorld());
}
};
pm.registerEvent(Event.Type.WORLD_LOAD, worldListener, Priority.Monitor, this);
pm.registerEvent(org.bukkit.event.Event.Type.WORLD_LOAD, worldListener, org.bukkit.event.Event.Priority.Monitor, this);
}
private static File combinePaths(File parent, String path) {

View File

@ -0,0 +1,34 @@
package org.dynmap;
import java.util.HashMap;
import java.util.Map;
public class Events {
public Map<String, Event<?>> events = new HashMap<String, Event<?>>();
public <T> void addListener(String eventName, Event.Listener<T> listener) {
Event<?> genericEvent = events.get(eventName);
Event<T> event = null;
if (genericEvent != null) {
event = (Event<T>)genericEvent;
} else {
events.put(eventName, event = new Event<T>());
}
event.addListener(listener);
}
public <T> void removeListener(String eventName, Event.Listener<T> listener) {
Event<?> genericEvent = events.get(eventName);
Event<T> event = null;
if (genericEvent != null) {
event = (Event<T>)genericEvent;
event.removeListener(listener);
}
}
public <T> void trigger(String eventName, T argument) {
Event<?> genericEvent = events.get(eventName);
if (genericEvent == null)
return;
((Event<T>)genericEvent).trigger(argument);
}
}

View File

@ -0,0 +1,68 @@
package org.dynmap;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
public class JSONUtils {
// Gets a value at the specified path.
public static Object g(JSONObject o, String path) {
int index = path.indexOf('/');
if (index == -1) {
return o.get(path);
} else {
String key = path.substring(0, index);
String subpath = path.substring(index+1);
Object oo = o.get(key);
JSONObject subobject;
if (oo == null) {
return null;
} else /*if (oo instanceof JSONObject)*/ {
subobject = (JSONObject)o;
}
return g(subobject, subpath);
}
}
// Sets a value on the specified path. If JSONObjects inside the path are missing, they'll be created.
public static void s(JSONObject o, String path, Object value) {
int index = path.indexOf('/');
if (index == -1) {
o.put(path, value);
} else {
String key = path.substring(0, index);
String subpath = path.substring(index+1);
Object oo = o.get(key);
JSONObject subobject;
if (oo == null) {
subobject = new JSONObject();
o.put(key, subobject);
} else /*if (oo instanceof JSONObject)*/ {
subobject = (JSONObject)oo;
}
s(subobject, subpath, value);
}
}
// Adds a value to the list at the specified path. If the list does not exist, it will be created.
public static void a(JSONObject o, String path, Object value) {
Object oo = g(o, path);
JSONArray array;
if (oo == null) {
array =new JSONArray();
s(o, path, array);
} else {
array = (JSONArray)oo;
}
array.add(value);
}
// Simply creates a JSONArray.
public static JSONArray l(Object... items) {
JSONArray arr = new JSONArray();
for(Object item : items) {
arr.add(item);
}
return arr;
}
}

View File

@ -34,6 +34,14 @@ public class MapManager {
Runnable run;
}
public DynmapWorld getWorld(String name) {
DynmapWorld world = worlds.get(name);
if(world == null) {
world = inactiveworlds.get(name);
}
return world;
}
private class FullWorldRenderState implements Runnable {
DynmapWorld world; /* Which world are we rendering */
Location loc; /* Start location */

View File

@ -3,6 +3,7 @@ package org.dynmap;
import java.io.File;
import org.bukkit.Location;
import org.json.simple.JSONObject;
public abstract class MapType {
public Event<MapTile> onTileInvalidated = new Event<MapTile>();
@ -14,4 +15,7 @@ public abstract class MapType {
public abstract DynmapChunk[] getRequiredChunks(MapTile tile);
public abstract boolean render(MapChunkCache cache, MapTile tile, File outputFile);
public void buildClientConfiguration(JSONObject worldObject) {
}
}

View File

@ -1,5 +1,8 @@
package org.dynmap.flat;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.s;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
@ -21,13 +24,16 @@ import org.dynmap.MapType;
import org.dynmap.debug.Debug;
import org.dynmap.kzedmap.KzedMap;
import org.dynmap.MapChunkCache;
import org.json.simple.JSONObject;
public class FlatMap extends MapType {
private ConfigurationNode configuration;
private String prefix;
private ColorScheme colorScheme;
private int maximumHeight = 127;
public FlatMap(ConfigurationNode configuration) {
this.configuration = configuration;
prefix = (String) configuration.get("prefix");
colorScheme = ColorScheme.getScheme((String) configuration.get("colorscheme"));
Object o = configuration.get("maximumheight");
@ -206,4 +212,15 @@ public class FlatMap extends MapType {
return map.prefix + "_" + size + "_" + -(y+1) + "_" + x + ".png";
}
}
@Override
public void buildClientConfiguration(JSONObject worldObject) {
ConfigurationNode c = configuration;
JSONObject o = new JSONObject();
s(o, "type", "FlatMapType");
s(o, "name", c.getString("name"));
s(o, "title", c.getString("title"));
s(o, "prefix", c.getString("prefix"));
a(worldObject, "maps", o);
}
}

View File

@ -1,5 +1,10 @@
package org.dynmap.kzedmap;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.s;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
@ -17,10 +22,12 @@ import org.dynmap.ConfigurationNode;
import org.dynmap.MapManager;
import org.dynmap.debug.Debug;
import org.dynmap.MapChunkCache;
import org.json.simple.JSONObject;
public class DefaultTileRenderer implements MapTileRenderer {
protected static final Color translucent = new Color(0, 0, 0, 0);
protected String name;
protected ConfigurationNode configuration;
protected int maximumHeight = 127;
protected ColorScheme colorScheme;
@ -34,6 +41,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
}
public DefaultTileRenderer(ConfigurationNode configuration) {
this.configuration = configuration;
name = (String) configuration.get("prefix");
Object o = configuration.get("maximumheight");
if (o != null) {
@ -356,4 +364,15 @@ public class DefaultTileRenderer implements MapTileRenderer {
c.setRGBA((c.getRed() * scale) >> 8, (c.getGreen() * scale) >> 8,
(c.getBlue() * scale) >> 8, c.getAlpha());
}
@Override
public void buildClientConfiguration(JSONObject worldObject) {
ConfigurationNode c = configuration;
JSONObject o = new JSONObject();
s(o, "type", "KzedMapType");
s(o, "name", c.getString("name"));
s(o, "title", c.getString("title"));
s(o, "prefix", c.getString("prefix"));
a(worldObject, "maps", o);
}
}

View File

@ -16,6 +16,7 @@ import org.dynmap.Log;
import org.dynmap.MapTile;
import org.dynmap.MapType;
import org.dynmap.MapChunkCache;
import org.json.simple.JSONObject;
public class KzedMap extends MapType {
protected static final Logger log = Logger.getLogger("Minecraft");
@ -300,4 +301,11 @@ public class KzedMap extends MapType {
}
}
}
@Override
public void buildClientConfiguration(JSONObject worldObject) {
for(MapTileRenderer renderer : renderers) {
renderer.buildClientConfiguration(worldObject);
}
}
}

View File

@ -3,8 +3,12 @@ package org.dynmap.kzedmap;
import java.io.File;
import org.dynmap.MapChunkCache;
import org.json.simple.JSONObject;
public interface MapTileRenderer {
String getName();
boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile);
void buildClientConfiguration(JSONObject worldObject);
}

View File

@ -4,22 +4,28 @@ import java.io.BufferedOutputStream;
import java.util.Date;
import java.util.Map;
import org.dynmap.DynmapPlugin;
import org.dynmap.web.HttpHandler;
import org.dynmap.web.HttpRequest;
import org.dynmap.web.HttpResponse;
import org.dynmap.web.HttpStatus;
import org.dynmap.web.Json;
import org.json.simple.JSONObject;
public class ClientConfigurationHandler implements HttpHandler {
private DynmapPlugin plugin;
private Map<?, ?> configuration;
private byte[] cachedConfiguration = null;
public ClientConfigurationHandler(Map<?, ?> configuration) {
public ClientConfigurationHandler(DynmapPlugin plugin, Map<?, ?> configuration) {
this.plugin = plugin;
this.configuration = configuration;
}
@Override
public void handle(String path, HttpRequest request, HttpResponse response) throws Exception {
if (cachedConfiguration == null) {
String s = Json.stringifyJson(configuration);
JSONObject configurationObject = new JSONObject();
plugin.events.<JSONObject>trigger("buildclientconfiguration", configurationObject);
String s = configurationObject.toJSONString();
cachedConfiguration = s.getBytes();
}