diff --git a/build.gradle b/build.gradle
index 8bdb1a122..41de17bb8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -82,6 +82,14 @@ artifactory {
 artifactoryPublish.skip = true
 
 subprojects {
+    repositories {
+        mavenCentral()
+        maven { url "http://maven.sk89q.com/repo/" }
+        maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
+    }
+}
+
+configure(['core', 'bukkit', 'forge', 'sponge'].collect { project(":worldedit-$it") }) {
     apply plugin: 'java'
     apply plugin: 'maven'
     apply plugin: 'checkstyle'
@@ -97,12 +105,6 @@ subprojects {
     checkstyle.configFile = new File(rootProject.projectDir, "config/checkstyle/checkstyle.xml")
     checkstyle.toolVersion = '7.6.1'
 
-    repositories {
-        mavenCentral()
-        maven { url "http://maven.sk89q.com/repo/" }
-        maven { url "http://repo.maven.apache.org/maven2" }
-    }
-
     if (JavaVersion.current().isJava8Compatible()) {
         // Java 8 turns on doclint which we fail
         tasks.withType(Javadoc) {
@@ -136,18 +138,6 @@ subprojects {
     build.dependsOn(checkstyleTest)
     build.dependsOn(javadocJar)
 
-    shadowJar {
-        classifier 'dist'
-        dependencies {
-            include(dependency('com.sk89q:jchronic:0.2.4a'))
-            include(dependency('com.thoughtworks.paranamer:paranamer:2.6'))
-            include(dependency('com.sk89q.lib:jlibnoise:1.0.0'))
-        }
-        exclude 'GradleStart**'
-        exclude '.cache'
-        exclude 'LICENSE*'
-    }
-
     artifactoryPublish {
         publishConfigs('archives')
     }
@@ -157,3 +147,17 @@ subprojects {
         include '**/*.java'
     }
 }
+
+configure(['bukkit', 'forge', 'sponge'].collect { project(":worldedit-$it") }) {
+    shadowJar {
+        classifier 'dist'
+        dependencies {
+            include(project(":worldedit-libs:core"))
+            include(project(":worldedit-libs:${project.name.replace("worldedit-", "")}"))
+            include(project(":worldedit-core"))
+        }
+        exclude 'GradleStart**'
+        exclude '.cache'
+        exclude 'LICENSE*'
+    }
+}
diff --git a/settings.gradle b/settings.gradle
index 576283ecc..efbda026f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,8 @@
 rootProject.name = 'worldedit'
 
-include 'worldedit-core', 'worldedit-bukkit', 'worldedit-forge', 'worldedit-sponge'
\ No newline at end of file
+include 'worldedit-libs'
+
+['bukkit', 'core', 'forge', 'sponge'].forEach {
+    include "worldedit-libs:$it"
+    include "worldedit-$it"
+}
\ No newline at end of file
diff --git a/worldedit-bukkit/build.gradle b/worldedit-bukkit/build.gradle
index 335b96c0a..975aee9d9 100644
--- a/worldedit-bukkit/build.gradle
+++ b/worldedit-bukkit/build.gradle
@@ -10,11 +10,12 @@ repositories {
 
 dependencies {
     compile project(':worldedit-core')
+    compile project(':worldedit-libs:bukkit')
     compile 'com.sk89q:dummypermscompat:1.10'
     compile 'org.bukkit:bukkit:1.13.2-R0.1-SNAPSHOT' // zzz
-    compile 'org.bstats:bstats-bukkit:1.4'
     compile "io.papermc:paperlib:1.0.1"
     compile 'org.slf4j:slf4j-jdk14:1.7.26'
+    compile 'org.bstats:bstats-bukkit:1.4'
     testCompile 'org.mockito:mockito-core:1.9.0-rc1'
 }
 
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
index 55929af02..b82dfe458 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
@@ -26,6 +26,8 @@ import com.sk89q.worldedit.extension.platform.Actor;
 import com.sk89q.worldedit.internal.cui.CUIEvent;
 import com.sk89q.worldedit.session.SessionKey;
 import com.sk89q.worldedit.util.auth.AuthorizationException;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
 
@@ -91,6 +93,11 @@ public class BukkitCommandSender implements Actor {
         }
     }
 
+    @Override
+    public void print(TextComponent component) {
+        TextAdapter.sendComponent(sender, component);
+    }
+
     @Override
     public boolean canDestroyBedrock() {
         return true;
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
index d814cc7ba..71941a69d 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
@@ -37,7 +37,8 @@ import com.sk89q.worldedit.world.block.BlockStateHolder;
 import com.sk89q.worldedit.world.block.BlockTypes;
 import com.sk89q.worldedit.world.gamemode.GameMode;
 import com.sk89q.worldedit.world.gamemode.GameModes;
-
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter;
 import org.bukkit.Bukkit;
 import org.bukkit.Location;
 import org.bukkit.entity.Player;
@@ -125,6 +126,11 @@ public class BukkitPlayer extends AbstractPlayerActor {
         }
     }
 
+    @Override
+    public void print(TextComponent component) {
+        TextAdapter.sendComponent(player, component);
+    }
+
     @Override
     public void setPosition(Vector3 pos, float pitch, float yaw) {
         player.teleport(new Location(player.getWorld(), pos.getX(), pos.getY(),
diff --git a/worldedit-core/build.gradle b/worldedit-core/build.gradle
index f91186083..828833d9a 100644
--- a/worldedit-core/build.gradle
+++ b/worldedit-core/build.gradle
@@ -2,15 +2,13 @@ apply plugin: 'eclipse'
 apply plugin: 'idea'
 
 dependencies {
+    compile project(':worldedit-libs:core')
     compile 'de.schlichtherle:truezip:6.8.3'
     compile 'rhino:js:1.7R2'
     compile 'org.yaml:snakeyaml:1.9'
     compile 'com.google.guava:guava:21.0'
-    compile 'com.sk89q:jchronic:0.2.4a'
     compile 'com.google.code.findbugs:jsr305:1.3.9'
-    compile 'com.thoughtworks.paranamer:paranamer:2.6'
     compile 'com.google.code.gson:gson:2.8.0'
-    compile 'com.sk89q.lib:jlibnoise:1.0.0'
     compile 'com.googlecode.json-simple:json-simple:1.1.1'
     compile 'org.slf4j:slf4j-api:1.7.26'
     //compile 'net.sf.trove4j:trove4j:3.0.3'
@@ -28,5 +26,3 @@ sourceSets {
         }
     }
 }
-
-build.dependsOn(shadowJar)
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java
index 24e92070b..3c1be05fd 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java
@@ -55,15 +55,15 @@ import com.sk89q.worldedit.regions.selector.SphereRegionSelector;
 import com.sk89q.worldedit.session.ClipboardHolder;
 import com.sk89q.worldedit.util.Countable;
 import com.sk89q.worldedit.util.Location;
-import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
-import com.sk89q.worldedit.util.formatting.Style;
-import com.sk89q.worldedit.util.formatting.StyledFragment;
 import com.sk89q.worldedit.util.formatting.component.CommandListBox;
+import com.sk89q.worldedit.util.formatting.component.SubtleFormat;
+import com.sk89q.worldedit.util.formatting.component.TextComponentProducer;
 import com.sk89q.worldedit.world.World;
 import com.sk89q.worldedit.world.block.BaseBlock;
 import com.sk89q.worldedit.world.block.BlockState;
 import com.sk89q.worldedit.world.item.ItemTypes;
 import com.sk89q.worldedit.world.storage.ChunkStore;
+import com.sk89q.worldedit.util.formatting.text.Component;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -76,7 +76,7 @@ import java.util.Set;
 public class SelectionCommands {
 
     private final WorldEdit we;
-    
+
     public SelectionCommands(WorldEdit we) {
         this.we = we;
     }
@@ -295,7 +295,7 @@ public class SelectionCommands {
     )
     @CommandPermissions("worldedit.wand.toggle")
     public void toggleWand(Player player, LocalSession session, CommandContext args) throws WorldEditException {
-        
+
         session.setToolControl(!session.isToolControlEnabled());
 
         if (session.isToolControlEnabled()) {
@@ -394,7 +394,7 @@ public class SelectionCommands {
 
         session.getRegionSelector(player.getWorld()).learnChanges();
         int newSize = region.getArea();
-        
+
         session.getRegionSelector(player.getWorld()).explainRegionAdjust(player, session);
 
         player.print("Region expanded " + (newSize - oldSize) + " blocks.");
@@ -465,7 +465,7 @@ public class SelectionCommands {
             }
             session.getRegionSelector(player.getWorld()).learnChanges();
             int newSize = region.getArea();
-            
+
             session.getRegionSelector(player.getWorld()).explainRegionAdjust(player, session);
 
 
@@ -754,19 +754,18 @@ public class SelectionCommands {
             limit.ifPresent(integer -> player.print(integer + " points maximum."));
         } else {
             CommandListBox box = new CommandListBox("Selection modes");
-            StyledFragment contents = box.getContents();
-            StyledFragment tip = contents.createFragment(Style.RED);
-            tip.append("Select one of the modes below:").newLine();
+            TextComponentProducer contents = box.getContents();
+            contents.append(SubtleFormat.wrap("Select one of the modes below:")).newline();
 
-            box.appendCommand("cuboid", "Select two corners of a cuboid");
-            box.appendCommand("extend", "Fast cuboid selection mode");
-            box.appendCommand("poly", "Select a 2D polygon with height");
-            box.appendCommand("ellipsoid", "Select an ellipsoid");
-            box.appendCommand("sphere", "Select a sphere");
-            box.appendCommand("cyl", "Select a cylinder");
-            box.appendCommand("convex", "Select a convex polyhedral");
+            box.appendCommand("cuboid", "Select two corners of a cuboid", "//sel cuboid");
+            box.appendCommand("extend", "Fast cuboid selection mode", "//sel extend");
+            box.appendCommand("poly", "Select a 2D polygon with height", "//sel poly");
+            box.appendCommand("ellipsoid", "Select an ellipsoid", "//sel ellipsoid");
+            box.appendCommand("sphere", "Select a sphere", "//sel sphere");
+            box.appendCommand("cyl", "Select a cylinder", "//sel cyl");
+            box.appendCommand("convex", "Select a convex polyhedral", "//sel convex");
 
-            player.printRaw(ColorCodeBuilder.asColorCodes(box));
+            player.print(box.create());
             return;
         }
 
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java
index 8768be6c8..2e2defc19 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java
@@ -58,12 +58,15 @@ import com.sk89q.worldedit.util.command.CommandMapping;
 import com.sk89q.worldedit.util.command.Dispatcher;
 import com.sk89q.worldedit.util.command.PrimaryAliasComparator;
 import com.sk89q.worldedit.util.command.binding.Text;
-import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
-import com.sk89q.worldedit.util.formatting.Style;
-import com.sk89q.worldedit.util.formatting.StyledFragment;
-import com.sk89q.worldedit.util.formatting.component.Code;
+import com.sk89q.worldedit.util.formatting.component.CodeFormat;
 import com.sk89q.worldedit.util.formatting.component.CommandListBox;
 import com.sk89q.worldedit.util.formatting.component.CommandUsageBox;
+import com.sk89q.worldedit.util.formatting.component.ErrorFormat;
+import com.sk89q.worldedit.util.formatting.component.SubtleFormat;
+import com.sk89q.worldedit.util.formatting.component.TextComponentProducer;
+import com.sk89q.worldedit.util.formatting.text.Component;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.format.TextColor;
 import com.sk89q.worldedit.world.World;
 import com.sk89q.worldedit.world.block.BaseBlock;
 import com.sk89q.worldedit.world.block.BlockTypes;
@@ -206,7 +209,7 @@ public class UtilityCommands {
     @CommandPermissions("worldedit.removeabove")
     @Logging(PLACEMENT)
     public void removeAbove(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException {
-        
+
         int size = args.argsLength() > 0 ? Math.max(1, args.getInteger(0)) : 1;
         we.checkMaxRadius(size);
         World world = player.getWorld();
@@ -274,7 +277,7 @@ public class UtilityCommands {
     @CommandPermissions("worldedit.replacenear")
     @Logging(PLACEMENT)
     public void replaceNear(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException {
-        
+
         int size = Math.max(1, args.getInteger(0));
         we.checkMaxRadius(size);
         int affected;
@@ -669,17 +672,19 @@ public class UtilityCommands {
 
             // Box
             CommandListBox box = new CommandListBox(String.format("Help: page %d/%d ", page + 1, pageTotal));
-            StyledFragment contents = box.getContents();
-            StyledFragment tip = contents.createFragment(Style.GRAY);
+            TextComponentProducer tip = new TextComponentProducer();
+            tip.getBuilder().content("").color(TextColor.GRAY);
+            TextComponentProducer contents = box.getContents();
 
             if (offset >= aliases.size()) {
-                tip.createFragment(Style.RED).append(String.format("There is no page %d (total number of pages is %d).", page + 1, pageTotal)).newLine();
+                tip.append(ErrorFormat.wrap(String.format("There is no page %d (total number of pages is %d).", page + 1, pageTotal))).newline();
             } else {
                 List<CommandMapping> list = aliases.subList(offset, Math.min(offset + perPage, aliases.size()));
 
                 tip.append("Type ");
-                tip.append(new Code().append("//help ").append("<command> [<page>]"));
-                tip.append(" for more information.").newLine();
+                tip.append(CodeFormat.wrap("//help "));
+                tip.append("<command> [<page>] for more information.");
+                tip.newline();
 
                 // Add each command
                 for (CommandMapping mapping : list) {
@@ -696,10 +701,11 @@ public class UtilityCommands {
                 }
             }
 
-            actor.printRaw(ColorCodeBuilder.asColorCodes(box));
+            contents.append(tip.create());
+            actor.print(box.create());
         } else {
             CommandUsageBox box = new CommandUsageBox(callable, Joiner.on(" ").join(visited));
-            actor.printRaw(ColorCodeBuilder.asColorCodes(box));
+            actor.print(box.create());
         }
     }
 
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java
index cf64b5974..45160de66 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java
@@ -23,6 +23,7 @@ import com.sk89q.worldedit.internal.cui.CUIEvent;
 import com.sk89q.worldedit.session.SessionOwner;
 import com.sk89q.worldedit.util.Identifiable;
 import com.sk89q.worldedit.util.auth.Subject;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
 
 import java.io.File;
 
@@ -75,6 +76,13 @@ public interface Actor extends Identifiable, SessionOwner, Subject {
      */
     void printError(String msg);
 
+    /**
+     * Print a {@link TextComponent}.
+     *
+     * @param component The component to print
+     */
+    void print(TextComponent component);
+
     /**
      * Returns true if the actor can destroy bedrock.
      *
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java
index a0be07b65..45b5265bb 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java
@@ -77,7 +77,6 @@ import com.sk89q.worldedit.util.command.parametric.ExceptionConverter;
 import com.sk89q.worldedit.util.command.parametric.LegacyCommandsHandler;
 import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
 import com.sk89q.worldedit.util.eventbus.Subscribe;
-import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
 import com.sk89q.worldedit.util.formatting.component.CommandUsageBox;
 import com.sk89q.worldedit.util.logging.DynamicStreamHandler;
 import com.sk89q.worldedit.util.logging.LogFormat;
@@ -300,7 +299,7 @@ public final class CommandManager {
             actor.printError("You are not permitted to do that. Are you in the right mode?");
         } catch (InvalidUsageException e) {
             if (e.isFullHelpSuggested()) {
-                actor.printRaw(ColorCodeBuilder.asColorCodes(new CommandUsageBox(e.getCommand(), e.getCommandUsed("/", ""), locals)));
+                actor.print(new CommandUsageBox(e.getCommand(), e.getCommandUsed("/", ""), locals).create());
                 String message = e.getMessage();
                 if (message != null) {
                     actor.printError(message);
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java
index 48173abbc..2159a2fce 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java
@@ -34,6 +34,7 @@ import com.sk89q.worldedit.util.Location;
 import com.sk89q.worldedit.world.World;
 import com.sk89q.worldedit.world.block.BlockStateHolder;
 import com.sk89q.worldedit.world.gamemode.GameMode;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
 
 import java.util.UUID;
 
@@ -132,6 +133,11 @@ class PlayerProxy extends AbstractPlayerActor {
         basePlayer.printError(msg);
     }
 
+    @Override
+    public void print(TextComponent component) {
+        basePlayer.print(component);
+    }
+
     @Override
     public String[] getGroups() {
         return permActor.getGroups();
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java
deleted file mode 100644
index dbaa904ce..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q <http://www.sk89q.com>
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser 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 Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.sk89q.worldedit.util.formatting;
-
-import com.google.common.base.Joiner;
-
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-
-public class ColorCodeBuilder {
-
-    private static final ColorCodeBuilder instance = new ColorCodeBuilder();
-    private static final Joiner newLineJoiner = Joiner.on("\n");
-    public static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 47;
-    
-    /**
-     * Convert a message into color-coded text.
-     * 
-     * @param message the message
-     * @return a list of lines
-     */
-    public String[] build(StyledFragment message) {
-        StringBuilder builder = new StringBuilder();
-        buildFragment(builder, message, message.getStyle(), new StyleSet());
-        return builder.toString().split("\r?\n");
-    }
-    
-    /**
-     * Build a fragment.
-     * 
-     * @param builder the string builder
-     * @param message the message
-     * @param parentStyle the parent style
-     * @param lastStyle the last style
-     * @return the last style used
-     */
-    private StyleSet buildFragment(StringBuilder builder, StyledFragment message, StyleSet parentStyle, StyleSet lastStyle) {
-        for (Fragment node : message.getChildren()) {
-            if (node instanceof StyledFragment) {
-                StyledFragment fragment = (StyledFragment) node;
-                lastStyle = buildFragment(
-                        builder, fragment, 
-                        parentStyle.extend(message.getStyle()), lastStyle);
-            } else {
-                StyleSet style = parentStyle.extend(message.getStyle());
-                builder.append(getAdditive(style, lastStyle));
-                builder.append(node);
-                lastStyle = style;
-            }
-        }
-        
-        return lastStyle;
-    }
-    
-    /**
-     * Get the formatting codes.
-     * 
-     * @param style the style
-     * @return the color codes
-     */
-    public static String getFormattingCode(StyleSet style) {
-        StringBuilder builder = new StringBuilder();
-        if (style.isBold()) {
-            builder.append(Style.BOLD);
-        }
-        if (style.isItalic()) {
-            builder.append(Style.ITALIC);
-        }
-        if (style.isUnderline()) {
-            builder.append(Style.UNDERLINE);
-        }
-        if (style.isStrikethrough()) {
-            builder.append(Style.STRIKETHROUGH);
-        }
-        return builder.toString();
-    }
-    
-    /**
-     * Get the formatting and color codes.
-     * 
-     * @param style the style
-     * @return the color codes
-     */
-    public static String getCode(StyleSet style) {
-        StringBuilder builder = new StringBuilder();
-        builder.append(getFormattingCode(style));
-        if (style.getColor() != null) {
-            builder.append(style.getColor());
-        }
-        return builder.toString();
-    }
-
-    /**
-     * Get the additional color codes needed to set the given style when the current
-     * style is the other given one.
-     * 
-     * @param resetTo the style to reset to
-     * @param resetFrom the style to reset from
-     * @return the color codes
-     */
-    public static String getAdditive(StyleSet resetTo, StyleSet resetFrom) {
-        if (!resetFrom.hasFormatting() && resetTo.hasFormatting()) {
-            StringBuilder builder = new StringBuilder();
-            builder.append(getFormattingCode(resetTo));
-            if (resetFrom.getColor() != resetTo.getColor()) {
-                builder.append(resetTo.getColor());
-            }
-            return builder.toString();
-        } else if (!resetFrom.hasEqualFormatting(resetTo) || 
-                (resetFrom.getColor() != null && resetTo.getColor() == null)) {
-            // Have to set reset code and add back all the formatting codes
-            return Style.RESET + getCode(resetTo);
-        } else {
-            if (resetFrom.getColor() != resetTo.getColor()) {
-                return String.valueOf(resetTo.getColor());
-            }
-        }
-        
-        return "";
-    }
-
-    /**
-     * Word wrap the given text and maintain color codes throughout lines.
-     * 
-     * <p>This is borrowed from Bukkit.</p>
-     * 
-     * @param rawString the raw string
-     * @param lineLength the maximum line length
-     * @return a list of lines
-     */
-    private String[] wordWrap(String rawString, int lineLength) {
-        // A null string is a single line
-        if (rawString == null) {
-            return new String[] {""};
-        }
-
-        // A string shorter than the lineWidth is a single line
-        if (rawString.length() <= lineLength && !rawString.contains("\n")) {
-            return new String[] {rawString};
-        }
-
-        char[] rawChars = (rawString + ' ').toCharArray(); // add a trailing space to trigger pagination
-        StringBuilder word = new StringBuilder();
-        StringBuilder line = new StringBuilder();
-        List<String> lines = new LinkedList<>();
-        int lineColorChars = 0;
-
-        for (int i = 0; i < rawChars.length; i++) {
-            char c = rawChars[i];
-
-            // skip chat color modifiers
-            if (c == Style.COLOR_CHAR) {
-                word.append(Style.getByChar(rawChars[i + 1]));
-                lineColorChars += 2;
-                i++; // Eat the next character as we have already processed it
-                continue;
-            }
-
-            if (c == ' ' || c == '\n') {
-                if (line.length() == 0 && word.length() > lineLength) { // special case: extremely long word begins a line
-                    String wordStr = word.toString();
-                    String transformed;
-                    if ((transformed = transform(wordStr)) != null) {
-                        line.append(transformed);
-                    } else {
-                        lines.addAll(Arrays.asList(word.toString().split("(?<=\\G.{" + lineLength + "})")));
-                    }
-                } else if (line.length() + word.length() - lineColorChars == lineLength) { // Line exactly the correct length...newline
-                    line.append(' ');
-                    line.append(word);
-                    lines.add(line.toString());
-                    line = new StringBuilder();
-                    lineColorChars = 0;
-                } else if (line.length() + 1 + word.length() - lineColorChars > lineLength) { // Line too long...break the line
-                    String wordStr = word.toString();
-                    String transformed;
-                    if (word.length() > lineLength && (transformed = transform(wordStr)) != null) {
-                        if (line.length() + 1 + transformed.length() - lineColorChars > lineLength) {
-                            lines.add(line.toString());
-                            line = new StringBuilder(transformed);
-                            lineColorChars = 0;
-                        } else {
-                            if (line.length() > 0) {
-                                line.append(' ');
-                            }
-                            line.append(transformed);
-                        }
-                    } else {
-                        for (String partialWord : wordStr.split("(?<=\\G.{" + lineLength + "})")) {
-                            lines.add(line.toString());
-                            line = new StringBuilder(partialWord);
-                        }
-                        lineColorChars = 0;
-                    }
-                } else {
-                    if (line.length() > 0) {
-                        line.append(' ');
-                    }
-                    line.append(word);
-                }
-                word = new StringBuilder();
-
-                if (c == '\n') { // Newline forces the line to flush
-                    lines.add(line.toString());
-                    line = new StringBuilder();
-                }
-            } else {
-                word.append(c);
-            }
-        }
-
-        if(line.length() > 0) { // Only add the last line if there is anything to add
-            lines.add(line.toString());
-        }
-
-        // Iterate over the wrapped lines, applying the last color from one line to the beginning of the next
-        if (lines.get(0).isEmpty() || lines.get(0).charAt(0) != Style.COLOR_CHAR) {
-            lines.set(0, Style.WHITE + lines.get(0));
-        }
-        for (int i = 1; i < lines.size(); i++) {
-            final String pLine = lines.get(i-1);
-            final String subLine = lines.get(i);
-
-            char color = pLine.charAt(pLine.lastIndexOf(Style.COLOR_CHAR) + 1);
-            if (subLine.isEmpty() || subLine.charAt(0) != Style.COLOR_CHAR) {
-                lines.set(i, Style.getByChar(color) + subLine);
-            }
-        }
-
-        return lines.toArray(new String[lines.size()]);
-    }
-    
-    /**
-     * Callback for transforming a word, such as a URL.
-     * 
-     * @param word the word
-     * @return the transformed value, or null to do nothing
-     */
-    protected String transform(String word) {
-        return null;
-    }
-
-    /**
-     * Convert the given styled fragment into color codes.
-     *
-     * @param fragment the fragment
-     * @return color codes
-     */
-    public static String asColorCodes(StyledFragment fragment) {
-        return newLineJoiner.join(instance.build(fragment));
-    }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java
deleted file mode 100644
index 3d1add7f4..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q <http://www.sk89q.com>
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser 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 Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.sk89q.worldedit.util.formatting;
-
-/**
- * A fragment of text.
- */
-public class Fragment {
-
-    private final StringBuilder builder = new StringBuilder();
-    
-    Fragment() {
-    }
-
-    public Fragment append(String str) {
-        builder.append(Style.stripColor(str));
-        return this;
-    }
-
-    public Fragment append(Object obj) {
-        append(String.valueOf(obj));
-        return this;
-    }
-
-    public Fragment append(StringBuffer sb) {
-        append(String.valueOf(sb));
-        return this;
-    }
-
-    public Fragment append(CharSequence s) {
-        append(String.valueOf(s));
-        return this;
-    }
-
-    public Fragment append(boolean b) {
-        append(String.valueOf(b));
-        return this;
-    }
-
-    public Fragment append(char c) {
-        append(String.valueOf(c));
-        return this;
-    }
-
-    public Fragment append(int i) {
-        append(String.valueOf(i));
-        return this;
-    }
-
-    public Fragment append(long lng) {
-        append(String.valueOf(lng));
-        return this;
-    }
-
-    public Fragment append(float f) {
-        append(String.valueOf(f));
-        return this;
-    }
-
-    public Fragment append(double d) {
-        append(String.valueOf(d));
-        return this;
-    }
-
-    public Fragment newLine() {
-        append("\n");
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return builder.toString();
-    }
-    
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/Style.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/Style.java
deleted file mode 100644
index d6c70eeb8..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/Style.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q <http://www.sk89q.com>
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser 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 Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.sk89q.worldedit.util.formatting;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.common.collect.Maps;
-
-import java.util.Map;
-import java.util.regex.Pattern;
-
-/**
- * All supported color values for chat.
- * 
- * <p>From Bukkit.</p>
- */
-public enum Style {
-    /**
-     * Represents black
-     */
-    BLACK('0', 0x00),
-    /**
-     * Represents dark blue
-     */
-    BLUE_DARK('1', 0x1),
-    /**
-     * Represents dark green
-     */
-    GREEN_DARK('2', 0x2),
-    /**
-     * Represents dark blue (aqua)
-     */
-    CYAN_DARK('3', 0x3),
-    /**
-     * Represents dark red
-     */
-    RED_DARK('4', 0x4),
-    /**
-     * Represents dark purple
-     */
-    PURPLE_DARK('5', 0x5),
-    /**
-     * Represents gold
-     */
-    YELLOW_DARK('6', 0x6),
-    /**
-     * Represents gray
-     */
-    GRAY('7', 0x7),
-    /**
-     * Represents dark gray
-     */
-    GRAY_DARK('8', 0x8),
-    /**
-     * Represents blue
-     */
-    BLUE('9', 0x9),
-    /**
-     * Represents green
-     */
-    GREEN('a', 0xA),
-    /**
-     * Represents aqua
-     */
-    CYAN('b', 0xB),
-    /**
-     * Represents red
-     */
-    RED('c', 0xC),
-    /**
-     * Represents light purple
-     */
-    PURPLE('d', 0xD),
-    /**
-     * Represents yellow
-     */
-    YELLOW('e', 0xE),
-    /**
-     * Represents white
-     */
-    WHITE('f', 0xF),
-    /**
-     * Represents magical characters that change around randomly
-     */
-    RANDOMIZE('k', 0x10, true),
-    /**
-     * Makes the text bold.
-     */
-    BOLD('l', 0x11, true),
-    /**
-     * Makes a line appear through the text.
-     */
-    STRIKETHROUGH('m', 0x12, true),
-    /**
-     * Makes the text appear underlined.
-     */
-    UNDERLINE('n', 0x13, true),
-    /**
-     * Makes the text italic.
-     */
-    ITALIC('o', 0x14, true),
-    /**
-     * Resets all previous chat colors or formats.
-     */
-    RESET('r', 0x15);
-
-    /**
-     * The special character which prefixes all chat color codes. Use this if you need to dynamically
-     * convert color codes from your custom format.
-     */
-    public static final char COLOR_CHAR = '\u00A7';
-    private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + COLOR_CHAR + "[0-9A-FK-OR]");
-
-    private final int intCode;
-    private final char code;
-    private final boolean isFormat;
-    private final String toString;
-    private final static Map<Integer, Style> BY_ID = Maps.newHashMap();
-    private final static Map<Character, Style> BY_CHAR = Maps.newHashMap();
-
-    Style(char code, int intCode) {
-        this(code, intCode, false);
-    }
-
-    Style(char code, int intCode, boolean isFormat) {
-        this.code = code;
-        this.intCode = intCode;
-        this.isFormat = isFormat;
-        this.toString = new String(new char[] {COLOR_CHAR, code});
-    }
-
-    /**
-     * Gets the char value associated with this color
-     *
-     * @return A char value of this color code
-     */
-    public char getChar() {
-        return code;
-    }
-
-    @Override
-    public String toString() {
-        return toString;
-    }
-
-    /**
-     * Checks if this code is a format code as opposed to a color code.
-     * 
-     * @return the if the code is a formatting code
-     */
-    public boolean isFormat() {
-        return isFormat;
-    }
-
-    /**
-     * Checks if this code is a color code as opposed to a format code.
-     * 
-     * @return the if the code is a color
-     */
-    public boolean isColor() {
-        return !isFormat && this != RESET;
-    }
-
-    /**
-     * Gets the color represented by the specified color code
-     *
-     * @param code Code to check
-     * @return Associative Style with the given code, or null if it doesn't exist
-     */
-    public static Style getByChar(char code) {
-        return BY_CHAR.get(code);
-    }
-
-    /**
-     * Gets the color represented by the specified color code
-     *
-     * @param code Code to check
-     * @return Associative Style with the given code, or null if it doesn't exist
-     */
-    public static Style getByChar(String code) {
-        checkNotNull(code);
-        checkArgument(!code.isEmpty(), "Code must have at least one character");
-
-        return BY_CHAR.get(code.charAt(0));
-    }
-
-    /**
-     * Strips the given message of all color codes
-     *
-     * @param input String to strip of color
-     * @return A copy of the input string, without any coloring
-     */
-    public static String stripColor(final String input) {
-        if (input == null) {
-            return null;
-        }
-
-        return STRIP_COLOR_PATTERN.matcher(input).replaceAll("");
-    }
-
-    /**
-     * Translates a string using an alternate color code character into a string that uses the internal
-     * ChatColor.COLOR_CODE color code character. The alternate color code character will only be replaced
-     * if it is immediately followed by 0-9, A-F, a-f, K-O, k-o, R or r.
-     *
-     * @param altColorChar The alternate color code character to replace. Ex: &amp;
-     * @param textToTranslate Text containing the alternate color code character.
-     * @return Text containing the ChatColor.COLOR_CODE color code character.
-     */
-    public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
-        char[] b = textToTranslate.toCharArray();
-        for (int i = 0; i < b.length - 1; i++) {
-            if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i+1]) > -1) {
-                b[i] = Style.COLOR_CHAR;
-                b[i+1] = Character.toLowerCase(b[i+1]);
-            }
-        }
-        return new String(b);
-    }
-
-    /**
-     * Gets the ChatColors used at the end of the given input string.
-     *
-     * @param input Input string to retrieve the colors from.
-     * @return Any remaining ChatColors to pass onto the next line.
-     */
-    public static String getLastColors(String input) {
-        String result = "";
-        int length = input.length();
-
-        // Search backwards from the end as it is faster
-        for (int index = length - 1; index > -1; index--) {
-            char section = input.charAt(index);
-            if (section == COLOR_CHAR && index < length - 1) {
-                char c = input.charAt(index + 1);
-                Style color = getByChar(c);
-
-                if (color != null) {
-                    result = color + result;
-
-                    // Once we find a color or reset we can stop searching
-                    if (color.isColor() || color == RESET) {
-                        break;
-                    }
-                }
-            }
-        }
-
-        return result;
-    }
-
-    static {
-        for (Style color : values()) {
-            BY_ID.put(color.intCode, color);
-            BY_CHAR.put(color.code, color);
-        }
-    }
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java
deleted file mode 100644
index 408dc1e43..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q <http://www.sk89q.com>
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser 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 Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.sk89q.worldedit.util.formatting;
-
-/**
- * Represents set of styles, such as color, bold, etc.
- */
-public class StyleSet {
-    
-    private Boolean bold;
-    private Boolean italic;
-    private Boolean underline;
-    private Boolean strikethrough;
-    private Style color;
-    
-    /**
-     * Create a new style set with no properties set.
-     */
-    public StyleSet() {
-    }
-
-    /**
-     * Create a new style set with the given styles.
-     * 
-     * <p>{@link Style#RESET} will be ignored if provided.</p>
-     * 
-     * @param styles a list of styles
-     */
-    public StyleSet(Style... styles) {
-        for (Style style : styles) {
-            if (style.isColor()) {
-                color = style;
-            } else if (style == Style.BOLD) {
-                bold = true;
-            } else if (style == Style.ITALIC) {
-                italic = true;
-            } else if (style == Style.UNDERLINE) {
-                underline = true;
-            } else if (style == Style.STRIKETHROUGH) {
-                strikethrough = true;
-            }
-        }
-    }
-    
-    /**
-     * Get whether this style set is bold.
-     * 
-     * @return true, false, or null if unset
-     */
-    public Boolean getBold() {
-        return bold;
-    }
-    
-    /**
-     * Get whether the text is bold.
-     * 
-     * @return true if bold
-     */
-    public boolean isBold() {
-        return getBold() != null && getBold();
-    }
-    
-    /**
-     * Set whether the text is bold.
-     * 
-     * @param bold true, false, or null to unset
-     */
-    public void setBold(Boolean bold) {
-        this.bold = bold;
-    }
-
-    /**
-     * Get whether this style set is italicized.
-     * 
-     * @return true, false, or null if unset
-     */
-    public Boolean getItalic() {
-        return italic;
-    }
-    
-    /**
-     * Get whether the text is italicized.
-     * 
-     * @return true if italicized
-     */
-    public boolean isItalic() {
-        return getItalic() != null && getItalic();
-    }
-    
-    /**
-     * Set whether the text is italicized.
-     * 
-     * @param italic false, or null to unset
-     */
-    public void setItalic(Boolean italic) {
-        this.italic = italic;
-    }
-
-    /**
-     * Get whether this style set is underlined.
-     * 
-     * @return true, false, or null if unset
-     */
-    public Boolean getUnderline() {
-        return underline;
-    }
-    
-    /**
-     * Get whether the text is underlined.
-     * 
-     * @return true if underlined
-     */
-    public boolean isUnderline() {
-        return getUnderline() != null && getUnderline();
-    }
-    
-    /**
-     * Set whether the text is underline.
-     * 
-     * @param underline false, or null to unset
-     */
-    public void setUnderline(Boolean underline) {
-        this.underline = underline;
-    }
-
-    /**
-     * Get whether this style set is stricken through.
-     * 
-     * @return true, false, or null if unset
-     */
-    public Boolean getStrikethrough() {
-        return strikethrough;
-    }
-    
-    /**
-     * Get whether the text is stricken through.
-     * 
-     * @return true if there is strikethrough applied
-     */
-    public boolean isStrikethrough() {
-        return getStrikethrough() != null && getStrikethrough();
-    }
-    
-    /**
-     * Set whether the text is stricken through.
-     * 
-     * @param strikethrough false, or null to unset
-     */
-    public void setStrikethrough(Boolean strikethrough) {
-        this.strikethrough = strikethrough;
-    }
-
-    /**
-     * Get the color of the text.
-     * 
-     * @return true, false, or null if unset
-     */
-    public Style getColor() {
-        return color;
-    }
-    
-    /**
-     * Set the color of the text.
-     * 
-     * @param color the color
-     */
-    public void setColor(Style color) {
-        this.color = color;
-    }
-    
-    /**
-     * Return whether text formatting (bold, italics, underline, strikethrough) is set.
-     * 
-     * @return true if formatting is set
-     */
-    public boolean hasFormatting() {
-        return getBold() != null || getItalic() != null
-                || getUnderline() != null || getStrikethrough() != null;
-    }
-
-    /**
-     * Return where the text formatting of the given style set is different from
-     * that assigned to this one.
-     * 
-     * @param other the other style set
-     * @return true if there is a difference
-     */
-    public boolean hasEqualFormatting(StyleSet other) {
-        return getBold() == other.getBold() && getItalic() == other.getItalic()
-                && getUnderline() == other.getUnderline() && 
-                getStrikethrough() == other.getStrikethrough();
-    }
-
-    /**
-     * Create a new instance with styles inherited from this one but with new styles
-     * from the given style set.
-     * 
-     * @param style the style set
-     * @return a new style set instance
-     */
-    public StyleSet extend(StyleSet style) {
-        StyleSet newStyle = clone();
-        if (style.getBold() != null) {
-            newStyle.setBold(style.getBold());
-        }
-        if (style.getItalic() != null) {
-            newStyle.setItalic(style.getItalic());
-        }
-        if (style.getUnderline() != null) {
-            newStyle.setUnderline(style.getUnderline());
-        }
-        if (style.getStrikethrough() != null) {
-            newStyle.setStrikethrough(style.getStrikethrough());
-        }
-        if (style.getColor() != null) {
-            newStyle.setColor(style.getColor());
-        }
-        return newStyle;
-    }
-    
-    @Override
-    public StyleSet clone() {
-        StyleSet style = new StyleSet();
-        style.setBold(getBold());
-        style.setItalic(getItalic());
-        style.setUnderline(getUnderline());
-        style.setStrikethrough(getStrikethrough());
-        style.setColor(getColor());
-        return style;
-    }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java
deleted file mode 100644
index 9ef38446b..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q <http://www.sk89q.com>
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser 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 Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.sk89q.worldedit.util.formatting;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A fragment of text that can be styled.
- */
-public class StyledFragment extends Fragment {
-    
-    private final List<Fragment> children = new ArrayList<>();
-    private StyleSet style;
-    private Fragment lastText;
-    
-    public StyledFragment() {
-        style = new StyleSet();
-    }
-    
-    public StyledFragment(StyleSet style) {
-        this.style = style;
-    }
-    
-    public StyledFragment(Style... styles) {
-        this.style = new StyleSet(styles);
-    }
-    
-    public StyleSet getStyle() {
-        return style;
-    }
-
-    public void setStyles(StyleSet style) {
-        this.style = style;
-    }
-
-    public List<Fragment> getChildren() {
-        return children;
-    }
-
-    protected Fragment lastText() {
-        Fragment text;
-        if (!children.isEmpty()) {
-            text = children.get(children.size() - 1);
-            if (text == lastText) {
-                return text;
-            }
-        }
-        
-        text = new Fragment();
-        this.lastText = text;
-        children.add(text);
-        return text;
-    }
-
-    public StyledFragment createFragment(Style... styles) {
-        StyledFragment fragment = new StyledFragment(styles);
-        append(fragment);
-        return fragment;
-    }
-
-    public StyledFragment append(StyledFragment fragment) {
-        children.add(fragment);
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(String str) {
-        lastText().append(str);
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(Object obj) {
-        append(String.valueOf(obj));
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(StringBuffer sb) {
-        append(String.valueOf(sb));
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(CharSequence s) {
-        append(String.valueOf(s));
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(boolean b) {
-        append(String.valueOf(b));
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(char c) {
-        append(String.valueOf(c));
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(int i) {
-        append(String.valueOf(i));
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(long lng) {
-        append(String.valueOf(lng));
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(float f) {
-        append(String.valueOf(f));
-        return this;
-    }
-
-    @Override
-    public StyledFragment append(double d) {
-        append(String.valueOf(d));
-        return this;
-    }
-
-    @Override
-    public StyledFragment newLine() {
-        append("\n");
-        return this;
-    }
-    
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Code.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CodeFormat.java
similarity index 62%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Code.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CodeFormat.java
index 953d83fe3..a92590bac 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Code.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CodeFormat.java
@@ -19,19 +19,30 @@
 
 package com.sk89q.worldedit.util.formatting.component;
 
-import com.sk89q.worldedit.util.formatting.Style;
-import com.sk89q.worldedit.util.formatting.StyledFragment;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.format.TextColor;
 
 /**
  * Represents a fragment representing a command that is to be typed.
  */
-public class Code extends StyledFragment {
+public class CodeFormat extends TextComponentProducer {
 
-    /**
-     * Create a new instance.
-     */
-    public Code() {
-        super(Style.CYAN);
+    private CodeFormat() {
+        getBuilder().content("").color(TextColor.AQUA);
     }
 
+    /**
+     * Creates a CodeFormat with the given message.
+     *
+     * @param texts The text
+     * @return The Component
+     */
+    public static TextComponent wrap(String ... texts) {
+        CodeFormat code = new CodeFormat();
+        for (String text: texts) {
+            code.append(text);
+        }
+
+        return code.create();
+    }
 }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandListBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandListBox.java
index 145b6baca..ac360e99c 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandListBox.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandListBox.java
@@ -19,7 +19,10 @@
 
 package com.sk89q.worldedit.util.formatting.component;
 
-import com.sk89q.worldedit.util.formatting.Style;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.event.ClickEvent;
+import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
+import com.sk89q.worldedit.util.formatting.text.format.TextColor;
 
 public class CommandListBox extends MessageBox {
 
@@ -31,14 +34,24 @@ public class CommandListBox extends MessageBox {
      * @param title the title
      */
     public CommandListBox(String title) {
-        super(title);
+        super(title, new TextComponentProducer());
     }
 
     public CommandListBox appendCommand(String alias, String description) {
+        return appendCommand(alias, description, null);
+    }
+
+    public CommandListBox appendCommand(String alias, String description, String insertion) {
         if (!first) {
-            getContents().newLine();
+            getContents().newline();
         }
-        getContents().createFragment(Style.YELLOW_DARK).append(alias).append(": ");
+        TextComponent commandName = TextComponent.of(alias, TextColor.GOLD);
+        if (insertion != null) {
+            commandName = commandName
+                    .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, insertion))
+                    .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to select")));
+        }
+        getContents().append(commandName.append(TextComponent.of(": ")));
         getContents().append(description);
         first = false;
         return this;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java
index 00a16c24e..04a31df3b 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java
@@ -28,7 +28,7 @@ import com.sk89q.worldedit.util.command.CommandMapping;
 import com.sk89q.worldedit.util.command.Description;
 import com.sk89q.worldedit.util.command.Dispatcher;
 import com.sk89q.worldedit.util.command.PrimaryAliasComparator;
-import com.sk89q.worldedit.util.formatting.StyledFragment;
+import com.sk89q.worldedit.util.formatting.text.Component;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -38,7 +38,7 @@ import javax.annotation.Nullable;
 /**
  * A box to describe usage of a command.
  */
-public class CommandUsageBox extends StyledFragment {
+public class CommandUsageBox extends TextComponentProducer {
 
     /**
      * Create a new usage box.
@@ -80,31 +80,31 @@ public class CommandUsageBox extends StyledFragment {
             }
         }
 
-        append(box);
+        append(box.create());
     }
 
     private void attachCommandUsage(Description description, String commandString) {
-        MessageBox box = new MessageBox("Help for " + commandString);
-        StyledFragment contents = box.getContents();
+        TextComponentProducer contents = new TextComponentProducer();
 
         if (description.getUsage() != null) {
-            contents.append(new Label().append("Usage: "));
+            contents.append(LabelFormat.wrap("Usage: "));
             contents.append(description.getUsage());
         } else {
-            contents.append(new Subtle().append("Usage information is not available."));
+            contents.append(SubtleFormat.wrap("Usage information is not available."));
         }
 
-        contents.newLine();
+        contents.append(Component.newline());
 
         if (description.getHelp() != null) {
             contents.append(description.getHelp());
         } else if (description.getDescription() != null) {
             contents.append(description.getDescription());
         } else {
-            contents.append(new Subtle().append("No further help is available."));
+            contents.append(SubtleFormat.wrap("No further help is available."));
         }
 
-        append(box);
+        MessageBox box = new MessageBox("Help for " + commandString, contents);
+        append(box.create());
     }
 
 }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/ErrorFormat.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/ErrorFormat.java
new file mode 100644
index 000000000..35343ca56
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/ErrorFormat.java
@@ -0,0 +1,51 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q <http://www.sk89q.com>
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.sk89q.worldedit.util.formatting.component;
+
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.format.TextColor;
+
+/**
+ * Represents a fragment representing an error.
+ */
+public class ErrorFormat extends TextComponentProducer {
+
+    /**
+     * Create a new instance.
+     */
+    private ErrorFormat() {
+        getBuilder().content("").color(TextColor.RED);
+    }
+
+    /**
+     * Creates an ErrorFormat with the given message.
+     *
+     * @param texts The text
+     * @return The Component
+     */
+    public static TextComponent wrap(String ... texts) {
+        ErrorFormat error = new ErrorFormat();
+        for (String component : texts) {
+            error.append(component);
+        }
+
+        return error.create();
+    }
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Label.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/LabelFormat.java
similarity index 60%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Label.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/LabelFormat.java
index 8a59732b0..f0a487114 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Label.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/LabelFormat.java
@@ -19,19 +19,33 @@
 
 package com.sk89q.worldedit.util.formatting.component;
 
-import com.sk89q.worldedit.util.formatting.Style;
-import com.sk89q.worldedit.util.formatting.StyledFragment;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.format.TextColor;
 
 /**
  * Represents a fragment representing a label.
  */
-public class Label extends StyledFragment {
+public class LabelFormat extends TextComponentProducer {
 
     /**
      * Create a new instance.
      */
-    public Label() {
-        super(Style.YELLOW);
+    private LabelFormat() {
+        getBuilder().content("").color(TextColor.YELLOW);
     }
 
+    /**
+     * Creates a LabelFormat with the given message.
+     *
+     * @param texts The text
+     * @return The Component
+     */
+    public static TextComponent wrap(String ... texts) {
+        LabelFormat label = new LabelFormat();
+        for (String component : texts) {
+            label.append(component);
+        }
+
+        return label.create();
+    }
 }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/MessageBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/MessageBox.java
index 086ce05e9..6828589bc 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/MessageBox.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/MessageBox.java
@@ -21,37 +21,39 @@ package com.sk89q.worldedit.util.formatting.component;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
-import com.sk89q.worldedit.util.formatting.Style;
-import com.sk89q.worldedit.util.formatting.StyledFragment;
+import com.sk89q.worldedit.util.formatting.text.Component;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.format.TextColor;
 
 /**
  * Makes for a box with a border above and below.
  */
-public class MessageBox extends StyledFragment {
+public class MessageBox extends TextComponentProducer {
 
-    private final StyledFragment contents = new StyledFragment();
+    private static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 47;
+
+    private final TextComponentProducer contents;
 
     /**
      * Create a new box.
      */
-    public MessageBox(String title) {
+    public MessageBox(String title, TextComponentProducer contents) {
         checkNotNull(title);
 
-        int leftOver = ColorCodeBuilder.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - title.length() - 2;
+        int leftOver = GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - title.length() - 2;
         int leftSide = (int) Math.floor(leftOver * 1.0/3);
         int rightSide = (int) Math.floor(leftOver * 2.0/3);
         if (leftSide > 0) {
-            createFragment(Style.YELLOW).append(createBorder(leftSide));
+            append(TextComponent.of(createBorder(leftSide), TextColor.YELLOW));
         }
-        append(" ");
+        append(Component.space());
         append(title);
-        append(" ");
+        append(Component.space());
         if (rightSide > 0) {
-            createFragment(Style.YELLOW).append(createBorder(rightSide));
+            append(TextComponent.of(createBorder(rightSide), TextColor.YELLOW));
         }
-        newLine();
-        append(contents);
+        newline();
+        this.contents = contents;
     }
 
     private String createBorder(int count) {
@@ -63,12 +65,17 @@ public class MessageBox extends StyledFragment {
     }
 
     /**
-     * Get the internal contents.
-     * 
-     * @return the contents
+     * Gets the message box contents producer.
+     *
+     * @return The contents producer
      */
-    public StyledFragment getContents() {
+    public TextComponentProducer getContents() {
         return contents;
     }
 
+    @Override
+    public TextComponent create() {
+        append(contents.create());
+        return super.create();
+    }
 }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Subtle.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SubtleFormat.java
similarity index 60%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Subtle.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SubtleFormat.java
index 34316c087..310cc4e01 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/Subtle.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SubtleFormat.java
@@ -19,19 +19,33 @@
 
 package com.sk89q.worldedit.util.formatting.component;
 
-import com.sk89q.worldedit.util.formatting.Style;
-import com.sk89q.worldedit.util.formatting.StyledFragment;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.format.TextColor;
 
 /**
  * Represents a subtle part of the message.
  */
-public class Subtle extends StyledFragment {
+public class SubtleFormat extends TextComponentProducer {
 
     /**
      * Create a new instance.
      */
-    public Subtle() {
-        super(Style.GRAY);
+    private SubtleFormat() {
+        getBuilder().content("").color(TextColor.GRAY);
     }
 
+    /**
+     * Creates a SubtleFormat with the given message.
+     *
+     * @param texts The text
+     * @return The Component
+     */
+    public static TextComponent wrap(String ... texts) {
+        SubtleFormat subtle = new SubtleFormat();
+        for (String component : texts) {
+            subtle.append(component);
+        }
+
+        return subtle.create();
+    }
 }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/TextComponentProducer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/TextComponentProducer.java
new file mode 100644
index 000000000..32a9f8d30
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/TextComponentProducer.java
@@ -0,0 +1,68 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q <http://www.sk89q.com>
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.sk89q.worldedit.util.formatting.component;
+
+import com.sk89q.worldedit.util.formatting.text.Component;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+
+public class TextComponentProducer {
+
+    private TextComponent.Builder builder = TextComponent.builder().content("");
+
+    public TextComponent.Builder getBuilder() {
+        return builder;
+    }
+
+    /**
+     * Adds a component as a child to this Producer.
+     *
+     * @param component The component
+     * @return The producer, for chaining
+     */
+    public TextComponentProducer append(Component component) {
+        getBuilder().append(component);
+        return this;
+    }
+
+    /**
+     * Adds a string as a child to this Producer.
+     *
+     * @param string The text
+     * @return The producer, for chaining
+     */
+    public TextComponentProducer append(String string) {
+        getBuilder().append(TextComponent.of(string));
+        return this;
+    }
+
+    /**
+     * Adds a newline as a child to this Producer.
+     *
+     * @return The producer, for chaining
+     */
+    public TextComponentProducer newline() {
+        getBuilder().append(Component.newline());
+        return this;
+    }
+
+    public TextComponent create() {
+        return builder.build();
+    }
+}
diff --git a/worldedit-forge/build.gradle b/worldedit-forge/build.gradle
index 8569d8506..dc6b8aeb9 100644
--- a/worldedit-forge/build.gradle
+++ b/worldedit-forge/build.gradle
@@ -87,7 +87,6 @@ shadowJar {
         relocate "org.slf4j", "com.sk89q.worldedit.slf4j"
         relocate "org.apache.logging.slf4j", "com.sk89q.worldedit.log4jbridge"
 
-        include(dependency(':worldedit-core'))
         include(dependency('org.slf4j:slf4j-api'))
         include(dependency("org.apache.logging.log4j:log4j-slf4j-impl"))
     }
diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
index 5b3b2ca84..edc5275ab 100644
--- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
+++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
@@ -31,9 +31,11 @@ import com.sk89q.worldedit.math.Vector3;
 import com.sk89q.worldedit.session.SessionKey;
 import com.sk89q.worldedit.util.HandSide;
 import com.sk89q.worldedit.util.Location;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.serializer.gson.GsonComponentSerializer;
 import com.sk89q.worldedit.world.block.BaseBlock;
 import com.sk89q.worldedit.world.block.BlockStateHolder;
-
+import io.netty.buffer.Unpooled;
 import net.minecraft.entity.player.EntityPlayerMP;
 import net.minecraft.item.ItemStack;
 import net.minecraft.network.PacketBuffer;
@@ -42,6 +44,7 @@ import net.minecraft.network.play.server.SPacketUpdateTileEntity;
 import net.minecraft.util.EnumHand;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.text.ITextComponent;
 import net.minecraft.util.text.TextComponentString;
 import net.minecraft.util.text.TextFormatting;
 
@@ -49,8 +52,6 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
-import io.netty.buffer.Unpooled;
-
 public class ForgePlayer extends AbstractPlayerActor {
 
     private final EntityPlayerMP player;
@@ -141,6 +142,11 @@ public class ForgePlayer extends AbstractPlayerActor {
         sendColorized(msg, TextFormatting.RED);
     }
 
+    @Override
+    public void print(TextComponent component) {
+        this.player.sendMessage(ITextComponent.Serializer.fromJson(GsonComponentSerializer.INSTANCE.serialize(component)));
+    }
+
     private void sendColorized(String msg, TextFormatting formatting) {
         for (String part : msg.split("\n")) {
             TextComponentString component = new TextComponentString(part);
diff --git a/worldedit-libs/build.gradle b/worldedit-libs/build.gradle
new file mode 100644
index 000000000..b05c601e8
--- /dev/null
+++ b/worldedit-libs/build.gradle
@@ -0,0 +1,116 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+/*
+
+This project shades <em>API</em> libraries, i.e. those libraries
+whose classes are publicly referenced from `-core` classes.
+
+This project <em>does not</em> shade implementation libraries, i.e.
+those libraries whose classes are internally depended on.
+
+This is because the main reason for shading those libraries is for
+their internal usage in each platform, not because we need them available to
+dependents of `-core` to compile and work with WorldEdit's API.
+
+ */
+subprojects {
+    apply plugin: 'maven'
+    apply plugin: 'com.github.johnrengelman.shadow'
+    apply plugin: 'com.jfrog.artifactory'
+    configurations {
+        create("shade")
+        getByName("archives").extendsFrom(getByName("default"))
+    }
+
+    group = rootProject.group + ".worldedit-libs"
+
+    tasks.register("jar", ShadowJar) {
+        configurations = [project.configurations.shade]
+        classifier = ""
+
+        dependencies {
+            exclude(dependency("com.google.guava:guava"))
+            exclude(dependency("com.google.code.gson:gson"))
+            exclude(dependency("org.checkerframework:checker-qual"))
+        }
+
+        relocate('net.kyori.text', 'com.sk89q.worldedit.util.formatting.text')
+    }
+    def altConfigFiles = { String artifactType ->
+        def deps = configurations.shade.incoming.dependencies
+                .collect { it.copy() }
+                .collect { dependency ->
+                    dependency.artifact { artifact ->
+                        artifact.name = dependency.name
+                        artifact.type = artifactType
+                        artifact.extension = 'jar'
+                        artifact.classifier = artifactType
+                    }
+                    dependency
+                }
+
+        return files(configurations.detachedConfiguration(deps as Dependency[])
+                .resolvedConfiguration.lenientConfiguration.getArtifacts()
+                .findAll { it.classifier == artifactType }
+                .collect { zipTree(it.file) })
+    }
+    tasks.register("sourcesJar", Jar) {
+        from {
+            altConfigFiles('sources')
+        }
+        def filePattern = ~'(.*)net/kyori/text((?:/|$).*)'
+        def textPattern = ~/net\.kyori\.text/
+        eachFile {
+            it.filter { String line ->
+                line.replaceFirst(textPattern, 'com.sk89q.worldedit.util.formatting.text')
+            }
+            it.path = it.path.replaceFirst(filePattern, '$1com/sk89q/worldedit/util/formatting/text$2')
+        }
+        classifier = "sources"
+    }
+
+    artifacts {
+        add("default", jar)
+        add("archives", sourcesJar)
+    }
+
+    tasks.register("install", Upload) {
+        configuration = configurations.archives
+        repositories.mavenInstaller {
+            pom.version = project.version
+            pom.artifactId = project.name
+        }
+    }
+
+    artifactoryPublish {
+        publishConfigs('default')
+    }
+
+    build.dependsOn(jar, sourcesJar)
+}
+
+project("core") {
+    dependencies {
+        shade 'net.kyori:text-api:2.0.0'
+        shade 'net.kyori:text-serializer-gson:2.0.0'
+        shade 'net.kyori:text-serializer-legacy:2.0.0'
+        shade('com.sk89q:jchronic:0.2.4a') {
+            exclude(group: "junit", module: "junit")
+        }
+        shade 'com.thoughtworks.paranamer:paranamer:2.6'
+        shade 'com.sk89q.lib:jlibnoise:1.0.0'
+    }
+}
+project("bukkit") {
+    dependencies {
+        shade 'net.kyori:text-adapter-bukkit:2.0.0-SNAPSHOT'
+    }
+}
+project("sponge") {
+    dependencies {
+        shade 'net.kyori:text-adapter-spongeapi:2.0.0-SNAPSHOT'
+    }
+}
+
+tasks.register("build") {
+    dependsOn(subprojects.collect { it.tasks.named("build") })
+}
diff --git a/worldedit-sponge/build.gradle b/worldedit-sponge/build.gradle
index 0ef1e5361..6d75c4d84 100644
--- a/worldedit-sponge/build.gradle
+++ b/worldedit-sponge/build.gradle
@@ -17,6 +17,7 @@ repositories {
 
 dependencies {
     compile project(':worldedit-core')
+    compile project(':worldedit-libs:sponge')
     compile 'org.spongepowered:spongeapi:7.1.0'
     compile 'org.bstats:bstats-sponge:1.4'
     testCompile group: 'org.mockito', name: 'mockito-core', version:'1.9.0-rc1'
@@ -40,15 +41,12 @@ jar {
 
 shadowJar {
     dependencies {
-        include(dependency(':worldedit-core'))
-        include(dependency('org.bstats:bstats-sponge:1.4'))
+        relocate ("org.bstats", "com.sk89q.worldedit.sponge.bstats") {
+            include(dependency('org.bstats:bstats-sponge:1.4'))
+        }
     }
 }
 
-artifacts {
-    archives shadowJar
-}
-
 if (project.hasProperty("signing")) {
     apply plugin: 'signing'
 
diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeCommandSender.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeCommandSender.java
index bf1ceb7bd..e44146b3c 100644
--- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeCommandSender.java
+++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeCommandSender.java
@@ -26,6 +26,8 @@ import com.sk89q.worldedit.extension.platform.Actor;
 import com.sk89q.worldedit.internal.cui.CUIEvent;
 import com.sk89q.worldedit.session.SessionKey;
 import com.sk89q.worldedit.util.auth.AuthorizationException;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.adapter.spongeapi.TextAdapter;
 import org.spongepowered.api.command.CommandSource;
 import org.spongepowered.api.entity.living.player.Player;
 import org.spongepowered.api.text.Text;
@@ -89,6 +91,11 @@ public class SpongeCommandSender implements Actor {
         sendColorized(msg, TextColors.RED);
     }
 
+    @Override
+    public void print(TextComponent component) {
+        TextAdapter.sendComponent(sender, component);
+    }
+
     private void sendColorized(String msg, TextColor formatting) {
         for (String part : msg.split("\n")) {
             sender.sendMessage(Text.of(formatting, TextSerializers.LEGACY_FORMATTING_CODE.deserialize(part)));
diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java
index 3793ffb87..099780b3d 100644
--- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java
+++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java
@@ -36,6 +36,8 @@ import com.sk89q.worldedit.world.gamemode.GameMode;
 import com.sk89q.worldedit.world.gamemode.GameModes;
 import com.sk89q.worldedit.world.item.ItemTypes;
 
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
+import com.sk89q.worldedit.util.formatting.text.adapter.spongeapi.TextAdapter;
 import org.spongepowered.api.Sponge;
 import org.spongepowered.api.data.type.HandTypes;
 import org.spongepowered.api.entity.living.player.Player;
@@ -149,6 +151,11 @@ public class SpongePlayer extends AbstractPlayerActor {
         sendColorized(msg, TextColors.RED);
     }
 
+    @Override
+    public void print(TextComponent component) {
+        TextAdapter.sendComponent(player, component);
+    }
+
     private void sendColorized(String msg, TextColor formatting) {
         for (String part : msg.split("\n")) {
             this.player.sendMessage(Text.of(formatting, TextSerializers.FORMATTING_CODE.deserialize(part)));