Fixed inventory lookups/rollbacks missing block placement actions
This commit is contained in:
parent
527be90249
commit
585d8b275a
14 changed files with 136 additions and 114 deletions
|
|
@ -96,7 +96,7 @@ public class ContainerRollback extends Queue {
|
|||
int rowTypeRaw = (Integer) lookupRow[6];
|
||||
int rowData = (Integer) lookupRow[7];
|
||||
int rowAction = (Integer) lookupRow[8];
|
||||
int rowRolledBack = (Integer) lookupRow[9];
|
||||
int rowRolledBack = Util.rolledBack((Integer) lookupRow[9], false);
|
||||
// int rowWid = (Integer)lookupRow[10];
|
||||
int rowAmount = (Integer) lookupRow[11];
|
||||
byte[] rowMetadata = (byte[]) lookupRow[12];
|
||||
|
|
|
|||
|
|
@ -187,22 +187,15 @@ public class Database extends Queue {
|
|||
}
|
||||
}
|
||||
|
||||
public static void performUpdate(Statement statement, long id, int action, int table) {
|
||||
public static void performUpdate(Statement statement, long id, int rb, int table) {
|
||||
try {
|
||||
int rolledBack = 1;
|
||||
if (action == 1) {
|
||||
rolledBack = 0;
|
||||
}
|
||||
|
||||
if (table == 1) {
|
||||
int rolledBack = Util.toggleRolledBack(rb, (table == 2 || table == 3 || table == 4)); // co_item, co_container, co_block
|
||||
if (table == 1 || table == 3) {
|
||||
statement.executeUpdate("UPDATE " + ConfigHandler.prefix + "container SET rolled_back='" + rolledBack + "' WHERE rowid='" + id + "'");
|
||||
}
|
||||
else if (table == 2) {
|
||||
statement.executeUpdate("UPDATE " + ConfigHandler.prefix + "item SET rolled_back='" + rolledBack + "' WHERE rowid='" + id + "'");
|
||||
}
|
||||
else if (table == 3) {
|
||||
statement.executeUpdate("UPDATE " + ConfigHandler.prefix + "container SET rolled_back_inventory='" + rolledBack + "' WHERE rowid='" + id + "'");
|
||||
}
|
||||
else {
|
||||
statement.executeUpdate("UPDATE " + ConfigHandler.prefix + "block SET rolled_back='" + rolledBack + "' WHERE rowid='" + id + "'");
|
||||
}
|
||||
|
|
@ -218,7 +211,7 @@ public class Database extends Queue {
|
|||
String signInsert = "INSERT INTO " + ConfigHandler.prefix + "sign (time, user, wid, x, y, z, action, color, data, line_1, line_2, line_3, line_4) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
String blockInsert = "INSERT INTO " + ConfigHandler.prefix + "block (time, user, wid, x, y, z, type, data, meta, blockdata, action, rolled_back) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
String skullInsert = "INSERT INTO " + ConfigHandler.prefix + "skull (time, owner) VALUES (?, ?)";
|
||||
String containerInsert = "INSERT INTO " + ConfigHandler.prefix + "container (time, user, wid, x, y, z, type, data, amount, metadata, action, rolled_back, rolled_back_inventory) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
String containerInsert = "INSERT INTO " + ConfigHandler.prefix + "container (time, user, wid, x, y, z, type, data, amount, metadata, action, rolled_back) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
String itemInsert = "INSERT INTO " + ConfigHandler.prefix + "item (time, user, wid, x, y, z, type, data, amount, action, rolled_back) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
String worldInsert = "INSERT INTO " + ConfigHandler.prefix + "world (id, world) VALUES (?, ?)";
|
||||
String chatInsert = "INSERT INTO " + ConfigHandler.prefix + "chat (time, user, wid, x, y, z, message) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
|
@ -339,7 +332,7 @@ public class Database extends Queue {
|
|||
index = ", INDEX(time), INDEX(user,time), INDEX(wid,x,z,time)";
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "command(rowid int(8) NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid),time int(10), user int(8), wid int(4), x int(8), y int (3), z int(8), message varchar(16000)" + index + ") ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
index = ", INDEX(wid,x,z,time), INDEX(user,time), INDEX(type,time)";
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "container(rowid int(10) NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid), time int(10), user int(8), wid int(4), x int(8), y int(3), z int(8), type int(6), data int(6), amount int(4), metadata blob, action int(2), rolled_back tinyint(1), rolled_back_inventory tinyint(1)" + index + ") ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "container(rowid int(10) NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid), time int(10), user int(8), wid int(4), x int(8), y int(3), z int(8), type int(6), data int(6), amount int(4), metadata blob, action int(2), rolled_back tinyint(1)" + index + ") ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
index = ", INDEX(wid,x,z,time), INDEX(user,time), INDEX(type,time)";
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "item(rowid int(10) NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid), time int(10), user int(8), wid int(4), x int(8), y int(3), z int(8), type int(6), data blob, amount int(4), action tinyint(1), rolled_back tinyint(1)" + index + ") ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "database_lock(rowid int(8) NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid),status tinyint(1),time int(10)) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
|
|
@ -417,7 +410,7 @@ public class Database extends Queue {
|
|||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "command (time INTEGER, user INTEGER, wid INTEGER, x INTEGER, y INTEGER, z INTEGER, message TEXT);");
|
||||
}
|
||||
if (!tableData.contains(prefix + "container")) {
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "container (time INTEGER, user INTEGER, wid INTEGER, x INTEGER, y INTEGER, z INTEGER, type INTEGER, data INTEGER, amount INTEGER, metadata BLOB, action INTEGER, rolled_back INTEGER, rolled_back_inventory INTEGER);");
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "container (time INTEGER, user INTEGER, wid INTEGER, x INTEGER, y INTEGER, z INTEGER, type INTEGER, data INTEGER, amount INTEGER, metadata BLOB, action INTEGER, rolled_back INTEGER);");
|
||||
}
|
||||
if (!tableData.contains(prefix + "item")) {
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "item (time INTEGER, user INTEGER, wid INTEGER, x INTEGER, y INTEGER, z INTEGER, type INTEGER, data BLOB, amount INTEGER, action INTEGER, rolled_back INTEGER);");
|
||||
|
|
|
|||
|
|
@ -297,6 +297,7 @@ public class Lookup extends Queue {
|
|||
restrictWorld = true;
|
||||
}
|
||||
|
||||
boolean inventoryQuery = (actionList.contains(4) && actionList.contains(11));
|
||||
boolean validAction = false;
|
||||
String queryBlock = "";
|
||||
String queryEntity = "";
|
||||
|
|
@ -530,7 +531,7 @@ public class Lookup extends Queue {
|
|||
if (validAction) {
|
||||
queryBlock = queryBlock + " action IN(" + action + ") AND";
|
||||
}
|
||||
else if (includeBlock.length() > 0 || includeEntity.length() > 0 || excludeBlock.length() > 0 || excludeEntity.length() > 0) {
|
||||
else if (inventoryQuery || includeBlock.length() > 0 || includeEntity.length() > 0 || excludeBlock.length() > 0 || excludeEntity.length() > 0) {
|
||||
queryBlock = queryBlock + " action NOT IN(-1) AND";
|
||||
}
|
||||
|
||||
|
|
@ -666,23 +667,36 @@ public class Lookup extends Queue {
|
|||
}
|
||||
}
|
||||
|
||||
boolean itemLookup = (actionList.contains(4) && actionList.contains(11));
|
||||
if (lookup && actionList.size() == 0) {
|
||||
boolean itemLookup = inventoryQuery;
|
||||
if ((lookup && actionList.size() == 0) || (itemLookup && !actionList.contains(0))) {
|
||||
if (!count) {
|
||||
rows = "rowid as id,time,user,wid,x,y,z,type,meta as metadata,data,-1 as amount,action,rolled_back";
|
||||
}
|
||||
|
||||
if (inventoryQuery) {
|
||||
if (validAction) {
|
||||
baseQuery = baseQuery.replace("action IN(" + action + ")", "action IN(1)");
|
||||
}
|
||||
else {
|
||||
baseQuery = baseQuery.replace("action NOT IN(-1)", "action IN(1)");
|
||||
}
|
||||
|
||||
if (!count) {
|
||||
rows = "rowid as id,time,user,wid,x,y,z,type,meta as metadata,data,1 as amount,action,rolled_back";
|
||||
}
|
||||
}
|
||||
|
||||
if (includeBlock.length() > 0 || excludeBlock.length() > 0) {
|
||||
baseQuery = baseQuery.replace("action NOT IN(-1)", "action NOT IN(3)"); // if block specified for include/exclude, filter out entity data
|
||||
}
|
||||
|
||||
query = unionSelect + "SELECT " + "'0' as tbl," + rows + " FROM " + ConfigHandler.prefix + queryTable + " " + index + "WHERE" + baseQuery + unionLimit + ") UNION ALL ";
|
||||
query = unionSelect + "SELECT " + "'0' as tbl," + rows + " FROM " + ConfigHandler.prefix + "block " + index + "WHERE" + baseQuery + unionLimit + ") UNION ALL ";
|
||||
itemLookup = true;
|
||||
}
|
||||
|
||||
if (itemLookup) {
|
||||
if (!count) {
|
||||
rows = "rowid as id,time,user,wid,x,y,z,type,metadata,data,amount,action,rolled_back_inventory as rolled_back";
|
||||
rows = "rowid as id,time,user,wid,x,y,z,type,metadata,data,amount,action,rolled_back";
|
||||
}
|
||||
query = query + unionSelect + "SELECT " + "'1' as tbl," + rows + " FROM " + ConfigHandler.prefix + "container WHERE" + queryBlock + unionLimit + ") UNION ALL ";
|
||||
|
||||
|
|
@ -735,7 +749,7 @@ public class Lookup extends Queue {
|
|||
int z = block.getZ();
|
||||
int time = (int) (System.currentTimeMillis() / 1000L);
|
||||
int worldId = Util.getWorldId(block.getWorld().getName());
|
||||
String query = "SELECT user,type FROM " + ConfigHandler.prefix + "block WHERE wid = '" + worldId + "' AND x = '" + x + "' AND z = '" + z + "' AND y = '" + y + "' AND rolled_back = '0' AND action='1' ORDER BY rowid DESC LIMIT 0, 1";
|
||||
String query = "SELECT user,type FROM " + ConfigHandler.prefix + "block WHERE wid = '" + worldId + "' AND x = '" + x + "' AND z = '" + z + "' AND y = '" + y + "' AND rolled_back IN(0,2) AND action='1' ORDER BY rowid DESC LIMIT 0, 1";
|
||||
|
||||
ResultSet results = statement.executeQuery(query);
|
||||
while (results.next()) {
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ public class Rollback extends Queue {
|
|||
// Perform update transaction(s) in consumer
|
||||
if (preview == 0) {
|
||||
if (actionList.contains(11)) {
|
||||
List<Object[]> blockList = new ArrayList<>();
|
||||
List<Object[]> inventoryList = new ArrayList<>();
|
||||
List<Object[]> containerList = new ArrayList<>();
|
||||
for (Object[] data : itemList) {
|
||||
|
|
@ -259,12 +260,16 @@ public class Rollback extends Queue {
|
|||
if (table == 2) { // item
|
||||
inventoryList.add(data);
|
||||
}
|
||||
else {
|
||||
else if (table == 1) { // container
|
||||
containerList.add(data);
|
||||
}
|
||||
else { // block
|
||||
blockList.add(data);
|
||||
}
|
||||
}
|
||||
Queue.queueRollbackUpdate(userString, location, inventoryList, Process.INVENTORY_ROLLBACK_UPDATE, rollbackType);
|
||||
Queue.queueRollbackUpdate(userString, location, containerList, Process.INVENTORY_CONTAINER_ROLLBACK_UPDATE, rollbackType);
|
||||
Queue.queueRollbackUpdate(userString, location, blockList, Process.BLOCK_INVENTORY_ROLLBACK_UPDATE, rollbackType);
|
||||
}
|
||||
else {
|
||||
Queue.queueRollbackUpdate(userString, location, lookupList, Process.ROLLBACK_UPDATE, rollbackType);
|
||||
|
|
@ -320,7 +325,7 @@ public class Rollback extends Queue {
|
|||
int rowTypeRaw = (Integer) row[6];
|
||||
int rowData = (Integer) row[7];
|
||||
int rowAction = (Integer) row[8];
|
||||
int rowRolledBack = (Integer) row[9];
|
||||
int rowRolledBack = Util.rolledBack((Integer) row[9], false);
|
||||
int rowWorldId = (Integer) row[10];
|
||||
byte[] rowMeta = (byte[]) row[12];
|
||||
byte[] rowBlockData = (byte[]) row[13];
|
||||
|
|
@ -1023,14 +1028,15 @@ public class Rollback extends Queue {
|
|||
int rowTypeRaw = (Integer) row[6];
|
||||
int rowData = (Integer) row[7];
|
||||
int rowAction = (Integer) row[8];
|
||||
int rowRolledBack = (Integer) row[9];
|
||||
int rowRolledBack = Util.rolledBack((Integer) row[9], false);
|
||||
int rowWorldId = (Integer) row[10];
|
||||
int rowAmount = (Integer) row[11];
|
||||
byte[] rowMetadata = (byte[]) row[12];
|
||||
Material rowType = Util.getType(rowTypeRaw);
|
||||
|
||||
if (rowType != null && ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1))) {
|
||||
if (inventoryRollback) {
|
||||
int rolledBackInventory = Util.rolledBack((Integer) row[9], true);
|
||||
if (rowType != null) {
|
||||
if (inventoryRollback && ((rollbackType == 0 && rolledBackInventory == 0) || (rollbackType == 1 && rolledBackInventory == 1))) {
|
||||
int rowUserId = (Integer) row[2];
|
||||
String rowUser = ConfigHandler.playerIdCacheReversed.get(rowUserId);
|
||||
if (rowUser == null) {
|
||||
|
|
@ -1071,77 +1077,79 @@ public class Rollback extends Queue {
|
|||
continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting)
|
||||
}
|
||||
|
||||
if (rowAction > 1) {
|
||||
if (inventoryRollback || rowAction > 1) {
|
||||
continue; // skip inventory & ender chest transactions
|
||||
}
|
||||
|
||||
ItemStack itemstack = new ItemStack(rowType, rowAmount, (short) rowData);
|
||||
Object[] populatedStack = populateItemStack(itemstack, rowMetadata);
|
||||
String faceData = (String) populatedStack[1];
|
||||
if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) {
|
||||
ItemStack itemstack = new ItemStack(rowType, rowAmount, (short) rowData);
|
||||
Object[] populatedStack = populateItemStack(itemstack, rowMetadata);
|
||||
String faceData = (String) populatedStack[1];
|
||||
|
||||
if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) {
|
||||
container = null; // container patch 2.14.0
|
||||
String world = Util.getWorldName(rowWorldId);
|
||||
if (world.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) {
|
||||
container = null; // container patch 2.14.0
|
||||
String world = Util.getWorldName(rowWorldId);
|
||||
if (world.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
World bukkitWorld = Bukkit.getServer().getWorld(world);
|
||||
if (bukkitWorld == null) {
|
||||
continue;
|
||||
}
|
||||
Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
if (!bukkitWorld.isChunkLoaded(block.getChunk())) {
|
||||
bukkitWorld.getChunkAt(block.getLocation());
|
||||
}
|
||||
World bukkitWorld = Bukkit.getServer().getWorld(world);
|
||||
if (bukkitWorld == null) {
|
||||
continue;
|
||||
}
|
||||
Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
if (!bukkitWorld.isChunkLoaded(block.getChunk())) {
|
||||
bukkitWorld.getChunkAt(block.getLocation());
|
||||
}
|
||||
|
||||
if (BlockGroup.CONTAINERS.contains(block.getType())) {
|
||||
container = Util.getContainerInventory(block.getState(), false);
|
||||
containerType = block.getType();
|
||||
}
|
||||
else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND) || BlockGroup.CONTAINERS.contains(Material.ITEM_FRAME)) {
|
||||
BlockFace blockFace = BlockFace.valueOf(faceData);
|
||||
for (Entity entity : block.getChunk().getEntities()) {
|
||||
if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) {
|
||||
if (entity instanceof ArmorStand) {
|
||||
container = Util.getEntityEquipment((LivingEntity) entity);
|
||||
containerType = Material.ARMOR_STAND;
|
||||
}
|
||||
else if (entity instanceof ItemFrame) {
|
||||
container = entity;
|
||||
containerType = Material.ITEM_FRAME;
|
||||
if (blockFace == ((ItemFrame) entity).getFacing()) {
|
||||
break;
|
||||
if (BlockGroup.CONTAINERS.contains(block.getType())) {
|
||||
container = Util.getContainerInventory(block.getState(), false);
|
||||
containerType = block.getType();
|
||||
}
|
||||
else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND) || BlockGroup.CONTAINERS.contains(Material.ITEM_FRAME)) {
|
||||
BlockFace blockFace = BlockFace.valueOf(faceData);
|
||||
for (Entity entity : block.getChunk().getEntities()) {
|
||||
if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) {
|
||||
if (entity instanceof ArmorStand) {
|
||||
container = Util.getEntityEquipment((LivingEntity) entity);
|
||||
containerType = Material.ARMOR_STAND;
|
||||
}
|
||||
else if (entity instanceof ItemFrame) {
|
||||
container = entity;
|
||||
containerType = Material.ITEM_FRAME;
|
||||
if (blockFace == ((ItemFrame) entity).getFacing()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastX = rowX;
|
||||
lastY = rowY;
|
||||
lastZ = rowZ;
|
||||
lastWorldId = rowWorldId;
|
||||
lastFace = faceData;
|
||||
}
|
||||
|
||||
lastX = rowX;
|
||||
lastY = rowY;
|
||||
lastZ = rowZ;
|
||||
lastWorldId = rowWorldId;
|
||||
lastFace = faceData;
|
||||
if (container != null) {
|
||||
int action = 0;
|
||||
if (rollbackType == 0 && rowAction == 0) {
|
||||
action = 1;
|
||||
}
|
||||
|
||||
if (rollbackType == 1 && rowAction == 1) {
|
||||
action = 1;
|
||||
}
|
||||
|
||||
int slot = (Integer) populatedStack[0];
|
||||
itemstack = (ItemStack) populatedStack[2];
|
||||
|
||||
modifyContainerItems(containerType, container, slot, itemstack, action);
|
||||
itemCount1 = itemCount1 + rowAmount;
|
||||
}
|
||||
containerInit = true;
|
||||
}
|
||||
|
||||
if (container != null) {
|
||||
int action = 0;
|
||||
if (rollbackType == 0 && rowAction == 0) {
|
||||
action = 1;
|
||||
}
|
||||
|
||||
if (rollbackType == 1 && rowAction == 1) {
|
||||
action = 1;
|
||||
}
|
||||
|
||||
int slot = (Integer) populatedStack[0];
|
||||
itemstack = (ItemStack) populatedStack[2];
|
||||
|
||||
modifyContainerItems(containerType, container, slot, itemstack, action);
|
||||
itemCount1 = itemCount1 + rowAmount;
|
||||
}
|
||||
containerInit = true;
|
||||
}
|
||||
|
||||
ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0 });
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ public class BlockLookup {
|
|||
}
|
||||
|
||||
String rbFormat = "";
|
||||
if (resultRolledBack == 1) {
|
||||
if (resultRolledBack == 1 || resultRolledBack == 3) {
|
||||
rbFormat = Color.STRIKETHROUGH;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public class ChestTransactionLookup {
|
|||
String selector = (resultAction != 0 ? Selector.FIRST : Selector.SECOND);
|
||||
String tag = (resultAction != 0 ? Color.GREEN + "+" : Color.RED + "-");
|
||||
String rbFormat = "";
|
||||
if (resultRolledBack == 1) {
|
||||
if (resultRolledBack == 1 || resultRolledBack == 3) {
|
||||
rbFormat = Color.STRIKETHROUGH;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public class InteractionLookup {
|
|||
found = true;
|
||||
|
||||
String rbFormat = "";
|
||||
if (resultRolledBack == 1) {
|
||||
if (resultRolledBack == 1 || resultRolledBack == 3) {
|
||||
rbFormat = Color.STRIKETHROUGH;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ public class ContainerStatement {
|
|||
preparedStmt.setObject(10, byteData);
|
||||
preparedStmt.setInt(11, action);
|
||||
preparedStmt.setInt(12, rolledBack);
|
||||
preparedStmt.setInt(13, 0); // rolled_back_inventory
|
||||
preparedStmt.addBatch();
|
||||
|
||||
if (batchCount > 0 && batchCount % 1000 == 0) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue