Compare commits

...

18 Commits

Author SHA1 Message Date
Sebastian Hartte 53f7046f93 Metrics WIP 2020-09-10 18:55:22 +02:00
yueh 44544c598f Fixes #4253: Just transfer the recipe id instead of every ingredient. (#4689)
* Fixes #4253: Just transfer the recipe id instead of every ingredient.
* If possible handle any IRecipe for processing mode
* Improvements and support for AE2 facades.
* Switched to using JEI's displayed stacks (which means we can remove the shapeless variant).

Co-authored-by: Sebastian Hartte <sebastian@hartte.de>
2020-09-09 14:08:24 +02:00
Sebastian Hartte bcee2ec427 Slightly increased the height of the item hitbox for crystals to make their pickup by annihilation planes less "clippy". 2020-09-09 02:23:58 +02:00
Sebastian Hartte 9e41d543f5 Merge remote-tracking branch 'origin/8.0.x-1.16.1' 2020-09-09 00:59:58 +02:00
Sebastian Hartte 81cc0b804d Fixes #4699: AE's glass should only hide it's sides adjacent to other AE glass.
Also disables spawn on AE's glass same as Vanilla glass.
2020-09-09 00:58:14 +02:00
Sebastian Hartte e1fc9908b3 Fixes #4693: Fix color applicator not working on cable-buses. 2020-09-09 00:51:45 +02:00
shartte 9ed1535477 Fixes #4661: Do not update a tile entity when it's in an unloaded chunk (i.e. during chunk unloading) (#4708) 2020-09-08 22:28:30 +02:00
Sebastian Hartte 1ef345d4a4 Formatting fixes. 2020-09-08 22:24:27 +02:00
Sebastian Hartte ff7d1b9719 Fixes #4704: QNB is not finding a registered QNB because it believes the chunk to be unloaded.
Also adds debug logging to the locatable registry to better debug this issues in the future.
2020-09-08 22:20:33 +02:00
shartte 88c9370e4f Crystal Growth Refactor (#4702)
* Refactored crystal in-world purification:
- Removed iterative formula
- Set the default growth factor to 1 rather than 0.5 to simplify settings
- Made the growth-tick-progress-per-tick a configuration setting for 0-6 accelerators
- Unfinished seeds now slowly sink when ejected by an annihilation plane or other system (emulates a player throwing it in)
- Instead of checking for "isLiquid", we now check for a new tag (appliedenergistics2:crystal_purification_medium) to check if the crystal is submerged in an allowed fluid for growth, defaults to #minecraft:water.

* Removed configuration options for changing how accelerators work,
replaced with improved fluid tag option with a clamped multiplier [1,10].
2020-09-08 18:10:45 +02:00
Sebastian Hartte f2aae3ef0f Added a global Voxelshape cache for cable bus voxel shapes, since combining voxel shapes turns out to be an extremely expensive process (i.e. 1.7s for 10000x5 shapes). 2020-09-08 17:56:02 +02:00
Sebastian Hartte 12739f1e66 Remove unneeded cable-bus inventory-model, which caused the part models to be loaded twice leading to unnecessary memory-usage. 2020-09-08 17:53:14 +02:00
shartte 81d2170ada Implemented a cache with maximum quad-count it will cache for the cable bus, (#4670)
which also fixes concurrent access to the hashmap.

(cherry picked from commit b756d68be1)
2020-09-08 14:28:08 +02:00
Sebastian Hartte 0f469e9df6 Bumped Forge version. 2020-09-07 23:34:56 +02:00
Sebastian Hartte 0f34f09b32 Remove object-scanning subscriptions and remove unused test-file. 2020-09-06 20:59:37 +02:00
Sebastian Hartte 8e54a8fed9 Fixes a problem where tile entities would become unremovable (well, one more of these at least),
because they would reset their block state upon removal and loss of power.
2020-09-06 20:59:19 +02:00
Sebastian Hartte d9bb81a00f Fixes #4680: Facade advancement is showing an untextured facade. 2020-09-03 17:36:20 +02:00
yueh 39e038d916 Fixes #4683: Hopefully LazyOptional works as advertised (#4684) 2020-09-02 21:36:40 +02:00
50 changed files with 1261 additions and 1420 deletions
+2 -2
View File
@@ -11,13 +11,13 @@ artifact_basename=appliedenergistics2
minecraft_release=1.16 minecraft_release=1.16
minecraft_version=1.16.2 minecraft_version=1.16.2
mcp_mappings=20200723-1.16.1 mcp_mappings=20200723-1.16.1
forge_version=33.0.22 forge_version=33.0.42
######################################################### #########################################################
# Provided APIs # # Provided APIs #
######################################################### #########################################################
jei_minecraft_version=1.16.2 jei_minecraft_version=1.16.2
jei_version=7.1.1.15 jei_version=7.3.2.25
top_version=3.0.3-beta-6 top_version=3.0.3-beta-6
hwyla_version=1.10.8-B72_1.15.2 hwyla_version=1.10.8-B72_1.15.2
ctm_version=MC1.15.2-1.1.0.9 ctm_version=MC1.15.2-1.1.0.9
@@ -23,13 +23,23 @@
package appeng.api.implementations.items; package appeng.api.implementations.items;
import net.minecraft.block.Block; import javax.annotation.Nullable;
import net.minecraft.block.material.Material;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public interface IGrowableCrystal { public interface IGrowableCrystal {
ItemStack triggerGrowth(ItemStack is); ItemStack triggerGrowth(ItemStack is);
float getMultiplier(Block blk, Material mat); /**
* Gets the crystal growth multiplier based on the material it is submerged in.
*
* @param state The blockstate of the block the crystal is currently in.
* @return The multiplier for the growth tick progress. Zero if the crystal
* cannot grow in the current material.
*/
float getMultiplier(BlockState state, @Nullable World world, @Nullable BlockPos pos);
} }
@@ -18,6 +18,7 @@
package appeng.client.render.cablebus; package appeng.client.render.cablebus;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
@@ -51,12 +52,21 @@ import net.minecraftforge.client.model.data.IModelData;
import appeng.api.parts.IPartModel; import appeng.api.parts.IPartModel;
import appeng.api.util.AECableType; import appeng.api.util.AECableType;
import appeng.api.util.AEColor; import appeng.api.util.AEColor;
import appeng.metrics.Metrics;
public class CableBusBakedModel implements IBakedModel { public class CableBusBakedModel implements IBakedModel {
// The number of quads overall that will be cached // The number of quads overall that will be cached
private static final int CACHE_QUAD_COUNT = 5000; private static final int CACHE_QUAD_COUNT = 5000;
// Keep a weak-ref to the last created cache for reporting metrics
private static WeakReference<LoadingCache<CableBusRenderState, List<BakedQuad>>> cacheForMetrics = new WeakReference<>(
null);
static {
Metrics.cache("cable_bus_model_cache", () -> cacheForMetrics.get());
}
private final LoadingCache<CableBusRenderState, List<BakedQuad>> cableModelCache; private final LoadingCache<CableBusRenderState, List<BakedQuad>> cableModelCache;
private final CableBuilder cableBuilder; private final CableBuilder cableBuilder;
@@ -74,6 +84,7 @@ public class CableBusBakedModel implements IBakedModel {
this.partModels = partModels; this.partModels = partModels;
this.particleTexture = particleTexture; this.particleTexture = particleTexture;
this.cableModelCache = CacheBuilder.newBuilder()// this.cableModelCache = CacheBuilder.newBuilder()//
.recordStats()//
.maximumWeight(CACHE_QUAD_COUNT)// .maximumWeight(CACHE_QUAD_COUNT)//
.weigher((Weigher<CableBusRenderState, List<BakedQuad>>) (key, value) -> value.size())// .weigher((Weigher<CableBusRenderState, List<BakedQuad>>) (key, value) -> value.size())//
.build(new CacheLoader<CableBusRenderState, List<BakedQuad>>() { .build(new CacheLoader<CableBusRenderState, List<BakedQuad>>() {
@@ -84,6 +95,7 @@ public class CableBusBakedModel implements IBakedModel {
return model; return model;
} }
}); });
cacheForMetrics = new WeakReference<>(cableModelCache);
} }
@Override @Override
+38
View File
@@ -31,6 +31,8 @@ import java.util.Set;
import java.util.function.DoubleSupplier; import java.util.function.DoubleSupplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -393,6 +395,19 @@ public final class AEConfig {
return this.meteoriteDimensionWhitelist; return this.meteoriteDimensionWhitelist;
} }
@Nullable
public String getImprovedFluidTag() {
return Strings.emptyToNull(COMMON.improvedFluidTag.get());
}
public float getImprovedFluidMultiplier() {
return COMMON.improvedFluidMultiplier.get().floatValue();
}
public int getPrometheusMetricsServerPort() {
return COMMON.prometheusMetricsServer.get();
}
// Setters keep visibility as low as possible. // Setters keep visibility as low as possible.
private static class ClientConfig { private static class ClientConfig {
@@ -463,6 +478,7 @@ public final class AEConfig {
public final BooleanValue removeCrashingItemsOnLoad; public final BooleanValue removeCrashingItemsOnLoad;
public final ConfigValue<Integer> formationPlaneEntityLimit; public final ConfigValue<Integer> formationPlaneEntityLimit;
public final ConfigValue<Integer> craftingCalculationTimePerTick; public final ConfigValue<Integer> craftingCalculationTimePerTick;
public final ConfigValue<Integer> prometheusMetricsServer;
// Spatial IO/Dimension // Spatial IO/Dimension
public final ConfigValue<Double> spatialPowerExponent; public final ConfigValue<Double> spatialPowerExponent;
@@ -507,6 +523,11 @@ public final class AEConfig {
public final ConfigValue<Integer> condenserMatterBallsPower; public final ConfigValue<Integer> condenserMatterBallsPower;
public final ConfigValue<Integer> condenserSingularityPower; public final ConfigValue<Integer> condenserSingularityPower;
// In-World Purification
// Settings for improved speed depending on fluid the crystal is in
public final ConfigValue<String> improvedFluidTag;
public final ConfigValue<Double> improvedFluidMultiplier;
public final Map<TickRates, ConfigValue<Integer>> tickRateMin = new HashMap<>(); public final Map<TickRates, ConfigValue<Integer>> tickRateMin = new HashMap<>();
public final Map<TickRates, ConfigValue<Integer>> tickRateMax = new HashMap<>(); public final Map<TickRates, ConfigValue<Integer>> tickRateMax = new HashMap<>();
@@ -542,6 +563,12 @@ public final class AEConfig {
.define("removeCrashingItemsOnLoad", false); .define("removeCrashingItemsOnLoad", false);
builder.pop(); builder.pop();
builder.push("metrics");
prometheusMetricsServer = builder.comment(
"Enables the Prometheus Metrics exporting endpoint on 127.0.0.1 on this port number. 0 to disable.")
.define("prometheusMetricsServer", 0);
builder.pop();
builder.push("automation"); builder.push("automation");
formationPlaneEntityLimit = builder.comment("TODO").define("formationPlaneEntityLimit", 128); formationPlaneEntityLimit = builder.comment("TODO").define("formationPlaneEntityLimit", 128);
builder.pop(); builder.pop();
@@ -617,6 +644,17 @@ public final class AEConfig {
tickRateMax.put(tickRate, builder.define(tickRate.name() + "Max", tickRate.getDefaultMax())); tickRateMax.put(tickRate, builder.define(tickRate.name() + "Max", tickRate.getDefaultMax()));
} }
builder.pop(); builder.pop();
builder.comment("Settings for in-world purification of crystals.").push("inWorldPurification");
improvedFluidTag = builder.comment(
"A fluid tag that identifies fluids that improve crystal purification speed. Does not affect purification with water/lava.")
.define("improvedFluidTag", "");
improvedFluidMultiplier = builder
.comment("The speed multiplier to use when the crystals are submerged in the improved fluid.")
.defineInRange("improvedFluidMultiplier", 2.0, 1.0, 10.0);
builder.pop();
} }
} }
+17
View File
@@ -66,6 +66,7 @@ import appeng.entity.TinyTNTPrimedEntity;
import appeng.entity.TinyTNTPrimedRenderer; import appeng.entity.TinyTNTPrimedRenderer;
import appeng.hooks.TickHandler; import appeng.hooks.TickHandler;
import appeng.integration.Integrations; import appeng.integration.Integrations;
import appeng.metrics.endpoint.PrometheusEndpoint;
import appeng.parts.PartPlacement; import appeng.parts.PartPlacement;
import appeng.server.ServerHelper; import appeng.server.ServerHelper;
@@ -90,6 +91,8 @@ public final class AppEng {
} }
INSTANCE = this; INSTANCE = this;
startMetricsServer();
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, AEConfig.CLIENT_SPEC); ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, AEConfig.CLIENT_SPEC);
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, AEConfig.COMMON_SPEC); ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, AEConfig.COMMON_SPEC);
@@ -148,6 +151,20 @@ public final class AppEng {
registerNetworkHandler(); registerNetworkHandler();
AddonLoader.loadAddons(Api.INSTANCE); AddonLoader.loadAddons(Api.INSTANCE);
startMetricsServer();
}
private void startMetricsServer() {
int prometheusPort = AEConfig.instance().getPrometheusMetricsServerPort();
if (prometheusPort != 0) {
try {
AELog.info("Starting Prometheus Metrics Server on Port %s", prometheusPort);
new PrometheusEndpoint("localhost", prometheusPort);
} catch (Exception e) {
AELog.warn("Failed to start Prometheus Metrics-Server: %s", e);
}
}
} }
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
@@ -22,7 +22,6 @@ import java.util.function.Supplier;
import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScreenManager; import net.minecraft.client.gui.ScreenManager;
import net.minecraft.client.particle.ParticleManager; import net.minecraft.client.particle.ParticleManager;
@@ -33,17 +32,7 @@ import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.particles.ParticleType; import net.minecraft.particles.ParticleType;
import net.minecraft.tileentity.TileEntityType; import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.WorldGenRegistries;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.gen.GenerationStage;
import net.minecraft.world.gen.feature.Feature;
import net.minecraft.world.gen.feature.IFeatureConfig;
import net.minecraft.world.gen.feature.OreFeatureConfig;
import net.minecraft.world.gen.feature.structure.Structure;
import net.minecraft.world.gen.placement.IPlacementConfig;
import net.minecraft.world.gen.placement.Placement;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.ColorHandlerEvent; import net.minecraftforge.client.event.ColorHandlerEvent;
@@ -62,14 +51,12 @@ import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLEnvironment; import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.network.IContainerFactory; import net.minecraftforge.fml.network.IContainerFactory;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.registries.IForgeRegistry;
import appeng.api.config.Upgrades; import appeng.api.config.Upgrades;
import appeng.api.definitions.IBlocks; import appeng.api.definitions.IBlocks;
import appeng.api.definitions.IItems; import appeng.api.definitions.IItems;
import appeng.api.definitions.IParts; import appeng.api.definitions.IParts;
import appeng.api.features.AEFeature;
import appeng.api.features.IRegistryContainer; import appeng.api.features.IRegistryContainer;
import appeng.api.features.IWirelessTermHandler; import appeng.api.features.IWirelessTermHandler;
import appeng.api.features.IWorldGen; import appeng.api.features.IWorldGen;
@@ -219,15 +206,8 @@ import appeng.recipes.game.FacadeRecipe;
import appeng.recipes.handlers.GrinderRecipeSerializer; import appeng.recipes.handlers.GrinderRecipeSerializer;
import appeng.recipes.handlers.InscriberRecipeSerializer; import appeng.recipes.handlers.InscriberRecipeSerializer;
import appeng.server.AECommand; import appeng.server.AECommand;
import appeng.spatial.SpatialStorageBiome;
import appeng.spatial.SpatialStorageChunkGenerator;
import appeng.spatial.SpatialStorageDimensionIds;
import appeng.tile.AEBaseTileEntity; import appeng.tile.AEBaseTileEntity;
import appeng.tile.crafting.MolecularAssemblerRenderer; import appeng.tile.crafting.MolecularAssemblerRenderer;
import appeng.worldgen.ChargedQuartzOreConfig;
import appeng.worldgen.ChargedQuartzOreFeature;
import appeng.worldgen.meteorite.MeteoriteStructure;
import appeng.worldgen.meteorite.MeteoriteStructurePiece;
final class Registration { final class Registration {
@@ -20,7 +20,9 @@ package appeng.core.api.definitions;
import static appeng.block.AEBaseBlock.defaultProps; import static appeng.block.AEBaseBlock.defaultProps;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.DispenserBlock; import net.minecraft.block.DispenserBlock;
import net.minecraft.block.SlabBlock; import net.minecraft.block.SlabBlock;
import net.minecraft.block.SoundType; import net.minecraft.block.SoundType;
@@ -28,6 +30,7 @@ import net.minecraft.block.StairsBlock;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.entity.EntityClassification; import net.minecraft.entity.EntityClassification;
import net.minecraft.entity.EntityType;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.ToolType; import net.minecraftforge.common.ToolType;
@@ -262,18 +265,20 @@ public final class ApiBlocks implements IBlocks {
this.chiseledQuartzBlock = deco.block("chiseled_quartz_block", () -> new AEDecorativeBlock(QUARTZ_PROPERTIES)) this.chiseledQuartzBlock = deco.block("chiseled_quartz_block", () -> new AEDecorativeBlock(QUARTZ_PROPERTIES))
.build(); .build();
this.quartzGlass = registry.features(AEFeature.QUARTZ_GLASS) AbstractBlock.IExtendedPositionPredicate<EntityType<?>> neverAllowSpawn = (p1, p2, p3, p4) -> false;
.block("quartz_glass", () -> new QuartzGlassBlock(defaultProps(Material.GLASS).notSolid())) this.quartzGlass = registry.features(AEFeature.QUARTZ_GLASS).block("quartz_glass", () -> {
.rendering(new BlockRenderingCustomizer() { return new QuartzGlassBlock(defaultProps(Material.GLASS).notSolid().setAllowsSpawn(neverAllowSpawn));
@Override }).rendering(new BlockRenderingCustomizer() {
@OnlyIn(Dist.CLIENT) @Override
public void customize(IBlockRendering rendering, IItemRendering itemRendering) { @OnlyIn(Dist.CLIENT)
rendering.renderType(RenderType.getCutout()); public void customize(IBlockRendering rendering, IItemRendering itemRendering) {
} rendering.renderType(RenderType.getCutout());
}).build(); }
}).build();
this.quartzVibrantGlass = deco this.quartzVibrantGlass = deco
.block("quartz_vibrant_glass", .block("quartz_vibrant_glass",
() -> new QuartzLampBlock(defaultProps(Material.GLASS).setLightLevel(b -> 15).notSolid())) () -> new QuartzLampBlock(defaultProps(Material.GLASS).setLightLevel(b -> 15).notSolid()
.setAllowsSpawn(neverAllowSpawn)))
.addFeatures(AEFeature.DECORATIVE_LIGHTS, AEFeature.QUARTZ_GLASS) .addFeatures(AEFeature.DECORATIVE_LIGHTS, AEFeature.QUARTZ_GLASS)
.rendering(new BlockRenderingCustomizer() { .rendering(new BlockRenderingCustomizer() {
@Override @Override
@@ -312,7 +312,7 @@ public final class ApiItems implements IItems {
GrowingCrystalEntity.TYPE = registry GrowingCrystalEntity.TYPE = registry
.<GrowingCrystalEntity>entity("growing_crystal", GrowingCrystalEntity::new, EntityClassification.MISC) .<GrowingCrystalEntity>entity("growing_crystal", GrowingCrystalEntity::new, EntityClassification.MISC)
.customize(builder -> builder.size(0.25F, 0.25F)).build(); .customize(builder -> builder.size(0.25F, 0.4F)).build();
// rv1 // rv1
this.encodedPattern = registry.item("encoded_pattern", EncodedPatternItem::new) this.encodedPattern = registry.item("encoded_pattern", EncodedPatternItem::new)
@@ -22,12 +22,12 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import appeng.api.events.LocatableEventAnnounce; import appeng.api.events.LocatableEventAnnounce;
import appeng.api.events.LocatableEventAnnounce.LocatableEvent; import appeng.api.events.LocatableEventAnnounce.LocatableEvent;
import appeng.api.features.ILocatable; import appeng.api.features.ILocatable;
import appeng.api.features.ILocatableRegistry; import appeng.api.features.ILocatableRegistry;
import appeng.core.AELog;
import appeng.util.Platform; import appeng.util.Platform;
public final class LocatableRegistry implements ILocatableRegistry { public final class LocatableRegistry implements ILocatableRegistry {
@@ -35,18 +35,19 @@ public final class LocatableRegistry implements ILocatableRegistry {
public LocatableRegistry() { public LocatableRegistry() {
this.set = new HashMap<>(); this.set = new HashMap<>();
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.addListener(this::updateLocatable);
} }
@SubscribeEvent private void updateLocatable(final LocatableEventAnnounce e) {
public void updateLocatable(final LocatableEventAnnounce e) {
if (Platform.isClient()) { if (Platform.isClient()) {
return; // IGNORE! return; // IGNORE!
} }
if (e.change == LocatableEvent.REGISTER) { if (e.change == LocatableEvent.REGISTER) {
AELog.debug("Registering locatable %s: %s", e.target.getLocatableSerial(), e.target);
this.set.put(e.target.getLocatableSerial(), e.target); this.set.put(e.target.getLocatableSerial(), e.target);
} else if (e.change == LocatableEvent.UNREGISTER) { } else if (e.change == LocatableEvent.UNREGISTER) {
AELog.debug("Unregistering locatable %s: %s", e.target.getLocatableSerial(), e.target);
this.set.remove(e.target.getLocatableSerial()); this.set.remove(e.target.getLocatableSerial());
} }
} }
@@ -18,15 +18,27 @@
package appeng.core.sync.packets; package appeng.core.sync.packets;
import java.util.Arrays;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.mojang.datafixers.util.Pair;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container; import net.minecraft.inventory.container.Container;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.item.crafting.IRecipe;
import net.minecraft.nbt.ListNBT; import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.item.crafting.ShapedRecipe;
import net.minecraft.network.PacketBuffer; import net.minecraft.network.PacketBuffer;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.crafting.IShapedRecipe;
import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandler;
import appeng.api.config.Actionable; import appeng.api.config.Actionable;
@@ -40,8 +52,10 @@ import appeng.api.networking.storage.IStorageGrid;
import appeng.api.storage.IMEMonitor; import appeng.api.storage.IMEMonitor;
import appeng.api.storage.channels.IItemStorageChannel; import appeng.api.storage.channels.IItemStorageChannel;
import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IAEItemStack;
import appeng.container.implementations.PatternTermContainer;
import appeng.core.Api; import appeng.core.Api;
import appeng.core.sync.BasePacket; import appeng.core.sync.BasePacket;
import appeng.core.sync.BasePacketHandler;
import appeng.core.sync.network.INetworkInfo; import appeng.core.sync.network.INetworkInfo;
import appeng.helpers.IContainerCraftingPacket; import appeng.helpers.IContainerCraftingPacket;
import appeng.items.storage.ViewCellItem; import appeng.items.storage.ViewCellItem;
@@ -54,152 +68,289 @@ import appeng.util.prioritylist.IPartitionList;
public class JEIRecipePacket extends BasePacket { public class JEIRecipePacket extends BasePacket {
private ItemStack[][] recipe; /**
* Transmit only a recipe ID.
*/
private static final int INLINE_RECIPE_NONE = 1;
/**
* Transmit the information about the recipe we actually need. This is
* explicitly limited since this is untrusted client->server info.
*/
private static final int INLINE_RECIPE_SHAPED = 2;
private ResourceLocation recipeId;
/**
* This is optional, in case the client already knows it could not resolve the
* recipe id.
*/
@Nullable
private IRecipe<?> recipe;
private boolean crafting;
public JEIRecipePacket(final PacketBuffer stream) { public JEIRecipePacket(final PacketBuffer stream) {
final CompoundNBT comp = stream.readCompoundTag(); this.crafting = stream.readBoolean();
if (comp != null) { final String id = stream.readString(Short.MAX_VALUE);
this.recipe = new ItemStack[9][]; this.recipeId = new ResourceLocation(id);
for (int x = 0; x < this.recipe.length; x++) {
final ListNBT list = comp.getList("#" + x, 10);
if (list.size() > 0) {
this.recipe[x] = new ItemStack[list.size()];
for (int y = 0; y < list.size(); y++) {
this.recipe[x][y] = ItemStack.read(list.getCompound(y));
}
}
}
}
}
// api int inlineRecipeType = stream.readVarInt();
public JEIRecipePacket(final CompoundNBT recipe) { switch (inlineRecipeType) {
final PacketBuffer data = new PacketBuffer(Unpooled.buffer()); case INLINE_RECIPE_NONE:
break;
data.writeInt(this.getPacketID()); case INLINE_RECIPE_SHAPED:
recipe = IRecipeSerializer.CRAFTING_SHAPED.read(this.recipeId, stream);
data.writeCompoundTag(recipe); break;
default:
this.configureWrite(data); throw new IllegalArgumentException("Invalid inline recipe type.");
}
@Override
public void serverPacketData(final INetworkInfo manager, final PlayerEntity player) {
final ServerPlayerEntity pmp = (ServerPlayerEntity) player;
final Container con = pmp.openContainer;
if (!(con instanceof IContainerCraftingPacket)) {
return;
}
final IContainerCraftingPacket cct = (IContainerCraftingPacket) con;
final IGridNode node = cct.getNetworkNode();
if (node == null) {
return;
}
final IGrid grid = node.getGrid();
if (grid == null) {
return;
}
final IStorageGrid inv = grid.getCache(IStorageGrid.class);
final IEnergyGrid energy = grid.getCache(IEnergyGrid.class);
final ISecurityGrid security = grid.getCache(ISecurityGrid.class);
final ICraftingGrid crafting = grid.getCache(ICraftingGrid.class);
final IItemHandler craftMatrix = cct.getInventoryByName("crafting");
final IItemHandler playerInventory = cct.getInventoryByName("player");
if (inv != null && this.recipe != null && security != null) {
final IMEMonitor<IAEItemStack> storage = inv
.getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class));
final IPartitionList<IAEItemStack> filter = ViewCellItem.createFilter(cct.getViewCells());
for (int x = 0; x < craftMatrix.getSlots(); x++) {
ItemStack currentItem = craftMatrix.getStackInSlot(x);
// prepare slots
if (!currentItem.isEmpty()) {
// already the correct item?
ItemStack newItem = this.canUseInSlot(x, currentItem);
// put away old item
if (newItem != currentItem && security.hasPermission(player, SecurityPermissions.INJECT)) {
final IAEItemStack in = AEItemStack.fromItemStack(currentItem);
final IAEItemStack out = cct.useRealItems()
? Platform.poweredInsert(energy, storage, in, cct.getActionSource())
: null;
if (out != null) {
currentItem = out.createItemStack();
} else {
currentItem = ItemStack.EMPTY;
}
}
}
if (currentItem.isEmpty() && this.recipe[x] != null) {
// for each variant
for (int y = 0; y < this.recipe[x].length && currentItem.isEmpty(); y++) {
final IAEItemStack request = AEItemStack.fromItemStack(this.recipe[x][y]);
if (request != null) {
// try ae
if ((filter == null || filter.isListed(request))
&& security.hasPermission(player, SecurityPermissions.EXTRACT)) {
request.setStackSize(1);
IAEItemStack out;
if (cct.useRealItems()) {
out = Platform.poweredExtraction(energy, storage, request, cct.getActionSource());
} else {
// Query the crafting grid if there is a pattern providing the item
if (!crafting.getCraftingFor(request, null, 0, null).isEmpty()) {
out = request;
} else {
// Fall back using an existing item
out = storage.extractItems(request, Actionable.SIMULATE, cct.getActionSource());
}
}
if (out != null) {
currentItem = out.createItemStack();
}
}
// try inventory
if (currentItem.isEmpty()) {
AdaptorItemHandler ad = new AdaptorItemHandler(playerInventory);
if (cct.useRealItems()) {
currentItem = ad.removeItems(1, this.recipe[x][y], null);
} else {
currentItem = ad.simulateRemove(1, this.recipe[x][y], null);
}
}
}
}
}
ItemHandlerUtil.setStackInSlot(craftMatrix, x, currentItem);
}
con.onCraftMatrixChanged(new WrapperInvItemHandler(craftMatrix));
} }
} }
/** /**
* * Sends a recipe identified by the given recipe ID to the server for either
* @param slot * filling a crafting grid or a pattern.
* @param is itemstack
* @return is if it can be used, else EMPTY
*/ */
private ItemStack canUseInSlot(int slot, ItemStack is) { public JEIRecipePacket(final ResourceLocation recipeId, final boolean crafting) {
if (this.recipe[slot] != null) { PacketBuffer data = createCommonHeader(recipeId, crafting, INLINE_RECIPE_NONE);
for (ItemStack option : this.recipe[slot]) { this.configureWrite(data);
if (is.isItemEqual(option)) { }
return is;
/**
* Sends a recipe to the server for either filling a crafting grid or a pattern.
* <p>
* Prefer the id-based constructor above whereever possible.
*/
public JEIRecipePacket(final ShapedRecipe recipe, final boolean crafting) {
PacketBuffer data = createCommonHeader(recipe.getId(), crafting, INLINE_RECIPE_SHAPED);
IRecipeSerializer.CRAFTING_SHAPED.write(data, recipe);
this.configureWrite(data);
}
private PacketBuffer createCommonHeader(ResourceLocation recipeId, boolean crafting, int inlineRecipeType) {
final PacketBuffer data = new PacketBuffer(Unpooled.buffer());
data.writeInt(this.getPacketID());
data.writeBoolean(crafting);
data.writeResourceLocation(recipeId);
data.writeVarInt(inlineRecipeType);
return data;
}
/**
* Servside handler for this packet.
* <p>
* Makes use of {@link Preconditions#checkArgument(boolean)} as the
* {@link BasePacketHandler} is catching them and in general these cases should
* never happen except in an error case and should be logged then.
*/
@Override
public void serverPacketData(final INetworkInfo manager, final PlayerEntity player) {
// Setup and verification
final ServerPlayerEntity pmp = (ServerPlayerEntity) player;
final Container con = pmp.openContainer;
Preconditions.checkArgument(con instanceof IContainerCraftingPacket);
IRecipe<?> recipe = player.getEntityWorld().getRecipeManager().getRecipe(this.recipeId).orElse(null);
if (recipe == null && this.recipe != null) {
// Certain recipes (i.e. AE2 facades) are represented in JEI as ShapedRecipe's,
// while in reality they
// are special recipes. Those recipes are sent across the wire...
recipe = this.recipe;
}
Preconditions.checkArgument(recipe != null);
final IContainerCraftingPacket cct = (IContainerCraftingPacket) con;
final IGridNode node = cct.getNetworkNode();
Preconditions.checkArgument(node != null);
final IGrid grid = node.getGrid();
Preconditions.checkArgument(grid != null);
final IStorageGrid inv = grid.getCache(IStorageGrid.class);
Preconditions.checkArgument(inv != null);
final ISecurityGrid security = grid.getCache(ISecurityGrid.class);
Preconditions.checkArgument(security != null);
final IEnergyGrid energy = grid.getCache(IEnergyGrid.class);
final ICraftingGrid crafting = grid.getCache(ICraftingGrid.class);
final IItemHandler craftMatrix = cct.getInventoryByName("crafting");
final IItemHandler playerInventory = cct.getInventoryByName("player");
final IMEMonitor<IAEItemStack> storage = inv
.getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class));
final IPartitionList<IAEItemStack> filter = ViewCellItem.createFilter(cct.getViewCells());
final NonNullList<Ingredient> ingredients = this.ensure3by3CraftingMatrix(recipe);
// Handle each slot
for (int x = 0; x < craftMatrix.getSlots(); x++) {
ItemStack currentItem = craftMatrix.getStackInSlot(x);
Ingredient ingredient = ingredients.get(x);
// prepare slots
if (!currentItem.isEmpty()) {
// already the correct item? True, skip everything else
ItemStack newItem = this.canUseInSlot(ingredient, currentItem);
// put away old item, if not correct
if (newItem != currentItem && security.hasPermission(player, SecurityPermissions.INJECT)) {
final IAEItemStack in = AEItemStack.fromItemStack(currentItem);
final IAEItemStack out = cct.useRealItems()
? Platform.poweredInsert(energy, storage, in, cct.getActionSource())
: null;
if (out != null) {
currentItem = out.createItemStack();
} else {
currentItem = ItemStack.EMPTY;
}
}
}
// Find item or pattern from the network
if (currentItem.isEmpty() && security.hasPermission(player, SecurityPermissions.EXTRACT)) {
IAEItemStack out;
if (cct.useRealItems()) {
IAEItemStack request = findBestMatchingItemStack(ingredient, filter, storage, cct);
out = request != null
? Platform.poweredExtraction(energy, storage, request.setStackSize(1),
cct.getActionSource())
: null;
} else {
out = findBestMatchingPattern(ingredient, filter, crafting, storage, cct);
if (out == null) {
out = findBestMatchingItemStack(ingredient, filter, storage, cct);
}
if (out == null && ingredient.getMatchingStacks().length > 0) {
out = AEItemStack.fromItemStack(ingredient.getMatchingStacks()[0]);
}
}
if (out != null) {
currentItem = out.createItemStack();
}
}
// If still nothing, search the player inventory.
if (currentItem.isEmpty()) {
ItemStack[] matchingStacks = ingredient.getMatchingStacks();
for (ItemStack matchingStack : matchingStacks) {
if (currentItem.isEmpty()) {
AdaptorItemHandler ad = new AdaptorItemHandler(playerInventory);
if (cct.useRealItems()) {
currentItem = ad.removeItems(1, matchingStack, null);
} else {
currentItem = ad.simulateRemove(1, matchingStack, null);
}
}
}
}
ItemHandlerUtil.setStackInSlot(craftMatrix, x, currentItem);
}
if (!this.crafting) {
this.handleProcessing(con, cct, recipe);
}
con.onCraftMatrixChanged(new WrapperInvItemHandler(craftMatrix));
}
/**
* Expand any recipe to a 3x3 matrix.
* <p>
* Will throw an {@link IllegalArgumentException} in case it has more than 9 or
* a shaped recipe is either wider or higher than 3. ingredients.
*/
private NonNullList<Ingredient> ensure3by3CraftingMatrix(IRecipe<?> recipe) {
NonNullList<Ingredient> ingredients = recipe.getIngredients();
NonNullList<Ingredient> expandedIngredients = NonNullList.withSize(9, Ingredient.EMPTY);
Preconditions.checkArgument(ingredients.size() <= 9);
// shaped recipes can be smaller than 3x3, expand to 3x3 to match the crafting
// matrix
if (recipe instanceof IShapedRecipe) {
IShapedRecipe<?> shapedRecipe = (IShapedRecipe<?>) recipe;
int width = shapedRecipe.getRecipeWidth();
int height = shapedRecipe.getRecipeHeight();
Preconditions.checkArgument(width <= 3 && height <= 3);
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
int source = w + h * width;
int target = w + h * 3;
Ingredient i = ingredients.get(source);
expandedIngredients.set(target, i);
} }
} }
} }
return ItemStack.EMPTY; // Anything else should be a flat list
else {
for (int i = 0; i < ingredients.size(); i++) {
expandedIngredients.set(i, ingredients.get(i));
}
}
return expandedIngredients;
}
/**
* @param is itemstack
* @return is if it can be used, else EMPTY
*/
private ItemStack canUseInSlot(Ingredient ingredient, ItemStack is) {
return Arrays.stream(ingredient.getMatchingStacks()).filter(p -> p.isItemEqual(is)).findFirst()
.orElse(ItemStack.EMPTY);
}
/**
* Finds the first matching itemstack with the highest stored amount.
*/
private IAEItemStack findBestMatchingItemStack(Ingredient ingredients, IPartitionList<IAEItemStack> filter,
IMEMonitor<IAEItemStack> storage, IContainerCraftingPacket cct) {
return Arrays.stream(ingredients.getMatchingStacks()).map(AEItemStack::fromItemStack) //
.filter(r -> r != null && (filter == null || filter.isListed(r))) //
.map(s -> {
// Determine the stored count
IAEItemStack stored = storage.extractItems(s.copy().setStackSize(Long.MAX_VALUE),
Actionable.SIMULATE, cct.getActionSource());
return Pair.of(s, stored != null ? stored.getStackSize() : 0);
}).min((left, right) -> Long.compare(right.getSecond(), left.getSecond()))//
.map(Pair::getFirst).orElse(null);
}
/**
* This tries to find the first pattern matching the list of ingredients.
* <p>
* As additional condition, it sorts by the stored amount to return the one with
* the highest stored amount.
*/
private IAEItemStack findBestMatchingPattern(Ingredient ingredients, IPartitionList<IAEItemStack> filter,
ICraftingGrid crafting, IMEMonitor<IAEItemStack> storage, IContainerCraftingPacket cct) {
return Arrays.stream(ingredients.getMatchingStacks()).map(AEItemStack::fromItemStack)
.filter(r -> r != null && (filter == null || filter.isListed(r)))
.map(s -> s.setCraftable(!crafting.getCraftingFor(s, null, 0, null).isEmpty()))
.filter(IAEItemStack::isCraftable).map(s -> {
final IAEItemStack stored = storage.extractItems(s, Actionable.SIMULATE, cct.getActionSource());
return s.setStackSize(stored != null ? stored.getStackSize() : 0);
}).min((left, right) -> {
final int craftable = Boolean.compare(left.isCraftable(), right.isCraftable());
return craftable != 0 ? craftable : Long.compare(right.getStackSize(), left.getStackSize());
}).orElse(null);
}
private void handleProcessing(Container con, IContainerCraftingPacket cct, IRecipe<?> recipe) {
if (con instanceof PatternTermContainer) {
PatternTermContainer patternTerm = (PatternTermContainer) con;
if (!patternTerm.craftingMode) {
final IItemHandler output = cct.getInventoryByName("output");
ItemHandlerUtil.setStackInSlot(output, 0, recipe.getRecipeOutput());
ItemHandlerUtil.setStackInSlot(output, 1, ItemStack.EMPTY);
ItemHandlerUtil.setStackInSlot(output, 2, ItemStack.EMPTY);
}
}
} }
} }
@@ -19,11 +19,9 @@
package appeng.core.sync.packets; package appeng.core.sync.packets;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.zip.GZIPOutputStream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -157,4 +157,8 @@ final class StorageData extends WorldSavedData implements IWorldGridStorageData
return tag; return tag;
} }
public int size() {
return storage.size();
}
} }
@@ -28,12 +28,13 @@ import com.google.common.base.Preconditions;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.world.server.ServerWorld; import net.minecraft.world.server.ServerWorld;
import appeng.metrics.Metrics;
import appeng.services.CompassService; import appeng.services.CompassService;
import appeng.services.compass.CompassThreadFactory; import appeng.services.compass.CompassThreadFactory;
/** /**
* Singleton access to anything related to world-based data. * Singleton access to anything related to world-based data.
* * <p>
* Data will change depending which world is loaded. Will probably not affect * Data will change depending which world is loaded. Will probably not affect
* SMP at all since only one world is loaded, but SSP more, cause they play on * SMP at all since only one world is loaded, but SSP more, cause they play on
* different worlds. * different worlds.
@@ -53,8 +54,15 @@ public final class WorldData implements IWorldData {
@Nullable @Nullable
private static MinecraftServer server; private static MinecraftServer server;
static {
Metrics.gauge("grid_storage_count", () -> {
WorldData worldData = (WorldData) instance;
return worldData != null ? worldData.storageData.size() : 0;
});
}
private final IWorldPlayerData playerData; private final IWorldPlayerData playerData;
private final IWorldGridStorageData storageData; private final StorageData storageData;
private final IWorldCompassData compassData; private final IWorldCompassData compassData;
private WorldData(@Nonnull final ServerWorld overworld) { private WorldData(@Nonnull final ServerWorld overworld) {
@@ -80,7 +88,6 @@ public final class WorldData implements IWorldData {
/** /**
* @return ae2 data related to a specific world * @return ae2 data related to a specific world
*
* @deprecated do not use singletons which are dependent on specific world state * @deprecated do not use singletons which are dependent on specific world state
*/ */
@Deprecated @Deprecated
@@ -102,7 +109,7 @@ public final class WorldData implements IWorldData {
/** /**
* Requires to start up from external from here * Requires to start up from external from here
* * <p>
* drawback of the singleton build style * drawback of the singleton build style
*/ */
public static void onServerStarting(MinecraftServer server) { public static void onServerStarting(MinecraftServer server) {
@@ -20,11 +20,8 @@ package appeng.decorative.solid;
import net.minecraft.block.AbstractGlassBlock; import net.minecraft.block.AbstractGlassBlock;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.material.Material;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import appeng.helpers.AEMaterials;
public class QuartzGlassBlock extends AbstractGlassBlock { public class QuartzGlassBlock extends AbstractGlassBlock {
public QuartzGlassBlock(Properties props) { public QuartzGlassBlock(Properties props) {
@@ -33,8 +30,7 @@ public class QuartzGlassBlock extends AbstractGlassBlock {
@Override @Override
public boolean isSideInvisible(BlockState state, BlockState adjacentBlockState, Direction side) { public boolean isSideInvisible(BlockState state, BlockState adjacentBlockState, Direction side) {
final Material mat = adjacentBlockState.getMaterial(); if (adjacentBlockState.getBlock() instanceof QuartzGlassBlock) {
if (mat == Material.GLASS || mat == AEMaterials.GLASS) {
if (adjacentBlockState.getRenderType() == state.getRenderType()) { if (adjacentBlockState.getRenderType() == state.getRenderType()) {
return true; return true;
} }
@@ -29,7 +29,6 @@ import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.ToolType; import net.minecraftforge.common.ToolType;
import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import appeng.block.AEBaseBlock; import appeng.block.AEBaseBlock;
import appeng.core.worlddata.WorldData; import appeng.core.worlddata.WorldData;
@@ -43,11 +42,10 @@ public class SkyStoneBlock extends AEBaseBlock {
super(props); super(props);
this.type = type; this.type = type;
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.addListener(this::breakFaster);
} }
@SubscribeEvent private void breakFaster(final PlayerEvent.BreakSpeed event) {
public void breakFaster(final PlayerEvent.BreakSpeed event) {
if (event.getState().getBlock() == this && event.getPlayer() != null) { if (event.getState().getBlock() == this && event.getPlayer() != null) {
final ItemStack is = event.getPlayer().getItemStackFromSlot(EquipmentSlotType.MAINHAND); final ItemStack is = event.getPlayer().getItemStackFromSlot(EquipmentSlotType.MAINHAND);
int level = -1; int level = -1;
@@ -19,13 +19,14 @@
package appeng.entity; package appeng.entity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.material.Material;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import appeng.api.features.AEFeature; import appeng.api.features.AEFeature;
@@ -35,12 +36,27 @@ import appeng.client.EffectType;
import appeng.core.AEConfig; import appeng.core.AEConfig;
import appeng.core.AppEng; import appeng.core.AppEng;
import appeng.items.misc.CrystalSeedItem; import appeng.items.misc.CrystalSeedItem;
import appeng.util.Platform;
public final class GrowingCrystalEntity extends AEBaseItemEntity { public final class GrowingCrystalEntity extends AEBaseItemEntity {
public static EntityType<GrowingCrystalEntity> TYPE; public static EntityType<GrowingCrystalEntity> TYPE;
// Growth tick progress per tick by number of adjacent accelerators
// Expressed as 1/1000th of a growth tick, applied to progress_1000
// each time this entity ticks.
private static final int[] GROWTH_TICK_PROGRESS = { 1, // no accelerators
40, // 1 accelerator
92, // 2 accelerators
159, // 3 accelerators
247, // 4 accelerators
361, // 5 accelerators
509 // 6 accelerators
};
/**
* The accumulated progress towards a single growth tick of the crystal in
* 1/1000th of a growth tick.
*/
private int progress_1000 = 0; private int progress_1000 = 0;
public GrowingCrystalEntity(EntityType<? extends GrowingCrystalEntity> type, World world) { public GrowingCrystalEntity(EntityType<? extends GrowingCrystalEntity> type, World world) {
@@ -56,126 +72,140 @@ public final class GrowingCrystalEntity extends AEBaseItemEntity {
public void tick() { public void tick() {
super.tick(); super.tick();
final ItemStack is = this.getItem();
final Item gc = is.getItem();
if (!(gc instanceof IGrowableCrystal)) {
return;
}
applyGrowthTick((IGrowableCrystal) gc, is);
}
private void applyGrowthTick(IGrowableCrystal cry, ItemStack is) {
if (!AEConfig.instance().isFeatureEnabled(AEFeature.IN_WORLD_PURIFICATION)) { if (!AEConfig.instance().isFeatureEnabled(AEFeature.IN_WORLD_PURIFICATION)) {
return; return;
} }
final ItemStack is = this.getItem(); final int x = MathHelper.floor(this.getPosX());
final Item gc = is.getItem(); final int y = MathHelper.floor((this.getBoundingBox().minY + this.getBoundingBox().maxY) / 2.0D);
final int z = MathHelper.floor(this.getPosZ());
if (gc instanceof IGrowableCrystal) // if it changes this just stops being an issue... BlockPos pos = new BlockPos(x, y, z);
{ final BlockState state = this.world.getBlockState(pos);
final int j = MathHelper.floor(this.getPosX());
final int i = MathHelper.floor((this.getBoundingBox().minY + this.getBoundingBox().maxY) / 2.0D);
final int k = MathHelper.floor(this.getPosZ());
final BlockState state = this.world.getBlockState(new BlockPos(j, i, k)); final float multiplier = cry.getMultiplier(state, world, pos);
final Material mat = state.getMaterial();
final IGrowableCrystal cry = (IGrowableCrystal) is.getItem();
final float multiplier = cry.getMultiplier(state.getBlock(), mat); if (multiplier <= 0) {
final int speed = (int) Math.max(1, this.getSpeed(j, i, k) * multiplier); // Crystal is in unsuitable material, reset progress and quit
this.progress_1000 = 0;
return;
}
final boolean isClient = Platform.isClient(); final int progressPerTick = (int) Math.max(1, this.getSpeed(pos) * multiplier);
if (mat.isLiquid()) { if (world.isRemote()) {
if (isClient) { // On the client, we reuse the growth-tick-progress
this.progress_1000++; // as a tick-counter for particle effects
} else { int len = getTicksBetweenParticleEffects(progressPerTick);
this.progress_1000 += speed; if (++this.progress_1000 >= len) {
}
} else {
this.progress_1000 = 0; this.progress_1000 = 0;
AppEng.proxy.spawnEffect(EffectType.Vibrant, this.world, this.getPosX(), this.getPosY() + 0.2,
this.getPosZ(), null);
} }
} else {
this.progress_1000 += progressPerTick;
if (isClient) { if (this.progress_1000 >= 1000) {
int len = 40; // We need to copy the stack or the change detection will not work and not sync
// this new stack to the client
ItemStack newItem = is.copy();
if (speed > 2) { // If we did not use a while loop here, the fastest growth for a crystal
len = 20; // would be limited to a minimum of 30 seconds (based on 600 required growth
} // ticks).
// Should a crystal decide to use a high multiplier for a certain material,
if (speed > 90) { // it should be possible to go faster.
len = 15; do {
} newItem = cry.triggerGrowth(newItem);
if (speed > 150) {
len = 10;
}
if (speed > 240) {
len = 7;
}
if (speed > 360) {
len = 3;
}
if (speed > 500) {
len = 1;
}
if (this.progress_1000 >= len) {
this.progress_1000 = 0;
AppEng.proxy.spawnEffect(EffectType.Vibrant, this.world, this.getPosX(), this.getPosY() + 0.2,
this.getPosZ(), null);
}
} else {
if (this.progress_1000 > 1000) {
this.progress_1000 -= 1000; this.progress_1000 -= 1000;
// We need to copy the stack or the change detection will not work and not sync // We assume that if the item changes, the process is complete and we can break
// this new stack to the client } while (this.progress_1000 >= 1000 && newItem.getItem() == is.getItem());
ItemStack newItem = cry.triggerGrowth(is.copy());
this.setItem(newItem); this.setItem(newItem);
}
} }
} }
} }
private int getSpeed(final int x, final int y, final int z) { private static int getTicksBetweenParticleEffects(int progressPerTick) {
final int per = 80; if (progressPerTick > 500) {
final float mul = 0.3f; return 1; // 20 times per second
} else if (progressPerTick > 360) {
int qty = 0; return 3;
} else if (progressPerTick > 240) {
if (this.isAccelerated(x + 1, y, z)) { return 7;
qty += per + qty * mul; } else if (progressPerTick > 150) {
return 10;
} else if (progressPerTick > 90) {
return 15;
} else if (progressPerTick > 2) {
return 20;
} else {
return 40; // Every 2 seconds
} }
if (this.isAccelerated(x, y + 1, z)) {
qty += per + qty * mul;
}
if (this.isAccelerated(x, y, z + 1)) {
qty += per + qty * mul;
}
if (this.isAccelerated(x - 1, y, z)) {
qty += per + qty * mul;
}
if (this.isAccelerated(x, y - 1, z)) {
qty += per + qty * mul;
}
if (this.isAccelerated(x, y, z - 1)) {
qty += per + qty * mul;
}
return qty;
} }
private boolean isAccelerated(final int x, final int y, final int z) { /**
final TileEntity te = this.world.getTileEntity(new BlockPos(x, y, z)); * Gets the extra progress per tick in 1/1000th of a growth tick based on the
* surrounding accelerators.
*/
private int getSpeed(BlockPos pos) {
int acceleratorCount = getAcceleratorCount(pos);
if (acceleratorCount < 0) {
return GROWTH_TICK_PROGRESS[0];
} else if (acceleratorCount >= GROWTH_TICK_PROGRESS.length) {
return GROWTH_TICK_PROGRESS[GROWTH_TICK_PROGRESS.length - 1];
} else {
return GROWTH_TICK_PROGRESS[acceleratorCount];
}
}
private int getAcceleratorCount(BlockPos pos) {
int count = 0;
BlockPos.Mutable testPos = new BlockPos.Mutable();
for (Direction direction : Direction.values()) {
if (this.isPoweredAccelerator(testPos.func_239622_a_(pos, direction))) {
count++;
}
}
return count;
}
private boolean isPoweredAccelerator(BlockPos pos) {
final TileEntity te = this.world.getTileEntity(pos);
return te instanceof ICrystalGrowthAccelerator && ((ICrystalGrowthAccelerator) te).isPowered(); return te instanceof ICrystalGrowthAccelerator && ((ICrystalGrowthAccelerator) te).isPowered();
} }
// Don't let seeds "float" on water surface
@Override @Override
protected void applyFloatMotion() { protected void applyFloatMotion() {
ItemStack item = getItem(); ItemStack item = getItem();
// Make ungrown seeds sink, and fully grown seeds bouyant allowing for
// automation based around dropping seeds between 5 CGAs, then catchiung
// them on their way up.
if (item.getItem() instanceof CrystalSeedItem) { if (item.getItem() instanceof CrystalSeedItem) {
Vector3d v = this.getMotion();
// Apply a much smaller acceleration to make them slowly sink
double yAccel = this.hasNoGravity() ? 0 : -0.002;
// Apply the x/z slow-down, and the y acceleration
this.setMotion(v.x * 0.99, v.y + yAccel, v.z * 0.99);
return; return;
} }
super.applyFloatMotion(); super.applyFloatMotion();
@@ -40,7 +40,6 @@ import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.Explosion; import net.minecraft.world.Explosion;
import net.minecraft.world.Explosion.Mode;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import net.minecraftforge.fml.network.NetworkHooks; import net.minecraftforge.fml.network.NetworkHooks;
@@ -15,7 +15,6 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.TranslationTextComponent; import net.minecraft.util.text.TranslationTextComponent;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidAttributes; import net.minecraftforge.fluids.FluidAttributes;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil; import net.minecraftforge.fluids.FluidUtil;
@@ -9,7 +9,6 @@ import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.container.ContainerType; import net.minecraft.inventory.container.ContainerType;
import net.minecraft.inventory.container.IContainerListener; import net.minecraft.inventory.container.IContainerListener;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil; import net.minecraftforge.fluids.FluidUtil;
@@ -96,16 +96,15 @@ public class FluidImportBusPart extends SharedFluidBusPart {
} }
final TileEntity te = this.getConnectedTE(); final TileEntity te = this.getConnectedTE();
LazyOptional<IFluidHandler> fhOpt = LazyOptional.empty();
if (te != null) { if (te != null) {
te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, this.getSide().getFacing().getOpposite()); LazyOptional<IFluidHandler> fhOpt = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY,
} this.getSide().getFacing().getOpposite());
if (fhOpt.isPresent()) {
try { if (fhOpt.isPresent()) {
final IFluidHandler fh = fhOpt.orElse(null); try {
final IMEMonitor<IAEFluidStack> inv = this.getProxy().getStorage().getInventory(this.getChannel()); final IFluidHandler fh = fhOpt.orElseThrow(() -> new IllegalStateException());
final IMEMonitor<IAEFluidStack> inv = this.getProxy().getStorage().getInventory(this.getChannel());
if (fh != null) {
final FluidStack fluidStack = fh.drain(this.calculateAmountToSend(), FluidAction.SIMULATE); final FluidStack fluidStack = fh.drain(this.calculateAmountToSend(), FluidAction.SIMULATE);
if (this.filterEnabled() && !this.isInFilter(fluidStack)) { if (this.filterEnabled() && !this.isInFilter(fluidStack)) {
@@ -128,12 +127,11 @@ public class FluidImportBusPart extends SharedFluidBusPart {
} }
return TickRateModulation.IDLE; return TickRateModulation.IDLE;
} catch (GridAccessException e) {
// skip
} }
} catch (GridAccessException e) {
e.printStackTrace();
} }
} }
return TickRateModulation.SLEEP; return TickRateModulation.SLEEP;
} }
@@ -57,6 +57,7 @@ import appeng.core.AppEng;
import appeng.core.sync.packets.PaintedEntityPacket; import appeng.core.sync.packets.PaintedEntityPacket;
import appeng.crafting.CraftingJob; import appeng.crafting.CraftingJob;
import appeng.me.Grid; import appeng.me.Grid;
import appeng.metrics.Metrics;
import appeng.tile.AEBaseTileEntity; import appeng.tile.AEBaseTileEntity;
import appeng.util.IWorldCallable; import appeng.util.IWorldCallable;
import appeng.util.Platform; import appeng.util.Platform;
@@ -77,6 +78,11 @@ public class TickHandler {
return INSTANCE; return INSTANCE;
} }
static {
Metrics.gauge("ticking_network_count", () -> INSTANCE.server.networks.size());
Metrics.gauge("crafting_job_count", INSTANCE.craftingJobs::size);
}
public static void setup(IEventBus eventBus) { public static void setup(IEventBus eventBus) {
eventBus.addListener(INSTANCE::onServerTick); eventBus.addListener(INSTANCE::onServerTick);
eventBus.addListener(INSTANCE::onWorldTick); eventBus.addListener(INSTANCE::onWorldTick);
@@ -18,6 +18,8 @@
package appeng.integration.abstraction; package appeng.integration.abstraction;
import mezz.jei.api.runtime.IJeiRuntime;
import appeng.integration.IIntegrationModule; import appeng.integration.IIntegrationModule;
/** /**
@@ -25,6 +27,10 @@ import appeng.integration.IIntegrationModule;
*/ */
public interface IJEI extends IIntegrationModule { public interface IJEI extends IIntegrationModule {
default IJeiRuntime getRuntime() {
return null;
}
default String getSearchText() { default String getSearchText() {
return ""; return "";
} }
@@ -0,0 +1,47 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2020 Team Appliedenergistics, All rights reserved.
*
* Applied Energistics 2 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.
*
* Applied Energistics 2 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 Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.integration.modules.jei;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.crafting.IRecipe;
import mezz.jei.api.gui.IRecipeLayout;
import mezz.jei.api.recipe.transfer.IRecipeTransferError;
import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper;
import appeng.container.implementations.CraftingTermContainer;
public class CraftingRecipeTransferHandler extends RecipeTransferHandler<CraftingTermContainer> {
CraftingRecipeTransferHandler(Class<CraftingTermContainer> containerClass, IRecipeTransferHandlerHelper helper) {
super(containerClass, helper);
}
@Override
protected IRecipeTransferError doTransferRecipe(CraftingTermContainer container, IRecipe<?> recipe,
IRecipeLayout recipeLayout, PlayerEntity player, boolean maxTransfer) {
return null;
}
@Override
protected boolean isCrafting() {
return true;
}
}
@@ -111,6 +111,8 @@ class FacadeRegistryPlugin implements IRecipeManagerPlugin {
ingredients.set(7, Ingredient.fromStacks(cableAnchor)); ingredients.set(7, Ingredient.fromStacks(cableAnchor));
ingredients.set(4, Ingredient.fromStacks(textureItem)); ingredients.set(4, Ingredient.fromStacks(textureItem));
result.setCount(4);
return new ShapedRecipe(id, "", 3, 3, ingredients, result); return new ShapedRecipe(id, "", 3, 3, ingredients, result);
} }
@@ -82,11 +82,15 @@ public class JEIPlugin implements IModPlugin {
@Override @Override
public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) { public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) {
// Allow recipe transfer from JEI to crafting and pattern terminal
registration.addRecipeTransferHandler(new RecipeTransferHandler<>(CraftingTermContainer.class), // Allow vanilla crafting recipe transfer from JEI to crafting terminal
VanillaRecipeCategoryUid.CRAFTING); registration.addRecipeTransferHandler(
registration.addRecipeTransferHandler(new RecipeTransferHandler<>(PatternTermContainer.class), new CraftingRecipeTransferHandler(CraftingTermContainer.class, registration.getTransferHelper()),
VanillaRecipeCategoryUid.CRAFTING); VanillaRecipeCategoryUid.CRAFTING);
// Universal handler for processing to try and handle all IRecipe
registration.addUniversalRecipeTransferHandler(
new PatternRecipeTransferHandler(PatternTermContainer.class, registration.getTransferHelper()));
} }
@Override @Override
@@ -37,6 +37,10 @@ class JeiRuntimeAdapter implements IJEI {
return true; return true;
} }
public IJeiRuntime getRuntime() {
return runtime;
}
@Override @Override
public String getSearchText() { public String getSearchText() {
return Strings.nullToEmpty(this.runtime.getIngredientFilter().getFilterText()); return Strings.nullToEmpty(this.runtime.getIngredientFilter().getFilterText());
@@ -0,0 +1,58 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2020 Team Appliedenergistics, All rights reserved.
*
* Applied Energistics 2 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.
*
* Applied Energistics 2 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 Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.integration.modules.jei;
import net.minecraft.client.resources.I18n;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.crafting.IRecipe;
import mezz.jei.api.constants.VanillaRecipeCategoryUid;
import mezz.jei.api.gui.IRecipeLayout;
import mezz.jei.api.recipe.transfer.IRecipeTransferError;
import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper;
import appeng.container.implementations.PatternTermContainer;
public class PatternRecipeTransferHandler extends RecipeTransferHandler<PatternTermContainer> {
PatternRecipeTransferHandler(Class<PatternTermContainer> containerClass, IRecipeTransferHandlerHelper helper) {
super(containerClass, helper);
}
protected IRecipeTransferError doTransferRecipe(PatternTermContainer container, IRecipe<?> recipe,
IRecipeLayout recipeLayout, PlayerEntity player, boolean maxTransfer) {
if (container.isCraftingMode()
&& recipeLayout.getRecipeCategory().getUid() != VanillaRecipeCategoryUid.CRAFTING) {
return this.helper
.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.requires_processing_mode"));
}
if (recipe.getRecipeOutput().isEmpty()) {
return this.helper.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.no_output"));
}
return null;
}
@Override
protected boolean isCrafting() {
return false;
}
}
@@ -18,101 +18,128 @@
package appeng.integration.modules.jei; package appeng.integration.modules.jei;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import net.minecraft.client.resources.I18n;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.container.Container; import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.Slot;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.item.crafting.IRecipe;
import net.minecraft.nbt.ListNBT; import net.minecraft.item.crafting.Ingredient;
import net.minecraft.item.crafting.ShapedRecipe;
import net.minecraft.item.crafting.ShapelessRecipe;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import mezz.jei.api.gui.IRecipeLayout; import mezz.jei.api.gui.IRecipeLayout;
import mezz.jei.api.gui.ingredient.IGuiIngredient; import mezz.jei.api.gui.ingredient.IGuiIngredient;
import mezz.jei.api.recipe.transfer.IRecipeTransferError; import mezz.jei.api.recipe.transfer.IRecipeTransferError;
import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper;
import appeng.container.slot.CraftingMatrixSlot;
import appeng.container.slot.FakeCraftingMatrixSlot;
import appeng.core.sync.network.NetworkHandler; import appeng.core.sync.network.NetworkHandler;
import appeng.core.sync.packets.JEIRecipePacket; import appeng.core.sync.packets.JEIRecipePacket;
import appeng.util.Platform; import appeng.helpers.IContainerCraftingPacket;
class RecipeTransferHandler<T extends Container> implements IRecipeTransferHandler<T> { abstract class RecipeTransferHandler<T extends Container & IContainerCraftingPacket>
implements IRecipeTransferHandler<T> {
private final Class<T> containerClass; private final Class<T> containerClass;
protected final IRecipeTransferHandlerHelper helper;
RecipeTransferHandler(Class<T> containerClass) { RecipeTransferHandler(Class<T> containerClass, IRecipeTransferHandlerHelper helper) {
this.containerClass = containerClass; this.containerClass = containerClass;
this.helper = helper;
} }
@Override @Override
public Class<T> getContainerClass() { public final Class<T> getContainerClass() {
return this.containerClass; return this.containerClass;
} }
@Nullable
@Override @Override
public IRecipeTransferError transferRecipe(T container, IRecipeLayout recipeLayout, PlayerEntity player, public final IRecipeTransferError transferRecipe(T container, Object recipe, IRecipeLayout recipeLayout,
boolean maxTransfer, boolean doTransfer) { PlayerEntity player, boolean maxTransfer, boolean doTransfer) {
if (!doTransfer) { if (!(recipe instanceof IRecipe)) {
return null; return this.helper.createInternalError();
}
final IRecipe<?> irecipe = (IRecipe<?>) recipe;
final ResourceLocation recipeId = irecipe.getId();
if (recipeId == null) {
return this.helper.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.missing_id"));
} }
Map<Integer, ? extends IGuiIngredient<ItemStack>> ingredients = recipeLayout.getItemStacks() // Check that the recipe can actually be looked up via the manager, i.e. our
.getGuiIngredients(); // facade recipes
// have an ID, but are never registered with the recipe manager.
final CompoundNBT recipe = new CompoundNBT(); boolean canSendReference = true;
if (!player.getEntityWorld().getRecipeManager().getRecipe(recipeId).isPresent()) {
int slotIndex = 0; // Validate that the recipe is a shapeless or shapedrecipe, since we can
for (Map.Entry<Integer, ? extends IGuiIngredient<ItemStack>> ingredientEntry : ingredients.entrySet()) { // serialize those
IGuiIngredient<ItemStack> ingredient = ingredientEntry.getValue(); if (!(recipe instanceof ShapedRecipe) && !(recipe instanceof ShapelessRecipe)) {
if (!ingredient.isInput()) { return this.helper.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.missing_id"));
continue;
} }
canSendReference = false;
}
for (final Slot slot : container.inventorySlots) { if (!irecipe.canFit(3, 3)) {
if (slot instanceof CraftingMatrixSlot || slot instanceof FakeCraftingMatrixSlot) { return this.helper.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.recipe_too_large"));
if (slot.getSlotIndex() == slotIndex) { }
final ListNBT tags = new ListNBT();
final List<ItemStack> list = new ArrayList<>();
final ItemStack displayed = ingredient.getDisplayedIngredient();
// prefer currently displayed item final IRecipeTransferError error = doTransferRecipe(container, irecipe, recipeLayout, player, maxTransfer);
if (displayed != null && !displayed.isEmpty()) {
list.add(displayed); if (error != null) {
return error;
}
if (doTransfer) {
if (canSendReference) {
NetworkHandler.instance().sendToServer(new JEIRecipePacket(recipeId, isCrafting()));
} else {
// To avoid earlier problems of too large packets being sent that crashed the
// client,
// as a fallback when the recipe ID could not be resolved, we'll just send the
// displayed
// items.
NonNullList<Ingredient> flatIngredients = NonNullList.withSize(9, Ingredient.EMPTY);
ItemStack output = ItemStack.EMPTY;
// Determine the first JEI slot that has an actual input, we'll use this to
// offset the
// crafting grid target slot
int firstInputSlot = recipeLayout.getItemStacks().getGuiIngredients().entrySet().stream()
.filter(e -> e.getValue().isInput()).mapToInt(Map.Entry::getKey).min().orElse(0);
// Now map the actual ingredients into the output/input
for (Map.Entry<Integer, ? extends IGuiIngredient<ItemStack>> entry : recipeLayout.getItemStacks()
.getGuiIngredients().entrySet()) {
IGuiIngredient<ItemStack> item = entry.getValue();
if (item.getDisplayedIngredient() == null) {
continue;
}
int inputIndex = entry.getKey() - firstInputSlot;
if (item.isInput() && inputIndex < flatIngredients.size()) {
ItemStack displayedIngredient = item.getDisplayedIngredient();
if (displayedIngredient != null) {
flatIngredients.set(inputIndex, Ingredient.fromStacks(displayedIngredient));
} }
} else if (!item.isInput() && output.isEmpty()) {
// prefer pure crystals. output = item.getDisplayedIngredient();
for (ItemStack stack : ingredient.getAllIngredients()) {
if (Platform.isRecipePrioritized(stack)) {
list.add(0, stack);
} else {
list.add(stack);
}
}
for (final ItemStack is : list) {
final CompoundNBT tag = new CompoundNBT();
is.write(tag);
tags.add(tag);
}
recipe.put("#" + slot.getSlotIndex(), tags);
break;
} }
} }
ShapedRecipe fallbackRecipe = new ShapedRecipe(recipeId, "", 3, 3, flatIngredients, output);
NetworkHandler.instance().sendToServer(new JEIRecipePacket(fallbackRecipe, isCrafting()));
} }
slotIndex++;
} }
NetworkHandler.instance().sendToServer(new JEIRecipePacket(recipe));
return null; return null;
} }
protected abstract IRecipeTransferError doTransferRecipe(T container, IRecipe<?> recipe, IRecipeLayout recipeLayout,
PlayerEntity player, boolean maxTransfer);
protected abstract boolean isCrafting();
} }
@@ -24,15 +24,19 @@ import javax.annotation.Nullable;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import net.minecraft.block.Block; import net.minecraft.block.BlockState;
import net.minecraft.block.material.Material;
import net.minecraft.client.util.ITooltipFlag; import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.fluid.Fluid;
import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.ITag;
import net.minecraft.util.IItemProvider; import net.minecraft.util.IItemProvider;
import net.minecraft.util.NonNullList; import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.StringTextComponent;
@@ -41,6 +45,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import appeng.api.implementations.items.IGrowableCrystal; import appeng.api.implementations.items.IGrowableCrystal;
import appeng.core.AEConfig;
import appeng.core.localization.ButtonToolTips; import appeng.core.localization.ButtonToolTips;
import appeng.entity.GrowingCrystalEntity; import appeng.entity.GrowingCrystalEntity;
import appeng.items.AEBaseItem; import appeng.items.AEBaseItem;
@@ -94,8 +99,24 @@ public class CrystalSeedItem extends AEBaseItem implements IGrowableCrystal {
} }
@Override @Override
public float getMultiplier(final Block blk, final Material mat) { public float getMultiplier(BlockState state, @Nullable World world, @Nullable BlockPos pos) {
return 0.5f;
// Check for the improved fluid tag and return the improved multiplier
String improvedFluidTagName = AEConfig.instance().getImprovedFluidTag();
if (improvedFluidTagName != null) {
ITag<Fluid> tag = FluidTags.getCollection().get(new ResourceLocation(improvedFluidTagName));
if (tag != null && state.getFluidState().isTagged(tag)) {
return AEConfig.instance().getImprovedFluidMultiplier();
}
}
// Check for the normal supported fluid
if (world != null && world.func_234923_W_() == World.field_234919_h_) {
// In the nether, use Lava as the "normal" fluid
return state.getFluidState().isTagged(FluidTags.LAVA) ? 1 : 0;
} else {
return state.getFluidState().isTagged(FluidTags.WATER) ? 1 : 0;
}
} }
@Override @Override
@@ -324,21 +324,21 @@ public class ColorApplicatorItem extends AEBasePoweredItem
return w.setBlockState(pos, newState); return w.setBlockState(pos, newState);
} }
if (blk instanceof CableBusBlock && p != null) {
return ((CableBusBlock) blk).recolorBlock(w, pos, side, newColor.dye, p);
}
TileEntity be = w.getTileEntity(pos); TileEntity be = w.getTileEntity(pos);
if (be instanceof IColorableTile) { if (be instanceof IColorableTile) {
IColorableTile ct = (IColorableTile) be; IColorableTile ct = (IColorableTile) be;
AEColor c = ct.getColor(); AEColor c = ct.getColor();
if (c != newColor) { if (c != newColor) {
ct.recolourBlock(side, newColor, null); ct.recolourBlock(side, newColor, p);
return true; return true;
} }
return false; return false;
} }
if (blk instanceof CableBusBlock && p != null) {
return ((CableBusBlock) blk).recolorBlock(w, pos, side, newColor.dye, p);
}
return false; return false;
} }
@@ -22,7 +22,6 @@ import java.util.Iterator;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.event.world.WorldEvent;
@@ -159,12 +158,16 @@ public class QuantumCluster implements ILocatable, IAECluster {
if (qc != null) { if (qc != null) {
final World theWorld = qc.center.getWorld(); final World theWorld = qc.center.getWorld();
if (!qc.isDestroyed) { if (!qc.isDestroyed) {
ChunkPos cPos = new ChunkPos(qc.center.getPos()); // In future versions, we might actually want to delay the entire registration
if (theWorld.getChunkProvider().isChunkLoaded(cPos)) { // until the center
// tile begins ticking normally.
if (theWorld.isBlockLoaded(qc.center.getPos())) {
final World cur = theWorld.getServer().getWorld(theWorld.func_234923_W_()); final World cur = theWorld.getServer().getWorld(theWorld.func_234923_W_());
final TileEntity te = theWorld.getTileEntity(qc.center.getPos()); final TileEntity te = theWorld.getTileEntity(qc.center.getPos());
return te != qc.center || theWorld != cur; return te != qc.center || theWorld != cur;
} else {
AELog.warn("Found a registered QNB with serial %s whose chunk seems to be unloaded: %s", qe, qc);
} }
} }
} }
@@ -275,4 +278,17 @@ public class QuantumCluster implements ILocatable, IAECluster {
private void setRing(final QuantumBridgeTileEntity[] ring) { private void setRing(final QuantumBridgeTileEntity[] ring) {
this.Ring = ring; this.Ring = ring;
} }
@Override
public String toString() {
if (center == null) {
return "QuantumCluster{no-center}";
}
World world = center.getWorld();
BlockPos pos = center.getPos();
return "QuantumCluster{" + world + "," + pos + "}";
}
} }
@@ -0,0 +1,54 @@
package appeng.metrics;
import java.util.function.Supplier;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
/**
* Cache Metrics simplify reporting of metrics for a Guava {@link Cache}.
*/
public class CacheMetrics extends Metric {
private final Supplier<Cache<?, ?>> cacheSupplier;
// Pre-allocate these to avoid repeated concatenation during reporting
private final String sizeId;
private final String hitCountId;
private final String missCountId;
private final String loadSuccessCountId;
private final String loadExceptionCountId;
private final String totalLoadTimeId;
private final String evictionCountId;
public CacheMetrics(String id, Supplier<Cache<?, ?>> cacheSupplier) {
super(id);
this.cacheSupplier = cacheSupplier;
this.sizeId = id + ".size";
this.hitCountId = id + ".hit-count";
this.missCountId = id + ".miss-count";
this.loadSuccessCountId = id + ".load-success-count";
this.loadExceptionCountId = id + ".load-exception-count";
this.totalLoadTimeId = id + ".total-load-time";
this.evictionCountId = id + ".eviction-count";
}
@Override
public void accept(MetricVisitor visitor) {
Cache<?, ?> cache = this.cacheSupplier.get();
if (cache != null) {
visitor.visitGauge(sizeId, cache.size()); // Thread-safe
CacheStats stats = cache.stats(); // Thread-safe
visitor.visitGauge(hitCountId, stats.hitCount());
visitor.visitGauge(missCountId, stats.missCount());
visitor.visitGauge(loadSuccessCountId, stats.loadSuccessCount());
visitor.visitGauge(loadExceptionCountId, stats.loadExceptionCount());
visitor.visitGauge(totalLoadTimeId, stats.totalLoadTime());
visitor.visitGauge(evictionCountId, stats.evictionCount());
}
}
}
+31
View File
@@ -0,0 +1,31 @@
package appeng.metrics;
import java.util.function.Supplier;
/**
* A Gauge is a Metric that will simply read the current value of something when
* the metrics are being reported.
*/
class Gauge extends Metric {
private final Supplier<Number> valueSupplier;
public Gauge(String id, Supplier<Number> valueSupplier) {
super(id);
this.valueSupplier = valueSupplier;
}
public Supplier<Number> getValueSupplier() {
return valueSupplier;
}
public Number getValue() {
return valueSupplier.get();
}
@Override
public void accept(MetricVisitor visitor) {
visitor.visitGauge(getId(), getValue());
}
}
+34
View File
@@ -0,0 +1,34 @@
package appeng.metrics;
import java.util.Objects;
abstract class Metric {
private final String id;
public Metric(String id) {
this.id = Objects.requireNonNull(id);
}
public String getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Metric metric = (Metric) o;
return id.equals(metric.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public abstract void accept(MetricVisitor visitor);
}
@@ -0,0 +1,7 @@
package appeng.metrics;
public interface MetricVisitor {
void visitGauge(String id, Number value);
}
+51
View File
@@ -0,0 +1,51 @@
package appeng.metrics;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
/**
* Registry to register Metrics.
*/
public final class Metrics {
private static final List<Metric> metrics = new ArrayList<>();
private Metrics() {
}
/**
* Registers a Gauge, which is a simple metric with no state that will be
* queried for its current value whenever metrics are being reported.
*/
public static synchronized void gauge(String id, Supplier<Number> valueSupplier) {
metrics.add(new Gauge(id, valueSupplier));
}
/**
* Register several standard metrics for a Guava {@link Cache}.
*/
public static synchronized void cache(String name, Cache<?, ?> cache) {
metrics.add(new CacheMetrics(name, () -> cache));
}
/**
* Register several standard metrics for a Guava {@link Cache} based on a
* supplier to support an underlying changing cache.
*/
public static synchronized void cache(String name, Supplier<Cache<?, ?>> cacheSupplier) {
metrics.add(new CacheMetrics(name, cacheSupplier));
}
public static synchronized void visit(MetricVisitor visitor) {
for (Metric metric : metrics) {
metric.accept(visitor);
}
}
}
@@ -0,0 +1,78 @@
package appeng.metrics.endpoint;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import appeng.metrics.Metrics;
import appeng.metrics.reporter.PrometheusReporter;
/**
* Starts an embedded HTTP-Server that will expose the Metrics in a format
* compatible with Prometheus.
*/
public class PrometheusEndpoint implements AutoCloseable {
private final HttpServer server;
private final PrometheusReporter reporter = new PrometheusReporter();
public PrometheusEndpoint(String hostname, int port) {
try {
this.server = HttpServer.create();
} catch (IOException e) {
throw new RuntimeException("Failed to create metrics HTTP-server", e);
}
InetSocketAddress address;
if (hostname.isEmpty()) {
address = new InetSocketAddress((InetAddress) null, port);
} else {
address = new InetSocketAddress(hostname, port);
}
// Default endpoint is /metrics in Prometheus
this.server.createContext("/metrics", this::handler);
try {
this.server.bind(address, 0);
} catch (IOException e) {
throw new RuntimeException("Failed to bind metrics HTTP server to " + address, e);
}
this.server.start();
}
@Override
public void close() {
this.server.stop(0);
}
private synchronized void handler(HttpExchange exchange) throws IOException {
// Update metrics
reporter.reset();
Metrics.visit(reporter);
CharSequence report = getReport();
exchange.getResponseHeaders().add("Content-Type", "text/plain; version=0.0.4");
exchange.sendResponseHeaders(200, report.length());
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody(), StandardCharsets.US_ASCII)) {
writer.append(report);
}
exchange.close();
}
private CharSequence getReport() {
return reporter.getReport();
}
}
@@ -0,0 +1,24 @@
package appeng.metrics.reporter;
import java.io.PrintStream;
import appeng.metrics.MetricVisitor;
/**
* Reports Metrics as simple key-value pairs to a {@link PrintStream} (i.e.
* {@link System#out}).
*/
public class PrintStreamReporter implements MetricVisitor {
private final PrintStream out;
public PrintStreamReporter(PrintStream out) {
this.out = out;
}
@Override
public void visitGauge(String id, Number value) {
out.println(id + '=' + value);
}
}
@@ -0,0 +1,54 @@
package appeng.metrics.reporter;
import appeng.metrics.MetricVisitor;
/**
* Reports the metrics in a format compatible with Prometheus.
* <p>
* See <a href=
* "https://prometheus.io/docs/instrumenting/exposition_formats/">Prometheus
* Docs</a>
*/
public class PrometheusReporter implements MetricVisitor {
private final StringBuilder response = new StringBuilder();
@Override
public void visitGauge(String id, Number value) {
printType(id, "gauge");
appendId(id);
response.append(' ').append(value.toString()).append('\n');
}
private void printType(String id, String type) {
response.append("# TYPE ");
appendId(id);
response.append(' ').append(type).append('\n');
}
public void reset() {
response.setLength(0);
}
public int length() {
return response.length();
}
public CharSequence getReport() {
return response;
}
private void appendId(String id) {
response.append("appeng_");
// Sanitize to [a-zA-Z0-9:_]
for (int i = 0; i < id.length(); i++) {
char ch = id.charAt(i);
if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == ':' || ch == '_') {
response.append(ch);
} else {
response.append('_');
}
}
}
}
@@ -7,7 +7,6 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.Biome;
import net.minecraft.world.gen.feature.StructureFeature;
import net.minecraft.world.gen.feature.structure.Structure; import net.minecraft.world.gen.feature.structure.Structure;
/** /**
@@ -41,7 +41,6 @@ import net.minecraft.util.Hand;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.math.vector.Vector3d; import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader; import net.minecraft.world.IBlockReader;
import net.minecraft.world.World; import net.minecraft.world.World;
@@ -82,7 +81,6 @@ public class CableBusContainer extends CableBusStorage implements AEMultiTile, I
private final EnumSet<LayerFlags> myLayerFlags = EnumSet.noneOf(LayerFlags.class); private final EnumSet<LayerFlags> myLayerFlags = EnumSet.noneOf(LayerFlags.class);
private YesNo hasRedstone = YesNo.UNDECIDED; private YesNo hasRedstone = YesNo.UNDECIDED;
private IPartHost tcb; private IPartHost tcb;
// TODO 1.10.2-R - does somebody seriously want to make parts TESR??? Hope not.
private boolean requiresDynamicRender = false; private boolean requiresDynamicRender = false;
private boolean inWorld = false; private boolean inWorld = false;
// Cached collision shape for living entities // Cached collision shape for living entities
@@ -1096,11 +1094,7 @@ public class CableBusContainer extends CableBusStorage implements AEMultiTile, I
} }
} }
VoxelShape shape = VoxelShapes.empty(); return VoxelShapeCache.get(boxes);
for (final AxisAlignedBB bx : boxes) {
shape = VoxelShapes.or(shape, VoxelShapes.create(bx));
}
return shape;
} }
private void invalidateShapes() { private void invalidateShapes() {
@@ -0,0 +1,81 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 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.
*
* Applied Energistics 2 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 Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.parts;
import java.util.List;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
/**
* While creation of a {@link VoxelShape} with
* {@link VoxelShapes#create(AxisAlignedBB)} is fast enough, combining voxel
* shapes with {@link VoxelShapes#or(VoxelShape, VoxelShape)} or any other
* combination method, as well as {@link VoxelShape#simplify()} are <b>extremely
* slow</b>. For example: Creating a VoxelShape for a list of 5 bounding boxes
* 10,000 times takes about 1.7 seconds.
*
* <p>
* To reduce the impact of this on cables, we introduce a global voxel shape
* cache so that cables can share their combined voxel shapes better.
*/
final class VoxelShapeCache {
// Why using a List here should not make much of a difference vs. using a Set:
// The part's bounding box depends on the side it is attached to, and the sides
// are iterated over in a fixed order, meaning the order of bounding boxes
// should
// be the same for a same set of parts.
private static final LoadingCache<List<AxisAlignedBB>, VoxelShape> CACHE = CacheBuilder.newBuilder()//
.maximumSize(10000L)//
.build(new CacheLoader<List<AxisAlignedBB>, VoxelShape>() {
@Override
public VoxelShape load(List<AxisAlignedBB> key) {
return create(key);
}
});
private VoxelShapeCache() {
}
public static VoxelShape get(List<AxisAlignedBB> boxes) {
return CACHE.getUnchecked(boxes);
}
private static VoxelShape create(List<AxisAlignedBB> boxes) {
if (boxes.isEmpty()) {
return VoxelShapes.empty();
}
int i = 0;
VoxelShape shape = VoxelShapes.create(boxes.get(i));
for (; i < boxes.size(); i++) {
AxisAlignedBB box = boxes.get(i);
shape = VoxelShapes.combine(shape, VoxelShapes.create(box), IBooleanFunction.OR);
}
return shape.simplify();
}
}
@@ -34,7 +34,6 @@ import net.minecraft.item.Items;
import net.minecraft.tags.BlockTags; import net.minecraft.tags.BlockTags;
import net.minecraft.tags.ITag; import net.minecraft.tags.ITag;
import net.minecraft.tags.ItemTags; import net.minecraft.tags.ItemTags;
import net.minecraft.tags.Tag;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
+2 -1
View File
@@ -19,6 +19,7 @@
package appeng.server; package appeng.server;
import appeng.server.subcommands.ChunkLogger; import appeng.server.subcommands.ChunkLogger;
import appeng.server.subcommands.MetricsCommand;
import appeng.server.subcommands.SpatialStorageCommand; import appeng.server.subcommands.SpatialStorageCommand;
import appeng.server.subcommands.Supporters; import appeng.server.subcommands.Supporters;
import appeng.server.subcommands.TestMeteoritesCommand; import appeng.server.subcommands.TestMeteoritesCommand;
@@ -27,7 +28,7 @@ import appeng.server.subcommands.TestOreGenCommand;
public enum Commands { public enum Commands {
Chunklogger(4, new ChunkLogger(), false), Supporters(0, new Supporters(), false), Chunklogger(4, new ChunkLogger(), false), Supporters(0, new Supporters(), false),
TestOreGen(4, new TestOreGenCommand(), true), TestMeteorites(4, new TestMeteoritesCommand(), true), TestOreGen(4, new TestOreGenCommand(), true), TestMeteorites(4, new TestMeteoritesCommand(), true),
Spatial(4, new SpatialStorageCommand(), false); Spatial(4, new SpatialStorageCommand(), false), Metrics(4, new MetricsCommand(), false);
public final int level; public final int level;
public final ISubCommand command; public final ISubCommand command;
@@ -0,0 +1,25 @@
package appeng.server.subcommands;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.command.CommandSource;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.text.StringTextComponent;
import appeng.metrics.Metrics;
import appeng.metrics.reporter.PrintStreamReporter;
import appeng.server.ISubCommand;
/**
* A simple way of printing out AE2's metrics on the server console.
*/
public class MetricsCommand implements ISubCommand {
@Override
public void call(MinecraftServer srv, CommandContext<CommandSource> ctx, CommandSource sender) {
System.out.println("-------- AE2 Metrics:");
Metrics.visit(new PrintStreamReporter(System.out));
System.out.println("-------- END");
sender.sendFeedback(new StringTextComponent("Metrics reported to server console..."), false);
}
}
@@ -253,9 +253,11 @@ public class AEBaseTileEntity extends TileEntity implements IOrientable, ICommon
if (this.renderFragment > 0) { if (this.renderFragment > 0) {
this.renderFragment |= 1; this.renderFragment |= 1;
} else { } else {
// Clearing the cached model-data is always harmless regardless of status
this.requestModelDataUpdate();
// TODO: Optimize Network Load // TODO: Optimize Network Load
if (this.world != null) { if (this.world != null && !this.isRemoved() && !notLoaded()) {
this.requestModelDataUpdate();
boolean alreadyUpdated = false; boolean alreadyUpdated = false;
// Let the block update it's own state with our internal state changes // Let the block update it's own state with our internal state changes
@@ -368,7 +370,6 @@ public class AEBaseTileEntity extends TileEntity implements IOrientable, ICommon
* null means nothing to store... * null means nothing to store...
* *
* @param from source of settings * @param from source of settings
*
* @return compound of source * @return compound of source
*/ */
public CompoundNBT downloadSettings(final SettingsFrom from) { public CompoundNBT downloadSettings(final SettingsFrom from) {
@@ -705,5 +705,9 @@
"waila.appliedenergistics2.P2POutput": "Linked (Output Side)", "waila.appliedenergistics2.P2POutput": "Linked (Output Side)",
"waila.appliedenergistics2.P2PUnlinked": "Unlinked", "waila.appliedenergistics2.P2PUnlinked": "Unlinked",
"waila.appliedenergistics2.Showing": "Showing", "waila.appliedenergistics2.Showing": "Showing",
"waila.appliedenergistics2.Unlocked": "Unlocked" "waila.appliedenergistics2.Unlocked": "Unlocked",
"jei.appliedenergistics2.missing_id": "Cannot identify recipe",
"jei.appliedenergistics2.recipe_too_large": "Recipe larger than 3x3",
"jei.appliedenergistics2.requires_processing_mode": "Requires processing mode",
"jei.appliedenergistics2.no_output": "Recipe has no output"
} }
@@ -1,3 +1 @@
{ {}
"parent": "appliedenergistics2:block/cable_bus"
}
@@ -1,7 +1,8 @@
{ {
"display": { "display": {
"icon": { "icon": {
"item": "appliedenergistics2:facade" "item": "appliedenergistics2:facade",
"nbt": "{item:\"minecraft:stone\"}"
}, },
"title": { "title": {
"translate": "achievement.ae2.Facade" "translate": "achievement.ae2.Facade"
File diff suppressed because it is too large Load Diff