Merge pull request #2282 from bonii-xx/dynbucket

Add a dynamic bucket model that displays the animated liquid contained
This commit is contained in:
Fry 2015-12-22 00:26:25 +03:00
commit c7790f7b35
22 changed files with 958 additions and 0 deletions

View File

@ -104,6 +104,7 @@ public class ForgeGuiFactory implements IModGuiFactory
{
List<IConfigElement> list = new ArrayList<IConfigElement>();
list.add(new DummyCategoryElement("forgeCfg", "forge.configgui.ctgy.forgeGeneralConfig", GeneralEntry.class));
list.add(new DummyCategoryElement("forgeClientCfg", "forge.configgui.ctgy.forgeClientConfig", ClientEntry.class));
list.add(new DummyCategoryElement("forgeChunkLoadingCfg", "forge.configgui.ctgy.forgeChunkLoadingConfig", ChunkLoaderEntry.class));
list.add(new DummyCategoryElement("forgeVersionCheckCfg", "forge.configgui.ctgy.VersionCheckConfig", VersionCheckEntry.class));
return list;
@ -133,6 +134,30 @@ public class ForgeGuiFactory implements IModGuiFactory
}
}
/**
* This custom list entry provides the Client only Settings entry on the Minecraft Forge Configuration screen.
* It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen.
*/
public static class ClientEntry extends CategoryEntry
{
public ClientEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
{
super(owningScreen, owningEntryList, prop);
}
@Override
protected GuiScreen buildChildScreen()
{
// This GuiConfig object specifies the configID of the object and as such will force-save when it is closed. The parent
// GuiConfig object's entryList will also be refreshed to reflect the changes.
return new GuiConfig(this.owningScreen,
(new ConfigElement(ForgeModContainer.getConfig().getCategory(Configuration.CATEGORY_CLIENT))).getChildElements(),
this.owningScreen.modID, Configuration.CATEGORY_CLIENT, this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart,
this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart,
GuiConfig.getAbridgedConfigPath(ForgeModContainer.getConfig().toString()));
}
}
/**
* This custom list entry provides the Forge Chunk Manager Config entry on the Minecraft Forge Configuration screen.
* It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen.

View File

@ -0,0 +1,273 @@
package net.minecraftforge.client.model;
import com.google.common.collect.Lists;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad;
import javax.vecmath.Vector4f;
import java.util.List;
public final class ItemTextureQuadConverter
{
private ItemTextureQuadConverter()
{
// non-instantiable
}
/**
* Takes a texture and converts it into BakedQuads.
* The conversion is done by scanning the texture horizontally and vertically and creating "strips" of the texture.
* Strips that are of the same size and follow each other are converted into one bigger quad.
* </br>
* The resulting list of quads is the texture represented as a list of horizontal OR vertical quads,
* depending on which creates less quads. If the amount of quads is equal, horizontal is preferred.
*
* @param format
* @param template The input texture to convert
* @param sprite The texture whose UVs shall be used @return The generated quads.
*/
public static List<UnpackedBakedQuad> convertTexture(VertexFormat format, TRSRTransformation transform, TextureAtlasSprite template, TextureAtlasSprite sprite, float z, EnumFacing facing, int color)
{
List<UnpackedBakedQuad> horizontal = convertTextureHorizontal(format, transform, template, sprite, z, facing, color);
List<UnpackedBakedQuad> vertical = convertTextureVertical(format, transform, template, sprite, z, facing, color);
return horizontal.size() >= vertical.size() ? horizontal : vertical;
}
/**
* Scans a texture and converts it into a list of horizontal strips stacked on top of each other.
* The height of the strips is as big as possible.
*/
public static List<UnpackedBakedQuad> convertTextureHorizontal(VertexFormat format, TRSRTransformation transform, TextureAtlasSprite template, TextureAtlasSprite sprite, float z, EnumFacing facing, int color)
{
int w = template.getIconWidth();
int h = template.getIconHeight();
int[] data = template.getFrameTextureData(0)[0];
List<UnpackedBakedQuad> quads = Lists.newArrayList();
// the upper left x-position of the current quad
int start = -1;
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
// current pixel
int pixel = data[y * w + x];
// no current quad but found a new one
if (start < 0 && isVisible(pixel))
{
start = x;
}
// got a current quad, but it ends here
if (start >= 0 && !isVisible(pixel))
{
// we now check if the visibility of the next row matches the one fo the current row
// if they are, we can extend the quad downwards
int endY = y + 1;
boolean sameRow = true;
while (sameRow)
{
for (int i = 0; i < w; i++)
{
int px1 = data[y * w + i];
int px2 = data[endY * w + i];
if (isVisible(px1) != isVisible(px2))
{
sameRow = false;
break;
}
}
if (sameRow)
{
endY++;
}
}
// create the quad
quads.add(genQuad(format, transform, start, y, x, endY, z, sprite, facing, color));
// update Y if all the rows match. no need to rescan
if (endY - y > 1)
{
y = endY - 1;
}
// clear current quad
start = -1;
}
}
}
return quads;
}
/**
* Scans a texture and converts it into a list of vertical strips stacked next to each other from left to right.
* The width of the strips is as big as possible.
*/
public static List<UnpackedBakedQuad> convertTextureVertical(VertexFormat format, TRSRTransformation transform, TextureAtlasSprite template, TextureAtlasSprite sprite, float z, EnumFacing facing, int color)
{
int w = template.getIconWidth();
int h = template.getIconHeight();
int[] data = template.getFrameTextureData(0)[0];
List<UnpackedBakedQuad> quads = Lists.newArrayList();
// the upper left y-position of the current quad
int start = -1;
for (int x = 0; x < w; x++)
{
for (int y = 0; y < h; y++)
{
// current pixel
int pixel = data[y * w + x];
// no current quad but found a new one
if (start < 0 && isVisible(pixel))
{
start = y;
}
// got a current quad, but it ends here
if (start >= 0 && !isVisible(pixel))
{
// we now check if the visibility of the next column matches the one fo the current row
// if they are, we can extend the quad downwards
int endX = x + 1;
boolean sameColumn = true;
while (sameColumn)
{
for (int i = 0; i < h; i++)
{
int px1 = data[i * w + x];
int px2 = data[i * w + endX];
if (isVisible(px1) != isVisible(px2))
{
sameColumn = false;
break;
}
}
if (sameColumn)
{
endX++;
}
}
// create the quad
quads.add(genQuad(format, transform, x, start, endX, y, z, sprite, facing, color));
// update X if all the columns match. no need to rescan
if (endX - x > 1)
{
x = endX - 1;
}
// clear current quad
start = -1;
}
}
}
return quads;
}
// true if alpha != 0
private static boolean isVisible(int color)
{
return (color >> 24 & 255) > 0;
}
/**
* Generates a Front/Back quad for an itemmodel. Therefore only supports facing NORTH and SOUTH.
*/
public static UnpackedBakedQuad genQuad(VertexFormat format, TRSRTransformation transform, float x1, float y1, float x2, float y2, float z, TextureAtlasSprite sprite, EnumFacing facing, int color)
{
float u1 = sprite.getInterpolatedU(x1);
float v1 = sprite.getInterpolatedV(y1);
float u2 = sprite.getInterpolatedU(x2);
float v2 = sprite.getInterpolatedV(y2);
x1 /= 16f;
y1 /= 16f;
x2 /= 16f;
y2 /= 16f;
float tmp = y1;
y1 = 1f - y2;
y2 = 1f - tmp;
return putQuad(format, transform, facing, color, x1, y1, x2, y2, z, u1, v1, u2, v2);
}
private static UnpackedBakedQuad putQuad(VertexFormat format, TRSRTransformation transform, EnumFacing side, int color,
float x1, float y1, float x2, float y2, float z,
float u1, float v1, float u2, float v2)
{
side = side.getOpposite();
UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(format);
builder.setQuadTint(-1);
builder.setQuadOrientation(side);
builder.setQuadColored();
if (side == EnumFacing.NORTH)
{
putVertex(builder, format, transform, side, x1, y1, z, u1, v2, color);
putVertex(builder, format, transform, side, x2, y1, z, u2, v2, color);
putVertex(builder, format, transform, side, x2, y2, z, u2, v1, color);
putVertex(builder, format, transform, side, x1, y2, z, u1, v1, color);
} else
{
putVertex(builder, format, transform, side, x1, y1, z, u1, v2, color);
putVertex(builder, format, transform, side, x1, y2, z, u1, v1, color);
putVertex(builder, format, transform, side, x2, y2, z, u2, v1, color);
putVertex(builder, format, transform, side, x2, y1, z, u2, v2, color);
}
return builder.build();
}
private static void putVertex(UnpackedBakedQuad.Builder builder, VertexFormat format, TRSRTransformation transform, EnumFacing side,
float x, float y, float z, float u, float v, int color)
{
Vector4f vec = new Vector4f();
for (int e = 0; e < format.getElementCount(); e++)
{
switch (format.getElement(e).getUsage())
{
case POSITION:
if (transform == TRSRTransformation.identity())
{
builder.put(e, x, y, z, 1);
}
// only apply the transform if it's not identity
else
{
vec.x = x;
vec.y = y;
vec.z = z;
vec.w = 1;
transform.getMatrix().transform(vec);
builder.put(e, vec.x, vec.y, vec.z, vec.w);
}
break;
case COLOR:
float r = ((color >> 16) & 0xFF) / 255f; // red
float g = ((color >> 8) & 0xFF) / 255f; // green
float b = ((color >> 0) & 0xFF) / 255f; // blue
float a = ((color >> 24) & 0xFF) / 255f; // alpha
builder.put(e, r, g, b, a);
break;
case UV:
if (format.getElement(e).getIndex() == 0)
{
builder.put(e, u, v, 0f, 1f);
break;
}
case NORMAL:
builder.put(e, (float) side.getFrontOffsetX(), (float) side.getFrontOffsetY(), (float) side.getFrontOffsetZ(), 0f);
break;
default:
builder.put(e);
break;
}
}
}
}

View File

@ -0,0 +1,274 @@
package net.minecraftforge.client.model;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.model.IBakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fluids.*;
import org.apache.commons.lang3.tuple.Pair;
import javax.vecmath.Matrix4f;
import javax.vecmath.Quat4f;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
public class ModelDynBucket implements IModel, IModelCustomData, IRetexturableModel
{
public static final ModelResourceLocation LOCATION = new ModelResourceLocation(new ResourceLocation("forge", "dynbucket"), "inventory");
// minimal Z offset to prevent depth-fighting
private static final float NORTH_Z_BASE = 7.496f / 16f;
private static final float SOUTH_Z_BASE = 8.504f / 16f;
private static final float NORTH_Z_FLUID = 7.498f / 16f;
private static final float SOUTH_Z_FLUID = 8.502f / 16f;
public static final IModel MODEL = new ModelDynBucket();
protected final ResourceLocation baseLocation;
protected final ResourceLocation liquidLocation;
protected final ResourceLocation coverLocation;
protected final Fluid fluid;
protected final boolean flipGas;
public ModelDynBucket()
{
this(null, null, null, FluidRegistry.WATER, false);
}
public ModelDynBucket(ResourceLocation baseLocation, ResourceLocation liquidLocation, ResourceLocation coverLocation, Fluid fluid, boolean flipGas)
{
this.baseLocation = baseLocation;
this.liquidLocation = liquidLocation;
this.coverLocation = coverLocation;
this.fluid = fluid;
this.flipGas = flipGas;
}
@Override
public Collection<ResourceLocation> getDependencies()
{
return ImmutableList.of();
}
@Override
public Collection<ResourceLocation> getTextures()
{
ImmutableSet.Builder<ResourceLocation> builder = ImmutableSet.builder();
if (baseLocation != null)
builder.add(baseLocation);
if (liquidLocation != null)
builder.add(liquidLocation);
if (coverLocation != null)
builder.add(coverLocation);
return builder.build();
}
@Override
public IFlexibleBakedModel bake(IModelState state, VertexFormat format,
Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
ImmutableMap<TransformType, TRSRTransformation> transformMap = IPerspectiveAwareModel.MapWrapper.getTransforms(state);
// if the fluid is a gas wi manipulate the initial state to be rotated 180° to turn it upside down
if (flipGas && fluid.isGaseous())
{
state = new ModelStateComposition(state, TRSRTransformation.blockCenterToCorner(new TRSRTransformation(null, new Quat4f(0, 0, 1, 0), null, null)));
}
TRSRTransformation transform = state.apply(Optional.<IModelPart>absent()).or(TRSRTransformation.identity());
TextureAtlasSprite fluidSprite = bakedTextureGetter.apply(fluid.getStill());
ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder();
if (baseLocation != null)
{
// build base (insidest)
IFlexibleBakedModel model = (new ItemLayerModel(ImmutableList.of(baseLocation))).bake(state, format, bakedTextureGetter);
builder.addAll(model.getGeneralQuads());
}
if (liquidLocation != null)
{
TextureAtlasSprite liquid = bakedTextureGetter.apply(liquidLocation);
// build liquid layer (inside)
builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, NORTH_Z_FLUID, EnumFacing.NORTH, fluid.getColor()));
builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, SOUTH_Z_FLUID, EnumFacing.SOUTH, fluid.getColor()));
}
if (coverLocation != null)
{
// cover (the actual item around the other two)
TextureAtlasSprite base = bakedTextureGetter.apply(coverLocation);
builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, NORTH_Z_BASE, base, EnumFacing.NORTH, 0xffffffff));
builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, SOUTH_Z_BASE, base, EnumFacing.SOUTH, 0xffffffff));
}
return new BakedDynBucket(this, builder.build(), fluidSprite, format, Maps.immutableEnumMap(transformMap), Maps.<String, IFlexibleBakedModel>newHashMap());
}
@Override
public IModelState getDefaultState()
{
return TRSRTransformation.identity();
}
/**
* Sets the liquid in the model.
* fluid - Name of the fluid in the FluidRegistry
* flipGas - If "true" the model will be flipped upside down if the liquid is a gas. If "false" it wont
* <p/>
* If the fluid can't be found, water is used
*/
@Override
public IModel process(ImmutableMap<String, String> customData)
{
String fluidName = customData.get("fluid");
Fluid fluid = FluidRegistry.getFluid(fluidName);
if (fluid == null) fluid = this.fluid;
boolean flip = flipGas;
if (customData.containsKey("flipGas"))
{
String flipStr = customData.get("flipGas");
if (flipStr.equals("true")) flip = true;
else if (flipStr.equals("false")) flip = false;
else
throw new IllegalArgumentException(String.format("DynBucket custom data \"flipGas\" must have value \'true\' or \'false\' (was \'%s\')", flipStr));
}
// create new model with correct liquid
return new ModelDynBucket(baseLocation, liquidLocation, coverLocation, fluid, flip);
}
/**
* Allows to use different textures for the model.
* There are 3 layers:
* base - The empty bucket/container
* fluid - A texture representing the liquid portion. Non-transparent = liquid
* cover - An overlay that's put over the liquid (optional)
* <p/>
* If no liquid is given a hardcoded variant for the bucket is used.
*/
@Override
public IModel retexture(ImmutableMap<String, String> textures)
{
ResourceLocation base = baseLocation;
ResourceLocation liquid = liquidLocation;
ResourceLocation cover = coverLocation;
if (textures.containsKey("base"))
base = new ResourceLocation(textures.get("base"));
if (textures.containsKey("fluid"))
liquid = new ResourceLocation(textures.get("fluid"));
if (textures.containsKey("cover"))
cover = new ResourceLocation(textures.get("cover"));
return new ModelDynBucket(base, liquid, cover, fluid, flipGas);
}
public enum LoaderDynBucket implements ICustomModelLoader
{
instance;
@Override
public boolean accepts(ResourceLocation modelLocation)
{
return modelLocation.getResourceDomain().equals("forge") && modelLocation.getResourcePath().contains("forgebucket");
}
@Override
public IModel loadModel(ResourceLocation modelLocation) throws IOException
{
return MODEL;
}
@Override
public void onResourceManagerReload(IResourceManager resourceManager)
{
// no need to clear cache since we create a new model instance
}
}
// the dynamic bucket is based on the empty bucket
protected static class BakedDynBucket extends ItemLayerModel.BakedModel implements ISmartItemModel, IPerspectiveAwareModel
{
private final ModelDynBucket parent;
private final Map<String, IFlexibleBakedModel> cache; // contains all the baked models since they'll never change
private final ImmutableMap<TransformType, TRSRTransformation> transforms;
public BakedDynBucket(ModelDynBucket parent,
ImmutableList<BakedQuad> quads, TextureAtlasSprite particle, VertexFormat format, ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms,
Map<String, IFlexibleBakedModel> cache)
{
super(quads, particle, format);
this.parent = parent;
this.transforms = transforms;
this.cache = cache;
}
@Override
public IBakedModel handleItemState(ItemStack stack)
{
FluidStack fluidStack = FluidContainerRegistry.getFluidForFilledItem(stack);
if (fluidStack == null)
{
if (stack.getItem() instanceof IFluidContainerItem)
{
fluidStack = ((IFluidContainerItem) stack.getItem()).getFluid(stack);
}
}
// not a fluid item apparently
if (fluidStack == null)
return this;
Fluid fluid = fluidStack.getFluid();
String name = fluid.getName();
if (!cache.containsKey(name))
{
IModel model = parent.process(ImmutableMap.of("fluid", name));
Function<ResourceLocation, TextureAtlasSprite> textureGetter;
textureGetter = new Function<ResourceLocation, TextureAtlasSprite>()
{
public TextureAtlasSprite apply(ResourceLocation location)
{
return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString());
}
};
IFlexibleBakedModel bakedModel = model.bake(new SimpleModelState(transforms), this.getFormat(), textureGetter);
cache.put(name, bakedModel);
return bakedModel;
}
return cache.get(name);
}
@Override
public Pair<? extends IFlexibleBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
return IPerspectiveAwareModel.MapWrapper.handlePerspective(this, transforms, cameraTransformType);
}
}
}

View File

@ -45,10 +45,17 @@ import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelRotation;
import net.minecraft.client.resources.model.SimpleBakedModel;
import net.minecraft.client.resources.model.WeightedBakedModel;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.IRegistry;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.ForgeModContainer;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidContainerRegistry;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.registry.GameData;
import net.minecraftforge.fml.common.registry.RegistryDelegate;
@ -177,6 +184,7 @@ public class ModelLoader extends ModelBakery
registerVariantNames();
for(Item item : GameData.getItemRegistry().typeSafeIterable())
{
// default loading
for(String s : (List<String>)getVariantNames(item))
{
ResourceLocation file = getItemLocation(s);
@ -201,6 +209,68 @@ public class ModelLoader extends ModelBakery
}
}
}
// replace vanilla bucket models if desired. done afterwards for performance reasons
if(ForgeModContainer.replaceVanillaBucketModel)
{
// empty bucket
for(String s : getVariantNames(Items.bucket))
{
ModelResourceLocation memory = new ModelResourceLocation(s, "inventory");
try
{
IModel model = getModel(new ResourceLocation("forge", "item/bucket"));
// only on successful load, otherwise continue using the old model
stateModels.put(memory, model);
}
catch(IOException e)
{
// use the original vanilla model
}
}
setBucketModel(Items.water_bucket);
setBucketModel(Items.lava_bucket);
// milk bucket only replaced if some mod adds milk
if(FluidRegistry.isFluidRegistered("milk"))
{
// can the milk be put into a bucket?
Fluid milk = FluidRegistry.getFluid("milk");
FluidStack milkStack = new FluidStack(milk, FluidContainerRegistry.BUCKET_VOLUME);
if(FluidContainerRegistry.getContainerCapacity(milkStack, new ItemStack(Items.bucket)) == FluidContainerRegistry.BUCKET_VOLUME)
{
setBucketModel(Items.milk_bucket);
}
}
else
{
// milk bucket if no milk fluid is present
for(String s : getVariantNames(Items.milk_bucket))
{
ModelResourceLocation memory = new ModelResourceLocation(s, "inventory");
try
{
IModel model = getModel(new ResourceLocation("forge", "item/bucket_milk"));
// only on successful load, otherwise continue using the old model
stateModels.put(memory, model);
}
catch(IOException e)
{
// use the original vanilla model
}
}
}
}
}
private void setBucketModel(Item item)
{
for(String s : getVariantNames(item))
{
ModelResourceLocation memory = new ModelResourceLocation(s, "inventory");
IModel model = stateModels.get(ModelDynBucket.LOCATION);
stateModels.put(memory, model);
}
}
public IModel getModel(ResourceLocation location) throws IOException

View File

@ -34,6 +34,7 @@ public class ModelLoaderRegistry
registerLoader(ModelFluid.FluidLoader.instance);
registerLoader(ItemLayerModel.Loader.instance);
registerLoader(MultiLayerModel.Loader.instance);
registerLoader(ModelDynBucket.LoaderDynBucket.instance);
}
/*

View File

@ -5,6 +5,7 @@
package net.minecraftforge.common;
import static net.minecraftforge.common.config.Configuration.CATEGORY_CLIENT;
import static net.minecraftforge.common.config.Configuration.CATEGORY_GENERAL;
import java.io.File;
@ -68,6 +69,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
public static int defaultSpawnFuzz = 20;
public static boolean defaultHasSpawnFuzz = true;
public static boolean forgeLightPipelineEnabled = true;
public static boolean replaceVanillaBucketModel = true;
private static Configuration config;
private static ForgeModContainer INSTANCE;
@ -244,6 +246,16 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
config.setCategoryPropertyOrder(VERSION_CHECK_CAT, propOrder);
// Client-Side only properties
propOrder = new ArrayList<String>();
prop = config.get(Configuration.CATEGORY_CLIENT, "replaceVanillaBucketModel", Boolean.FALSE,
"Replace the vanilla bucket models with Forges own dynamic bucket model. Unifies bucket visuals if a mod uses the Forge bucket model.");
prop.setLanguageKey("forge.configgui.replaceBuckets").setRequiresMcRestart(true);
replaceVanillaBucketModel = prop.getBoolean(Boolean.FALSE);
propOrder.add(prop.getName());
config.setCategoryPropertyOrder(CATEGORY_CLIENT, propOrder);
if (config.hasChanged())
{
config.save();

View File

@ -48,6 +48,7 @@ import net.minecraftforge.fml.relauncher.FMLInjectionData;
public class Configuration
{
public static final String CATEGORY_GENERAL = "general";
public static final String CATEGORY_CLIENT = "client";
public static final String ALLOWED_CHARS = "._-";
public static final String DEFAULT_ENCODING = "UTF-8";
public static final String CATEGORY_SPLITTER = ".";

View File

@ -0,0 +1,18 @@
{
"forge_marker": 1,
"variants": {
"inventory": {
"model": "forge:forgebucket",
"textures": {
"base": "forge:items/bucket_base",
"fluid": "forge:items/bucket_fluid",
"cover": "forge:items/bucket_cover"
},
"transform": "forge:default-item",
"custom": {
"fluid": "water",
"flipGas": true
}
}
}
}

View File

@ -12,6 +12,8 @@ forge.update.beta.2=Major issues may arise, verify before reporting.
forge.configgui.forgeConfigTitle=Minecraft Forge Configuration
forge.configgui.ctgy.forgeGeneralConfig.tooltip=This is where you can edit the settings contained in forge.cfg.
forge.configgui.ctgy.forgeGeneralConfig=General Settings
forge.configgui.ctgy.forgeClientConfig.tooltip=Settings in the forge.cfg that only concern the local client. Usually graphical settings.
forge.configgui.ctgy.forgeClientConfig=Client Settings
forge.configgui.ctgy.forgeChunkLoadingConfig.tooltip=This is where you can edit the settings contained in forgeChunkLoading.cfg.
forge.configgui.ctgy.forgeChunkLoadingConfig=Forge Chunk Loader Default Settings
forge.configgui.ctgy.forgeChunkLoadingModConfig.tooltip=This is where you can define mod-specific override settings that will be used instead of the defaults.
@ -43,6 +45,7 @@ forge.configgui.zombieBaseSummonChance.tooltip=Base zombie summoning spawn chanc
forge.configgui.zombieBaseSummonChance=Zombie Summon Chance
forge.configgui.stencilbits=Enable GL Stencil Bits
forge.configgui.spawnfuzz=Respawn Fuzz Diameter
forge.configgui.replaceBuckets=Use Forges' bucket model
forge.configgui.modID.tooltip=The mod ID that you want to define override settings for.
forge.configgui.modID=Mod ID

View File

@ -0,0 +1,18 @@
{
"parent": "builtin/generated",
"textures": {
"layer0": "forge:items/bucket_base"
},
"display": {
"thirdperson": {
"rotation": [ -90, 0, 0 ],
"translation": [ 0, 1, -3 ],
"scale": [ 0.55, 0.55, 0.55 ]
},
"firstperson": {
"rotation": [ 0, -135, 25 ],
"translation": [ 0, 4, 2 ],
"scale": [ 1.7, 1.7, 1.7 ]
}
}
}

View File

@ -0,0 +1,20 @@
{
"parent": "builtin/generated",
"textures": {
"layer0": "forge:items/bucket_base",
"layer1": "forge:items/bucket_fluid", # doubles as milk
"layer2": "forge:items/bucket_cover"
},
"display": {
"thirdperson": {
"rotation": [ -90, 0, 0 ],
"translation": [ 0, 1, -3 ],
"scale": [ 0.55, 0.55, 0.55 ]
},
"firstperson": {
"rotation": [ 0, -135, 25 ],
"translation": [ 0, 4, 2 ],
"scale": [ 1.7, 1.7, 1.7 ]
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

View File

@ -0,0 +1,179 @@
package net.minecraftforge.debug;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.ItemMeshDefinition;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.ModelDynBucket;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.debug.ModelFluidDebug.TestFluid;
import net.minecraftforge.debug.ModelFluidDebug.TestGas;
import net.minecraftforge.event.entity.player.FillBucketEvent;
import net.minecraftforge.fluids.*;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.Event.Result;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.GameRegistry;
import java.util.List;
@Mod(modid = "DynBucketTest", version = "0.1", dependencies = "after:" + ModelFluidDebug.MODID)
public class DynBucketTest
{
public static final Item dynBucket = new DynBucket();
public static final Item dynBottle = new DynBottle();
@SidedProxy(clientSide = "net.minecraftforge.debug.DynBucketTest$ClientProxy", serverSide = "net.minecraftforge.debug.DynBucketTest$CommonProxy")
public static CommonProxy proxy;
public static class CommonProxy
{
void setupModels()
{
}
}
public static class ClientProxy extends CommonProxy
{
@Override
void setupModels()
{
ModelLoader.setCustomMeshDefinition(dynBucket, new ItemMeshDefinition()
{
@Override
public ModelResourceLocation getModelLocation(ItemStack stack)
{
return ModelDynBucket.LOCATION;
}
});
ModelBakery.addVariantName(dynBucket, "forge:dynbucket");
ModelLoader.setCustomMeshDefinition(dynBottle, new ItemMeshDefinition()
{
@Override
public ModelResourceLocation getModelLocation(ItemStack stack)
{
return new ModelResourceLocation(new ResourceLocation("forge", "dynbottle"), "inventory");
}
});
ModelBakery.addVariantName(dynBottle, "forge:dynbottle");
}
}
@EventHandler
public void preInit(FMLPreInitializationEvent event)
{
GameRegistry.registerItem(dynBucket, "dynbucket");
GameRegistry.registerItem(dynBottle, "dynbottle");
// register fluid containers
int i = 0;
//registerFluidContainer(FluidRegistry.WATER, i++);
//registerFluidContainer(FluidRegistry.LAVA, i++);
registerFluidContainer(FluidRegistry.getFluid(TestFluid.name), i++);
registerFluidContainer(FluidRegistry.getFluid(TestGas.name), i++);
i = 0;
registerFluidContainer2(FluidRegistry.WATER, i++);
registerFluidContainer2(FluidRegistry.LAVA, i++);
registerFluidContainer2(FluidRegistry.getFluid(TestFluid.name), i++);
registerFluidContainer2(FluidRegistry.getFluid(TestGas.name), i++);
// Set TestFluidBlocks blockstate to use milk instead of testfluid for the texture to be loaded
FluidContainerRegistry.registerFluidContainer(FluidRegistry.getFluid("milk"), new ItemStack(Items.milk_bucket), FluidContainerRegistry.EMPTY_BUCKET);
proxy.setupModels();
MinecraftForge.EVENT_BUS.register(this);
}
private void registerFluidContainer(Fluid fluid, int meta)
{
if (fluid == null)
return;
FluidStack fs = new FluidStack(fluid, FluidContainerRegistry.BUCKET_VOLUME);
ItemStack stack = new ItemStack(dynBucket, 1, meta);
FluidContainerRegistry.registerFluidContainer(fs, stack, new ItemStack(Items.bucket));
}
private void registerFluidContainer2(Fluid fluid, int meta)
{
if (fluid == null)
return;
FluidStack fs = new FluidStack(fluid, 250);
ItemStack stack = new ItemStack(dynBottle, 1, meta);
FluidContainerRegistry.registerFluidContainer(fs, stack, new ItemStack(Items.glass_bottle));
}
@SubscribeEvent
public void onBucketFill(FillBucketEvent event)
{
IBlockState state = event.world.getBlockState(event.target.getBlockPos());
if (state.getBlock() instanceof IFluidBlock)
{
Fluid fluid = ((IFluidBlock) state.getBlock()).getFluid();
FluidStack fs = new FluidStack(fluid, FluidContainerRegistry.BUCKET_VOLUME);
ItemStack filled = FluidContainerRegistry.fillFluidContainer(fs, event.current);
if (filled != null)
{
event.result = filled;
event.setResult(Result.ALLOW);
}
}
}
public static class DynBucket extends Item
{
public DynBucket()
{
setUnlocalizedName("dynbucket");
setMaxStackSize(1);
setHasSubtypes(true);
setCreativeTab(CreativeTabs.tabMisc);
}
@Override
public void getSubItems(Item itemIn, CreativeTabs tab, List<ItemStack> subItems)
{
for (int i = 0; i < 4; i++)
{
ItemStack bucket = new ItemStack(this, 1, i);
if (FluidContainerRegistry.isFilledContainer(bucket))
subItems.add(bucket);
}
}
}
public static class DynBottle extends Item
{
public DynBottle()
{
setUnlocalizedName("dynbottle");
setMaxStackSize(1);
setHasSubtypes(true);
setCreativeTab(CreativeTabs.tabMisc);
}
@Override
public void getSubItems(Item itemIn, CreativeTabs tab, List<ItemStack> subItems)
{
for (int i = 0; i < 4; i++)
{
ItemStack bucket = new ItemStack(this, 1, i);
if (FluidContainerRegistry.isFilledContainer(bucket))
subItems.add(bucket);
}
}
}
}

View File

@ -29,6 +29,8 @@ public class ModelFluidDebug
@SidedProxy(serverSide = "net.minecraftforge.debug.ModelFluidDebug$CommonProxy", clientSide = "net.minecraftforge.debug.ModelFluidDebug$ClientProxy")
public static CommonProxy proxy;
public static final Fluid milkFluid = new Fluid("milk", new ResourceLocation("forge", "blocks/milk_still"), new ResourceLocation("forge", "blocks/milk_flow"));
@EventHandler
public void preInit(FMLPreInitializationEvent event) { proxy.preInit(event); }
@ -38,8 +40,10 @@ public class ModelFluidDebug
{
FluidRegistry.registerFluid(TestFluid.instance);
FluidRegistry.registerFluid(TestGas.instance);
FluidRegistry.registerFluid(milkFluid);
GameRegistry.registerBlock(TestFluidBlock.instance, TestFluidBlock.name);
GameRegistry.registerBlock(TestGasBlock.instance, TestGasBlock.name);
GameRegistry.registerBlock(MilkFluidBlock.instance, MilkFluidBlock.name);
}
}
@ -47,6 +51,7 @@ public class ModelFluidDebug
{
private static ModelResourceLocation fluidLocation = new ModelResourceLocation(MODID.toLowerCase() + ":" + TestFluidBlock.name, "fluid");
private static ModelResourceLocation gasLocation = new ModelResourceLocation(MODID.toLowerCase() + ":" + TestFluidBlock.name, "gas");
private static ModelResourceLocation milkLocation = new ModelResourceLocation(MODID.toLowerCase() + ":" + TestFluidBlock.name, "milk");
@Override
public void preInit(FMLPreInitializationEvent event)
@ -54,8 +59,10 @@ public class ModelFluidDebug
super.preInit(event);
Item fluid = Item.getItemFromBlock(TestFluidBlock.instance);
Item gas = Item.getItemFromBlock(TestGasBlock.instance);
Item milk = Item.getItemFromBlock(MilkFluidBlock.instance);
ModelBakery.addVariantName(fluid);
ModelBakery.addVariantName(gas);
ModelBakery.addVariantName(milk);
ModelLoader.setCustomMeshDefinition(fluid, new ItemMeshDefinition()
{
public ModelResourceLocation getModelLocation(ItemStack stack)
@ -70,6 +77,13 @@ public class ModelFluidDebug
return gasLocation;
}
});
ModelLoader.setCustomMeshDefinition(milk, new ItemMeshDefinition()
{
public ModelResourceLocation getModelLocation(ItemStack stack)
{
return milkLocation;
}
});
ModelLoader.setCustomStateMapper(TestFluidBlock.instance, new StateMapperBase()
{
protected ModelResourceLocation getModelResourceLocation(IBlockState state)
@ -84,6 +98,13 @@ public class ModelFluidDebug
return gasLocation;
}
});
ModelLoader.setCustomStateMapper(MilkFluidBlock.instance, new StateMapperBase()
{
protected ModelResourceLocation getModelResourceLocation(IBlockState state)
{
return milkLocation;
}
});
}
}
@ -136,6 +157,19 @@ public class ModelFluidDebug
}
}
public static final class MilkFluidBlock extends BlockFluidClassic
{
public static final MilkFluidBlock instance = new MilkFluidBlock();
public static final String name = "MilkFluidBlock";
private MilkFluidBlock()
{
super(milkFluid, Material.water);
setCreativeTab(CreativeTabs.tabBlock);
setUnlocalizedName(MODID + ":" + name);
}
}
public static final class TestGasBlock extends BlockFluidClassic
{
public static final TestGasBlock instance = new TestGasBlock();

View File

@ -0,0 +1,18 @@
{
"forge_marker": 1,
"variants": {
"inventory": {
"model": "forge:forgebucket",
"textures": {
"base": "minecraft:items/potion_bottle_empty",
"fluid": "minecraft:items/potion_overlay",
"cover": "minecraft:items/potion_bottle_empty"
},
"transform": "forge:default-item",
"custom": {
"fluid": "water",
"flipGas": false
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,3 @@
{
"animation": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

View File

@ -8,6 +8,10 @@
"gas": {
"model": "forge:fluid",
"custom": { "fluid": "testgas" }
},
"milk": {
"model": "forge:fluid",
"custom": { "fluid": "milk" }
}
}
}