merge mc1.19/dev

This commit is contained in:
zelophed 2023-08-29 23:04:07 +02:00
commit c847680b46
52 changed files with 1071 additions and 1387 deletions

View file

@ -1,138 +0,0 @@
## Additions
- Netherite Backtank
- Netherite Diving Helmet and Boots
- Contraption Controls
- Elevator Pulley
- Copycat Panels and Copycat Steps
- Block of Andesite Alloy
- Block of Industrial Iron
- Large Water Wheel
- Mechanical Roller
- Andesite, Brass and Copper Doors
- Andesite, Brass and Copper Bars
- Andesite, Brass and Copper Scaffolding
- Clipboard
- Mangrove Windows (1.19)
## Changes
- Overhauled models and textures of Andesite & Brass components (Kryppers)
- Reworked textures of colored blocks such as seats and sails (dani)
- New filter sprites (vectorwing)
- Valve handles can now be used to precisely turn mechanical bearings by a set angle
- Pulley ropes are now climbable
- Lowered hitbox of seats for improved traversability inside contraptions
- Improved safety for players standing on vertically moving contraptions
- Pulley contraptions will now make an effort to place remote players at y values sensible to the client
- Fixed seated entities on rotating contraptions not rendering at the correct location
- Deployers no longer fail to activate in chunks claimed or protected by the player that placed them
- Fixed couplings, schematics and in-world overlays not rendering correctly at coordinates far from the origin
- Fixed Bearings, Pistons, Pulleys and Gantries powered by a Sequenced Gearshift not moving precisely to its instructions at high speeds
- Minecart contraptions no longer visually jump to a location when stalled
- Mechanical bearings now snap to a rounded angle when stopped
- Contraption storage now accepts more chests and barrels from other mods
- Players can now open chests and barrels on assembled contraptions
- Mechanical Pumps no longer reverse direction based on kinetic input
- Fixed pipe connections pulling fluids with half the speed compared to a directly attached pump
- Substantially increased speed of visual flow propagation inside pipe networks
- Portable storage interfaces now stall for longer after an exchange has happened, and shorter otherwise
- Single train track blocks with slopes connected on either side will angle themselves to create a smoother ascend across both
- Multiple pulleys can now attach to contraptions in a synchronised group
- Display Boards now update text instantaneously at high input rpm
- Diving helmets now always grant aqua affinity
- Diving helmets can no longer be enchanted with aqua affinity
- Water wheel fins are no longer directional
- Water wheels now only have one speed level
- Water wheels can now take the appearance of any reasonably implemented wood type
- Added sided door control options to elevator contact and train station UI
- Liquid can no longer spread perpendicularly on top of water wheels
- Overhauled UX of scroll values and item filtering
- Filtered item extraction can now be configured to pull "up to x items" per operation
- Connected textures now use and apply the getAppearance() standard by Forge, allowing them to connect across other mods' facades, etc. (1.19)
- Boiler status now highlights information about water flow when insufficient
- The majority of in-world options no longer require a wrench
- Chutes can now be encased in Industrial Iron Blocks
- Chutes are now less prone to resetting shape when moved or rotated
- Moved metal block variants to Building Blocks tab
- Changed stonecutting ingredient of metal block variants from sheet to ingot
- Base stone blocks can now be stonecut back from their cut variants
- Fixed track placement allowing an s-bend between two sloped track pieces in specific arrangements
- Fixed funnels losing filters when changing between types
- New randomised textures for natural palette stone types (Kryppers)
- Readjusted palette stone generation to use taller layers
- World generation now places fewer stone type veins by default
- Fixed lava fans voiding items that have smoking & smelting recipes with different outputs
- Filter items now filter for their own item type if left empty
- Valve handles no longer create stress config entries for each dyed variant
- Place near initial angle mode on bearings now has a smaller interval considered 'near'
- Players can now take items from saws via right-click
- Item Drains now accept dropped items as input
- Train track placement overlay now explicitly mentions the ctrl key
- Fixed Mechanical Saws not rendering as animated when using rubidium
- Fixed a ui element of the Station Screen rendering behind the background
- Belts printed instantly or via cannon now retain the correct type of casing
- Scheduled trains no longer slow down for slight ascends/descents on a straight track
- Fixed saplings and other non-collidables sticking to chassis or super glue
- Encased Fluid Pipes no longer z-fight on open pipe faces
- Valve handles now turn twice as quickly
- Bearings no longer have the angle-indicating nook on their block
- Depot hitbox is now a simple cuboid
- Fixed belts encased with andesite briefly showing brass textures
- Fixed Filters and Attribute Filters not stacking with unmodified, equivalent stacks
- Fixed Attribute Filters saving the name tag preview item in their data
- Filters and Schedules can now be reset via crafting
- Renamed Sails to Windmill Sails
- Crushing gold ore now yields more experience nuggets
- Fixed valve pipes sometimes not rotating their indicator fully
- Horizontal, encased belts now render a support structure when solid blocks are above them
- Added placement assist for mechanical drills, saws and deployers
- Mechanical Belts can now be waterlogged
- Depots and Ejectors can now be Waterlogged
- Chutes and Funnels can now be Waterlogged
- Fixed upright mechanical saws only able to be oriented in two directions
- Deployers now have their filter slot on the side of the block
- Deployers can now be rotated by wrenching them near the edge of the front face
- Deployers now set filters on blocks only by targeting any location on a correct side
- Fixed Schematics loaded for deployer printing not rotating block entity contents
- Added tripwire to #movable_empty_collider
- Renamed Stockpile Switch to Threshold Switch
- Renamed Content Observer to Smart Observer
- Smart observer and threshold switch can now be oriented to face blocks above or below them
- Smart observer will now also emit redstone when the block in front of it matches its filter
- Fixed non-vanilla signs not accepted as valid display targets
- Brass tunnels with no distribution behaviour no longer show the mode switcher
- Used more contrasting colours for diode and tunnel value inputs
- Fixed crash when hose pulley cannot find reference fluid for infinite draining
- Clipboards can now be used to transfer settings between blocks
- Clipboards can now be used to manually write to Display Boards and Nixie Tubes
- Clipboards can now be used as Material Checklists in the Schematicannon
- Fixed and edited existing tooltips and ponder scenes to include behavioural changes in 0.5.1
- New ponder scenes for Smart Observer, Threshold Switch, Elevator Pulley, Contraption Controls and Mechanical Rollers
- Fixed ponder overlay text rendering with wonky pixels
- Added a ponder category for recently added/changed blocks
- Renamed Filter to List Filter
- Deployers can now apply filters to a Redstone link with less required precision
- Bezier track segments now render with a slight angle to reduce z-fighting
- Fixed offset shaft rotation on encased large cogwheels
- Fixed Smart Fluid Pipe not dropping filter when broken
- Placards and Creative Crates will no longer hold on to special nbt content (except potion data, damage, enchants) of the contained item when imported via Schematicannon
- Schematicannons can no longer print mobs
- Fixed item frames not requiring an exact nbt match for printed contents
- Players can now sneak while using exp nuggets to only consume one item at a time
- Minecart contraption items can no longer be placed in container items like toolboxes or shulkers (configurable)
- Implemented ComputerCraft interaction for Speed Controllers, Display Links, Speedometers, Stressometers, Sequenced Gearshifts and Train Stations (caelwarner)
- Hand crank no longer drains hunger when using the extendo grip (Xstoudi)
- Fixed Encased Chain Drives not reacting to block rotation and mirroring correctly
- Open Ended Pipes now correctly handle Builder's Tea (NerdsOfAFeather)
- Added Config entry for brass tunnel distribution cooldown (Walle123)
- API for custom bogey & track types (Rabbitminers, techno-sam)
- Fixed server crash caused by Gantry Contraptions assembling (Lucasmellof)
- Fix "Lighter than air" fluids displayed incorrectly in spouts (cakeGit)
- Added rotate and mirror methods to Fluid Pipes (xieve)
- Chocolate & Honey fluid fog distance is now configurable (radimous)
- Added a TrackGraph merge event (DaComputerNerd717)
- Fixed players dismounting when trains get assembled (Equinoxxe)
- Added GameTests (TropheusJ)
- Added armor tags (NerdsOfAFeather)
- Major updates now release as patch A

View file

@ -214,7 +214,7 @@ dependencies {
jarJar.ranged(it, '[MC1.19-1.1.5,)')
}
jarJar("com.jozufozu.flywheel:flywheel-forge-${flywheel_minecraft_version}:${flywheel_version}") {
jarJar.ranged(it, '[0.6.9,0.6.10)')
jarJar.ranged(it, '[0.6.10,0.6.11)')
}
implementation fg.deobf("com.tterrag.registrate:Registrate:${registrate_version}")

View file

@ -4,7 +4,7 @@ org.gradle.jvmargs = -Xmx3G
org.gradle.daemon = false
# mod version info
mod_version = 0.5.1.c
mod_version = 0.5.1.e
artifact_minecraft_version = 1.19.2
minecraft_version = 1.19.2
@ -23,7 +23,7 @@ use_parchment = true
# dependency versions
registrate_version = MC1.19-1.1.5
flywheel_minecraft_version = 1.19.2
flywheel_version = 0.6.9-18
flywheel_version = 0.6.10-20
jei_minecraft_version = 1.19.2
jei_version = 11.2.0.254
curios_minecraft_version = 1.19.2

View file

@ -0,0 +1,2 @@
// 1.19.2 2023-08-24T18:35:23.8733985 Create's Recipe Serializer Tags
0d8718f7383761bc5d7bc45306ed266ebf25dc1d data/create/tags/recipe_serializer/automation_ignore.json

View file

@ -0,0 +1,12 @@
{
"values": [
{
"id": "occultism:spirit_trade",
"required": false
},
{
"id": "occultism:ritual",
"required": false
}
]
}

View file

@ -1,12 +1,10 @@
package com.simibubi.create;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableSet;
import com.simibubi.create.compat.jei.ConversionRecipe;
import com.simibubi.create.content.equipment.sandPaper.SandPaperPolishingRecipe;
import com.simibubi.create.content.equipment.toolbox.ToolboxDyeingRecipe;
@ -16,8 +14,8 @@ import com.simibubi.create.content.kinetics.crafter.MechanicalCraftingRecipe;
import com.simibubi.create.content.kinetics.crusher.CrushingRecipe;
import com.simibubi.create.content.kinetics.deployer.DeployerApplicationRecipe;
import com.simibubi.create.content.kinetics.deployer.ManualApplicationRecipe;
import com.simibubi.create.content.kinetics.fan.HauntingRecipe;
import com.simibubi.create.content.kinetics.fan.SplashingRecipe;
import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe;
import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe;
import com.simibubi.create.content.kinetics.millstone.MillingRecipe;
import com.simibubi.create.content.kinetics.mixer.CompactingRecipe;
import com.simibubi.create.content.kinetics.mixer.MixingRecipe;
@ -29,7 +27,6 @@ import com.simibubi.create.content.processing.recipe.ProcessingRecipeSerializer;
import com.simibubi.create.content.processing.sequenced.SequencedAssemblyRecipeSerializer;
import com.simibubi.create.foundation.recipe.IRecipeTypeInfo;
import net.createmod.catnip.platform.CatnipServices;
import net.createmod.catnip.utility.lang.Lang;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
@ -127,12 +124,9 @@ public enum AllRecipeTypes implements IRecipeTypeInfo {
.getRecipeFor(getType(), inv, world);
}
public static final Set<ResourceLocation> RECIPE_DENY_SET =
ImmutableSet.of(new ResourceLocation("occultism", "spirit_trade"), new ResourceLocation("occultism", "ritual"));
public static boolean shouldIgnoreInAutomation(Recipe<?> recipe) {
RecipeSerializer<?> serializer = recipe.getSerializer();
if (serializer != null && RECIPE_DENY_SET.contains(CatnipServices.REGISTRIES.getKeyOrThrow(serializer)))
if (serializer != null && AllTags.AllRecipeSerializerTags.AUTOMATION_IGNORE.matches(serializer))
return true;
return recipe.getId()
.getPath()

View file

@ -19,6 +19,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
@ -315,11 +316,54 @@ public class AllTags {
private static void init() {}
}
public enum AllRecipeSerializerTags {
AUTOMATION_IGNORE,
;
public final TagKey<RecipeSerializer<?>> tag;
public final boolean alwaysDatagen;
AllRecipeSerializerTags() {
this(MOD);
}
AllRecipeSerializerTags(NameSpace namespace) {
this(namespace, namespace.optionalDefault, namespace.alwaysDatagenDefault);
}
AllRecipeSerializerTags(NameSpace namespace, String path) {
this(namespace, path, namespace.optionalDefault, namespace.alwaysDatagenDefault);
}
AllRecipeSerializerTags(NameSpace namespace, boolean optional, boolean alwaysDatagen) {
this(namespace, null, optional, alwaysDatagen);
}
AllRecipeSerializerTags(NameSpace namespace, String path, boolean optional, boolean alwaysDatagen) {
ResourceLocation id = new ResourceLocation(namespace.id, path == null ? Lang.asId(name()) : path);
if (optional) {
tag = optionalTag(ForgeRegistries.RECIPE_SERIALIZERS, id);
} else {
tag = TagKey.create(Registry.RECIPE_SERIALIZER_REGISTRY, id);
}
this.alwaysDatagen = alwaysDatagen;
}
public boolean matches(RecipeSerializer<?> recipeSerializer) {
return ForgeRegistries.RECIPE_SERIALIZERS.getHolder(recipeSerializer).orElseThrow().is(tag);
}
private static void init() {}
}
public static void init() {
AllBlockTags.init();
AllItemTags.init();
AllFluidTags.init();
AllEntityTags.init();
AllRecipeSerializerTags.init();
}
}

View file

@ -16,10 +16,10 @@ import com.simibubi.create.content.decoration.palettes.AllPaletteBlocks;
import com.simibubi.create.content.equipment.potatoCannon.BuiltinPotatoProjectileTypes;
import com.simibubi.create.content.fluids.tank.BoilerHeaters;
import com.simibubi.create.content.kinetics.TorquePropagator;
import com.simibubi.create.content.kinetics.fan.processing.AllFanProcessingTypes;
import com.simibubi.create.content.kinetics.mechanicalArm.AllArmInteractionPointTypes;
import com.simibubi.create.content.redstone.displayLink.AllDisplayBehaviours;
import com.simibubi.create.content.redstone.link.RedstoneLinkNetworkHandler;
import com.simibubi.create.content.schematics.SchematicInstances;
import com.simibubi.create.content.schematics.ServerSchematicLoader;
import com.simibubi.create.content.trains.GlobalRailwayManager;
import com.simibubi.create.content.trains.bogey.BogeySizes;
@ -29,6 +29,7 @@ import com.simibubi.create.foundation.block.CopperRegistries;
import com.simibubi.create.foundation.data.AllLangPartials;
import com.simibubi.create.foundation.data.CreateRegistrate;
import com.simibubi.create.foundation.data.LangMerger;
import com.simibubi.create.foundation.data.RecipeSerializerTagGen;
import com.simibubi.create.foundation.data.TagGen;
import com.simibubi.create.foundation.data.recipe.MechanicalCraftingRecipeGen;
import com.simibubi.create.foundation.data.recipe.ProcessingRecipeGen;
@ -71,7 +72,7 @@ public class Create {
public static final String ID = "create";
public static final String NAME = "Create";
public static final String VERSION = "0.5.1c";
public static final String VERSION = "0.5.1e";
public static final Logger LOGGER = LogUtils.getLogger();
@ -126,21 +127,26 @@ public class Create {
AllParticleTypes.register(modEventBus);
AllStructureProcessorTypes.register(modEventBus);
AllEntityDataSerializers.register(modEventBus);
AllPackets.registerPackets();
AllOreFeatureConfigEntries.init();
AllFeatures.register(modEventBus);
AllPlacementModifiers.register(modEventBus);
BuiltinRegistration.register(modEventBus);
BogeySizes.init();
AllBogeyStyles.register();
AllConfigs.register(modLoadingContext);
// FIXME: some of these registrations are not thread-safe
AllMovementBehaviours.registerDefaults();
AllInteractionBehaviours.registerDefaults();
AllDisplayBehaviours.registerDefaults();
ContraptionMovementSetting.registerDefaults();
AllArmInteractionPointTypes.register();
AllFanProcessingTypes.register();
BlockSpoutingBehaviour.registerDefaults();
BogeySizes.init();
AllBogeyStyles.register();
// ----
ComputerCraftProxy.register();
ForgeMod.enableMilkFluid();
@ -152,21 +158,25 @@ public class Create {
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> CreateClient.onCtorClient(modEventBus, forgeEventBus));
// FIXME: this is not thread-safe
Mods.CURIOS.executeIfInstalled(() -> () -> Curios.init(modEventBus, forgeEventBus));
}
public static void init(final FMLCommonSetupEvent event) {
AllPackets.registerPackets();
SchematicInstances.register();
BuiltinPotatoProjectileTypes.register();
AllFluids.registerFluidInteractions();
CreateNBTProcessors.register();
event.enqueueWork(() -> {
// TODO: custom registration should all happen in one place
// Most registration happens in the constructor.
// These registrations use Create's registered objects directly so they must run after registration has finished.
BuiltinPotatoProjectileTypes.register();
BoilerHeaters.registerDefaults();
// --
AttachedRegistry.unwrapAll();
AllAdvancements.register();
AllTriggers.register();
BoilerHeaters.registerDefaults();
AllFluids.registerFluidInteractions();
});
}
@ -178,11 +188,13 @@ public class Create {
gen.addProvider(event.includeClient(), new LangMerger(gen, ID, NAME, AllLangPartials.values()));
gen.addProvider(event.includeClient(), AllSoundEvents.provider(gen));
gen.addProvider(event.includeServer(), new RecipeSerializerTagGen(gen, event.getExistingFileHelper()));
gen.addProvider(event.includeServer(), new AllAdvancements(gen));
gen.addProvider(event.includeServer(), new StandardRecipeGen(gen));
gen.addProvider(event.includeServer(), new MechanicalCraftingRecipeGen(gen));
gen.addProvider(event.includeServer(), new SequencedAssemblyRecipeGen(gen));
if (event.includeServer()) {
ProcessingRecipeGen.registerAll(gen);
//AllOreFeatureConfigEntries.gatherData(event);

View file

@ -13,26 +13,42 @@ import net.minecraftforge.registries.ForgeRegistries;
* For compatibility with and without another mod present, we have to define load conditions of the specific code
*/
public enum Mods {
DYNAMICTREES,
TCONSTRUCT,
CURIOS,
COMPUTERCRAFT,
CONNECTIVITY,
CURIOS,
DYNAMICTREES,
OCCULTISM,
PACKETFIXER,
STORAGEDRAWERS,
TCONSTRUCT,
XLPACKETS;
/**
* @return a boolean of whether the mod is loaded or not based on mod id
*/
public boolean isLoaded() {
return ModList.get().isLoaded(asId());
private final String id;
Mods() {
id = Lang.asId(name());
}
/**
* @return the mod id
*/
public String asId() {
return Lang.asId(name());
public String id() {
return id;
}
public ResourceLocation rl(String path) {
return new ResourceLocation(id, path);
}
public Block getBlock(String id) {
return ForgeRegistries.BLOCKS.getValue(rl(id));
}
/**
* @return a boolean of whether the mod is loaded or not based on mod id
*/
public boolean isLoaded() {
return ModList.get().isLoaded(id);
}
/**
@ -55,8 +71,4 @@ public enum Mods {
toExecute.get().run();
}
}
public Block getBlock(String id) {
return ForgeRegistries.BLOCKS.getValue(new ResourceLocation(asId(), id));
}
}

View file

@ -51,8 +51,8 @@ import com.simibubi.create.content.kinetics.crusher.AbstractCrushingRecipe;
import com.simibubi.create.content.kinetics.deployer.DeployerApplicationRecipe;
import com.simibubi.create.content.kinetics.deployer.ItemApplicationRecipe;
import com.simibubi.create.content.kinetics.deployer.ManualApplicationRecipe;
import com.simibubi.create.content.kinetics.fan.HauntingRecipe;
import com.simibubi.create.content.kinetics.fan.SplashingRecipe;
import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe;
import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe;
import com.simibubi.create.content.kinetics.press.MechanicalPressBlockEntity;
import com.simibubi.create.content.kinetics.press.PressingRecipe;
import com.simibubi.create.content.kinetics.saw.CuttingRecipe;

View file

@ -4,7 +4,7 @@ import org.jetbrains.annotations.NotNull;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.compat.jei.category.animations.AnimatedKinetics;
import com.simibubi.create.content.kinetics.fan.HauntingRecipe;
import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe;
import com.simibubi.create.foundation.gui.AllGuiTextures;
import net.createmod.catnip.gui.element.GuiGameElement;

View file

@ -4,7 +4,7 @@ import org.jetbrains.annotations.NotNull;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.compat.jei.category.animations.AnimatedKinetics;
import com.simibubi.create.content.kinetics.fan.SplashingRecipe;
import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe;
import net.createmod.catnip.gui.element.GuiGameElement;
import net.minecraft.world.level.material.Fluids;

View file

@ -3,39 +3,35 @@ package com.simibubi.create.compat.storageDrawers;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringBehaviour;
import net.createmod.catnip.platform.CatnipServices;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.items.IItemHandler;
public class StorageDrawers {
public static boolean isDrawer(BlockEntity be) {
return be != null && Mods.STORAGEDRAWERS.asId()
.equals(BlockEntityType.getKey(be.getType())
.getNamespace());
return be != null && Mods.STORAGEDRAWERS.id().equals(
CatnipServices.REGISTRIES.getKeyOrThrow(be.getType()).getNamespace());
}
public static float getTrueFillLevel(IItemHandler inv, FilteringBehaviour filtering) {
float occupied = 0;
float totalSpace = 0;
for (int slot = 1; slot < inv.getSlots(); slot++) {
ItemStack stackInSlot = inv.getStackInSlot(slot);
int space = inv.getSlotLimit(slot);
int count = stackInSlot.getCount();
if (space == 0)
continue;
if (space == 0) continue;
totalSpace += 1;
if (filtering.test(stackInSlot))
occupied += count * (1f / space);
if (filtering.test(stackInSlot)) occupied += count * (1f / space);
}
if (totalSpace == 0)
return 0;
if (totalSpace == 0) return 0;
return occupied / totalSpace;
}
}

View file

@ -337,7 +337,7 @@ public class BlockMovementChecks {
return direction == state.getValue(StickerBlock.FACING)
&& !isNotSupportive(world.getBlockState(pos.relative(direction)), direction.getOpposite());
}
if (block instanceof AbstractBogeyBlock bogey)
if (block instanceof AbstractBogeyBlock<?> bogey)
return bogey.getStickySurfaces(world, pos, state)
.contains(direction);
if (block instanceof WhistleBlock)

View file

@ -1376,7 +1376,7 @@ public abstract class Contraption {
return blocks.values();
}
public Collection<BlockEntity> getSpecialRenderedTEs() {
public Collection<BlockEntity> getSpecialRenderedBEs() {
return specialRenderedBlockEntities;
}

View file

@ -14,17 +14,46 @@ public class ContraptionData {
/**
* A sane, default maximum for contraption data size.
*/
public static final int DEFAULT_MAX = 2_000_000;
public static final int DEFAULT_LIMIT = 2_000_000;
/**
* Connectivity expands the NBT packet limit to 2 GB.
*/
public static final int CONNECTIVITY_LIMIT = Integer.MAX_VALUE;
/**
* Packet Fixer expands the NBT packet limit to 200 MB.
*/
public static final int PACKET_FIXER_LIMIT = 209_715_200;
/**
* XL Packets expands the NBT packet limit to 2 GB.
*/
public static final int EXPANDED_MAX = 2_000_000_000;
public static final int XL_PACKETS_LIMIT = 2_000_000_000;
/**
* Minecart item sizes are limited by the vanilla slot change packet ({@link ClientboundContainerSetSlotPacket}).
* {@link ContraptionData#DEFAULT_MAX} is used as the default.
* XL Packets expands the size limit to ~2 GB. If the mod is loaded, we take advantage of it and use the higher limit.
* {@link #DEFAULT_LIMIT} is used as the default.
* Connectivity, PacketFixer, and XL Packets expand the size limit.
* If one of these mods is loaded, we take advantage of it and use the higher limit.
*/
public static final int PICKUP_MAX = Mods.XLPACKETS.isLoaded() ? EXPANDED_MAX : DEFAULT_MAX;
public static final int PICKUP_LIMIT;
static {
int limit = DEFAULT_LIMIT;
// Check from largest to smallest to use the smallest limit if multiple mods are loaded.
// It is necessary to use the smallest limit because even if multiple mods are loaded,
// not all of their mixins may be applied. Therefore, it is safest to only assume that
// the mod with the smallest limit is actually active.
if (Mods.CONNECTIVITY.isLoaded()) {
limit = CONNECTIVITY_LIMIT;
}
if (Mods.XLPACKETS.isLoaded()) {
limit = XL_PACKETS_LIMIT;
}
if (Mods.PACKETFIXER.isLoaded()) {
limit = PACKET_FIXER_LIMIT;
}
PICKUP_LIMIT = limit;
}
/**
* @return true if the given NBT is too large for a contraption to be synced to clients.
@ -38,7 +67,7 @@ public class ContraptionData {
* @return true if the given NBT is too large for a contraption to be picked up with a wrench.
*/
public static boolean isTooLargeForPickup(CompoundTag data) {
return packetSize(data) > PICKUP_MAX;
return packetSize(data) > PICKUP_LIMIT;
}
/**

View file

@ -119,9 +119,9 @@ public class ContraptionRenderDispatcher {
ContraptionWorld contraptionWorld = c.getContraptionWorld();
BlockPos origin = c.anchor;
int height = contraptionWorld.getHeight();
int minBuildHeight = contraptionWorld.getMinBuildHeight();
VirtualRenderWorld renderWorld = new VirtualRenderWorld(world, origin, height, minBuildHeight) {
int height = contraptionWorld.getHeight();
VirtualRenderWorld renderWorld = new VirtualRenderWorld(world, minBuildHeight, height, origin) {
@Override
public boolean supportsFlywheel() {
return canInstance();
@ -134,13 +134,13 @@ public class ContraptionRenderDispatcher {
// Skip individual lighting updates to prevent lag with large contraptions
renderWorld.setBlock(info.pos, info.state, Block.UPDATE_SUPPRESS_LIGHT);
renderWorld.runLightingEngine();
renderWorld.runLightEngine();
return renderWorld;
}
public static void renderBlockEntities(Level world, VirtualRenderWorld renderWorld, Contraption c,
ContraptionMatrices matrices, MultiBufferSource buffer) {
BlockEntityRenderHelper.renderBlockEntities(world, renderWorld, c.getSpecialRenderedTEs(),
BlockEntityRenderHelper.renderBlockEntities(world, renderWorld, c.getSpecialRenderedBEs(),
matrices.getModelViewProjection(), matrices.getLight(), buffer);
}

View file

@ -31,15 +31,14 @@ public class CopycatBarsModel extends CopycatModel {
protected List<BakedQuad> getCroppedQuads(BlockState state, Direction side, RandomSource rand, BlockState material,
ModelData wrappedData, RenderType renderType) {
BakedModel model = getModelOf(material);
List<BakedQuad> templateQuads = model.getQuads(material, null, rand, wrappedData, renderType);
List<BakedQuad> superQuads = originalModel.getQuads(state, side, rand, wrappedData, renderType);
List<BakedQuad> quads = new ArrayList<>();
TextureAtlasSprite targetSprite = model.getParticleIcon(wrappedData);
boolean vertical = state.getValue(CopycatPanelBlock.FACING)
.getAxis() == Axis.Y;
if (side != null && (vertical || side.getAxis() == Axis.Y))
if (side != null && (vertical || side.getAxis() == Axis.Y)) {
List<BakedQuad> templateQuads = model.getQuads(material, null, rand, wrappedData, renderType);
for (int i = 0; i < templateQuads.size(); i++) {
BakedQuad quad = templateQuads.get(i);
if (quad.getDirection() != Direction.UP)
@ -47,10 +46,13 @@ public class CopycatBarsModel extends CopycatModel {
targetSprite = quad.getSprite();
break;
}
}
if (targetSprite == null)
return superQuads;
List<BakedQuad> quads = new ArrayList<>();
for (int i = 0; i < superQuads.size(); i++) {
BakedQuad quad = superQuads.get(i);
TextureAtlasSprite original = quad.getSprite();

View file

@ -1,5 +1,6 @@
package com.simibubi.create.content.decoration.copycat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -110,14 +111,24 @@ public abstract class CopycatModel extends BakedModelWrapperWithData {
// Rubidium: render side!=null versions of the base material during side==null,
// to avoid getting culled away
if (side == null && state.getBlock() instanceof CopycatBlock ccb)
if (side == null && state.getBlock() instanceof CopycatBlock ccb) {
boolean immutable = true;
for (Direction nonOcclusionSide : Iterate.directions)
if (ccb.shouldFaceAlwaysRender(state, nonOcclusionSide))
if (ccb.shouldFaceAlwaysRender(state, nonOcclusionSide)) {
if (immutable) {
croppedQuads = new ArrayList<>(croppedQuads);
immutable = false;
}
croppedQuads.addAll(getCroppedQuads(state, nonOcclusionSide, rand, material, wrappedData, renderType));
}
}
return croppedQuads;
}
/**
* The returned list must not be mutated.
*/
protected abstract List<BakedQuad> getCroppedQuads(BlockState state, Direction side, RandomSource rand,
BlockState material, ModelData wrappedData, RenderType renderType);

View file

@ -3,7 +3,7 @@ package com.simibubi.create.content.kinetics.belt.transport;
import java.util.Random;
import com.simibubi.create.content.kinetics.belt.BeltHelper;
import com.simibubi.create.content.kinetics.fan.FanProcessing;
import com.simibubi.create.content.kinetics.fan.processing.FanProcessingType;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
@ -25,7 +25,7 @@ public class TransportedItemStack implements Comparable<TransportedItemStack> {
public float prevBeltPosition;
public float prevSideOffset;
public FanProcessing.Type processedBy;
public FanProcessingType processedBy;
public int processingTime;
public TransportedItemStack(ItemStack stack) {

View file

@ -16,4 +16,9 @@ public abstract class AbstractCrushingRecipe extends ProcessingRecipe<RecipeWrap
protected int getMaxInputCount() {
return 1;
}
@Override
protected boolean canSpecifyDuration() {
return true;
}
}

View file

@ -10,7 +10,9 @@ import com.simibubi.create.AllTags;
import com.simibubi.create.content.decoration.copycat.CopycatBlock;
import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour;
import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour.TransportedResult;
import com.simibubi.create.content.kinetics.fan.FanProcessing.Type;
import com.simibubi.create.content.kinetics.fan.processing.AllFanProcessingTypes;
import com.simibubi.create.content.kinetics.fan.processing.FanProcessing;
import com.simibubi.create.content.kinetics.fan.processing.FanProcessingType;
import com.simibubi.create.foundation.advancement.AllAdvancements;
import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
import com.simibubi.create.infrastructure.config.AllConfigs;
@ -29,7 +31,6 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
@ -49,7 +50,7 @@ public class AirCurrent {
public boolean pushing;
public float maxDistance;
protected List<Pair<TransportedItemStackHandlerBehaviour, FanProcessing.Type>> affectedItemHandlers =
protected List<Pair<TransportedItemStackHandlerBehaviour, FanProcessingType>> affectedItemHandlers =
new ArrayList<>();
protected List<Entity> caughtEntities = new ArrayList<>();
@ -110,14 +111,14 @@ public class AirCurrent {
((ServerPlayer) entity).connection.aboveGroundTickCount = 0;
entityDistance -= .5f;
FanProcessing.Type processingType = getSegmentAt((float) entityDistance);
FanProcessingType processingType = getSegmentAt((float) entityDistance);
if (processingType == null || processingType == Type.NONE)
if (processingType == AllFanProcessingTypes.NONE)
continue;
if (entity instanceof ItemEntity itemEntity) {
if (world.isClientSide) {
processingType.spawnParticlesForProcessing(world, entity.position());
if (world != null && world.isClientSide) {
processingType.spawnProcessingParticles(world, entity.position());
continue;
}
if (FanProcessing.canProcess(itemEntity, processingType))
@ -127,7 +128,8 @@ public class AirCurrent {
continue;
}
processingType.affectEntity(entity, world);
if (world != null)
processingType.affectEntity(entity, world);
}
}
@ -155,7 +157,7 @@ public class AirCurrent {
AirCurrentSegment currentSegment = new AirCurrentSegment();
segments.clear();
currentSegment.startOffset = 0;
FanProcessing.Type type = Type.NONE;
FanProcessingType type = AllFanProcessingTypes.NONE;
int limit = (int) (maxDistance + .5f);
int searchStart = pushing ? 0 : limit;
@ -164,8 +166,8 @@ public class AirCurrent {
for (int i = searchStart; i * searchStep <= searchEnd * searchStep; i += searchStep) {
BlockPos currentPos = start.relative(direction, i);
FanProcessing.Type newType = FanProcessing.Type.byBlock(world, currentPos);
if (newType != Type.NONE)
FanProcessingType newType = FanProcessingType.getAt(world, currentPos);
if (newType != AllFanProcessingTypes.NONE)
type = newType;
if (currentSegment.type != type || currentSegment.startOffset == 0) {
currentSegment.endOffset = i;
@ -258,21 +260,18 @@ public class AirCurrent {
BlockPos start = source.getAirCurrentPos();
affectedItemHandlers.clear();
for (int i = 0; i < maxDistance + 1; i++) {
Type type = getSegmentAt(i);
if (type == null)
continue;
FanProcessingType segmentType = getSegmentAt(i);
for (int offset : Iterate.zeroAndOne) {
BlockPos pos = start.relative(direction, i)
.below(offset);
TransportedItemStackHandlerBehaviour behaviour =
BlockEntityBehaviour.get(world, pos, TransportedItemStackHandlerBehaviour.TYPE);
FanProcessing.Type typeAtHandler = type;
if (world.getFluidState(pos)
.is(Fluids.WATER))
typeAtHandler = Type.SPLASHING;
if (behaviour != null)
affectedItemHandlers.add(Pair.of(behaviour, typeAtHandler));
if (behaviour == null)
continue;
FanProcessingType type = FanProcessingType.getAt(world, pos);
if (type == AllFanProcessingTypes.NONE)
type = segmentType;
affectedItemHandlers.add(Pair.of(behaviour, type));
if (direction.getAxis()
.isVertical())
break;
@ -281,15 +280,14 @@ public class AirCurrent {
}
public void tickAffectedHandlers() {
for (Pair<TransportedItemStackHandlerBehaviour, Type> pair : affectedItemHandlers) {
for (Pair<TransportedItemStackHandlerBehaviour, FanProcessingType> pair : affectedItemHandlers) {
TransportedItemStackHandlerBehaviour handler = pair.getKey();
Level world = handler.getWorld();
FanProcessing.Type processingType = pair.getRight();
FanProcessingType processingType = pair.getRight();
handler.handleProcessingOnAllItems((transported) -> {
handler.handleProcessingOnAllItems(transported -> {
if (world.isClientSide) {
if (world != null)
processingType.spawnParticlesForProcessing(world, handler.getWorldPositionOf(transported));
processingType.spawnProcessingParticles(world, handler.getWorldPositionOf(transported));
return TransportedResult.doNothing();
}
TransportedResult applyProcessing = FanProcessing.applyProcessing(transported, world, processingType);
@ -304,7 +302,7 @@ public class AirCurrent {
return AllTags.AllBlockTags.FAN_TRANSPARENT.matches(state);
}
public FanProcessing.Type getSegmentAt(float offset) {
public FanProcessingType getSegmentAt(float offset) {
for (AirCurrentSegment airCurrentSegment : segments) {
if (offset > airCurrentSegment.endOffset && pushing)
continue;
@ -312,11 +310,11 @@ public class AirCurrent {
continue;
return airCurrentSegment.type;
}
return FanProcessing.Type.NONE;
return AllFanProcessingTypes.NONE;
}
public static class AirCurrentSegment {
FanProcessing.Type type;
FanProcessingType type;
int startOffset;
int endOffset;
}

View file

@ -2,8 +2,10 @@ package com.simibubi.create.content.kinetics.fan;
import javax.annotation.Nonnull;
import com.simibubi.create.content.kinetics.fan.processing.AllFanProcessingTypes;
import com.simibubi.create.content.kinetics.fan.processing.FanProcessingType;
import net.createmod.catnip.utility.VecHelper;
import net.createmod.catnip.utility.theme.Color;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleProvider;
@ -12,16 +14,15 @@ import net.minecraft.client.particle.SimpleAnimatedParticle;
import net.minecraft.client.particle.SpriteSet;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.util.Mth;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.Vec3;
public class AirFlowParticle extends SimpleAnimatedParticle {
private final IAirCurrentSource source;
private final Access access = new Access();
protected AirFlowParticle(ClientLevel world, IAirCurrentSource source, double x, double y, double z,
SpriteSet sprite) {
@ -31,11 +32,12 @@ public class AirFlowParticle extends SimpleAnimatedParticle {
this.lifetime = 40;
hasPhysics = false;
selectSprite(7);
Vec3 offset = VecHelper.offsetRandomly(Vec3.ZERO, world.random, .25f);
Vec3 offset = VecHelper.offsetRandomly(Vec3.ZERO, random, .25f);
this.setPos(x + offset.x, y + offset.y, z + offset.z);
this.xo = x;
this.yo = y;
this.zo = z;
setColor(0xEEEEEE);
setAlpha(.25f);
}
@ -47,36 +49,44 @@ public class AirFlowParticle extends SimpleAnimatedParticle {
@Override
public void tick() {
if (source == null || source.isSourceRemoved()) {
dissipate();
remove();
return;
}
this.xo = this.x;
this.yo = this.y;
this.zo = this.z;
if (this.age++ >= this.lifetime) {
this.remove();
remove();
} else {
if (source.getAirCurrent() == null || !source.getAirCurrent().bounds.inflate(.25f).contains(x, y, z)) {
dissipate();
AirCurrent airCurrent = source.getAirCurrent();
if (airCurrent == null || !airCurrent.bounds.inflate(.25f).contains(x, y, z)) {
remove();
return;
}
Vec3 directionVec = Vec3.atLowerCornerOf(source.getAirCurrent().direction.getNormal());
Vec3 directionVec = Vec3.atLowerCornerOf(airCurrent.direction.getNormal());
Vec3 motion = directionVec.scale(1 / 8f);
if (!source.getAirCurrent().pushing)
motion = motion.scale(-1);
double distance = new Vec3(x, y, z).subtract(VecHelper.getCenterOf(source.getAirCurrentPos()))
.multiply(directionVec).length() - .5f;
if (distance > source.getAirCurrent().maxDistance + 1 || distance < -.25f) {
dissipate();
if (distance > airCurrent.maxDistance + 1 || distance < -.25f) {
remove();
return;
}
motion = motion.scale(source.getAirCurrent().maxDistance - (distance - 1f)).scale(.5f);
selectSprite((int) Mth.clamp((distance / source.getAirCurrent().maxDistance) * 8 + level.random.nextInt(4),
0, 7));
motion = motion.scale(airCurrent.maxDistance - (distance - 1f)).scale(.5f);
morphType(distance);
FanProcessingType type = getType(distance);
if (type == AllFanProcessingTypes.NONE) {
setColor(0xEEEEEE);
setAlpha(.25f);
selectSprite((int) Mth.clamp((distance / airCurrent.maxDistance) * 8 + random.nextInt(4),
0, 7));
} else {
type.morphAirFlow(access, random);
selectSprite(random.nextInt(3));
}
xd = motion.x;
yd = motion.y;
@ -92,68 +102,10 @@ public class AirFlowParticle extends SimpleAnimatedParticle {
}
public void morphType(double distance) {
private FanProcessingType getType(double distance) {
if (source.getAirCurrent() == null)
return;
FanProcessing.Type type = source.getAirCurrent().getSegmentAt((float) distance);
if (type == FanProcessing.Type.SPLASHING) {
setColor(Color.mixColors(0x4499FF, 0x2277FF, level.random.nextFloat()));
setAlpha(1f);
selectSprite(level.random.nextInt(3));
if (level.random.nextFloat() < 1 / 32f)
level.addParticle(ParticleTypes.BUBBLE, x, y, z, xd * .125f, yd * .125f,
zd * .125f);
if (level.random.nextFloat() < 1 / 32f)
level.addParticle(ParticleTypes.BUBBLE_POP, x, y, z, xd * .125f, yd * .125f,
zd * .125f);
}
if (type == FanProcessing.Type.SMOKING) {
setColor(Color.mixColors(0x0, 0x555555, level.random.nextFloat()));
setAlpha(1f);
selectSprite(level.random.nextInt(3));
if (level.random.nextFloat() < 1 / 32f)
level.addParticle(ParticleTypes.SMOKE, x, y, z, xd * .125f, yd * .125f,
zd * .125f);
if (level.random.nextFloat() < 1 / 32f)
level.addParticle(ParticleTypes.LARGE_SMOKE, x, y, z, xd * .125f, yd * .125f,
zd * .125f);
}
if (type == FanProcessing.Type.HAUNTING) {
setColor(Color.mixColors(0x0, 0x126568, level.random.nextFloat()));
setAlpha(1f);
selectSprite(level.random.nextInt(3));
if (level.random.nextFloat() < 1 / 128f)
level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, x, y, z, xd * .125f, yd * .125f,
zd * .125f);
if (level.random.nextFloat() < 1 / 32f)
level.addParticle(ParticleTypes.SMOKE, x, y, z, xd * .125f, yd * .125f,
zd * .125f);
}
if (type == FanProcessing.Type.BLASTING) {
setColor(Color.mixColors(0xFF4400, 0xFF8855, level.random.nextFloat()));
setAlpha(.5f);
selectSprite(level.random.nextInt(3));
if (level.random.nextFloat() < 1 / 32f)
level.addParticle(ParticleTypes.FLAME, x, y, z, xd * .25f, yd * .25f,
zd * .25f);
if (level.random.nextFloat() < 1 / 16f)
level.addParticle(new BlockParticleOption(ParticleTypes.BLOCK, Blocks.LAVA.defaultBlockState()), x, y,
z, xd * .25f, yd * .25f, zd * .25f);
}
if (type == null) {
setColor(0xEEEEEE);
setAlpha(.25f);
setSize(.2f, .2f);
}
}
private void dissipate() {
remove();
return AllFanProcessingTypes.NONE;
return source.getAirCurrent().getSegmentAt((float) distance);
}
public int getLightColor(float partialTick) {
@ -181,4 +133,21 @@ public class AirFlowParticle extends SimpleAnimatedParticle {
}
}
private class Access implements FanProcessingType.AirFlowParticleAccess {
@Override
public void setColor(int color) {
AirFlowParticle.this.setColor(color);
}
@Override
public void setAlpha(float alpha) {
AirFlowParticle.this.setAlpha(alpha);
}
@Override
public void spawnExtraParticle(ParticleOptions options, float speedMultiplier) {
level.addParticle(options, x, y, z, xd * speedMultiplier, yd * speedMultiplier, zd * speedMultiplier);
}
}
}

View file

@ -1,452 +0,0 @@
package com.simibubi.create.content.kinetics.fan;
import static com.simibubi.create.content.processing.burner.BlazeBurnerBlock.getHeatLevelOf;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import com.mojang.math.Vector3f;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllRecipeTypes;
import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour.TransportedResult;
import com.simibubi.create.content.kinetics.belt.transport.TransportedItemStack;
import com.simibubi.create.content.processing.burner.BlazeBurnerBlock;
import com.simibubi.create.content.processing.burner.LitBlazeBurnerBlock;
import com.simibubi.create.foundation.recipe.RecipeApplier;
import com.simibubi.create.infrastructure.config.AllConfigs;
import net.createmod.catnip.utility.VecHelper;
import net.createmod.catnip.utility.theme.Color;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.animal.horse.Horse;
import net.minecraft.world.entity.animal.horse.SkeletonHorse;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.BlastingRecipe;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.item.crafting.SmeltingRecipe;
import net.minecraft.world.item.crafting.SmokingRecipe;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.CampfireBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.wrapper.RecipeWrapper;
public class FanProcessing {
private static final DamageSource FIRE_DAMAGE_SOURCE = new DamageSource("create.fan_fire").setScalesWithDifficulty()
.setIsFire();
private static final DamageSource LAVA_DAMAGE_SOURCE = new DamageSource("create.fan_lava").setScalesWithDifficulty()
.setIsFire();
private static final RecipeWrapper RECIPE_WRAPPER = new RecipeWrapper(new ItemStackHandler(1));
private static final SplashingWrapper SPLASHING_WRAPPER = new SplashingWrapper();
private static final HauntingWrapper HAUNTING_WRAPPER = new HauntingWrapper();
public static boolean canProcess(ItemEntity entity, Type type) {
if (entity.getPersistentData()
.contains("CreateData")) {
CompoundTag compound = entity.getPersistentData()
.getCompound("CreateData");
if (compound.contains("Processing")) {
CompoundTag processing = compound.getCompound("Processing");
if (Type.valueOf(processing.getString("Type")) != type)
return type.canProcess(entity.getItem(), entity.level);
else if (processing.getInt("Time") >= 0)
return true;
else if (processing.getInt("Time") == -1)
return false;
}
}
return type.canProcess(entity.getItem(), entity.level);
}
public static boolean isWashable(ItemStack stack, Level world) {
SPLASHING_WRAPPER.setItem(0, stack);
Optional<SplashingRecipe> recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, world);
return recipe.isPresent();
}
public static boolean isHauntable(ItemStack stack, Level world) {
HAUNTING_WRAPPER.setItem(0, stack);
Optional<HauntingRecipe> recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, world);
return recipe.isPresent();
}
public static boolean applyProcessing(ItemEntity entity, Type type) {
if (decrementProcessingTime(entity, type) != 0)
return false;
List<ItemStack> stacks = process(entity.getItem(), type, entity.level);
if (stacks == null)
return false;
if (stacks.isEmpty()) {
entity.discard();
return false;
}
entity.setItem(stacks.remove(0));
for (ItemStack additional : stacks) {
ItemEntity entityIn = new ItemEntity(entity.level, entity.getX(), entity.getY(), entity.getZ(), additional);
entityIn.setDeltaMovement(entity.getDeltaMovement());
entity.level.addFreshEntity(entityIn);
}
return true;
}
public static TransportedResult applyProcessing(TransportedItemStack transported, Level world, Type type) {
TransportedResult ignore = TransportedResult.doNothing();
if (transported.processedBy != type) {
transported.processedBy = type;
int timeModifierForStackSize = ((transported.stack.getCount() - 1) / 16) + 1;
int processingTime =
(int) (AllConfigs.server().kinetics.fanProcessingTime.get() * timeModifierForStackSize) + 1;
transported.processingTime = processingTime;
if (!type.canProcess(transported.stack, world))
transported.processingTime = -1;
return ignore;
}
if (transported.processingTime == -1)
return ignore;
if (transported.processingTime-- > 0)
return ignore;
List<ItemStack> stacks = process(transported.stack, type, world);
if (stacks == null)
return ignore;
List<TransportedItemStack> transportedStacks = new ArrayList<>();
for (ItemStack additional : stacks) {
TransportedItemStack newTransported = transported.getSimilar();
newTransported.stack = additional.copy();
transportedStacks.add(newTransported);
}
return TransportedResult.convertTo(transportedStacks);
}
private static List<ItemStack> process(ItemStack stack, Type type, Level world) {
if (type == Type.SPLASHING) {
SPLASHING_WRAPPER.setItem(0, stack);
Optional<SplashingRecipe> recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, world);
if (recipe.isPresent())
return RecipeApplier.applyRecipeOn(stack, recipe.get());
return null;
}
if (type == Type.HAUNTING) {
HAUNTING_WRAPPER.setItem(0, stack);
Optional<HauntingRecipe> recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, world);
if (recipe.isPresent())
return RecipeApplier.applyRecipeOn(stack, recipe.get());
return null;
}
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmokingRecipe> smokingRecipe = world.getRecipeManager()
.getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, world);
if (type == Type.BLASTING) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<? extends AbstractCookingRecipe> smeltingRecipe = world.getRecipeManager()
.getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, world);
if (!smeltingRecipe.isPresent()) {
RECIPE_WRAPPER.setItem(0, stack);
smeltingRecipe = world.getRecipeManager()
.getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, world);
}
if (smeltingRecipe.isPresent()) {
if (!smokingRecipe.isPresent() || !ItemStack.isSame(smokingRecipe.get()
.getResultItem(),
smeltingRecipe.get()
.getResultItem())) {
return RecipeApplier.applyRecipeOn(stack, smeltingRecipe.get());
}
}
return Collections.emptyList();
}
if (type == Type.SMOKING && smokingRecipe.isPresent())
return RecipeApplier.applyRecipeOn(stack, smokingRecipe.get());
return null;
}
private static int decrementProcessingTime(ItemEntity entity, Type type) {
CompoundTag nbt = entity.getPersistentData();
if (!nbt.contains("CreateData"))
nbt.put("CreateData", new CompoundTag());
CompoundTag createData = nbt.getCompound("CreateData");
if (!createData.contains("Processing"))
createData.put("Processing", new CompoundTag());
CompoundTag processing = createData.getCompound("Processing");
if (!processing.contains("Type") || Type.valueOf(processing.getString("Type")) != type) {
processing.putString("Type", type.name());
int timeModifierForStackSize = ((entity.getItem()
.getCount() - 1) / 16) + 1;
int processingTime =
(int) (AllConfigs.server().kinetics.fanProcessingTime.get() * timeModifierForStackSize) + 1;
processing.putInt("Time", processingTime);
}
int value = processing.getInt("Time") - 1;
processing.putInt("Time", value);
return value;
}
public enum Type {
SPLASHING {
@Override
public void spawnParticlesForProcessing(Level level, Vec3 pos) {
if (level.random.nextInt(8) != 0)
return;
Vector3f color = new Color(0x0055FF).asVectorF();
level.addParticle(new DustParticleOptions(color, 1), pos.x + (level.random.nextFloat() - .5f) * .5f,
pos.y + .5f, pos.z + (level.random.nextFloat() - .5f) * .5f, 0, 1 / 8f, 0);
level.addParticle(ParticleTypes.SPIT, pos.x + (level.random.nextFloat() - .5f) * .5f, pos.y + .5f,
pos.z + (level.random.nextFloat() - .5f) * .5f, 0, 1 / 8f, 0);
}
@Override
public void affectEntity(Entity entity, Level level) {
if (level.isClientSide)
return;
if (entity instanceof EnderMan || entity.getType() == EntityType.SNOW_GOLEM
|| entity.getType() == EntityType.BLAZE) {
entity.hurt(DamageSource.DROWN, 2);
}
if (entity.isOnFire()) {
entity.clearFire();
level.playSound(null, entity.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE,
SoundSource.NEUTRAL, 0.7F, 1.6F + (level.random.nextFloat() - level.random.nextFloat()) * 0.4F);
}
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
return isWashable(stack, level);
}
},
SMOKING {
@Override
public void spawnParticlesForProcessing(Level level, Vec3 pos) {
if (level.random.nextInt(8) != 0)
return;
level.addParticle(ParticleTypes.POOF, pos.x, pos.y + .25f, pos.z, 0, 1 / 16f, 0);
}
@Override
public void affectEntity(Entity entity, Level level) {
if (level.isClientSide)
return;
if (!entity.fireImmune()) {
entity.setSecondsOnFire(2);
entity.hurt(FIRE_DAMAGE_SOURCE, 2);
}
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmokingRecipe> recipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level);
return recipe.isPresent();
}
},
HAUNTING {
@Override
public void spawnParticlesForProcessing(Level level, Vec3 pos) {
if (level.random.nextInt(8) != 0)
return;
pos = pos.add(VecHelper.offsetRandomly(Vec3.ZERO, level.random, 1)
.multiply(1, 0.05f, 1)
.normalize()
.scale(0.15f));
level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, pos.x, pos.y + .45f, pos.z, 0, 0, 0);
if (level.random.nextInt(2) == 0)
level.addParticle(ParticleTypes.SMOKE, pos.x, pos.y + .25f, pos.z, 0, 0, 0);
}
@Override
public void affectEntity(Entity entity, Level level) {
if (level.isClientSide) {
if (entity instanceof Horse) {
Vec3 p = entity.getPosition(0);
Vec3 v = p.add(0, 0.5f, 0)
.add(VecHelper.offsetRandomly(Vec3.ZERO, level.random, 1)
.multiply(1, 0.2f, 1)
.normalize()
.scale(1f));
level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, v.x, v.y, v.z, 0, 0.1f, 0);
if (level.random.nextInt(3) == 0)
level.addParticle(ParticleTypes.LARGE_SMOKE, p.x, p.y + .5f, p.z,
(level.random.nextFloat() - .5f) * .5f, 0.1f, (level.random.nextFloat() - .5f) * .5f);
}
return;
}
if (entity instanceof LivingEntity livingEntity) {
livingEntity.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 30, 0, false, false));
livingEntity.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 20, 1, false, false));
}
if (entity instanceof Horse horse) {
int progress = horse.getPersistentData()
.getInt("CreateHaunting");
if (progress < 100) {
if (progress % 10 == 0) {
level.playSound(null, entity.blockPosition(), SoundEvents.SOUL_ESCAPE, SoundSource.NEUTRAL,
1f, 1.5f * progress / 100f);
}
horse.getPersistentData()
.putInt("CreateHaunting", progress + 1);
return;
}
level.playSound(null, entity.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE,
SoundSource.NEUTRAL, 1.25f, 0.65f);
SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(level);
CompoundTag serializeNBT = horse.saveWithoutId(new CompoundTag());
serializeNBT.remove("UUID");
if (!horse.getArmor()
.isEmpty())
horse.spawnAtLocation(horse.getArmor());
skeletonHorse.deserializeNBT(serializeNBT);
skeletonHorse.setPos(horse.getPosition(0));
level.addFreshEntity(skeletonHorse);
horse.discard();
}
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
return isHauntable(stack, level);
}
},
BLASTING {
@Override
public void spawnParticlesForProcessing(Level level, Vec3 pos) {
if (level.random.nextInt(8) != 0)
return;
level.addParticle(ParticleTypes.LARGE_SMOKE, pos.x, pos.y + .25f, pos.z, 0, 1 / 16f, 0);
}
@Override
public void affectEntity(Entity entity, Level level) {
if (level.isClientSide)
return;
if (!entity.fireImmune()) {
entity.setSecondsOnFire(10);
entity.hurt(LAVA_DAMAGE_SOURCE, 4);
}
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmeltingRecipe> smeltingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, level);
if (smeltingRecipe.isPresent())
return true;
RECIPE_WRAPPER.setItem(0, stack);
Optional<BlastingRecipe> blastingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, level);
if (blastingRecipe.isPresent())
return true;
return !stack.getItem()
.isFireResistant();
}
},
NONE {
@Override
public void spawnParticlesForProcessing(Level level, Vec3 pos) {}
@Override
public void affectEntity(Entity entity, Level level) {}
@Override
public boolean canProcess(ItemStack stack, Level level) {
return false;
}
};
public abstract boolean canProcess(ItemStack stack, Level level);
public abstract void spawnParticlesForProcessing(Level level, Vec3 pos);
public abstract void affectEntity(Entity entity, Level level);
public static Type byBlock(BlockGetter reader, BlockPos pos) {
FluidState fluidState = reader.getFluidState(pos);
if (fluidState.getType() == Fluids.WATER || fluidState.getType() == Fluids.FLOWING_WATER)
return Type.SPLASHING;
BlockState blockState = reader.getBlockState(pos);
Block block = blockState.getBlock();
if (block == Blocks.SOUL_FIRE
|| block == Blocks.SOUL_CAMPFIRE && blockState.getOptionalValue(CampfireBlock.LIT)
.orElse(false)
|| AllBlocks.LIT_BLAZE_BURNER.has(blockState)
&& blockState.getOptionalValue(LitBlazeBurnerBlock.FLAME_TYPE)
.map(flame -> flame == LitBlazeBurnerBlock.FlameType.SOUL)
.orElse(false))
return Type.HAUNTING;
if (block == Blocks.FIRE
|| blockState.is(BlockTags.CAMPFIRES) && blockState.getOptionalValue(CampfireBlock.LIT)
.orElse(false)
|| AllBlocks.LIT_BLAZE_BURNER.has(blockState)
&& blockState.getOptionalValue(LitBlazeBurnerBlock.FLAME_TYPE)
.map(flame -> flame == LitBlazeBurnerBlock.FlameType.REGULAR)
.orElse(false)
|| getHeatLevelOf(blockState) == BlazeBurnerBlock.HeatLevel.SMOULDERING)
return Type.SMOKING;
if (block == Blocks.LAVA || getHeatLevelOf(blockState).isAtLeast(BlazeBurnerBlock.HeatLevel.FADING))
return Type.BLASTING;
return Type.NONE;
}
}
public static class SplashingWrapper extends RecipeWrapper {
public SplashingWrapper() {
super(new ItemStackHandler(1));
}
}
public static class HauntingWrapper extends RecipeWrapper {
public HauntingWrapper() {
super(new ItemStackHandler(1));
}
}
}

View file

@ -0,0 +1,488 @@
package com.simibubi.create.content.kinetics.fan.processing;
import static com.simibubi.create.content.processing.burner.BlazeBurnerBlock.getHeatLevelOf;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import com.mojang.math.Vector3f;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllRecipeTypes;
import com.simibubi.create.Create;
import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe.HauntingWrapper;
import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe.SplashingWrapper;
import com.simibubi.create.content.processing.burner.BlazeBurnerBlock;
import com.simibubi.create.content.processing.burner.LitBlazeBurnerBlock;
import com.simibubi.create.foundation.recipe.RecipeApplier;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import net.createmod.catnip.utility.VecHelper;
import net.createmod.catnip.utility.theme.Color;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.animal.horse.Horse;
import net.minecraft.world.entity.animal.horse.SkeletonHorse;
import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.BlastingRecipe;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.item.crafting.SmeltingRecipe;
import net.minecraft.world.item.crafting.SmokingRecipe;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.CampfireBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.wrapper.RecipeWrapper;
public class AllFanProcessingTypes {
public static final NoneType NONE = register("none", new NoneType());
public static final BlastingType BLASTING = register("blasting", new BlastingType());
public static final HauntingType HAUNTING = register("haunting", new HauntingType());
public static final SmokingType SMOKING = register("smoking", new SmokingType());
public static final SplashingType SPLASHING = register("splashing", new SplashingType());
private static final Map<String, FanProcessingType> LEGACY_NAME_MAP;
static {
Object2ReferenceOpenHashMap<String, FanProcessingType> map = new Object2ReferenceOpenHashMap<>();
map.put("NONE", NONE);
map.put("BLASTING", BLASTING);
map.put("HAUNTING", HAUNTING);
map.put("SMOKING", SMOKING);
map.put("SPLASHING", SPLASHING);
map.trim();
LEGACY_NAME_MAP = map;
}
private static <T extends FanProcessingType> T register(String id, T type) {
FanProcessingTypeRegistry.register(Create.asResource(id), type);
return type;
}
@Nullable
public static FanProcessingType ofLegacyName(String name) {
return LEGACY_NAME_MAP.get(name);
}
public static void register() {
}
public static FanProcessingType parseLegacy(String str) {
FanProcessingType type = ofLegacyName(str);
if (type != null) {
return type;
}
return FanProcessingType.parse(str);
}
public static class NoneType implements FanProcessingType {
@Override
public boolean isValidAt(Level level, BlockPos pos) {
return true;
}
@Override
public int getPriority() {
return -1000000;
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
return false;
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
return null;
}
@Override
public void spawnProcessingParticles(Level level, Vec3 pos) {
}
@Override
public void morphAirFlow(AirFlowParticleAccess particleAccess, RandomSource random) {
}
@Override
public void affectEntity(Entity entity, Level level) {
}
}
public static class BlastingType implements FanProcessingType {
private static final RecipeWrapper RECIPE_WRAPPER = new RecipeWrapper(new ItemStackHandler(1));
private static final DamageSource LAVA_DAMAGE_SOURCE = new DamageSource("create.fan_lava").setScalesWithDifficulty()
.setIsFire();
@Override
public boolean isValidAt(Level level, BlockPos pos) {
BlockState blockState = level.getBlockState(pos);
Block block = blockState.getBlock();
return block == Blocks.LAVA || getHeatLevelOf(blockState).isAtLeast(BlazeBurnerBlock.HeatLevel.FADING);
}
@Override
public int getPriority() {
return 100;
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmeltingRecipe> smeltingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, level);
if (smeltingRecipe.isPresent())
return true;
RECIPE_WRAPPER.setItem(0, stack);
Optional<BlastingRecipe> blastingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, level);
if (blastingRecipe.isPresent())
return true;
return !stack.getItem()
.isFireResistant();
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmokingRecipe> smokingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level);
RECIPE_WRAPPER.setItem(0, stack);
Optional<? extends AbstractCookingRecipe> smeltingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, level);
if (!smeltingRecipe.isPresent()) {
RECIPE_WRAPPER.setItem(0, stack);
smeltingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, level);
}
if (smeltingRecipe.isPresent()) {
if (!smokingRecipe.isPresent() || !ItemStack.isSame(smokingRecipe.get()
.getResultItem(),
smeltingRecipe.get()
.getResultItem())) {
return RecipeApplier.applyRecipeOn(stack, smeltingRecipe.get());
}
}
return Collections.emptyList();
}
@Override
public void spawnProcessingParticles(Level level, Vec3 pos) {
if (level.random.nextInt(8) != 0)
return;
level.addParticle(ParticleTypes.LARGE_SMOKE, pos.x, pos.y + .25f, pos.z, 0, 1 / 16f, 0);
}
@Override
public void morphAirFlow(AirFlowParticleAccess particleAccess, RandomSource random) {
particleAccess.setColor(Color.mixColors(0xFF4400, 0xFF8855, random.nextFloat()));
particleAccess.setAlpha(.5f);
if (random.nextFloat() < 1 / 32f)
particleAccess.spawnExtraParticle(ParticleTypes.FLAME, .25f);
if (random.nextFloat() < 1 / 16f)
particleAccess.spawnExtraParticle(new BlockParticleOption(ParticleTypes.BLOCK, Blocks.LAVA.defaultBlockState()), .25f);
}
@Override
public void affectEntity(Entity entity, Level level) {
if (level.isClientSide)
return;
if (!entity.fireImmune()) {
entity.setSecondsOnFire(10);
entity.hurt(LAVA_DAMAGE_SOURCE, 4);
}
}
}
public static class HauntingType implements FanProcessingType {
private static final HauntingWrapper HAUNTING_WRAPPER = new HauntingWrapper();
@Override
public boolean isValidAt(Level level, BlockPos pos) {
BlockState blockState = level.getBlockState(pos);
Block block = blockState.getBlock();
return block == Blocks.SOUL_FIRE
|| block == Blocks.SOUL_CAMPFIRE && blockState.getOptionalValue(CampfireBlock.LIT)
.orElse(false)
|| AllBlocks.LIT_BLAZE_BURNER.has(blockState)
&& blockState.getOptionalValue(LitBlazeBurnerBlock.FLAME_TYPE)
.map(flame -> flame == LitBlazeBurnerBlock.FlameType.SOUL)
.orElse(false);
}
@Override
public int getPriority() {
return 300;
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
HAUNTING_WRAPPER.setItem(0, stack);
Optional<HauntingRecipe> recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, level);
return recipe.isPresent();
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
HAUNTING_WRAPPER.setItem(0, stack);
Optional<HauntingRecipe> recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, level);
if (recipe.isPresent())
return RecipeApplier.applyRecipeOn(stack, recipe.get());
return null;
}
@Override
public void spawnProcessingParticles(Level level, Vec3 pos) {
if (level.random.nextInt(8) != 0)
return;
pos = pos.add(VecHelper.offsetRandomly(Vec3.ZERO, level.random, 1)
.multiply(1, 0.05f, 1)
.normalize()
.scale(0.15f));
level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, pos.x, pos.y + .45f, pos.z, 0, 0, 0);
if (level.random.nextInt(2) == 0)
level.addParticle(ParticleTypes.SMOKE, pos.x, pos.y + .25f, pos.z, 0, 0, 0);
}
@Override
public void morphAirFlow(AirFlowParticleAccess particleAccess, RandomSource random) {
particleAccess.setColor(Color.mixColors(0x0, 0x126568, random.nextFloat()));
particleAccess.setAlpha(1f);
if (random.nextFloat() < 1 / 128f)
particleAccess.spawnExtraParticle(ParticleTypes.SOUL_FIRE_FLAME, .125f);
if (random.nextFloat() < 1 / 32f)
particleAccess.spawnExtraParticle(ParticleTypes.SMOKE, .125f);
}
@Override
public void affectEntity(Entity entity, Level level) {
if (level.isClientSide) {
if (entity instanceof Horse) {
Vec3 p = entity.getPosition(0);
Vec3 v = p.add(0, 0.5f, 0)
.add(VecHelper.offsetRandomly(Vec3.ZERO, level.random, 1)
.multiply(1, 0.2f, 1)
.normalize()
.scale(1f));
level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, v.x, v.y, v.z, 0, 0.1f, 0);
if (level.random.nextInt(3) == 0)
level.addParticle(ParticleTypes.LARGE_SMOKE, p.x, p.y + .5f, p.z,
(level.random.nextFloat() - .5f) * .5f, 0.1f, (level.random.nextFloat() - .5f) * .5f);
}
return;
}
if (entity instanceof LivingEntity livingEntity) {
livingEntity.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 30, 0, false, false));
livingEntity.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 20, 1, false, false));
}
if (entity instanceof Horse horse) {
int progress = horse.getPersistentData()
.getInt("CreateHaunting");
if (progress < 100) {
if (progress % 10 == 0) {
level.playSound(null, entity.blockPosition(), SoundEvents.SOUL_ESCAPE, SoundSource.NEUTRAL,
1f, 1.5f * progress / 100f);
}
horse.getPersistentData()
.putInt("CreateHaunting", progress + 1);
return;
}
level.playSound(null, entity.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE,
SoundSource.NEUTRAL, 1.25f, 0.65f);
SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(level);
CompoundTag serializeNBT = horse.saveWithoutId(new CompoundTag());
serializeNBT.remove("UUID");
if (!horse.getArmor()
.isEmpty())
horse.spawnAtLocation(horse.getArmor());
skeletonHorse.deserializeNBT(serializeNBT);
skeletonHorse.setPos(horse.getPosition(0));
level.addFreshEntity(skeletonHorse);
horse.discard();
}
}
}
public static class SmokingType implements FanProcessingType {
private static final RecipeWrapper RECIPE_WRAPPER = new RecipeWrapper(new ItemStackHandler(1));
private static final DamageSource FIRE_DAMAGE_SOURCE = new DamageSource("create.fan_fire").setScalesWithDifficulty()
.setIsFire();
@Override
public boolean isValidAt(Level level, BlockPos pos) {
BlockState blockState = level.getBlockState(pos);
Block block = blockState.getBlock();
return block == Blocks.FIRE
|| blockState.is(BlockTags.CAMPFIRES) && blockState.getOptionalValue(CampfireBlock.LIT)
.orElse(false)
|| AllBlocks.LIT_BLAZE_BURNER.has(blockState)
&& blockState.getOptionalValue(LitBlazeBurnerBlock.FLAME_TYPE)
.map(flame -> flame == LitBlazeBurnerBlock.FlameType.REGULAR)
.orElse(false)
|| getHeatLevelOf(blockState) == BlazeBurnerBlock.HeatLevel.SMOULDERING;
}
@Override
public int getPriority() {
return 200;
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmokingRecipe> recipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level);
return recipe.isPresent();
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmokingRecipe> smokingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level);
if (smokingRecipe.isPresent())
return RecipeApplier.applyRecipeOn(stack, smokingRecipe.get());
return null;
}
@Override
public void spawnProcessingParticles(Level level, Vec3 pos) {
if (level.random.nextInt(8) != 0)
return;
level.addParticle(ParticleTypes.POOF, pos.x, pos.y + .25f, pos.z, 0, 1 / 16f, 0);
}
@Override
public void morphAirFlow(AirFlowParticleAccess particleAccess, RandomSource random) {
particleAccess.setColor(Color.mixColors(0x0, 0x555555, random.nextFloat()));
particleAccess.setAlpha(1f);
if (random.nextFloat() < 1 / 32f)
particleAccess.spawnExtraParticle(ParticleTypes.SMOKE, .125f);
if (random.nextFloat() < 1 / 32f)
particleAccess.spawnExtraParticle(ParticleTypes.LARGE_SMOKE, .125f);
}
@Override
public void affectEntity(Entity entity, Level level) {
if (level.isClientSide)
return;
if (!entity.fireImmune()) {
entity.setSecondsOnFire(2);
entity.hurt(FIRE_DAMAGE_SOURCE, 2);
}
}
}
public static class SplashingType implements FanProcessingType {
private static final SplashingWrapper SPLASHING_WRAPPER = new SplashingWrapper();
@Override
public boolean isValidAt(Level level, BlockPos pos) {
FluidState fluidState = level.getFluidState(pos);
Fluid fluid = fluidState.getType();
return fluid == Fluids.WATER || fluid == Fluids.FLOWING_WATER;
}
@Override
public int getPriority() {
return 400;
}
@Override
public boolean canProcess(ItemStack stack, Level level) {
SPLASHING_WRAPPER.setItem(0, stack);
Optional<SplashingRecipe> recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, level);
return recipe.isPresent();
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
SPLASHING_WRAPPER.setItem(0, stack);
Optional<SplashingRecipe> recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, level);
if (recipe.isPresent())
return RecipeApplier.applyRecipeOn(stack, recipe.get());
return null;
}
@Override
public void spawnProcessingParticles(Level level, Vec3 pos) {
if (level.random.nextInt(8) != 0)
return;
Vector3f color = new Color(0x0055FF).asVectorF();
level.addParticle(new DustParticleOptions(color, 1), pos.x + (level.random.nextFloat() - .5f) * .5f,
pos.y + .5f, pos.z + (level.random.nextFloat() - .5f) * .5f, 0, 1 / 8f, 0);
level.addParticle(ParticleTypes.SPIT, pos.x + (level.random.nextFloat() - .5f) * .5f, pos.y + .5f,
pos.z + (level.random.nextFloat() - .5f) * .5f, 0, 1 / 8f, 0);
}
@Override
public void morphAirFlow(AirFlowParticleAccess particleAccess, RandomSource random) {
particleAccess.setColor(Color.mixColors(0x4499FF, 0x2277FF, random.nextFloat()));
particleAccess.setAlpha(1f);
if (random.nextFloat() < 1 / 32f)
particleAccess.spawnExtraParticle(ParticleTypes.BUBBLE, .125f);
if (random.nextFloat() < 1 / 32f)
particleAccess.spawnExtraParticle(ParticleTypes.BUBBLE_POP, .125f);
}
@Override
public void affectEntity(Entity entity, Level level) {
if (level.isClientSide)
return;
if (entity instanceof EnderMan || entity.getType() == EntityType.SNOW_GOLEM
|| entity.getType() == EntityType.BLAZE) {
entity.hurt(DamageSource.DROWN, 2);
}
if (entity.isOnFire()) {
entity.clearFire();
level.playSound(null, entity.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE,
SoundSource.NEUTRAL, 0.7F, 1.6F + (level.random.nextFloat() - level.random.nextFloat()) * 0.4F);
}
}
}
}

View file

@ -0,0 +1,108 @@
package com.simibubi.create.content.kinetics.fan.processing;
import java.util.ArrayList;
import java.util.List;
import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour.TransportedResult;
import com.simibubi.create.content.kinetics.belt.transport.TransportedItemStack;
import com.simibubi.create.infrastructure.config.AllConfigs;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
public class FanProcessing {
public static boolean canProcess(ItemEntity entity, FanProcessingType type) {
if (entity.getPersistentData()
.contains("CreateData")) {
CompoundTag compound = entity.getPersistentData()
.getCompound("CreateData");
if (compound.contains("Processing")) {
CompoundTag processing = compound.getCompound("Processing");
if (AllFanProcessingTypes.parseLegacy(processing.getString("Type")) != type)
return type.canProcess(entity.getItem(), entity.level);
else if (processing.getInt("Time") >= 0)
return true;
else if (processing.getInt("Time") == -1)
return false;
}
}
return type.canProcess(entity.getItem(), entity.level);
}
public static boolean applyProcessing(ItemEntity entity, FanProcessingType type) {
if (decrementProcessingTime(entity, type) != 0)
return false;
List<ItemStack> stacks = type.process(entity.getItem(), entity.level);
if (stacks == null)
return false;
if (stacks.isEmpty()) {
entity.discard();
return false;
}
entity.setItem(stacks.remove(0));
for (ItemStack additional : stacks) {
ItemEntity entityIn = new ItemEntity(entity.level, entity.getX(), entity.getY(), entity.getZ(), additional);
entityIn.setDeltaMovement(entity.getDeltaMovement());
entity.level.addFreshEntity(entityIn);
}
return true;
}
public static TransportedResult applyProcessing(TransportedItemStack transported, Level world, FanProcessingType type) {
TransportedResult ignore = TransportedResult.doNothing();
if (transported.processedBy != type) {
transported.processedBy = type;
int timeModifierForStackSize = ((transported.stack.getCount() - 1) / 16) + 1;
int processingTime =
(int) (AllConfigs.server().kinetics.fanProcessingTime.get() * timeModifierForStackSize) + 1;
transported.processingTime = processingTime;
if (!type.canProcess(transported.stack, world))
transported.processingTime = -1;
return ignore;
}
if (transported.processingTime == -1)
return ignore;
if (transported.processingTime-- > 0)
return ignore;
List<ItemStack> stacks = type.process(transported.stack, world);
if (stacks == null)
return ignore;
List<TransportedItemStack> transportedStacks = new ArrayList<>();
for (ItemStack additional : stacks) {
TransportedItemStack newTransported = transported.getSimilar();
newTransported.stack = additional.copy();
transportedStacks.add(newTransported);
}
return TransportedResult.convertTo(transportedStacks);
}
private static int decrementProcessingTime(ItemEntity entity, FanProcessingType type) {
CompoundTag nbt = entity.getPersistentData();
if (!nbt.contains("CreateData"))
nbt.put("CreateData", new CompoundTag());
CompoundTag createData = nbt.getCompound("CreateData");
if (!createData.contains("Processing"))
createData.put("Processing", new CompoundTag());
CompoundTag processing = createData.getCompound("Processing");
if (!processing.contains("Type") || AllFanProcessingTypes.parseLegacy(processing.getString("Type")) != type) {
processing.putString("Type", FanProcessingTypeRegistry.getIdOrThrow(type).toString());
int timeModifierForStackSize = ((entity.getItem()
.getCount() - 1) / 16) + 1;
int processingTime =
(int) (AllConfigs.server().kinetics.fanProcessingTime.get() * timeModifierForStackSize) + 1;
processing.putInt("Time", processingTime);
}
int value = processing.getInt("Time") - 1;
processing.putInt("Time", value);
return value;
}
}

View file

@ -0,0 +1,60 @@
package com.simibubi.create.content.kinetics.fan.processing;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
public interface FanProcessingType {
boolean isValidAt(Level level, BlockPos pos);
int getPriority();
boolean canProcess(ItemStack stack, Level level);
@Nullable
List<ItemStack> process(ItemStack stack, Level level);
void spawnProcessingParticles(Level level, Vec3 pos);
void morphAirFlow(AirFlowParticleAccess particleAccess, RandomSource random);
void affectEntity(Entity entity, Level level);
static FanProcessingType parse(String str) {
ResourceLocation id = ResourceLocation.tryParse(str);
if (id == null) {
return AllFanProcessingTypes.NONE;
}
FanProcessingType type = FanProcessingTypeRegistry.getType(id);
if (type == null) {
return AllFanProcessingTypes.NONE;
}
return type;
}
static FanProcessingType getAt(Level level, BlockPos pos) {
for (FanProcessingType type : FanProcessingTypeRegistry.getSortedTypesView()) {
if (type.isValidAt(level, pos)) {
return type;
}
}
return AllFanProcessingTypes.NONE;
}
interface AirFlowParticleAccess {
void setColor(int color);
void setAlpha(float alpha);
void spawnExtraParticle(ParticleOptions options, float speedMultiplier);
}
}

View file

@ -0,0 +1,68 @@
package com.simibubi.create.content.kinetics.fan.processing;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
import net.minecraft.resources.ResourceLocation;
public class FanProcessingTypeRegistry {
private static final Map<ResourceLocation, FanProcessingType> TYPES = new Object2ReferenceOpenHashMap<>();
private static final Map<FanProcessingType, ResourceLocation> IDS = new Reference2ObjectOpenHashMap<>();
private static final List<FanProcessingType> SORTED_TYPES = new ReferenceArrayList<>();
private static final List<FanProcessingType> SORTED_TYPES_VIEW = Collections.unmodifiableList(SORTED_TYPES);
public static void register(ResourceLocation id, FanProcessingType type) {
if (TYPES.put(id, type) != null) {
throw new IllegalArgumentException("Tried to override FanProcessingType registration for id '" + id + "'. This is not supported!");
}
ResourceLocation prevId = IDS.put(type, id);
if (prevId != null) {
throw new IllegalArgumentException("Tried to register same FanProcessingType instance for multiple ids '" + prevId + "' and '" + id + "'. This is not supported!");
}
insertSortedType(type, id);
}
private static void insertSortedType(FanProcessingType type, ResourceLocation id) {
int index = Collections.binarySearch(SORTED_TYPES, type, (type1, type2) -> type2.getPriority() - type1.getPriority());
if (index >= 0) {
throw new IllegalStateException();
}
SORTED_TYPES.add(-index - 1, type);
}
@Nullable
public static FanProcessingType getType(ResourceLocation id) {
return TYPES.get(id);
}
public static FanProcessingType getTypeOrThrow(ResourceLocation id) {
FanProcessingType type = getType(id);
if (type == null) {
throw new IllegalArgumentException("Could not get FanProcessingType for id '" + id + "'!");
}
return type;
}
@Nullable
public static ResourceLocation getId(FanProcessingType type) {
return IDS.get(type);
}
public static ResourceLocation getIdOrThrow(FanProcessingType type) {
ResourceLocation id = getId(type);
if (id == null) {
throw new IllegalArgumentException("Could not get id for FanProcessingType " + type + "!");
}
return id;
}
public static List<FanProcessingType> getSortedTypesView() {
return SORTED_TYPES_VIEW;
}
}

View file

@ -1,22 +1,25 @@
package com.simibubi.create.content.kinetics.fan;
package com.simibubi.create.content.kinetics.fan.processing;
import javax.annotation.ParametersAreNonnullByDefault;
import com.simibubi.create.AllRecipeTypes;
import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe.HauntingWrapper;
import com.simibubi.create.content.processing.recipe.ProcessingRecipe;
import com.simibubi.create.content.processing.recipe.ProcessingRecipeBuilder.ProcessingRecipeParams;
import net.minecraft.world.level.Level;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.wrapper.RecipeWrapper;
@ParametersAreNonnullByDefault
public class HauntingRecipe extends ProcessingRecipe<FanProcessing.HauntingWrapper> {
public class HauntingRecipe extends ProcessingRecipe<HauntingWrapper> {
public HauntingRecipe(ProcessingRecipeParams params) {
super(AllRecipeTypes.HAUNTING, params);
}
@Override
public boolean matches(FanProcessing.HauntingWrapper inv, Level worldIn) {
public boolean matches(HauntingWrapper inv, Level worldIn) {
if (inv.isEmpty())
return false;
return ingredients.get(0)
@ -33,4 +36,10 @@ public class HauntingRecipe extends ProcessingRecipe<FanProcessing.HauntingWrapp
return 12;
}
public static class HauntingWrapper extends RecipeWrapper {
public HauntingWrapper() {
super(new ItemStackHandler(1));
}
}
}

View file

@ -1,16 +1,18 @@
package com.simibubi.create.content.kinetics.fan;
package com.simibubi.create.content.kinetics.fan.processing;
import javax.annotation.ParametersAreNonnullByDefault;
import com.simibubi.create.AllRecipeTypes;
import com.simibubi.create.content.kinetics.fan.FanProcessing.SplashingWrapper;
import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe.SplashingWrapper;
import com.simibubi.create.content.processing.recipe.ProcessingRecipe;
import com.simibubi.create.content.processing.recipe.ProcessingRecipeBuilder.ProcessingRecipeParams;
import net.minecraft.world.level.Level;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.wrapper.RecipeWrapper;
@ParametersAreNonnullByDefault
public class SplashingRecipe extends ProcessingRecipe<FanProcessing.SplashingWrapper> {
public class SplashingRecipe extends ProcessingRecipe<SplashingWrapper> {
public SplashingRecipe(ProcessingRecipeParams params) {
super(AllRecipeTypes.SPLASHING, params);
@ -34,4 +36,10 @@ public class SplashingRecipe extends ProcessingRecipe<FanProcessing.SplashingWra
return 12;
}
public static class SplashingWrapper extends RecipeWrapper {
public SplashingWrapper() {
super(new ItemStackHandler(1));
}
}
}

View file

@ -47,6 +47,11 @@ public class CuttingRecipe extends ProcessingRecipe<RecipeWrapper> implements IA
return 4;
}
@Override
protected boolean canSpecifyDuration() {
return true;
}
@Override
public void addAssemblyIngredients(List<Ingredient> list) {}

View file

@ -13,7 +13,7 @@ import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.AllRecipeTypes;
import com.simibubi.create.content.kinetics.fan.FanProcessing;
import com.simibubi.create.content.kinetics.fan.processing.AllFanProcessingTypes;
import com.simibubi.create.content.logistics.filter.attribute.BookAuthorAttribute;
import com.simibubi.create.content.logistics.filter.attribute.BookCopyAttribute;
import com.simibubi.create.content.logistics.filter.attribute.ColorAttribute;
@ -146,8 +146,8 @@ public interface ItemAttribute {
EQUIPABLE(s -> LivingEntity.getEquipmentSlotForItem(s)
.getType() != EquipmentSlot.Type.HAND),
FURNACE_FUEL(AbstractFurnaceBlockEntity::isFuel),
WASHABLE(FanProcessing::isWashable),
HAUNTABLE(FanProcessing::isHauntable),
WASHABLE(AllFanProcessingTypes.SPLASHING::canProcess),
HAUNTABLE(AllFanProcessingTypes.HAUNTING::canProcess),
CRUSHABLE((s, w) -> testRecipe(s, w, AllRecipeTypes.CRUSHING.getType())
|| testRecipe(s, w, AllRecipeTypes.MILLING.getType())),
SMELTABLE((s, w) -> testRecipe(s, w, RecipeType.SMELTING)),

View file

@ -208,6 +208,11 @@ public class BasinRecipe extends ProcessingRecipe<SmartInventory> {
return true;
}
@Override
protected boolean canSpecifyDuration() {
return true;
}
@Override
public boolean matches(SmartInventory inv, @Nonnull Level worldIn) {
return false;

View file

@ -71,7 +71,7 @@ public abstract class ProcessingRecipe<T extends Container> implements Recipe<T>
}
protected boolean canSpecifyDuration() {
return true;
return false;
}
protected int getMaxFluidInputCount() {

View file

@ -237,10 +237,10 @@ public class AllDisplayBehaviours {
Mods.COMPUTERCRAFT.executeIfInstalled(() -> () -> {
DisplayBehaviour computerDisplaySource = register(Create.asResource("computer_display_source"), new ComputerDisplaySource());
assignBlockEntity(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "wired_modem_full"));
assignBlockEntity(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_normal"));
assignBlockEntity(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_advanced"));
assignBlockEntity(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_command"));
assignBlockEntity(computerDisplaySource, Mods.COMPUTERCRAFT.rl("wired_modem_full"));
assignBlockEntity(computerDisplaySource, Mods.COMPUTERCRAFT.rl("computer_normal"));
assignBlockEntity(computerDisplaySource, Mods.COMPUTERCRAFT.rl("computer_advanced"));
assignBlockEntity(computerDisplaySource, Mods.COMPUTERCRAFT.rl("computer_command"));
});
}
}

View file

@ -24,19 +24,13 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp
public class SchematicInstances {
public static final WorldAttached<Cache<Integer, SchematicLevel>> loadedSchematics;
static {
loadedSchematics = new WorldAttached<>($ -> CacheBuilder.newBuilder()
private static final WorldAttached<Cache<Integer, SchematicLevel>> LOADED_SCHEMATICS = new WorldAttached<>($ -> CacheBuilder.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build());
}
public static void register() {}
@Nullable
public static SchematicLevel get(Level world, ItemStack schematic) {
Cache<Integer, SchematicLevel> map = loadedSchematics.get(world);
Cache<Integer, SchematicLevel> map = LOADED_SCHEMATICS.get(world);
int hash = getHash(schematic);
SchematicLevel ifPresent = map.getIfPresent(hash);
if (ifPresent != null)

View file

@ -289,9 +289,9 @@ public class CarriageContraption extends Contraption {
}
@Override
public Collection<BlockEntity> getSpecialRenderedTEs() {
public Collection<BlockEntity> getSpecialRenderedBEs() {
if (notInPortal())
return super.getSpecialRenderedTEs();
return super.getSpecialRenderedBEs();
return specialRenderedBEsOutsidePortal;
}

View file

@ -11,6 +11,7 @@ import com.simibubi.create.AllSpecialTextures;
import com.simibubi.create.AllTags;
import com.simibubi.create.content.equipment.blueprint.BlueprintOverlayRenderer;
import com.simibubi.create.foundation.block.ProperWaterloggedBlock;
import com.simibubi.create.foundation.utility.BlockHelper;
import com.simibubi.create.foundation.utility.CreateLang;
import com.simibubi.create.infrastructure.config.AllConfigs;
@ -44,7 +45,6 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.HitResult.Type;
@ -478,18 +478,6 @@ public class TrackPlacement {
info.requiredPavement += TrackPaver.paveCurve(level, info.curve, block, simulate, visited);
}
private static BlockState copyProperties(BlockState from, BlockState onto) {
for (Property property : onto.getProperties()) {
if (from.hasProperty(property))
onto = onto.setValue(property, from.getValue(property));
}
return onto;
}
private static BlockState copyProperties(BlockState from, BlockState onto, boolean keepFrom) {
return keepFrom ? from : copyProperties(from, onto);
}
private static PlacementInfo placeTracks(Level level, PlacementInfo info, BlockState state1, BlockState state2,
BlockPos targetPos1, BlockPos targetPos2, boolean simulate) {
info.requiredTracks = 0;
@ -518,7 +506,7 @@ public class TrackPlacement {
BlockPos offsetPos = pos.offset(offset.x, offset.y, offset.z);
BlockState stateAtPos = level.getBlockState(offsetPos);
// copy over all shared properties from the shaped state to the correct track material block
BlockState toPlace = copyProperties(state, info.trackMaterial.getBlock().defaultBlockState());
BlockState toPlace = BlockHelper.copyProperties(state, info.trackMaterial.getBlock().defaultBlockState());
boolean canPlace = stateAtPos.getMaterial()
.isReplaceable();
@ -544,12 +532,12 @@ public class TrackPlacement {
BlockState onto = info.trackMaterial.getBlock().defaultBlockState();
BlockState stateAtPos = level.getBlockState(targetPos1);
level.setBlock(targetPos1, ProperWaterloggedBlock.withWater(level,
(AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : copyProperties(state1, onto))
(AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : BlockHelper.copyProperties(state1, onto))
.setValue(TrackBlock.HAS_BE, true), targetPos1), 3);
stateAtPos = level.getBlockState(targetPos2);
level.setBlock(targetPos2, ProperWaterloggedBlock.withWater(level,
(AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : copyProperties(state2, onto))
(AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : BlockHelper.copyProperties(state2, onto))
.setValue(TrackBlock.HAS_BE, true), targetPos2), 3);
}

View file

@ -0,0 +1,31 @@
package com.simibubi.create.foundation.data;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.AllTags.AllRecipeSerializerTags;
import com.simibubi.create.Create;
import com.simibubi.create.compat.Mods;
import net.minecraft.core.Registry;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraftforge.common.data.ExistingFileHelper;
public class RecipeSerializerTagGen extends TagsProvider<RecipeSerializer<?>> {
public RecipeSerializerTagGen(DataGenerator generator, @Nullable ExistingFileHelper existingFileHelper) {
super(generator, Registry.RECIPE_SERIALIZER, Create.ID, existingFileHelper);
}
@Override
public String getName() {
return "Create's Recipe Serializer Tags";
}
@Override
protected void addTags() {
this.tag(AllRecipeSerializerTags.AUTOMATION_IGNORE.tag)
.addOptional(Mods.OCCULTISM.rl("spirit_trade"))
.addOptional(Mods.OCCULTISM.rl("ritual"));
}
}

View file

@ -1,36 +0,0 @@
package com.simibubi.create.foundation.mixin.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.mojang.blaze3d.vertex.PoseStack;
@Mixin(PoseStack.class)
public class FixNormalScalingMixin {
/**
* Minecraft negates the normal matrix if all scales are equal and negative, but
* does not return afterward. This allows the rest of the method's logic to be
* applied, which negates the matrix again, resulting in the matrix being the
* same as in the beginning.
*/
@Inject(at = @At(value = "INVOKE", target = "Lcom/mojang/math/Matrix3f;mul(F)V", shift = Shift.AFTER), method = "scale(FFF)V", cancellable = true)
private void create$returnAfterNegate(float x, float y, float z, CallbackInfo ci) {
ci.cancel();
}
/**
* Minecraft takes the inverse cube root of the product of all scales to provide a
* rough estimate for normalization so that it does not need to be done later. It
* does not make sense for this "normalization factor" to be negative though, as
* that would invert all normals. Additionally, Minecraft's fastInverseCbrt method
* does not work for negative numbers.
*/
@ModifyArg(at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Mth;fastInvCubeRoot(F)F"), method = "scale(FFF)V")
private float create$absInvCbrtInput(float input) {
return Math.abs(input);
}
}

View file

@ -31,7 +31,7 @@ public class CKinetics extends ConfigBase {
public final ConfigGroup contraptions = group(1, "contraptions", "Moving Contraptions");
public final ConfigInt maxBlocksMoved = i(2048, 1, "maxBlocksMoved", Comments.maxBlocksMoved);
public final ConfigInt maxDataSize =
i(ContraptionData.DEFAULT_MAX, 0, "maxDataSize", Comments.bytes, Comments.maxDataDisable, Comments.maxDataSize, Comments.maxDataSize2);
i(ContraptionData.DEFAULT_LIMIT, 0, "maxDataSize", Comments.bytes, Comments.maxDataDisable, Comments.maxDataSize, Comments.maxDataSize2);
public final ConfigInt maxChassisRange = i(16, 1, "maxChassisRange", Comments.maxChassisRange);
public final ConfigInt maxPistonPoles = i(64, 1, "maxPistonPoles", Comments.maxPistonPoles);
public final ConfigInt maxRopeLength = i(256, 1, "maxRopeLength", Comments.maxRopeLength);

View file

@ -9,7 +9,6 @@ import com.tterrag.registrate.util.entry.RegistryEntry;
import net.createmod.ponder.foundation.CustomPonderRegistrationHelper;
import net.createmod.ponder.foundation.PonderTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
@ -318,7 +317,7 @@ public class AllPonderTags {
.add(Blocks.TARGET);
Mods.COMPUTERCRAFT.executeIfInstalled(() -> () -> {
Block computer = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_advanced"));
Block computer = ForgeRegistries.BLOCKS.getValue(Mods.COMPUTERCRAFT.rl("computer_advanced"));
if (computer != null)
HELPER.addToTag(DISPLAY_SOURCES).add(computer);
});

View file

@ -32,7 +32,7 @@ ${mod_description}
[[dependencies.create]]
modId="flywheel"
mandatory=true
versionRange="[0.6.9,0.6.10)"
versionRange="[0.6.10,0.6.11)"
ordering="AFTER"
side="CLIENT"

View file

@ -28,7 +28,6 @@
"client.BlockDestructionProgressMixin",
"client.CameraMixin",
"client.EntityContraptionInteractionMixin",
"client.FixNormalScalingMixin",
"client.GameRendererMixin",
"client.HeavyBootsOnPlayerMixin",
"client.HumanoidArmorLayerMixin",

View file

@ -1,88 +0,0 @@
| Method | Description |
|---------------------------------------|------------------------------------------------------|
| [`setCursorPos(x, y)`](#setCursorPos) | Sets the cursor position |
| [`getCursorPos()`](#getCursorPos) | Gets the current cursor position |
| [`getSize()`](#getSize) | Gets the display size of the connected target |
| [`isColor()`](#isColor) | Whether the connected display target supports color |
| [`isColour()`](#isColour) | Whether the connected display target supports colour |
| [`write(text)`](#writetext) | Writes text at the current cursor position |
| [`clearLine()`](#clearLine) | Clears the line at the current cursor position |
| [`clear()`](#clear) | Clears the whole display |
| [`update()`](#update) | Pushes an update to the display target |
---
### `setCursorPos(x, y)`
Sets the cursor position. Can be outside the bounds of the connected display.
**Parameters**
- _x:_ `number` The cursor x position.
- _y:_ `number` The cursor y position.
---
### `getCursorPos()`
Gets the current cursor position.
**Returns**
- `number` The cursor x position.
- `number` The cursor y position.
---
### `getSize()`
Gets the size of the connected display target.
**Returns**
- `number` The width of the display.
- `number` The height of the display.
---
### `isColor()`
Checks whether the connected display target supports color.
**Returns**
- `boolean` Whether the display supports color.
---
### `isColour()`
Checks whether the connected display target supports colour.
**Returns**
- `boolean` Whether the display supports colour.
---
### `write(text)`
Writes text at the current cursor position, moving the cursor to the end of the text.
This only writes to an internal buffer. For the changes to show up on the display [`update()`](#update) must be used.
If the cursor is outside the bounds of the connected display, the text will not show up on the display.
This will overwrite any text currently at the cursor position.
**Parameters**
- _text:_ `string` The text to write.
**See also**
- [`update()`](#update) To push the changes to the display target.
---
### `clearLine()`
Clears the line at the current cursor position.
**See also**
- [`update()`](#update) To push the changes to the display target.
---
### `clear()`
Clears the whole display.
**See also**
- [`update()`](#update) To push the changes to the display target.
---
### `update()`
Pushes any changes to the connected display target.
Any changes made are only made to an internal buffer.
For them to show up on the display they must be pushed to the display using this function.
This allows for this peripheral to be better multithreaded and for users to be able to change multiple lines at once by
using multiple [`write(text)`](#writetext) calls and then one [`update()`](#update) call.
**See also**
- [`write(text)`](#writetext) To write text to the display target.

View file

@ -1,18 +0,0 @@
| Method | Description |
|-------------------------------------------------|----------------------------------------|
| [`setTargetSpeed(speed)`](#setTargetSpeedspeed) | Sets the target rotation speed |
| [`getTargetSpeed()`](#getTargetSpeed) | Gets the current target rotation speed |
---
### `setTargetSpeed(speed)`
Sets the rotation speed controller's target speed.
**Parameters**
- _speed:_ `number` The target speed in RPM. Must be an integer within the range of [-256..256]. Values outside of this range will be clamped.
---
### `getTargetSpeed()`
Gets the rotation speed controller's current target speed.
**Returns**
- `number` The current target rotation speed in RPM.

View file

@ -1,28 +0,0 @@
| Method | Description |
|--------------------------------------------------------|--------------------------------------------------------------|
| [`rotate(angle, [modifier])`](#rotateangle-modifier) | Rotates shaft by a set angle |
| [`move(distance, [modifier])`](#movedistance-modifier) | Rotates shaft to move Piston/Pulley/Gantry by a set distance |
| [`isRunning()`](#isRunning) | Whether the gearshift is currently spinning |
---
### `rotate(angle, [modifier])`
Rotates connected components by a set angle.
**Parameters**
- _angle:_ `number` Angle to rotate the shaft by in degrees. Must be a positive integer. To do backwards rotation, set _modifier_ to a negative value.
- _modifier?:_ `number = 1` Speed modifier which can be used to reverse rotation. Must be an integer within the range of [-2..2]. Values out of this range are ignored and the default of 1 is used.
---
### `move(distance, [modifier])`
Rotates connected components to move connected piston, pulley or gantry contractions by a set distance.
**Parameters**
- _distance:_ `number` Distance to move connected piston, pulley or gantry contraptions by. Must be a positive integer. To do backwards movement, set _modifier_ to a negative value.
- _modifier?:_ `number = 1` Speed modifier which can be used to reverse direction. Must be an integer within the range of [-2..2]. Values out of this range are ignored and the default of 1 is used.
---
### `isRunning()`
Checks if the sequenced gearshift is currently spinning.
**Returns**
- `boolean` Whether the sequenced gearshift is currently spinning.

View file

@ -1,10 +0,0 @@
| Method | Description |
|---------------------------|---------------------------------|
| [`getSpeed()`](#getSpeed) | Gets the current rotation speed |
---
### `getSpeed()`
Gets the current rotation speed of the attached components.
**Returns**
- `number` The current rotation speed in RPM.

View file

@ -1,18 +0,0 @@
| Method | Description |
|---------------------------------------------|--------------------------------|
| [`getStress()`](#getStress) | Gets the current stress level |
| [`getStressCapacity()`](#getStressCapacity) | Gets the total stress capacity |
---
### `getStress()`
Gets the connected network's current stress level.
**Returns**
- `number` The current stress level in SU.
---
### `getStressCapacity()`
Gets the connected network's total stress capacity.
**Returns**
- `number` The total stress capacity in SU.

View file

@ -1,195 +0,0 @@
Train schedules are represented by a table in Lua. The table contains a list of entries where each entry has a single instruction and multiple conditions.
Each instruction and condition has a `data` table that stores specific data about the instruction or condition.
```lua
schedule = {
cyclic = true, -- Does the schedule repeat itself after the end has been reached?
entries = { -- List of entries, each entry contains a single instruction and multiple conditions.
{
instruction = {
id = "create:destination", -- The different instructions are described below.
data = { -- Data that is stored about the instruction. Different for each instruction type.
text = "Station 1",
},
},
conditions = { -- List of lists of conditions. The outer list is the "OR" list
{ -- and the inner lists are "AND" lists.
{
id = "create:delay", -- The different conditions are described below.
data = { -- Data that is stored about the condition. Different for each condition type.
value = 5,
time_unit = 1,
},
},
{
id = "create:powered",
data = {},
},
},
{
{
id = "create:time_of_day",
data = {
rotation = 0,
hour = 14,
minute = 0,
},
},
},
},
},
},
}
```
---
## Instructions
| ID | Description |
|----------------------------------------------|---------------------------------|
| [`"create:destination"`](#createdestination) | Move to a certain train station |
| [`"create:rename"`](#createrename) | Change the schedule title |
| [`"create:throttle"`](#createthrottle) | Change the train's throttle |
---
### `"create:destination"`
Moves the train to the chosen train station. This instruction must have at least one condition.
**Data**
- _text:_ `string` The name of the station to travel to. Can include * as a wildcard.
---
### `"create:rename"`
Renames the schedule. This name shows up on display link targets. This instruction cannot have conditions.
**Data**
- _text:_ `string` The name to rename the schedule to.
---
### `"create:throttle"`
Changes the throttle of the train. This instruction cannot have conditions.
**Data**
- _value:_ `number` The throttle to set the train to. Must be an integer within the range of [5..100].
---
## Conditions
Conditions are stored in a list of lists of conditions. The inner lists contain conditions that get `AND`'ed together. They must all be met for that group to be true.
The outer list contains the `AND`'ed groups of conditions that get `OR`'ed together. Only one of the groups needs to be true for the schedule to move onto the next instruction.
| ID | Description |
|-----------------------------------------------------|-----------------------------------------------------|
| [`"create:delay"`](#createdelay) | Wait for a certain delay |
| [`"create:time_of_day"`](#createtimeofday) | Wait for a specific time of day |
| [`"create:fluid_threshold"`](#createfluidthreshold) | Wait for a certain amount of fluid to be on board |
| [`"create:item_threshold"`](#createitemthreshold) | Wait for a certain amount of items to be on board |
| [`"create:redstone_link"`](#createredstonelink) | Wait for a redstone link to be powered |
| [`"create:player_count"`](#createplayercount) | Wait for a certain amount of players to be on board |
| [`"create:idle"`](#createidle) | Wait for cargo loading inactivity |
| [`"create:unloaded"`](#createunloaded) | Wait for the current chunk to be unloaded |
| [`"create:powered"`](#createpowered) | Wait for the station to be powered |
---
### `"create:delay"`
Wait for a set delay. Can be measured in ticks, seconds or minutes.
**Data**
- _value:_ `number` The amount of time to wait for.
- _time_unit:_ `number` The unit of time. 0 for ticks, 1 for seconds and 2 for minutes.
---
### `"create:time_of_day"`
Wait for a time of day, then repeat at a specified interval.
**Data**
- _hour:_ `number` The hour of the day to wait for in a 24-hour format. Must be an integer within the range of [0..23].
- _minute:_ `number` The minute of the hour to wait for. Must be an integer within the range of [0..59].
- _rotation:_ `number` The interval to repeat at after the time of day has been met. Check the rotation table below for valid values. Must be an integer within the range of [0..9].
**Rotation**
| Rotation | Time Interval |
|----------|------------------|
| 0 | Every Day |
| 1 | Every 12 Hours |
| 2 | Every 6 Hours |
| 3 | Every 4 Hours |
| 4 | Every 3 Hours |
| 5 | Every 2 Hours |
| 6 | Every Hour |
| 7 | Every 45 Minutes |
| 8 | Every 30 Minutes |
| 9 | Every 15 Minutes |
---
### `"create:fluid_threshold"`
Wait for a certain amount of a specific fluid to be loaded onto the train.
**Data**
- _bucket:_ `table` The bucket item of the fluid.
- _threshold:_ `number` The threshold in number of buckets of fluid. Must be a positive integer.
- _operator:_ `number` Whether the condition should wait for the train to be loaded above the threshold, below the threshold or exactly at the threshold. 0 for greater than, 1 for less than, 2 for equal to.
- _measure:_ `number` The unit to measure the fluid in. This condition supports buckets as the only unit. Set to 0.
**See also**
- [Items](#items) How items are represented in Lua.
---
### `"create:item_threshold"`
Wait for a certain amount of a specific item to be loaded onto the train.
**Data**
- _item:_ `table` The item.
- _threshold:_ `number` The threshold of items. Must be a positive integer.
- _operator:_ `number` Whether the condition should wait for the train to be loaded above the threshold, below the threshold or exactly at the threshold. 0 for greater than, 1 for less than, 2 for equal to.
- _measure:_ `number` The unit to measure the items in. 0 for items. 1 for stacks of items.
**See also**
- [Items](#items) How items are represented in Lua.
---
### `"create:redstone_link"`
Wait for a redstone link to be powered.
**Data**
- _frequency:_ `{ table... }` A list of the two items making up the redstone link frequency.
- _inverted:_ `number` Whether the redstone link should be powered or not to meet the condition. 0 for powered. 1 for not powered.
**See also**
- [Items](#items) How items are represented in Lua.
---
### `"create:player_count"`
Wait for a certain amount of players to be seated on the train.
**Data**
- _count:_ `number` The number of players to be seated on the train. Must be a positive integer.
- _exact:_ `number` Whether the seated player count has to be exact to meet the condition. 0 for the exact amount of players seated, 1 for a greater than or equal amount of seated players.
---
### `"create:idle"`
Wait for a period of inactivity in loading or unloading the train. Can be measured in ticks, seconds or minutes.
**Data**
- _value:_ `number` The amount of idle time to meet the condition. Must be a positive integer.
- _time_unit:_ `number` The unit of time. 0 for ticks, 1 for seconds and 2 for minutes.
---
### `"create:unloaded"`
Wait for the chunk the train is in to be unloaded.
---
### `"create:powered"`
Wait for the station to be powered with a redstone signal.
---
## Items
In Lua, items are represented with an ID and a count.
```lua
item = {
id = "minecraft:stone",
count = 1,
}
```
- _id:_ `string` The ID of the item.
- _count:_ `number` The amount of items in the stack. For the purposes of working with train schedules the count should always be 1. Must be an integer.

View file

@ -1,179 +0,0 @@
| Method | Description |
|-----------------------------------------------------------------|----------------------------------------------------|
| [`assemble()`](#assemble) | Assembles a new train at the station |
| [`disassemble()`](#disassemble) | Disassembles the currently present train |
| [`setAssemblyMode(assemblyMode)`](#setAssemblyModeassemblyMode) | Sets the station's assembly mode |
| [`isInAssemblyMode()`](#isInAssemblyMode) | Whether the station is in assembly mode |
| [`getStationName()`](#getStationName) | Gets the station's current name |
| [`setStationName(name)`](#setStationNamename) | Sets the station's name |
| [`isTrainPresent()`](#isTrainPresent) | Whether a train is present at the station |
| [`isTrainImminent()`](#isTrainImminent) | Whether a train is imminent to the station |
| [`isTrainEnroute()`](#isTrainEnroute) | Whether a train is enroute to the station |
| [`getTrainName()`](#getTrainName) | Gets the currently present train's name |
| [`setTrainName(name)`](#setTrainNamename) | Sets the currently present train's name |
| [`hasSchedule()`](#hasSchedule) | Whether the currently present train has a schedule |
| [`getSchedule()`](#getSchedule) | Gets the currently present train's schedule |
| [`setSchedule(schedule)`](#setScheduleschedule) | Sets the currently present train's schedule |
---
### `assemble()`
Assembles a new train at the station. The station must be in assembly mode prior to calling this function.
This function also causes the station to exit assembly mode after the train is done assembing.
**Throws**
- If the station is not in assembly mode.
- If the station is not connected to a track.
- If the train failed to assemble.
- If the station failed to exit assembly mode.
**See also**
- [`setAssemblyMode(assemblyMode)`](#setAssemblyModeassemblyMode) To set the assembly mode of the station.
---
### `disassemble()`
Disassembles the station's currently present train. The station must not be in assembly mode.
**Throws**
- If the station is in assembly mode.
- If the station is not connected to a track.
- If there is currently no train present at the station.
- If the train failed to disassemble.
**See also**
- [`setAssemblyMode(assemblyMode)`](#setAssemblyModeassemblyMode) To set the assembly mode of the station.
---
### `setAssemblyMode(assemblyMode)`
Sets the station's assembly mode.
**Parameters**
- _assemblyMode:_ `boolean` Whether the station should be in assembly mode.
**Throws**
- If the station fails to enter or exit assembly mode.
- If the station is not connected to a track.
---
### `isInAssemblyMode()`
Checks whether the station is in assembly mode.
**Returns**
- `boolean` Whether the station is in assembly mode.
---
### `getStationName()`
Gets the station's current name.
**Returns**
- `string` The station's current name.
**Throws**
- If the station is not connected to a track.
---
### `setStationName(name)`
Sets the station's name.
**Parameters**
- _name:_ `string` What to set the station's name to.
**Throws**
- If the station name fails to be set.
- If the station is not connected to a track.
---
### `isTrainPresent()`
Checks whether a train is currently present at the station.
**Returns**
- `boolean` Whether a train is present at the station.
**Throws**
- If the station is not connected to a track.
---
### `isTrainImminent()`
Checks whether a train is imminently arriving at the station.
Imminent is defined as being within 30 blocks of the station.
This will not be true if the train has arrived and stopped at the station.
**Returns**
- `boolean` Whether a train is imminent to the station.
**Throws**
- If the station is not connected to a track.
**See also**
- [`isTrainPresent()`](#isTrainPresent) To check if a train is present at the station.
---
### `isTrainEnroute()`
Checks whether a train is enroute and navigating to the station.
**Returns**
- `boolean` Whether a train is enroute to the station.
**Throws**
- If the station is not connected to a track.
---
### `getTrainName()`
Gets the currently present train's name.
**Returns**
- `string` The currently present train's name.
**Throws**
- If the station is not connected to a track.
- If there is currently no train present at the station.
---
### `setTrainName(name)`
Sets the currently present train's name.
**Parameters**
- _name:_ `string` What to set the currently present train's name to.
**Throws**
- If the station is not connected to a track.
- If there is currently no train present at the station.
---
### `hasSchedule()`
Checks whether the currently present train has a schedule.
**Returns**
- `boolean` Whether the currently present train has a schedule.
**Throws**
- If the station is not connected to a track.
- If there is currently no train present at the station.
---
### `getSchedule()`
Gets the currently present train's schedule.
**Returns**
- `table` The train's schedule
**Throws**
- If the station is not connected to a track.
- If there is currently no train present at the station.
- If the present train doesn't have a schedule.
**See also**
- [Lua Train Schedules](#Lua-Train-Schedules) How train schedules are represented in Lua.
---
### `setSchedule(schedule)`
Sets the currently present train's schedule. This will overwrite the currently set schedule.
**Parameters**
- _schedule:_ `table` The schedule to set the present train to.
**Throws**
- If the station is not connected to a track.
- If there is currently no train present at the station.
**See also**
- [Lua Train Schedules](#Lua-Train-Schedules) How train schedules are represented in Lua.

View file

@ -1,2 +0,0 @@
Just before this PR is about to be merged this /wiki folder will be removed from the PR and the pages will be added to
the wiki section of the Create GitHub under API Reference