Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Update the Base Metals and OreSpawn dependencies and move the Java packages to z
Use OreSpawn 4 for ore generation, with new depths and deposit shapes for the seven minerals. Existing chunks are not regenerated.
Fix material and fluid switches, charcoal ownership, disabled-content recipes and achievements, and client models and molten textures.
Add restart-required material settings to Forge Mods and OreSpawn Mods, plus automated build, compatibility and world-generation checks.
Use KiriCattus's ore overlays for all seven ores. Generate the models during the build, with support for full ore textures as well.

Known issues:
- OreSpawn 4.1.2 can add a second lithium rule when an OS1-imported world is moved without its original global import file.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ materials, ingots, blends and molten forms. Charcoal uses the material supplied
by Base Metals. Silicon is made from sand and coal or charcoal dust; it does
not generate as ore.

The seven ores use KiriCattus's overlays on Minecraft's stone texture.

This branch targets Minecraft 1.10.2 and Forge 12.18.3.2511 or later. It requires
Base Metals 2.5.1.110021 and OreSpawn 4.1.2.110021, or compatible versions in the
declared ranges. Mineralogy is optional. Tinkers' Construct adds melting and
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ tasks.named('jar', Jar) {
}

ext.baseMineralsBuild = [developmentOutput: developmentOutput].asImmutable()
apply from: 'gradle/ore-models.gradle'
apply from: 'gradle/release/artifacts.gradle'
apply from: 'gradle/release/publishing.gradle'
apply from: 'gradle/verification/runtime.gradle'
Expand Down
13 changes: 13 additions & 0 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ ForgeGradle's `source-sets-not-merged-for-eclipse` diagnostic refers to its own
automatic merge. This project deliberately merges production output itself;
the generated profiles, Buildship model and Java 8 Eclipse compiler are audited.

## Ore models

`generateOreModels` writes the ore block models to `build/generated/ore-models`.
The normal build includes them in the game and sources jars. Blockstates and
inventory models use the existing model names.

The seven ores use KiriCattus's transparent PNGs in
`src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/`. The build
layers each PNG over Minecraft stone for the block and inventory models.
An ore with only a full texture in `textures/blocks/` uses that texture on a
single cube. To switch it to an overlay, put a PNG with the same name in
`ore_overlays/` and rebuild.

## Verification

`test` checks native declarations, configuration latching and namespace compatibility.
Expand Down
77 changes: 77 additions & 0 deletions gradle/ore-models.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import groovy.json.JsonOutput

// Forge 1.10 predates Minecraft's data generator. Generate models during the build.
def assets = layout.projectDirectory.dir('src/main/resources/assets/baseminerals')
def textures = assets.dir('textures/blocks')
def solidTextures = fileTree(textures) { include '*_ore.png' }
def overlayTextures = fileTree(textures.dir('ore_overlays')) { include '*_ore.png' }
def oreStates = fileTree(assets.dir('blockstates')) {
include '*_ore.json'
// Historical placeholders with no registered ore blocks.
exclude 'charcoal_ore.json', 'silicon_ore.json'
}
def generated = layout.buildDirectory.dir('generated/ore-models')

def generateOreModels = tasks.register('generateOreModels') {
group = 'build'
description = 'Generates ore models from the available overlay or solid textures.'
inputs.files(oreStates, solidTextures, overlayTextures).withPathSensitivity(PathSensitivity.RELATIVE)
outputs.dir(generated)

doLast {
def names = oreStates.files.collect { it.name - '.json' }.sort()
def overlays = overlayTextures.files.collect { it.name - '.png' }.toSet()
def solid = solidTextures.files.collect { it.name - '.png' }.toSet()
def extra = overlays - names
def missing = names.findAll { !overlays.contains(it) && !solid.contains(it) }
if (extra || missing) {
throw new GradleException("Invalid ore textures. Missing textures: ${missing}. Unmatched overlays: ${extra}.")
}

def directory = generated.get().dir('assets/baseminerals/models/block').asFile
directory.mkdirs()
def writeModel = { String name, Map model ->
new File(directory, "${name}.json").setText(JsonOutput.prettyPrint(JsonOutput.toJson(model)) + '\n', 'UTF-8')
}
def cube = { Number from, Number to, String texture ->
def faces = ['down', 'up', 'north', 'south', 'west', 'east'].collectEntries { face ->
[(face): [texture: texture, cullface: face, uv: [0, 0, 16, 16]]]
}
[from: [from, from, from], to: [to, to, to], faces: faces]
}
writeModel('ore_overlay', [
parent: 'minecraft:block/block',
elements: [cube(0, 16, '#base'), cube(-0.05, 16.05, '#overlay')]
])
names.each { name ->
if (overlays.contains(name)) {
writeModel(name, [
parent: 'baseminerals:block/ore_overlay',
textures: [particle: 'minecraft:blocks/stone', base: 'minecraft:blocks/stone',
overlay: "baseminerals:blocks/ore_overlays/${name}"]
])
} else {
writeModel(name, [
parent: 'block/cube_all',
textures: [all: "baseminerals:blocks/${name}"]
])
}
}
directory.listFiles().findAll {
it.isFile() && it.name.endsWith('_ore.json') && !names.contains(it.name - '.json')
}.each {
if (!it.delete()) throw new GradleException("Could not remove stale generated ore model ${it.name}")
}
}
}

tasks.named('processResources', ProcessResources) {
dependsOn generateOreModels
from generated
duplicatesStrategy = DuplicatesStrategy.FAIL
}

tasks.named('sourcesJar', Jar) {
dependsOn generateOreModels
from generated
}
2 changes: 1 addition & 1 deletion gradle/verification/runtime.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ tasks.named('test', Test) {

tasks.register('verifyProjectResources', Exec) {
group = 'verification'
dependsOn tasks.named('test')
dependsOn tasks.named('test'), tasks.named('processResources')
doFirst {
commandLine providers.gradleProperty('pythonExecutable').getOrElse('python3'),
'tools/validation/legacy110/audit_resources.py', '--mirror',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package zone.moddev.mc.baseminerals.block;

import com.mcmoddev.lib.block.BlockMMDOre;
import com.mcmoddev.lib.material.MMDMaterial;
import net.minecraft.util.BlockRenderLayer;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

/** Uses the shared ore behaviour with alpha testing for overlay models. */
public final class OverlayOreBlock extends BlockMMDOre {
public OverlayOreBlock(MMDMaterial material) {
super(material);
}

@Override
@SideOnly(Side.CLIENT)
public BlockRenderLayer getBlockLayer() {
return BlockRenderLayer.CUTOUT_MIPPED;
}
}
17 changes: 14 additions & 3 deletions src/main/java/zone/moddev/mc/baseminerals/init/Blocks.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package zone.moddev.mc.baseminerals.init;

import zone.moddev.mc.baseminerals.init.ItemGroups;

import java.util.Arrays;
import java.util.List;

import zone.moddev.mc.baseminerals.block.OverlayOreBlock;
import zone.moddev.mc.baseminerals.data.MaterialNames;
import com.mcmoddev.lib.data.Names;
import com.mcmoddev.lib.data.SharedStrings;
import com.mcmoddev.lib.init.Materials;
import com.mcmoddev.lib.material.MMDMaterial;
import com.mcmoddev.lib.util.Oredicts;
import net.minecraft.block.Block;

/**
* This class initializes all blocks in Base Minerals.
Expand Down Expand Up @@ -40,12 +41,22 @@ public static void init() {
final MMDMaterial material = Materials.getMaterialByName(materialName);

create(Names.BLOCK, material);
create(Names.ORE, material);
createOre(material);
});
/*
if (zone.moddev.mc.baseminerals.util.Config.isEnabled(MaterialNames.SILICON) && Materials.hasMaterial(MaterialNames.SILICON)) {
create(Names.BLOCK, Materials.getMaterialByName(MaterialNames.SILICON));
}
*/
}

private static void createOre(MMDMaterial material) {
if (!material.hasOre() || material.hasBlock(Names.ORE) || !isNameEnabled(Names.ORE)) {
return;
}
final Block ore = addBlock(new OverlayOreBlock(material), Names.ORE, material,
ItemGroups.getTab(SharedStrings.TAB_BLOCKS));
material.addNewBlock(Names.ORE.toString(), ore);
Oredicts.registerOre("ore" + material.getCapitalizedName(), ore);
}
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

27 changes: 25 additions & 2 deletions tools/validation/legacy110/audit_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
def main():
parser=argparse.ArgumentParser();parser.add_argument('--mirror',type=Path);args=parser.parse_args()
count=0
for path in (ROOT/'src/main/resources').rglob('*'):
resources=ROOT/'build/resources/main'
for path in resources.rglob('*'):
if path.is_file() and (path.suffix in ('.json','.mcmeta') or path.name=='mcmod.info'):
json.loads(path.read_text(encoding='utf-8-sig'));count+=1
specs=json.loads((ROOT/'gradle/dependencies.json').read_text())
Expand All @@ -21,7 +22,29 @@ def main():
Draft202012Validator(schema,registry=registry).validate(provider)
assert len(provider['ores'])==7
assert all(not provider.get(section) for section in ['rocks','terrain_dimensions','geomes','fluid_deposits','biome_palettes'])
assets=ROOT/'src/main/resources/assets/baseminerals'
assets=resources/'assets/baseminerals'
source_textures=ROOT/'src/main/resources/assets/baseminerals/textures/blocks'
for ore in provider['ores'].values():
name=ore['block'].split(':',1)[1]
state=json.loads((assets/'blockstates'/f'{name}.json').read_text())
item=json.loads((assets/'models/item'/f'{name}.json').read_text())
model=json.loads((assets/'models/block'/f'{name}.json').read_text())
assert state['variants']['normal']['model']==f'baseminerals:{name}',name
assert item['parent']==f'baseminerals:block/{name}',name
textures=assets/'textures/blocks'
if (textures/'ore_overlays'/f'{name}.png').is_file():
assert model['parent']=='baseminerals:block/ore_overlay',name
assert (assets/'models/block/ore_overlay.json').is_file(),name
assert model['textures']=={
'particle':'minecraft:blocks/stone','base':'minecraft:blocks/stone',
'overlay':f'baseminerals:blocks/ore_overlays/{name}'
},name
if not (source_textures/f'{name}.png').is_file():
assert not (textures/f'{name}.png').exists(),f'{name}: stale solid texture'
else:
assert (textures/f'{name}.png').is_file(),f'{name}: missing solid texture'
assert model['parent']=='block/cube_all',name
assert model['textures']=={'all':f'baseminerals:blocks/{name}'},name
language=dict(line.split('=',1) for line in (assets/'lang/en_US.lang').read_text().splitlines() if '=' in line and not line.startswith('#'))
for name in ['charcoal','lithium','niter','phosphorus','potash','salt','saltpeter','silicon','sulfur']:
assert 'config.baseminerals.enable_'+name in language,name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public static final class Checks {
if(!"baseminerals".equals(item.getRegistryName().getResourceDomain()))continue;
require(mc.getRenderItem().getItemModelMesher().getItemModel(new ItemStack(item))!=mc.getRenderItem().getItemModelMesher().getModelManager().getMissingModel(),"Missing baked item: "+item.getRegistryName());
}
OreModelChecks.verify(mc);
check=mc.getIntegratedServer().addScheduledTask(()->{try{BaseMineralsProbe.run(mc.getIntegratedServer());}catch(Exception e){throw new RuntimeException(e);}});
} else if(state==3&&check!=null&&check.isDone()) {
check.get();screenshot(mc,"integrated-world.png");integrated=mc.getIntegratedServer();integrated.initiateShutdown();state=4;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package review.minerals;

import com.mcmoddev.lib.init.Materials;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.resources.IResource;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import net.minecraftforge.oredict.OreDictionary;
import zone.moddev.mc.baseminerals.util.Config;

import static review.minerals.ClientChecks.require;

/** Checks the texture and geometry of the baked block and inventory models. */
final class OreModelChecks {
static void verify(Minecraft minecraft) throws IOException {
int overlays = 0;
int solids = 0;
for (String material : BaseMineralsProbe.ORES) {
if (!Config.isEnabled(material)) continue;
String name = material + "_ore";
ResourceLocation id = new ResourceLocation("baseminerals", name);
Block ore = ForgeRegistries.BLOCKS.getValue(id);
require(ore != null && id.equals(ore.getRegistryName()), "Wrong ore block ID: " + id);
Item item = Item.getItemFromBlock(ore);
require(id.equals(item.getRegistryName()), "Wrong ore item ID: " + id);
String oreKey = "ore" + Materials.getMaterialByName(material).getCapitalizedName();
require(OreDictionary.getOres(oreKey).stream().anyMatch(stack -> stack.getItem() == item),
"Missing Ore Dictionary entry: " + oreKey);
require(ore.getBlockLayer() == BlockRenderLayer.CUTOUT_MIPPED, "Wrong ore render layer: " + id);

String overlay = "baseminerals:blocks/ore_overlays/" + name;
boolean layered = hasOverlay(minecraft, name);
String base = layered ? "minecraft:blocks/stone" : "baseminerals:blocks/" + name;
IBlockState state = ore.getDefaultState();
IBakedModel placed = minecraft.getBlockRendererDispatcher().getBlockModelShapes().getModelForState(state);
IBakedModel inventory = minecraft.getRenderItem().getItemModelMesher().getItemModel(new ItemStack(item));
verifyModel(placed, state, base, layered ? overlay : null);
verifyModel(inventory, null, base, layered ? overlay : null);
if (layered) overlays++;
else solids++;
}
System.out.println("BASE_MINERALS_ORE_MODELS_PASS solids=" + solids + " overlays=" + overlays);
}

private static boolean hasOverlay(Minecraft minecraft, String name) throws IOException {
ResourceLocation path = new ResourceLocation("baseminerals", "textures/blocks/ore_overlays/" + name + ".png");
try (IResource resource = minecraft.getResourceManager().getResource(path)) {
return true;
} catch (FileNotFoundException missing) {
return false;
}
}

private static void verifyModel(IBakedModel model, IBlockState state, String base, String overlay) {
require(base.equals(model.getParticleTexture().getIconName()), "Wrong ore particles: " + base);
for (EnumFacing face : EnumFacing.values()) {
List<BakedQuad> quads = model.getQuads(state, face, 0);
require(quads.size() == (overlay == null ? 1 : 2), "Wrong ore face count: " + base + " " + face);
verifyFace(quads, face, base, 0.0F);
if (overlay != null) verifyFace(quads, face, overlay, 0.05F / 16.0F);
}
}

private static void verifyFace(List<BakedQuad> quads, EnumFacing face, String texture, float offset) {
BakedQuad quad = quads.stream().filter(candidate -> texture.equals(candidate.getSprite().getIconName()))
.findFirst().orElseThrow(() -> new AssertionError("Missing ore sprite: " + texture));
int axis = face.getAxis().ordinal();
float expected = face.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE ? 1.0F + offset : -offset;
int[] data = quad.getVertexData();
for (int vertex = 0; vertex < 4; vertex++) {
float depth = Float.intBitsToFloat(data[vertex * (data.length / 4) + axis]);
require(Math.abs(depth - expected) < 0.0001F, "Wrong ore face depth: " + texture + " " + face);
}
}
}
Loading