use of net.countercraft.movecraft.craft.SinkingCraft in project Movecraft by APDevTeam.
the class TranslationTask method execute.
@Override
protected void execute() throws InterruptedException, ExecutionException {
// Check if theres anything to move
if (oldHitBox.isEmpty())
return;
if (getCraft().getDisabled() && !(craft instanceof SinkingCraft)) {
fail(I18nSupport.getInternationalisedString("Translation - Failed Craft Is Disabled"));
return;
}
// call event
final CraftPreTranslateEvent preTranslateEvent = new CraftPreTranslateEvent(craft, dx, dy, dz, world);
Bukkit.getServer().getPluginManager().callEvent(preTranslateEvent);
if (preTranslateEvent.isCancelled()) {
fail(preTranslateEvent.getFailMessage(), preTranslateEvent.isPlayingFailSound());
return;
}
if (dx != preTranslateEvent.getDx())
dx = preTranslateEvent.getDx();
if (dy != preTranslateEvent.getDy())
dy = preTranslateEvent.getDy();
if (dz != preTranslateEvent.getDz())
dz = preTranslateEvent.getDz();
world = preTranslateEvent.getWorld();
final int minY = oldHitBox.getMinY();
final int maxY = oldHitBox.getMaxY();
// proccess nether portals
if (Settings.CraftsUseNetherPortals && craft.getWorld().getEnvironment() != Environment.THE_END && world.equals(craft.getWorld())) {
// ensure chunks are loaded for portal checking only if change in location is
// large
Set<MovecraftChunk> chunksToLoad = ChunkManager.getChunks(oldHitBox, world, dx, dy, dz);
MovecraftChunk.addSurroundingChunks(chunksToLoad, 2);
ChunkManager.checkChunks(chunksToLoad);
if (!chunksToLoad.isEmpty())
ChunkManager.syncLoadChunks(chunksToLoad).get();
for (MovecraftLocation oldLocation : oldHitBox) {
Location location = oldLocation.translate(dx, dy, dz).toBukkit(craft.getWorld());
Block block = craft.getWorld().getBlockAt(location);
if (block.getType() == Material.NETHER_PORTAL) {
if (processNetherPortal(block)) {
sound = Sound.BLOCK_PORTAL_TRAVEL;
volume = 0.25f;
break;
}
}
}
}
// ensure chunks are loaded only if world is different or change in location is
// large
Set<MovecraftChunk> chunksToLoad = ChunkManager.getChunks(oldHitBox, craft.getWorld());
chunksToLoad.addAll(ChunkManager.getChunks(oldHitBox, world, dx, dy, dz));
MovecraftChunk.addSurroundingChunks(chunksToLoad, 1);
ChunkManager.checkChunks(chunksToLoad);
if (!chunksToLoad.isEmpty())
ChunkManager.syncLoadChunks(chunksToLoad).get();
// Check if the craft is too high
if (world.equals(craft.getWorld()) && (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_LIMIT, craft.getWorld()) < craft.getHitBox().getMinY())
dy = Math.min(dy, -1);
else if (world.equals(craft.getWorld()) && (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_ABOVE_GROUND, craft.getWorld()) > 0) {
final MovecraftLocation middle = oldHitBox.getMidPoint();
int testY = minY;
while (testY > 0) {
testY--;
if (!craft.getWorld().getBlockAt(middle.getX(), testY, middle.getZ()).getType().isAir())
break;
}
if (maxY - testY > (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_ABOVE_GROUND, world))
dy = Math.min(dy, -1);
}
// Process gravity
if (world.equals(craft.getWorld()) && craft.getType().getBoolProperty(CraftType.USE_GRAVITY) && !(craft instanceof SinkingCraft)) {
int incline = inclineCraft(oldHitBox);
if (incline > 0) {
boolean tooSteep = craft.getType().getIntProperty(CraftType.GRAVITY_INCLINE_DISTANCE) > -1 && incline > craft.getType().getIntProperty(CraftType.GRAVITY_INCLINE_DISTANCE);
if (tooSteep && craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION) <= 0F) {
fail(I18nSupport.getInternationalisedString("Translation - Failed Incline too steep"));
return;
}
dy = tooSteep ? 0 : incline;
} else if (!isOnGround(oldHitBox) && craft.getType().getBoolProperty(CraftType.CAN_HOVER)) {
MovecraftLocation midPoint = oldHitBox.getMidPoint();
int centreMinY = oldHitBox.getMinYAt(midPoint.getX(), midPoint.getZ());
int groundY = centreMinY;
World w = craft.getWorld();
while (groundY - 1 >= WorldUtils.getWorldMinHeightLimit(w) && (w.getBlockAt(midPoint.getX(), groundY - 1, midPoint.getZ()).getType().isAir() || craft.getType().getMaterialSetProperty(CraftType.PASSTHROUGH_BLOCKS).contains(w.getBlockAt(midPoint.getX(), groundY - 1, midPoint.getZ()).getType()))) {
groundY--;
}
if (centreMinY - groundY > craft.getType().getIntProperty(CraftType.HOVER_LIMIT))
dy = -1;
} else if (!isOnGround(oldHitBox))
dy = dropDistance(oldHitBox);
}
// Fail the movement if the craft is too high and if the craft is not explosive
int maxHeightLimit = (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_LIMIT, world);
int minHeightLimit = (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MIN_HEIGHT_LIMIT, world);
if (dy > 0 && maxY + dy > maxHeightLimit && craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION) <= 0F) {
fail(I18nSupport.getInternationalisedString("Translation - Failed Craft hit height limit"));
return;
} else if (dy > 0 && maxY + dy > maxHeightLimit) {
// If explosive and too high, set dy to 0
dy = 0;
} else if (minY + dy < minHeightLimit && dy < 0 && !(craft instanceof SinkingCraft) && !craft.getType().getBoolProperty(CraftType.USE_GRAVITY)) {
fail(I18nSupport.getInternationalisedString("Translation - Failed Craft hit minimum height limit"));
return;
} else if (minY + dy < minHeightLimit && dy < 0 && craft.getType().getBoolProperty(CraftType.USE_GRAVITY))
// if a craft using gravity hits the minimum height limit, set dy = 0 instead of failing
dy = 0;
if (!(dy < 0 && dx == 0 && dz == 0) && !checkFuel()) {
fail(I18nSupport.getInternationalisedString("Translation - Failed Craft out of fuel"));
return;
}
final EnumSet<Material> harvestBlocks = craft.getType().getMaterialSetProperty(CraftType.HARVEST_BLOCKS);
final List<MovecraftLocation> harvestedBlocks = new ArrayList<>();
final EnumSet<Material> harvesterBladeBlocks = craft.getType().getMaterialSetProperty(CraftType.HARVESTER_BLADE_BLOCKS);
final SetHitBox collisionBox = new SetHitBox();
for (MovecraftLocation oldLocation : oldHitBox) {
final MovecraftLocation newLocation = oldLocation.translate(dx, dy, dz);
// itself
if (world.equals(craft.getWorld()) && oldHitBox.contains(newLocation)) {
newHitBox.add(newLocation);
continue;
}
final Material testMaterial = newLocation.toBukkit(world).getBlock().getType();
if (Tags.CHESTS.contains(testMaterial) && checkChests(testMaterial, newLocation)) {
// prevent chests collision
fail(String.format(I18nSupport.getInternationalisedString("Translation - Failed Craft is obstructed") + " @ %d,%d,%d,%s", newLocation.getX(), newLocation.getY(), newLocation.getZ(), newLocation.toBukkit(craft.getWorld()).getBlock().getType()));
return;
}
if (!withinWorldBorder(world, newLocation)) {
fail(I18nSupport.getInternationalisedString("Translation - Failed Craft cannot pass world border") + String.format(" @ %d,%d,%d", newLocation.getX(), newLocation.getY(), newLocation.getZ()));
return;
}
boolean blockObstructed;
if (craft instanceof SinkingCraft)
blockObstructed = !Tags.FALL_THROUGH_BLOCKS.contains(testMaterial);
else
blockObstructed = !testMaterial.equals(Material.AIR) && !craft.getType().getMaterialSetProperty(CraftType.PASSTHROUGH_BLOCKS).contains(testMaterial);
boolean ignoreBlock = oldLocation.toBukkit(craft.getWorld()).getBlock().getType().isAir() && blockObstructed;
if (blockObstructed && !harvestBlocks.isEmpty() && harvestBlocks.contains(testMaterial)) {
Material tmpType = oldLocation.toBukkit(craft.getWorld()).getBlock().getType();
if (harvesterBladeBlocks.size() > 0 && harvesterBladeBlocks.contains(tmpType)) {
blockObstructed = false;
harvestedBlocks.add(newLocation);
}
}
if (blockObstructed) {
if (!(craft instanceof SinkingCraft) && craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION) <= 0F) {
fail(String.format(I18nSupport.getInternationalisedString("Translation - Failed Craft is obstructed") + " @ %d,%d,%d,%s", newLocation.getX(), newLocation.getY(), newLocation.getZ(), testMaterial));
return;
}
collisionBox.add(newLocation);
} else if (!ignoreBlock)
newHitBox.add(newLocation);
}
if (!oldFluidList.isEmpty()) {
for (MovecraftLocation fluidLoc : oldFluidList) {
newFluidList.add(fluidLoc.translate(dx, dy, dz));
}
}
if (craft.getType().getMaterialSetProperty(CraftType.FORBIDDEN_HOVER_OVER_BLOCKS).size() > 0) {
MovecraftLocation test = new MovecraftLocation(newHitBox.getMidPoint().getX(), newHitBox.getMinY(), newHitBox.getMidPoint().getZ());
test = test.translate(0, -1, 0);
while (test.toBukkit(world).getBlock().getType().isAir()) {
test = test.translate(0, -1, 0);
}
Material testType = test.toBukkit(world).getBlock().getType();
if (craft.getType().getMaterialSetProperty(CraftType.FORBIDDEN_HOVER_OVER_BLOCKS).contains(testType)) {
fail(String.format(I18nSupport.getInternationalisedString("Translation - Failed Craft over block"), testType.name().toLowerCase().replace("_", " ")));
}
}
// call event
CraftTranslateEvent translateEvent = new CraftTranslateEvent(craft, oldHitBox, newHitBox, world);
Bukkit.getServer().getPluginManager().callEvent(translateEvent);
if (translateEvent.isCancelled()) {
this.fail(translateEvent.getFailMessage(), translateEvent.isPlayingFailSound());
return;
}
// do not switch world if sinking
if (craft instanceof SinkingCraft) {
List<MovecraftLocation> air = new ArrayList<>();
for (MovecraftLocation location : oldHitBox) {
if (location.toBukkit(craft.getWorld()).getBlock().getType().isAir()) {
air.add(location.translate(dx, dy, dz));
}
}
newHitBox.removeAll(air);
for (MovecraftLocation location : collisionBox) {
if (craft.getType().getFloatProperty(CraftType.EXPLODE_ON_CRASH) > 0F) {
if (System.currentTimeMillis() - craft.getOrigPilotTime() <= 1000) {
continue;
}
Location loc = location.toBukkit(craft.getWorld());
if (!loc.getBlock().getType().isAir() && ThreadLocalRandom.current().nextDouble(1) < .05) {
updates.add(new ExplosionUpdateCommand(loc, craft.getType().getFloatProperty(CraftType.EXPLODE_ON_CRASH)));
collisionExplosion = true;
}
}
SetHitBox toRemove = new SetHitBox();
MovecraftLocation next = location.translate(-dx, -dy, -dz);
while (oldHitBox.contains(next)) {
toRemove.add(next);
next = next.translate(0, 1, 0);
}
craft.getCollapsedHitBox().addAll(toRemove);
newHitBox.removeAll(toRemove);
}
} else if ((craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION) > 0F) && System.currentTimeMillis() - craft.getOrigPilotTime() > Settings.CollisionPrimer) {
for (MovecraftLocation location : collisionBox) {
float explosionForce = craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION);
if (craft.getType().getBoolProperty(CraftType.FOCUSED_EXPLOSION)) {
explosionForce *= Math.min(oldHitBox.size(), craft.getType().getIntProperty(CraftType.MAX_SIZE));
}
// TODO: Account for underwater explosions
/*if (location.getY() < waterLine) { // underwater explosions require more force to do anything
explosionForce += 25;//TODO: find the correct amount
}*/
Location oldLocation = location.translate(-dx, -dy, -dz).toBukkit(craft.getWorld());
Location newLocation = location.toBukkit(world);
if (!oldLocation.getBlock().getType().isAir()) {
CraftCollisionExplosionEvent e = new CraftCollisionExplosionEvent(craft, newLocation, craft.getWorld());
Bukkit.getServer().getPluginManager().callEvent(e);
if (!e.isCancelled()) {
updates.add(new ExplosionUpdateCommand(newLocation, explosionForce));
collisionExplosion = true;
}
}
if (craft.getType().getBoolProperty(CraftType.FOCUSED_EXPLOSION)) {
// don't handle any further collisions if it is set to focusedexplosion
break;
}
}
}
if (!collisionBox.isEmpty() && craft.getType().getBoolProperty(CraftType.CRUISE_ON_PILOT)) {
CraftManager.getInstance().release(craft, CraftReleaseEvent.Reason.EMPTY, false);
for (MovecraftLocation location : oldHitBox) {
BlockData phaseBlock = craft.getPhaseBlocks().getOrDefault(location.toBukkit(craft.getWorld()), Material.AIR.createBlockData());
updates.add(new BlockCreateCommand(craft.getWorld(), location, phaseBlock));
}
newHitBox = new SetHitBox();
}
if (!collisionBox.isEmpty()) {
Bukkit.getServer().getPluginManager().callEvent(new CraftCollisionEvent(craft, collisionBox, world));
}
updates.add(new CraftTranslateCommand(craft, new MovecraftLocation(dx, dy, dz), world));
// prevents torpedo and rocket pilots
if (!(craft instanceof SinkingCraft && craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS)) && craft.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) {
Location midpoint = new Location(craft.getWorld(), (oldHitBox.getMaxX() + oldHitBox.getMinX()) / 2.0, (oldHitBox.getMaxY() + oldHitBox.getMinY()) / 2.0, (oldHitBox.getMaxZ() + oldHitBox.getMinZ()) / 2.0);
for (Entity entity : craft.getWorld().getNearbyEntities(midpoint, oldHitBox.getXLength() / 2.0 + 1, oldHitBox.getYLength() / 2.0 + 2, oldHitBox.getZLength() / 2.0 + 1)) {
if (entity.getType() == EntityType.PLAYER) {
if (craft instanceof SinkingCraft)
continue;
EntityUpdateCommand eUp = new EntityUpdateCommand(entity, dx, dy, dz, 0, 0, world, sound, volume);
updates.add(eUp);
} else if (!craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS) || entity.getType() == EntityType.PRIMED_TNT) {
EntityUpdateCommand eUp = new EntityUpdateCommand(entity, dx, dy, dz, 0, 0, world);
updates.add(eUp);
}
}
} else {
// add releaseTask without playermove to manager
if (!craft.getType().getBoolProperty(CraftType.CRUISE_ON_PILOT) && !(craft instanceof SinkingCraft))
// not necessary to release cruiseonpilot crafts, because they will already be released
CraftManager.getInstance().addReleaseTask(craft);
}
captureYield(harvestedBlocks);
}
use of net.countercraft.movecraft.craft.SinkingCraft in project Movecraft by APDevTeam.
the class ContactsCommand method onCommand.
@Override
public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) {
if (!command.getName().equalsIgnoreCase("contacts")) {
return false;
}
if (!(commandSender instanceof Player)) {
commandSender.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("Contacts - Must Be Player"));
return true;
}
Player player = (Player) commandSender;
if (CraftManager.getInstance().getCraftByPlayer(player) == null) {
player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("You must be piloting a craft"));
return true;
}
int page;
try {
if (args.length == 0)
page = 1;
else
page = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
commandSender.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("Paginator - Invalid Page") + " \"" + args[0] + "\"");
return true;
}
TopicPaginator pageinator = new TopicPaginator(I18nSupport.getInternationalisedString("Contacts"));
Craft ccraft = CraftManager.getInstance().getCraftByPlayer(player);
HitBox hitBox = ccraft.getHitBox();
MovecraftLocation center = hitBox.getMidPoint();
for (Craft tcraft : ccraft.getContacts()) {
HitBox tHitBox = tcraft.getHitBox();
if (tHitBox.isEmpty())
continue;
MovecraftLocation tCenter = tHitBox.getMidPoint();
int distsquared = center.distanceSquared(tCenter);
String notification = I18nSupport.getInternationalisedString("Contact");
notification += ": ";
notification += tcraft instanceof SinkingCraft ? ChatColor.RED : tcraft.getDisabled() ? ChatColor.BLUE : "";
notification += tcraft.getName().length() >= 1 ? tcraft.getName() + " (" : "";
notification += tcraft.getType().getStringProperty(CraftType.NAME);
notification += tcraft.getName().length() >= 1 ? ") " : " ";
notification += ChatColor.RESET;
notification += I18nSupport.getInternationalisedString("Contact - Commanded By") + ", ";
notification += tcraft instanceof PilotedCraft ? ((PilotedCraft) tcraft).getPilot().getDisplayName() : "null";
notification += " ";
notification += I18nSupport.getInternationalisedString("Contact - Size") + " ";
notification += tcraft.getOrigBlockCount();
notification += ", " + I18nSupport.getInternationalisedString("Contact - Range") + " ";
notification += (int) Math.sqrt(distsquared);
notification += " " + I18nSupport.getInternationalisedString("Contact - To The");
int diffx = center.getX() - tCenter.getX();
int diffz = center.getZ() - tCenter.getZ();
if (Math.abs(diffx) > Math.abs(diffz))
if (diffx < 0)
notification += " " + I18nSupport.getInternationalisedString("Contact/Subcraft Rotate - East") + ".";
else
notification += " " + I18nSupport.getInternationalisedString("Contact/Subcraft Rotate - West") + ".";
else if (diffz < 0)
notification += " " + I18nSupport.getInternationalisedString("Contact/Subcraft Rotate - South") + ".";
else
notification += " " + I18nSupport.getInternationalisedString("Contact/Subcraft Rotate - North") + ".";
pageinator.addLine(notification);
}
if (pageinator.isEmpty()) {
player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("Contacts - None Found"));
return true;
}
if (!pageinator.isInBounds(page)) {
commandSender.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("Paginator - Invalid page") + "\"" + page + "\"");
return true;
}
for (String line : pageinator.getPage(page)) commandSender.sendMessage(line);
return true;
}
use of net.countercraft.movecraft.craft.SinkingCraft in project Movecraft by APDevTeam.
the class ManOverboardCommand method onCommand.
@Override
public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) {
if (!command.getName().equalsIgnoreCase("manOverBoard"))
return false;
if (!(commandSender instanceof Player)) {
commandSender.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("ManOverboard - Must Be Player"));
return true;
}
Player player = (Player) commandSender;
Craft craft = CraftManager.getInstance().getCraftByPlayerName(player.getName());
if (craft == null) {
player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("ManOverboard - No Craft Found"));
return true;
}
Location telPoint = getCraftTeleportPoint(craft);
if (craft.getWorld() != player.getWorld()) {
player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("ManOverboard - Other World"));
return true;
}
if ((System.currentTimeMillis() - CraftManager.getInstance().getTimeFromOverboard(player)) / 1_000 > Settings.ManOverboardTimeout && !MathUtils.locIsNearCraftFast(craft, MathUtils.bukkit2MovecraftLoc(player.getLocation()))) {
player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("ManOverboard - Timed Out"));
return true;
}
if (telPoint.distanceSquared(player.getLocation()) > Settings.ManOverboardDistSquared) {
player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("ManOverboard - Distance Too Far"));
return true;
}
if (craft.getDisabled() || craft instanceof SinkingCraft) {
player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("ManOverboard - Disabled"));
return true;
}
ManOverboardEvent event = new ManOverboardEvent(craft, telPoint);
Bukkit.getServer().getPluginManager().callEvent(event);
player.setVelocity(new Vector(0, 0, 0));
player.setFallDistance(0);
player.teleport(telPoint);
return true;
}
use of net.countercraft.movecraft.craft.SinkingCraft in project Movecraft by APDevTeam.
the class AsyncManager method processSinking.
// Controls sinking crafts
private void processSinking() {
// copy the crafts before iteration to prevent concurrent modifications
List<Craft> crafts = Lists.newArrayList(CraftManager.getInstance());
for (Craft craft : crafts) {
if (!(craft instanceof SinkingCraft))
continue;
if (craft.getHitBox().isEmpty() || craft.getHitBox().getMinY() < 5) {
CraftManager.getInstance().release(craft, CraftReleaseEvent.Reason.SUNK, false);
continue;
}
long ticksElapsed = (System.currentTimeMillis() - craft.getLastCruiseUpdate()) / 50;
if (Math.abs(ticksElapsed) < craft.getType().getIntProperty(CraftType.SINK_RATE_TICKS))
continue;
int dx = 0;
int dz = 0;
if (craft.getType().getBoolProperty(CraftType.KEEP_MOVING_ON_SINK)) {
dx = craft.getLastTranslation().getX();
dz = craft.getLastTranslation().getZ();
}
craft.translate(dx, -1, dz);
craft.setLastCruiseUpdate(System.currentTimeMillis());
}
}
use of net.countercraft.movecraft.craft.SinkingCraft in project Movecraft by APDevTeam.
the class AsyncManager method detectSinking.
private void detectSinking() {
for (Craft craft : CraftManager.getInstance()) {
if (craft instanceof SinkingCraft)
continue;
if (craft.getType().getDoubleProperty(CraftType.SINK_PERCENT) == 0.0 || !craft.isNotProcessing())
continue;
long ticksElapsed = (System.currentTimeMillis() - craft.getLastBlockCheck()) / 50;
if (ticksElapsed <= Settings.SinkCheckTicks)
continue;
CraftStatus status = checkCraftStatus(craft);
// Only do this if the craft isn't already disabled.
if (status.isDisabled() && craft.isNotProcessing() && !craft.getDisabled()) {
craft.setDisabled(true);
craft.getAudience().playSound(Sound.sound(Key.key("entity.iron_golem.death"), Sound.Source.NEUTRAL, 5.0f, 5.0f));
}
// update the time for the next check
if (status.isSinking() && craft.isNotProcessing()) {
craft.getAudience().sendMessage(I18nSupport.getInternationalisedComponent("Player - Craft is sinking"));
craft.setCruising(false);
CraftManager.getInstance().sink(craft);
} else {
craft.setLastBlockCheck(System.currentTimeMillis());
}
}
}
Aggregations