Merge remote-tracking branch 'origin/mc1.14-v0.2.1' into mc1.15-v0.2.1

Conflicts:
	build.gradle
This commit is contained in:
tterrag 2020-03-22 14:27:03 -04:00
commit 96106f2a9e
38 changed files with 420 additions and 238 deletions

View file

@ -13,7 +13,7 @@ apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
version = 'mc1.15.2_v0.2'
version = 'mc1.15.2_v0.2.1'
group = 'com.simibubi.create'
archivesBaseName = 'create'

View file

@ -127,6 +127,8 @@ public class ClientEvents {
public static void addToItemTooltip(ItemTooltipEvent event) {
if (!AllConfigs.CLIENT.tooltips.get())
return;
if (Minecraft.getInstance().player == null)
return;
ItemStack stack = event.getItemStack();
String translationKey = stack.getItem().getTranslationKey(stack);

View file

@ -8,6 +8,7 @@ import org.apache.logging.log4j.Logger;
import com.simibubi.create.config.AllConfigs;
import com.simibubi.create.foundation.command.CreateCommand;
import com.simibubi.create.foundation.command.ServerLagger;
import com.simibubi.create.foundation.world.AllWorldFeatures;
import com.simibubi.create.modules.ModuleLoadedCondition;
import com.simibubi.create.modules.contraptions.TorquePropagator;
import com.simibubi.create.modules.logistics.RedstoneLinkNetworkHandler;
@ -62,6 +63,7 @@ public class Create {
modEventBus.addListener(AllConfigs::onLoad);
modEventBus.addListener(AllConfigs::onReload);
CreateClient.addListeners(modEventBus);
AllWorldFeatures.reload();
}
public static void init(final FMLCommonSetupEvent event) {

View file

@ -15,6 +15,7 @@ import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.Tags;
import net.minecraftforge.event.TickEvent.Phase;
import net.minecraftforge.event.TickEvent.ServerTickEvent;
@ -22,6 +23,7 @@ import net.minecraftforge.event.entity.player.PlayerInteractEvent.RightClickBloc
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.Event.Result;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import net.minecraftforge.fml.event.server.FMLServerStoppingEvent;
@ -46,7 +48,8 @@ public class Events {
IWorld world = event.getWorld();
Create.redstoneLinkNetworkHandler.onLoadWorld(world);
Create.torquePropagator.onLoadWorld(world);
// Create.logisticalNetworkHandler.onLoadWorld(world);
if (event.getWorld().isRemote())
DistExecutor.runWhenOn(Dist.CLIENT, () -> CreateClient.bufferCache::invalidate);
}
@SubscribeEvent
@ -54,7 +57,6 @@ public class Events {
IWorld world = event.getWorld();
Create.redstoneLinkNetworkHandler.onUnloadWorld(world);
Create.torquePropagator.onUnloadWorld(world);
// Create.logisticalNetworkHandler.onUnloadWorld(world);
}
@SubscribeEvent

View file

@ -7,6 +7,7 @@ public class CKinetics extends ConfigBase {
public ConfigInt crushingDamage = i(4, 0, "crushingDamage", Comments.crushingDamage);
public ConfigInt maxMotorSpeed = i(256, 64, "maxMotorSpeed", Comments.rpm, Comments.maxMotorSpeed);
public ConfigInt waterWheelSpeed = i(5, 1, "waterWheelSpeed", Comments.rpm, Comments.waterWheelSpeed);
public ConfigInt furnaceEngineSpeed = i(16, 1, "furnaceEngineSpeed", Comments.rpm, Comments.furnaceEngineSpeed);
public ConfigInt maxRotationSpeed = i(256, 64, "maxRotationSpeed", Comments.rpm, Comments.maxRotationSpeed);
public ConfigEnum<DeployerAggroSetting> ignoreDeployerAttacks =
e(DeployerAggroSetting.CREEPERS, "ignoreDeployerAttacks", Comments.ignoreDeployerAttacks);
@ -69,6 +70,7 @@ public class CKinetics extends ConfigBase {
static String stress = "Fine tune the kinetic stats of individual components";
static String ignoreDeployerAttacks = "Select what mobs should ignore Deployers when attacked by them.";
static String waterWheelSpeed = "Rotation speed gained by a water wheel for each side with running water. (halved if not against blades)";
static String furnaceEngineSpeed = "Base rotation speed for the furnace engine generator";
static String disableStress = "Disable the Stress mechanic altogether.";
static String kineticValidationFrequency = "Game ticks between Kinetic Blocks checking whether their source is still valid.";
}

View file

@ -49,7 +49,7 @@ public class CStress extends ConfigBase {
@Override
public String getName() {
return "stressValues";
return "stressValues.v" + StressConfigDefaults.forcedUpdateVersion;
}
private static class Comments {

View file

@ -20,6 +20,12 @@ public class CWorldGen extends ConfigBase {
super.onReload();
}
@Override
public void onLoad() {
AllWorldFeatures.reload();
super.onLoad();
}
@Override
public String getName() {
return "world";

View file

@ -4,20 +4,26 @@ import com.simibubi.create.AllBlocks;
public class StressConfigDefaults {
/**
* Increment this number if all stress entries should be updated in this update.
* Worlds from the previous version will overwrite potentially changed values with the new defaults.
*/
public static final int forcedUpdateVersion = 1;
public static double getDefaultStressCapacity(AllBlocks block) {
switch (block) {
case CREATIVE_MOTOR:
return 1024;
return 2048;
case FURNACE_ENGINE:
return 512;
return 1024;
case MECHANICAL_BEARING:
return 256;
return 512;
case ENCASED_FAN:
case HAND_CRANK:
return 16;
return 32;
case WATER_WHEEL:
return 4;
return 8;
default:
return -1;
}

View file

@ -0,0 +1,52 @@
package com.simibubi.create.foundation.behaviour.simple;
import java.util.function.Supplier;
import com.simibubi.create.foundation.behaviour.base.IBehaviourType;
import com.simibubi.create.foundation.behaviour.base.SmartTileEntity;
import com.simibubi.create.foundation.behaviour.base.TileEntityBehaviour;
import net.minecraft.nbt.CompoundNBT;
public class DeferralBehaviour extends TileEntityBehaviour {
public static IBehaviourType<DeferralBehaviour> TYPE = new IBehaviourType<DeferralBehaviour>() {
};
private boolean needsUpdate;
private Supplier<Boolean> callback;
public DeferralBehaviour(SmartTileEntity te, Supplier<Boolean> callback) {
super(te);
this.callback = callback;
}
@Override
public void writeNBT(CompoundNBT nbt) {
nbt.putBoolean("NeedsUpdate", needsUpdate);
super.writeNBT(nbt);
}
@Override
public void readNBT(CompoundNBT nbt) {
needsUpdate = nbt.getBoolean("NeedsUpdate");
super.readNBT(nbt);
}
@Override
public void tick() {
super.tick();
if (needsUpdate && callback.get())
needsUpdate = false;
}
public void scheduleUpdate() {
needsUpdate = true;
}
@Override
public IBehaviourType<?> getType() {
return TYPE;
}
}

View file

@ -1,26 +1,5 @@
package com.simibubi.create.foundation.item;
import static com.simibubi.create.foundation.item.TooltipHelper.cutString;
import static net.minecraft.util.text.TextFormatting.AQUA;
import static net.minecraft.util.text.TextFormatting.BLUE;
import static net.minecraft.util.text.TextFormatting.DARK_GRAY;
import static net.minecraft.util.text.TextFormatting.DARK_GREEN;
import static net.minecraft.util.text.TextFormatting.DARK_PURPLE;
import static net.minecraft.util.text.TextFormatting.DARK_RED;
import static net.minecraft.util.text.TextFormatting.GOLD;
import static net.minecraft.util.text.TextFormatting.GRAY;
import static net.minecraft.util.text.TextFormatting.GREEN;
import static net.minecraft.util.text.TextFormatting.LIGHT_PURPLE;
import static net.minecraft.util.text.TextFormatting.RED;
import static net.minecraft.util.text.TextFormatting.STRIKETHROUGH;
import static net.minecraft.util.text.TextFormatting.WHITE;
import static net.minecraft.util.text.TextFormatting.YELLOW;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.simibubi.create.AllItems;
import com.simibubi.create.config.AllConfigs;
import com.simibubi.create.config.CKinetics;
@ -28,8 +7,10 @@ import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.modules.contraptions.base.IRotate;
import com.simibubi.create.modules.contraptions.base.IRotate.SpeedLevel;
import com.simibubi.create.modules.contraptions.base.IRotate.StressImpact;
import com.simibubi.create.modules.contraptions.components.fan.EncasedFanBlock;
import com.simibubi.create.modules.contraptions.components.flywheel.engine.EngineBlock;
import com.simibubi.create.modules.contraptions.components.flywheel.engine.FurnaceEngineBlock;
import com.simibubi.create.modules.contraptions.components.waterwheel.WaterWheelBlock;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screen.Screen;
@ -40,6 +21,14 @@ import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.ForgeConfigSpec.ConfigValue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static com.simibubi.create.foundation.item.TooltipHelper.cutString;
import static net.minecraft.util.text.TextFormatting.*;
public class ItemDescription {
public static final ItemDescription MISSING = new ItemDescription(null);
@ -144,6 +133,11 @@ public class ItemDescription {
add(linesOnShift, GRAY + Lang.translate("tooltip.capacityProvided"));
add(linesOnShift, level);
String genSpeed = generatorSpeed(block, rpmUnit);
if (!genSpeed.equals("")) {
add(linesOnShift, GREEN + " " + genSpeed);
}
}
if (hasSpeedRequirement || hasStressImpact || hasStressCapacity)
@ -269,4 +263,23 @@ public class ItemDescription {
return linesOnShift;
}
private String generatorSpeed(Block block, String unitRPM) {
String value = "";
if (block instanceof WaterWheelBlock) {
int baseSpeed = AllConfigs.SERVER.kinetics.waterWheelSpeed.get();
value = baseSpeed + "-" + (baseSpeed * 3);
}
else if (block instanceof EncasedFanBlock)
value = AllConfigs.SERVER.kinetics.generatingFanSpeed.get().toString();
else if (block instanceof FurnaceEngineBlock) {
int baseSpeed = AllConfigs.SERVER.kinetics.furnaceEngineSpeed.get();
value = baseSpeed + "-" + (baseSpeed * 2);
}
return !value.equals("") ? Lang.translate("tooltip.generationSpeed", value, unitRPM) : "";
}
}

View file

@ -1,8 +1,5 @@
package com.simibubi.create.foundation.world;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.tuple.Pair;
@ -20,7 +17,6 @@ import net.minecraft.world.gen.feature.OreFeatureConfig;
import net.minecraft.world.gen.placement.IPlacementConfig;
import net.minecraft.world.gen.placement.Placement;
import net.minecraftforge.common.ForgeConfigSpec.Builder;
import net.minecraftforge.registries.ForgeRegistries;
public abstract class OreFeature<T extends IPlacementConfig> extends ConfigBase implements IFeature {
@ -32,7 +28,7 @@ public abstract class OreFeature<T extends IPlacementConfig> extends ConfigBase
protected ConfigInt maxHeight;
private Block block;
private List<Biome> biomeWhitelist;
private Biome.Category biomeWhitelist;
public OreFeature(Block block, int clusterSize) {
this.block = block;
@ -50,16 +46,8 @@ public abstract class OreFeature<T extends IPlacementConfig> extends ConfigBase
return this;
}
public OreFeature<T> inBiomes(Biome... biomes) {
biomeWhitelist = Arrays.asList(biomes);
return this;
}
public OreFeature<T> inBiomes(Biome.Category category) {
biomeWhitelist = new LinkedList<>();
for (Biome biome : ForgeRegistries.BIOMES)
if (biome.getCategory() == category)
biomeWhitelist.add(biome);
biomeWhitelist = category;
return this;
}
@ -70,7 +58,7 @@ public abstract class OreFeature<T extends IPlacementConfig> extends ConfigBase
@Override
public Optional<ConfiguredFeature<?>> createFeature(Biome biome) {
if (biomeWhitelist != null && !biomeWhitelist.contains(biome))
if (biomeWhitelist != null && biome.getCategory() == biomeWhitelist)
return Optional.empty();
if (!canGenerate())
return Optional.empty();

View file

@ -1,6 +1,7 @@
package com.simibubi.create.modules.contraptions;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.simibubi.create.modules.contraptions.base.KineticTileEntity;
@ -16,31 +17,42 @@ public class KineticNetwork {
private float currentStress;
private float unloadedCapacity;
private float unloadedStress;
private int unloadedMembers;
public KineticNetwork() {
sources = new HashMap<>();
members = new HashMap<>();
}
public void initFromTE(float maxStress, float currentStress) {
public void initFromTE(float maxStress, float currentStress, int members) {
unloadedCapacity = maxStress;
unloadedStress = currentStress;
unloadedMembers = members;
initialized = true;
updateStress();
updateCapacity();
}
public void addSilently(KineticTileEntity te) {
public void addSilently(KineticTileEntity te, float lastCapacity, float lastStress) {
if (members.containsKey(te))
return;
if (te.isSource()) {
float capacity = te.getAddedStressCapacity();
unloadedCapacity -= capacity * getStressMultiplierForSpeed(te.getGeneratedSpeed());
sources.put(te, capacity);
unloadedCapacity -= lastCapacity * getStressMultiplierForSpeed(te.getGeneratedSpeed());
float addedStressCapacity = te.getAddedStressCapacity();
sources.put(te, addedStressCapacity);
}
unloadedStress -= lastStress * getStressMultiplierForSpeed(te.getTheoreticalSpeed());
float stressApplied = te.getStressApplied();
unloadedStress -= stressApplied * getStressMultiplierForSpeed(te.getTheoreticalSpeed());
members.put(te, stressApplied);
unloadedMembers--;
if (unloadedMembers < 0)
unloadedMembers = 0;
if (unloadedCapacity < 0)
unloadedCapacity = 0;
if (unloadedStress < 0)
unloadedStress = 0;
}
public void add(KineticTileEntity te) {
@ -112,22 +124,46 @@ public class KineticNetwork {
public float calculateCapacity() {
float presentCapacity = 0;
for (KineticTileEntity te : sources.keySet())
presentCapacity += sources.get(te) * getStressMultiplierForSpeed(te.getGeneratedSpeed());
for (Iterator<KineticTileEntity> iterator = sources.keySet().iterator(); iterator.hasNext();) {
KineticTileEntity te = iterator.next();
if (te.getWorld().getTileEntity(te.getPos()) != te) {
iterator.remove();
continue;
}
presentCapacity += getActualCapacityOf(te);
}
float newMaxStress = presentCapacity + unloadedCapacity;
return newMaxStress;
}
public float calculateStress() {
float presentStress = 0;
for (KineticTileEntity te : members.keySet())
presentStress += members.get(te) * getStressMultiplierForSpeed(te.getTheoreticalSpeed());
for (Iterator<KineticTileEntity> iterator = members.keySet().iterator(); iterator.hasNext();) {
KineticTileEntity te = iterator.next();
if (te.getWorld().getTileEntity(te.getPos()) != te) {
iterator.remove();
continue;
}
presentStress += getActualStressOf(te);
}
float newStress = presentStress + unloadedStress;
return newStress;
}
private float getStressMultiplierForSpeed(float speed) {
public float getActualCapacityOf(KineticTileEntity te) {
return sources.get(te) * getStressMultiplierForSpeed(te.getGeneratedSpeed());
}
public float getActualStressOf(KineticTileEntity te) {
return members.get(te) * getStressMultiplierForSpeed(te.getTheoreticalSpeed());
}
private static float getStressMultiplierForSpeed(float speed) {
return Math.abs(speed);
}
public int getSize() {
return unloadedMembers + members.size();
}
}

View file

@ -22,7 +22,7 @@ public class TorquePropagator {
Create.logger.debug("Removed Kinetic Network Space for " + world.getDimension().getType().getRegistryName());
}
public KineticNetwork getNetworkFor(KineticTileEntity te) {
public KineticNetwork getOrCreateNetworkFor(KineticTileEntity te) {
Long id = te.network;
KineticNetwork network;
Map<Long, KineticNetwork> map = networks.get(te.getWorld());

View file

@ -39,7 +39,10 @@ public abstract class KineticTileEntity extends SmartTileEntity implements ITick
protected boolean overStressed;
private int flickerTally;
private int networkSize;
private int validationCountdown;
private float lastStressApplied;
private float lastCapacityProvided;
public KineticTileEntity(TileEntityType<?> typeIn) {
super(typeIn);
@ -48,14 +51,14 @@ public abstract class KineticTileEntity extends SmartTileEntity implements ITick
@Override
public void initialize() {
super.initialize();
if (!hasNetwork())
return;
if (hasNetwork()) {
KineticNetwork network = getOrCreateNetwork();
if (!network.initialized)
network.initFromTE(capacity, stress, networkSize);
network.addSilently(this, lastCapacityProvided, lastStressApplied);
}
KineticNetwork network = getOrCreateNetwork();
if (!network.initialized)
network.initFromTE(capacity, stress);
network.addSilently(this);
super.initialize();
}
@Override
@ -166,9 +169,18 @@ public abstract class KineticTileEntity extends SmartTileEntity implements ITick
if (hasNetwork()) {
CompoundNBT networkTag = new CompoundNBT();
networkTag.putLong("Id", network);
networkTag.putLong("Id", this.network);
networkTag.putFloat("Stress", stress);
networkTag.putFloat("Capacity", capacity);
networkTag.putInt("Size", getOrCreateNetwork().getSize());
float stressApplied = getStressApplied();
float addedStressCapacity = getAddedStressCapacity();
if (stressApplied != 0)
networkTag.putFloat("AddedStress", stressApplied);
if (addedStressCapacity != 0)
networkTag.putFloat("AddedCapacity", addedStressCapacity);
compound.put("Network", networkTag);
}
@ -184,6 +196,8 @@ public abstract class KineticTileEntity extends SmartTileEntity implements ITick
overStressed = false;
stress = 0;
capacity = 0;
lastStressApplied = 0;
lastCapacityProvided = 0;
if (compound.contains("Source"))
source = NBTUtil.readBlockPos(compound.getCompound("Source"));
@ -193,6 +207,9 @@ public abstract class KineticTileEntity extends SmartTileEntity implements ITick
network = networkTag.getLong("Id");
stress = networkTag.getFloat("Stress");
capacity = networkTag.getFloat("Capacity");
networkSize = networkTag.getInt("Size");
lastStressApplied = networkTag.getFloat("AddedStress");
lastCapacityProvided = networkTag.getFloat("AddedCapacity");
overStressed = capacity < stress && StressImpact.isEnabled();
}
@ -275,7 +292,7 @@ public abstract class KineticTileEntity extends SmartTileEntity implements ITick
}
public KineticNetwork getOrCreateNetwork() {
return Create.torquePropagator.getNetworkFor(this);
return Create.torquePropagator.getOrCreateNetworkFor(this);
}
public boolean hasNetwork() {

View file

@ -31,7 +31,8 @@ public class SawMovementBehaviour extends BlockBreakingMovementBehaviour {
@Override
public boolean canBreak(World world, BlockPos breakingPos, BlockState state) {
return super.canBreak(world, breakingPos, state) && state.isIn(BlockTags.LOGS);
return super.canBreak(world, breakingPos, state)
&& (state.isIn(BlockTags.LOGS) || state.isIn(BlockTags.LEAVES));
}
@Override

View file

@ -1,58 +1,52 @@
package com.simibubi.create.modules.contraptions.components.contraptions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.mojang.blaze3d.platform.GlStateManager;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllItems;
import com.simibubi.create.AllKeys;
import com.simibubi.create.foundation.utility.TessellatorHelper;
import com.simibubi.create.modules.contraptions.components.contraptions.chassis.ChassisTileEntity;
import com.simibubi.create.modules.contraptions.components.contraptions.chassis.LinearChassisBlock;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.World;
public class ChassisRangeDisplay {
private static final VoxelShape BLOCK_OUTLINE = Block.makeCuboidShape(-.5f, -.5f, -.5f, 16.5f, 16.5f, 16.5f);
private static final int DISPLAY_TIME = 200;
private static GroupEntry lastHoveredGroup = null;
private static class Entry {
VoxelShape shape;
Set<BlockPos> includedPositions;
ChassisTileEntity te;
int timer;
public Entry(ChassisTileEntity te) {
this.te = te;
this.shape = createSelection(te);
includedPositions = createSelection(te);
timer = DISPLAY_TIME;
}
protected VoxelShape createSelection(ChassisTileEntity chassis) {
List<BlockPos> positions = chassis.getIncludedBlockPositions(null, true);
VoxelShape shape = VoxelShapes.empty();
if (positions == null)
return shape;
for (BlockPos blockPos : positions)
shape =
VoxelShapes.or(shape, BLOCK_OUTLINE.withOffset(blockPos.getX(), blockPos.getY(), blockPos.getZ()));
return shape;
protected Set<BlockPos> createSelection(ChassisTileEntity chassis) {
Set<BlockPos> positions = new HashSet<>();
List<BlockPos> includedBlockPositions = chassis.getIncludedBlockPositions(null, true);
if (includedBlockPositions == null)
return Collections.emptySet();
positions.addAll(includedBlockPositions);
return positions;
}
}
@ -66,21 +60,14 @@ public class ChassisRangeDisplay {
}
@Override
protected VoxelShape createSelection(ChassisTileEntity chassis) {
VoxelShape shape = VoxelShapes.empty();
protected Set<BlockPos> createSelection(ChassisTileEntity chassis) {
Set<BlockPos> list = new HashSet<>();
includedTEs = te.collectChassisGroup();
if (includedTEs == null)
return shape;
// outlining algo is not very scalable -> display only single chassis if group gets too large
if (LinearChassisBlock.isChassis(chassis.getBlockState()) && includedTEs.size() > 32)
includedTEs = Arrays.asList(chassis);
if (AllBlocks.ROTATION_CHASSIS.typeOf(chassis.getBlockState()) && includedTEs.size() > 8)
includedTEs = Arrays.asList(chassis);
return list;
for (ChassisTileEntity chassisTileEntity : includedTEs)
shape = VoxelShapes.or(shape, super.createSelection(chassisTileEntity));
return shape;
list.addAll(super.createSelection(chassisTileEntity));
return list;
}
}
@ -106,35 +93,40 @@ public class ChassisRangeDisplay {
}
}
if (hasWrench) {
RayTraceResult over = Minecraft.getInstance().objectMouseOver;
if (!(over instanceof BlockRayTraceResult))
if (!hasWrench)
return;
RayTraceResult over = Minecraft.getInstance().objectMouseOver;
if (!(over instanceof BlockRayTraceResult))
return;
BlockRayTraceResult ray = (BlockRayTraceResult) over;
BlockPos pos = ray.getPos();
TileEntity tileEntity = world.getTileEntity(pos);
if (tileEntity == null || tileEntity.isRemoved())
return;
if (!(tileEntity instanceof ChassisTileEntity))
return;
boolean ctrl = AllKeys.ctrlDown();
ChassisTileEntity chassisTileEntity = (ChassisTileEntity) tileEntity;
if (ctrl) {
GroupEntry existingGroupForPos = getExistingGroupForPos(pos);
if (existingGroupForPos != null) {
for (ChassisTileEntity included : existingGroupForPos.includedTEs)
entries.remove(included.getPos());
existingGroupForPos.timer = DISPLAY_TIME;
return;
BlockRayTraceResult ray = (BlockRayTraceResult) over;
BlockPos pos = ray.getPos();
TileEntity tileEntity = world.getTileEntity(pos);
if (tileEntity == null || tileEntity.isRemoved())
return;
if (tileEntity instanceof ChassisTileEntity) {
ChassisTileEntity chassisTileEntity = (ChassisTileEntity) tileEntity;
if (AllKeys.ctrlDown()) {
GroupEntry existingGroupForPos = getExistingGroupForPos(pos);
if (existingGroupForPos != null) {
for (ChassisTileEntity included : existingGroupForPos.includedTEs)
entries.remove(included.getPos());
existingGroupForPos.timer = DISPLAY_TIME;
return;
}
}
if (!entries.containsKey(pos) || AllKeys.ctrlDown())
display(chassisTileEntity);
else {
deselect();
if (!AllKeys.ctrlDown())
entries.get(pos).timer = DISPLAY_TIME;
}
}
}
if (!entries.containsKey(pos) || ctrl)
display(chassisTileEntity);
else {
deselect();
if (!ctrl)
entries.get(pos).timer = DISPLAY_TIME;
}
}
private static void deselect() {
@ -184,23 +176,31 @@ public class ChassisRangeDisplay {
GlStateManager.lineWidth(2);
TessellatorHelper.prepareForDrawing();
GlStateManager.disableTexture();
GlStateManager.enableAlphaTest();
for (Entry entry : entries.values()) {
float timer = entry.timer - partialTicks;
float alpha = timer > 20 ? 1 : timer / 20f;
WorldRenderer.drawShape(entry.shape, 0, 0, 0, 1, .7f, 0, alpha);
}
for (Entry entry : groupEntries) {
float timer = entry.timer - partialTicks;
float alpha = timer > 20 ? 1 : timer / 20f;
WorldRenderer.drawShape(entry.shape, 0, 0, 0, 1, .7f, 0, alpha);
}
for (Entry entry : entries.values())
renderPositions(entry, partialTicks);
for (Entry groupEntry : groupEntries)
renderPositions(groupEntry, partialTicks);
GlStateManager.enableTexture();
GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
TessellatorHelper.cleanUpAfterDrawing();
GlStateManager.lineWidth(1);
}
public static void renderPositions(Entry entry, float partialTicks) {
TessellatorHelper.begin();
BlockPos size = new BlockPos(1, 1, 1);
float timer = entry.timer - partialTicks;
float alpha = timer > 20 ? .5f : timer / 40f;
GlStateManager.color4f(1, .7f, 0, alpha);
Set<BlockPos> includedPositions = entry.includedPositions;
for (BlockPos pos : includedPositions)
TessellatorHelper.cube(Tessellator.getInstance().getBuffer(), pos, size, 1 / 1024f, true, false);
TessellatorHelper.draw();
}
private static GroupEntry getExistingGroupForPos(BlockPos pos) {
for (GroupEntry groupEntry : groupEntries)
for (ChassisTileEntity chassis : groupEntry.includedTEs)

View file

@ -77,7 +77,8 @@ public class CrushingWheelBlock extends RotatedPillarKineticBlock {
controllerShouldExist = true;
KineticTileEntity te = (KineticTileEntity) world.getTileEntity(pos);
KineticTileEntity otherTe = (KineticTileEntity) world.getTileEntity(otherWheelPos);
if (te != null && otherTe != null && -te.getSpeed() == otherTe.getSpeed() && te.getSpeed() != 0) {
if (te != null && otherTe != null && (te.getSpeed() > 0) != (otherTe.getSpeed() > 0)
&& te.getSpeed() != 0) {
float signum = Math.signum(te.getSpeed()) * (state.get(AXIS) == Axis.X ? -1 : 1);
controllerShouldBeValid = facing.getAxisDirection().getOffset() != signum;
}

View file

@ -28,6 +28,7 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.Difficulty;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
@ -88,7 +89,10 @@ public class CrushingWheelControllerBlock extends Block implements IHaveNoBlockI
((ItemEntity) entityIn).setPickupDelay(10);
if (te.isOccupied())
return;
if ((entityIn instanceof PlayerEntity) && ((PlayerEntity) entityIn).isCreative())
boolean isPlayer = entityIn instanceof PlayerEntity;
if (isPlayer && ((PlayerEntity) entityIn).isCreative())
return;
if (isPlayer && entityIn.world.getDifficulty() == Difficulty.PEACEFUL)
return;
te.startCrushing(entityIn);

View file

@ -58,7 +58,7 @@ public class MechanicalMixerTileEntity extends BasinOperatingTileEntity {
};
minIngredients = new ScrollValueBehaviour(Lang.translate("mechanical_mixer.min_ingredients"), this, slot);
minIngredients.between(1, 9);
minIngredients.withCallback(i -> checkBasin = true);
minIngredients.withCallback(i -> basinChecker.scheduleUpdate());
minIngredients.requiresWrench();
behaviours.add(minIngredients);
}
@ -217,7 +217,7 @@ public class MechanicalMixerTileEntity extends BasinOperatingTileEntity {
@Override
public void startProcessingBasin() {
if (running)
if (running && runningTicks <= 20)
return;
super.startProcessingBasin();
running = true;
@ -244,4 +244,9 @@ public class MechanicalMixerTileEntity extends BasinOperatingTileEntity {
return shapelessOrMixingRecipesKey;
}
@Override
protected boolean isRunning() {
return running;
}
}

View file

@ -32,6 +32,7 @@ public class MotorTileEntity extends GeneratingKineticTileEntity {
generatedSpeed = new ScrollValueBehaviour(Lang.translate("generic.speed"), this, slot);
generatedSpeed.between(-max, max);
generatedSpeed.value = DEFAULT_SPEED;
generatedSpeed.scrollableValue = DEFAULT_SPEED;
generatedSpeed.withUnit(i -> Lang.translate("generic.unit.rpm"));
generatedSpeed.withCallback(i -> this.updateGeneratedRotation());
generatedSpeed.withStepFunction(MotorTileEntity::step);

View file

@ -190,8 +190,10 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
}
if (!world.isRemote) {
world.playSound(null, getPos(), AllSoundEvents.MECHANICAL_PRESS_ITEM_BREAK.get(), SoundCategory.BLOCKS, .5f, 1f);
world.playSound(null, getPos(), AllSoundEvents.MECHANICAL_PRESS_ACTIVATION.get(), SoundCategory.BLOCKS, .125f, 1f);
world.playSound(null, getPos(), AllSoundEvents.MECHANICAL_PRESS_ITEM_BREAK.get(), SoundCategory.BLOCKS,
.5f, 1f);
world.playSound(null, getPos(), AllSoundEvents.MECHANICAL_PRESS_ACTIVATION.get(), SoundCategory.BLOCKS,
.125f, 1f);
}
}
@ -257,8 +259,8 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
public Optional<PressingRecipe> getRecipe(ItemStack item) {
pressingInv.setInventorySlotContents(0, item);
Optional<PressingRecipe> recipe = world.getRecipeManager().getRecipe(AllRecipes.PRESSING.getType(), pressingInv,
world);
Optional<PressingRecipe> recipe =
world.getRecipeManager().getRecipe(AllRecipes.PRESSING.getType(), pressingInv, world);
return recipe;
}
@ -305,7 +307,7 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
@Override
public void startProcessingBasin() {
if (running)
if (running && runningTicks <= 30)
return;
super.startProcessingBasin();
start(Mode.BASIN);
@ -320,4 +322,9 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
sendData();
}
@Override
protected boolean isRunning() {
return running;
}
}

View file

@ -39,7 +39,6 @@ public class WaterWheelTileEntity extends GeneratingKineticTileEntity {
@Override
public CompoundNBT write(CompoundNBT compound) {
CompoundNBT flows = new CompoundNBT();
for (Direction d : Direction.values())
flows.putFloat(d.getName(), this.flows.get(d));

View file

@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.simibubi.create.foundation.behaviour.base.TileEntityBehaviour;
import com.simibubi.create.foundation.behaviour.simple.DeferralBehaviour;
import com.simibubi.create.foundation.utility.recipe.RecipeFinder;
import com.simibubi.create.modules.contraptions.base.KineticTileEntity;
import com.simibubi.create.modules.contraptions.processing.BasinTileEntity.BasinInventory;
@ -16,6 +18,7 @@ import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.NonNullList;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
@ -24,7 +27,7 @@ import net.minecraftforge.items.ItemHandlerHelper;
public abstract class BasinOperatingTileEntity extends KineticTileEntity {
public boolean checkBasin;
public DeferralBehaviour basinChecker;
public boolean basinRemoved;
protected IRecipe<?> lastRecipe;
protected LazyOptional<IItemHandler> basinInv = LazyOptional.empty();
@ -32,7 +35,13 @@ public abstract class BasinOperatingTileEntity extends KineticTileEntity {
public BasinOperatingTileEntity(TileEntityType<?> typeIn) {
super(typeIn);
checkBasin = true;
}
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
super.addBehaviours(behaviours);
basinChecker = new DeferralBehaviour(this, this::updateBasin);
behaviours.add(basinChecker);
}
@Override
@ -40,7 +49,7 @@ public abstract class BasinOperatingTileEntity extends KineticTileEntity {
super.onSpeedChanged(prevSpeed);
if (getSpeed() == 0)
basinRemoved = true;
checkBasin = true;
basinChecker.scheduleUpdate();
}
public void gatherInputs() {
@ -57,8 +66,6 @@ public abstract class BasinOperatingTileEntity extends KineticTileEntity {
@Override
public void tick() {
super.tick();
if (basinRemoved) {
basinRemoved = false;
basinRemoved();
@ -66,40 +73,41 @@ public abstract class BasinOperatingTileEntity extends KineticTileEntity {
return;
}
super.tick();
}
protected boolean updateBasin() {
if (!isSpeedRequirementFulfilled())
return;
return true;
if (getSpeed() == 0)
return;
if (!isCheckingBasin())
return;
if (!checkBasin)
return;
checkBasin = false;
return true;
if (isRunning())
return false;
TileEntity basinTE = world.getTileEntity(pos.down(2));
if (basinTE == null || !(basinTE instanceof BasinTileEntity))
return;
return true;
if (!basinInv.isPresent())
basinInv = basinTE.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY);
if (!basinInv.isPresent())
return;
return true;
if (world.isRemote)
return;
return true;
gatherInputs();
List<IRecipe<?>> recipes = getMatchingRecipes();
if (recipes.isEmpty())
return;
return true;
lastRecipe = recipes.get(0);
startProcessingBasin();
sendData();
}
protected boolean isCheckingBasin() {
return true;
}
protected abstract boolean isRunning();
public void startProcessingBasin() {
}
@ -122,7 +130,9 @@ public abstract class BasinOperatingTileEntity extends KineticTileEntity {
List<ItemStack> catalysts = new ArrayList<>();
int buckets = 0;
Ingredients: for (Ingredient ingredient : lastRecipe.getIngredients()) {
NonNullList<Ingredient> ingredients = lastRecipe.getIngredients();
Ingredients: for (int i = 0; i < ingredients.size(); i++) {
Ingredient ingredient = ingredients.get(i);
for (int slot = 0; slot < inputs.getSlots(); slot++) {
if (!ingredient.test(inputs.extractItem(slot, 1, true)))
continue;
@ -131,8 +141,7 @@ public abstract class BasinOperatingTileEntity extends KineticTileEntity {
buckets++;
if ((lastRecipe instanceof ProcessingRecipe)) {
ProcessingRecipe<?> pr = (ProcessingRecipe<?>) lastRecipe;
if (pr.getRollableIngredients().get(slot).remains())
if (((ProcessingRecipe<?>) lastRecipe).getRollableIngredients().get(i).remains())
catalysts.add(extracted.copy());
}
continue Ingredients;
@ -152,6 +161,10 @@ public abstract class BasinOperatingTileEntity extends KineticTileEntity {
continueWithPreviousRecipe();
sendData();
}
TileEntity basinTE = world.getTileEntity(pos.down(2));
if (basinTE instanceof BasinTileEntity)
((BasinTileEntity) basinTE).contentsChanged = false;
}
protected List<IRecipe<?>> getMatchingRecipes() {

View file

@ -19,7 +19,7 @@ import net.minecraftforge.items.wrapper.RecipeWrapper;
public class BasinTileEntity extends SyncedTileEntity implements ITickableTileEntity {
protected boolean updateProcessing;
public boolean contentsChanged;
protected ItemStackHandler outputInventory = new ItemStackHandler(9) {
protected void onContentsChanged(int slot) {
@ -36,7 +36,7 @@ public class BasinTileEntity extends SyncedTileEntity implements ITickableTileEn
protected ItemStackHandler inputInventory = new ItemStackHandler(9) {
protected void onContentsChanged(int slot) {
updateProcessing = true;
contentsChanged = true;
sendData();
markDirty();
};
@ -91,7 +91,7 @@ public class BasinTileEntity extends SyncedTileEntity implements ITickableTileEn
public BasinTileEntity() {
super(AllTileEntities.BASIN.type);
updateProcessing = true;
contentsChanged = true;
recipeInventory = new BasinInputInventory();
}
@ -134,15 +134,15 @@ public class BasinTileEntity extends SyncedTileEntity implements ITickableTileEn
@Override
public void tick() {
if (!updateProcessing)
if (!contentsChanged)
return;
updateProcessing = false;
contentsChanged = false;
TileEntity te = world.getTileEntity(pos.up(2));
if (te == null)
return;
if (te instanceof BasinOperatingTileEntity)
((BasinOperatingTileEntity) te).checkBasin = true;
((BasinOperatingTileEntity) te).basinChecker.scheduleUpdate();
}

View file

@ -69,4 +69,7 @@ public class AnalogLeverTileEntity extends SmartTileEntity {
sendData();
}
public int getState() {
return state;
}
}

View file

@ -20,6 +20,7 @@ import com.simibubi.create.modules.contraptions.base.IRotate.SpeedLevel;
import com.simibubi.create.modules.contraptions.base.IRotate.StressImpact;
import com.simibubi.create.modules.contraptions.base.KineticTileEntity;
import com.simibubi.create.modules.contraptions.redstone.AnalogLeverTileEntity;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screen.Screen;
@ -42,6 +43,7 @@ import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
public class GaugeInformationRenderer {
private static DecimalFormat decimalFormat = new DecimalFormat("#.##");
private static String spacing = " ";
@SubscribeEvent
public static void lookingAtBlocksThroughGogglesShowsTooltip(RenderGameOverlayEvent.Post event) {
@ -64,7 +66,7 @@ public class GaugeInformationRenderer {
if (!AllItems.GOGGLES.typeOf(goggles) && !notFastEnough)
return;
if (mc.player.isSneaking())
if (mc.player.isSneaking() && !(te instanceof AnalogLeverTileEntity))
return;
List<String> tooltip = new ArrayList<>();
@ -79,6 +81,8 @@ public class GaugeInformationRenderer {
addGeneratorTooltip(state, tooltip, (GeneratingKineticTileEntity) te);
if (te instanceof KineticTileEntity)
addStressTooltip(state, tooltip, (KineticTileEntity) te);
if (te instanceof AnalogLeverTileEntity)
addLeverTooltip(state, tooltip, (AnalogLeverTileEntity) te);
}
if (tooltip.isEmpty())
@ -117,7 +121,6 @@ public class GaugeInformationRenderer {
}
private static void addStressTooltip(BlockState state, List<String> tooltip, KineticTileEntity te) {
String spacing = " ";
float stressApplied = te.getStressApplied();
if (stressApplied == 0 || !StressImpact.isEnabled())
return;
@ -139,7 +142,6 @@ public class GaugeInformationRenderer {
}
private static void addGeneratorTooltip(BlockState state, List<String> tooltip, GeneratingKineticTileEntity te) {
String spacing = " ";
float addedStressCapacity = te.getAddedStressCapacity();
if (addedStressCapacity == 0 || !StressImpact.isEnabled())
return;
@ -182,7 +184,6 @@ public class GaugeInformationRenderer {
String _atCurrentSpeed = Lang.translate("gui.goggles.at_current_speed");
String _baseValue = Lang.translate("gui.goggles.base_value");
String spacing = " ";
tooltip.add(spacing + _infoHeader);
if (AllBlocks.STRESS_GAUGE.typeOf(state)) {
@ -261,6 +262,11 @@ public class GaugeInformationRenderer {
}
}
private static void addLeverTooltip(BlockState state, List<String> tooltip, AnalogLeverTileEntity te) {
int leverState = te.getState();
tooltip.add(spacing + Lang.translate("tooltip.analogStrength", leverState));
}
private static String format(double d) {
return decimalFormat.format(d);
}

View file

@ -30,7 +30,11 @@ public class LatchBlock extends ToggleLatchBlock {
boolean back = state.get(POWERED);
boolean shouldBack = this.shouldBePowered(worldIn, pos, state);
boolean side = state.get(POWERED_SIDE);
boolean shouldSide = getPowerOnSides(worldIn, pos, state) > 0;
Direction direction = state.get(HORIZONTAL_FACING);
Direction left = direction.rotateY();
Direction right = direction.rotateYCCW();
boolean shouldSide = worldIn.isBlockPowered(pos.offset(left)) || worldIn.isBlockPowered(pos.offset(right));
TickPriority tickpriority = TickPriority.HIGH;
if (this.isFacingTowardsRepeater(worldIn, pos, state))
@ -49,7 +53,11 @@ public class LatchBlock extends ToggleLatchBlock {
boolean back = state.get(POWERED);
boolean shouldBack = this.shouldBePowered(worldIn, pos, state);
boolean side = state.get(POWERED_SIDE);
boolean shouldSide = getPowerOnSides(worldIn, pos, state) > 0;
Direction direction = state.get(HORIZONTAL_FACING);
Direction left = direction.rotateY();
Direction right = direction.rotateYCCW();
boolean shouldSide = worldIn.isBlockPowered(pos.offset(left)) || worldIn.isBlockPowered(pos.offset(right));
BlockState stateIn = state;
if (back != shouldBack) {

View file

@ -84,6 +84,7 @@ public class FlexcrateBlock extends ProperDirectionalBlock {
other.inventory.setStackInSlot(slot, ItemStack.EMPTY);
}
te.allowedAmount = other.allowedAmount;
other.invHandler.invalidate();
}
}

View file

@ -4,7 +4,7 @@ loaderVersion="[28,)"
[[mods]]
modId="create"
version="0.2"
version="mc1.14-0.2.1"
displayName="Create"
#updateJSONURL=""
authors="simibubi"
@ -14,7 +14,7 @@ Technology that empowers the player.'''
[[dependencies.create]]
modId="forge"
mandatory=true
versionRange="[28.1.0,)"
versionRange="[28.1.20,)"
ordering="NONE"
side="BOTH"

View file

@ -310,7 +310,7 @@
"create.generic.unit.ticks": "Ticks",
"create.generic.unit.seconds": "Seconds",
"create.generic.unit.minutes": "Minutes",
"create.generic.unit.rpm": "rpm",
"create.generic.unit.rpm": "RPM",
"create.generic.unit.stress": "su",
"create.generic.unit.degrees": "°",
@ -607,6 +607,9 @@
"create.tooltip.capacityProvided.medium": "Medium",
"create.tooltip.capacityProvided.high": "Large",
"create.tooltip.capacityProvided.asGenerator": "(As Generator)",
"create.tooltip.generationSpeed" : "Generates at %1$s %2$s",
"create.tooltip.analogStrength": "Analog Strength: %1$s/15",
"create.tooltip.wip": "WIP",
"create.tooltip.workInProgress": "Work in progress!",

View file

@ -0,0 +1,16 @@
{
"replace": false,
"values": [
"create:limestone",
"create:polished_limestone",
"create:weathered_limestone",
"create:polished_weathered_limestone",
"create:gabbro",
"create:polished_gabbro",
"create:dolomite",
"create:polished_dolomite",
"create:scoria",
"create:polished_scoria"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"#forge:storage_blocks/copper"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"create:copper_block"
]
}

View file

@ -0,0 +1,16 @@
{
"replace": false,
"values": [
"create:limestone",
"create:polished_limestone",
"create:weathered_limestone",
"create:polished_weathered_limestone",
"create:gabbro",
"create:polished_gabbro",
"create:dolomite",
"create:polished_dolomite",
"create:scoria",
"create:polished_scoria"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"#forge:storage_blocks/copper"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"create:copper_block"
]
}

View file

@ -1,52 +0,0 @@
Some of the most prominent machines and components require **Rotational Force** to operate. Sometimes the provided rotation speed and direction reflects their behaviour, and some components may have a more significant _cost_ than others.
1. Generate & Convey
2. Changing Gear
3. Stress & Capacity
## Generate & Convey
Using appropiate generators(link), you can start setting things in motion. These kinetic components will apply the speed they generate at to attached shafts, cogs, etc. Any component can be connected to any other, so long as their integrated shafts/cogs are linked together.
(waterwheel powering something) (mechanical crafters powering each other)
**Multiple generators** can be connected together in order achieve a greater Capacity score for the attached kinetic network. When attaching new generators to running components, it is important that the added generator rotates in the **same direction** as the component it is attached to. When connecting two kinetic blocks with **incompatible direction** of rotation, you'll notice that the blocks just break. However, trouble-shooting this will be quite straight-forward - all you need to do is to include a means of reversing the rotation between the generator and the rest:
Relaying rotational power between two components is one of the most important tasks when creating with Create. There are a variety of options and multiple possible solutions for each situation. These are the components that allow you to move, spread and modify rotational behaviour most effectively:
(mesh of these components showing off their behaviour)
- **Shafts**, cheapest option for relaying in a straight line.
- **Cogwheels**, move sideways while keeping the same rotation axis; reverse the direction
- **Belts**, move sideways while while keeping the same rotation axis; cannot be vertical; do not reverse direction
- **Gearboxes**, relay between two different rotation axes in a compact fashion; reverse connections on the same axis
- **Encased Belts**, relay sideways and between different rotation axes; do not reverse direction
Best is play around with each component and familiarizing yourself with its abilities. It is important to know the options when having to deal with complex connection tasks in a potentially less forgiving environment.
## Changing Gear
Some kinetic blocks have the ability to **speed up** or **slow down** connected components. Attach a large to a regular cogwheel diagonally: powering one of them at their shaft will result in the other rotating twice or half the speed respectively.
With this and other more compact blocks, you will have full control over the speed provided to your contraptions. This is especially important for machines that require a **minimum level of speed** to operate (e.g. the Mechanical Mixer).
Connecting faster components to other slower components **directly** will cause the faster network to overpower the rest, alinging the speed of everything that is now part of it. (That is, if the direction lines up)
(image of something ridiculous)
With this mechanic you can take things to the extreme and either rotate machines at the configurated maximum speed (256rpm by default) or slow them down to a fraction. But you may notice that speeding up brings a cost with it...
## Stress & Capacity
_In Create 0.2+, a bit of balance had been brought to rotational power: something to resemble torque in a highly simplified fashion._
Rotational generators have limited capacity for what they power. "Stress Impact" and "Stress Capacity" are the two opposing values in a kinetic network: **generators add capacity, machines and components add impact**. If the capacity is exhausted, all connected parts will simply stop moving, until capacity is increased or stress is relieved again.
**Stress Impact is tied to rotation speed**. Increasing the speed increases a components stress impact or capacity proportionally.
(image of fans and water wheel)
Consider the following example:
Assume one Water Wheel can provide just enough power in order to power four fans at the same speed.
* Doubling the speed of the fans using cogwheels will make the fans blow stronger, but the network will cease to function until the count of fans is halved, or the count of water wheels is doubled.
* Similarly, you would be able to power eight fans running at half the speed of the water wheel.
By default, components used **only for relaying** rotational power, such as shafts and cogwheels, have **no stress impact** at all. This makes predicting the amount of generators required for your contraptions much simpler and prevents punishment for aesthetic detours between machines and generators.
Optimizing stress impact and comparing net capacity of sources at base speed can become quite scientific. For those who are interested in seeing some actual numbers and more exhaustive information, it is recommended to look into crafting a pair of Goggles and a Stress Gauge.