diff --git a/CHANGELOG.txt b/CHANGELOG.txt index dabdb6c..3379ab0 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,13 @@ +0.1.11.110021 - Alpha + +I've added iron guttering and downpipes to the Rye kit. + +- Fit half-round gutters and outlets to the clay fascia. The pieces join along the eaves. +- Use the offsets to tuck a downpipe back under a half-block or full-block overhang. +- Finish the pipe with a short angled shoe so it ends just above the paving and + sends the drips away from the wall. +- In the rain, exposed outlets and the open ends of connected downpipes drip. + 0.1.10.110021 - Alpha I've added the clay tiles and timber roof trim I've been trying out on the Rye cottages. diff --git a/README.md b/README.md index e3f9290..4b147b6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ All six vanilla wood types are available. Cob comes in blocks, slabs and stairs, thatch, slate and clay tile roofs have automatic corners and separate ridge caps. Clay tiles also come as full and half-depth hanging panels for gables and upper walls. Finish clay roofs with timber fascia boards at the eaves and matching trim along the gable and ridge. +Fit iron guttering to the fascia and run a downpipe under the overhang. Connected outlets and pipes drip in the rain. Leaded windows bring some light into the houses. Choose diamond or rectangular leading, match the timber and daub to your walls, then stack sections to make taller casements. diff --git a/docs/BUILDING-GUIDE.md b/docs/BUILDING-GUIDE.md index dec792b..68039c8 100644 --- a/docs/BUILDING-GUIDE.md +++ b/docs/BUILDING-GUIDE.md @@ -3,10 +3,10 @@ Permanent building components for the Iron Age series, starting on Minecraft 1.10.2 Forge. Architecture runs independently of Iron Age Furniture. -Version 0.1.10.110021 contains raw and limewashed cob blocks, +Version 0.1.11.110021 contains raw and limewashed cob blocks, stairs and slabs; modular wattle-and-daub walls in all six vanilla woods; and smooth thatch, slate and clay tile roofs with automatic corners and separate ridge caps, plus jettied-storey walls, -timber floors, support brackets, hanging clay tiles and openable leaded windows. There are 517 visible +timber floors, support brackets, hanging clay tiles and openable leaded windows. There are 525 visible variants including the wood and finish choices. Architecture remains standalone. ## Wattle and daub @@ -75,6 +75,28 @@ Use full hanging tiles to fill the stepped gable beneath a clay roof. Thin hangi tiles share the half-block plane with straight thin timber panels and thin leaded windows. Place against one of these pieces to inherit its occupied half. Sneak to choose the half manually. +## Guttering + +Use a gutter or outlet on the outside face of a bare clay fascia. Level sections join +and cap their exposed ends. Sneak and use an empty hand on the metal to recover the fitting. + +Start a pipe against the underside of the outlet and build down from the pipe end. The +eave offset brings the pipe towards the wall. For a half-block step, use the half-block +offset beneath an outside pipe in a clear bay beside the thin wall. For a full-block jetty, +run the pipe close to the upper wall, place the outer elbow below it and fit the inner elbow +on the wall side of that elbow. Continue down from the inner elbow. To select the position +of a straight pipe by hand, sneak, face the wall and click where you want it to sit. + +Three iron ingots across a row make six gutters. Add an ingot to a gutter for an outlet. +Three iron ingots in a column make six pipes. Craft a pipe with a stick for an eave offset. +Craft an offset alone to cycle through the eave, half-block, outer and inner shapes. + +Craft a straight downpipe alone to make a shoe. Fit it under the last pipe, in the +block above the paving. The short bend turns out from the wall, with enough room +under its mouth for rain to drip onto the cobbles. + +An exposed outlet drips during rain. A connected pipe takes those drips to its open foot. + ## Leaded windows Use full-depth surrounds in full walls and half-depth surrounds in thin walls. Both come @@ -213,9 +235,9 @@ switches still apply separately. All content families are enabled by default. Framed panels require the cob family. The `blocks.leadedWindows` switch controls both glazing and windows and requires timber frames and cob. Era recipe filtering is separate from this content switch. -The `blocks.clayTiles` switch controls the clay slopes, ridge caps, trim and both hanging panels. +The `blocks.clayTiles` switch controls the clay slopes, ridge caps, trim, hanging panels and iron guttering. Thatch and timber-bearing walls burn; plain daub, cob and slate do not. There is no world generation or collapse simulation. See [development instructions](DEVELOPMENT.md) for builds, Eclipse and disposable runtime checks. This is an early alpha. Automated checks and manual visual/gameplay review are recorded separately. -The Rye Roof Trim Preview shows how the fascia and gable trim fit on three houses stepping down a cobbled street. +The Rye Downpipe Shoe Preview shows both pipe offsets and the shoe on a cobbled street. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index a1b9045..a9c0026 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -164,7 +164,7 @@ resource generator and item tooltips. Run scripts/invoke-runtime-probe.ps1 after prepareProbe. Modes cover core, reload, furniture, disabled, windows-disabled, legacy, legacy-daub, legacy-jetties, iron-age, late-medieval, -mixed-eras, no-eras, invalid-era, clay-disabled and clay-only. +mixed-eras, no-eras, invalid-era, clay-disabled, clay-only and legacy-fascia. Only Forge/server libraries and the explicitly named Furniture JAR are read from retained runtimes; no existing worlds or configs are copied or changed. Each mode has an isolated runtime except reload, which verifies the saved core world. Servers bind to loopback. Keep failed logs when correcting failures. @@ -185,11 +185,13 @@ entity clearance, owner/helper rays, player passage, harvesting, explosions and Clay tile checks cover cap joining, four wall orientations, half-depth collision and placement alongside windows. Roof trim checks cover both verge hands in every facing, clearance behind the fascia and the ridge end join. The clay-only mode checks that crafting works with the -other building families switched off. Use a fresh runtime root for each candidate. +other building families switched off. Guttering checks cover fitting and recovery, joined +ends, both offsets and the rain path. The legacy-fascia mode opens a copy of the roof trim +preview and verifies the original bare fascias. Use a fresh runtime root for each candidate. The optional client probe requires iaa.probe=true, iaa.clientProbe=true and iaa.keepRunning=true. -It creates its own Rye Roof Trim Preview, checks mesh and material variants and both -creative tabs, captures three views of the clay tile street and then shuts down. +It creates its own Rye Downpipe Shoe Preview, checks mesh and material variants and both +creative tabs, captures dry and rain views of the cottage row, then shuts down. Every visible item must also provide a translated crafting hint without tooltip errors. The client script's PreviewName argument selects a separate save without replacing an earlier preview. After verification copy the preview save into the ordinary run/saves directory and launch without the probe. diff --git a/gradle.properties b/gradle.properties index 60aed6c..eda3dfe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.parallel=false net.minecraftforge.gradle.merge-source-sets=false minecraft_version=1.10.2 forge_version=12.18.3.2511 -mod_version=0.1.10.110021 +mod_version=0.1.11.110021 mod_group=zone.moddev.mc mod_id=ironagearchitecture mod_name=Iron Age Architecture diff --git a/scripts/generate-resources.cjs b/scripts/generate-resources.cjs index a7085a0..52cbb46 100644 --- a/scripts/generate-resources.cjs +++ b/scripts/generate-resources.cjs @@ -138,11 +138,36 @@ for (const [material,label] of [['thatch','Thatch'],['slate','Slate'],['clay_til for (const [part,label] of [['fascia','Fascia'],['verge','Verge'],['ridge_end','Ridge End']]) { const name = `clay_tile_${part}`, variants = {}; const mesh = part === 'ridge_end' ? 'clay_tile_cap_mesh' : 'clay_tile_mesh'; - for (const facing of Object.keys(facings)) for (const right of [false,true]) - variants[`facing=${facing},right=${right}`] = {model:`${ns}:${mesh}`}; + for (const facing of Object.keys(facings)) { + if (part === 'fascia') { + for (let ends=0;ends<4;ends++) for (let fitting=0;fitting<3;fitting++) + variants[`ends=${ends},facing=${facing},fitting=${fitting}`] = {model:`${ns}:${mesh}`}; + } else for (const right of [false,true]) + variants[`facing=${facing},right=${right}`] = {model:`${ns}:${mesh}`}; + } state(name,variants); item(name,mesh); lang[`tile.${ns}.${name}.name`] = `Clay Tile ${label}`; } +model('drain_mesh',{parent:'minecraft:block/block',textures:{particle:'minecraft:blocks/iron_block'}}); +for (const [name,label] of [['iron_gutter','Iron Gutter'],['iron_gutter_outlet','Iron Gutter Outlet']]) { + item(name,'drain_mesh'); + lang[`item.${ns}.${name}.name`] = label; +} +for (const [name,labels] of [ + ['iron_downpipe',['Iron Downpipe']], + ['iron_downpipe_shoe',['Iron Downpipe Shoe']], + ['iron_downpipe_offset',['Eave Offset','Half-block Offset','Full-block Outer Elbow','Full-block Inner Elbow']] +]) { + const variants = {}; + for (const facing of Object.keys(facings)) for (let profile=0;profile<4;profile++) + variants[`facing=${facing},profile=${profile}`] = {model:`${ns}:drain_mesh`}; + state(name,variants); + for (const [i,label] of labels.entries()) { + const suffix = labels.length > 1 ? '_' + i : ''; + item(name + suffix,'drain_mesh'); + lang[`tile.${ns}.${name}${suffix ? '.' + i : ''}.name`] = label; + } +} model('clay_tile_hanging',{parent:'minecraft:block/cube_all',textures:{all:`${ns}:blocks/clay_tile`}}); const tileFaces = Object.fromEntries(['down','up','north','south','west','east'].map(face => [face, face === 'south' ? {texture:'#tile'} : {texture:'#tile',cullface:face}])); @@ -165,6 +190,19 @@ for(const [w,wood] of woods.entries()) for(const finish of [0,1]) for(const thin } state('open_window_leaf',Object.fromEntries(Object.keys(facings).map(facing=>[`facing=${facing}`,{model:`${ns}:window_mesh`}]))); for(let meta=0;meta<2;meta++) {item(`leaded_pane_${meta}`,'window_mesh');lang[`item.${ns}.leaded_pane.${meta}.name`]=`${meta?'Rectangular':'Diamond'} Leaded Pane`;} +for (const [key,label] of Object.entries({ + 'gutter.fit':'Fit to the outside face of a bare clay fascia. Sneak and use an empty hand to recover it.', + 'gutter.outlet':'Place a downpipe beneath the outlet to carry rain down.', + 'gutter.craft':'Three iron ingots in a row make six gutters. Add an ingot to make an outlet.', + 'pipe.place':'Place against a pipe end to line up. Sneak, face the wall and click to pick the depth.', + 'pipe.craft':'Three iron ingots in a column make six pipes. Add a stick to make an offset.', + 'pipe.offset.0':'Fits under an outlet and brings the pipe back to the wall.', + 'pipe.offset.1':'Fits under an outside pipe and brings it half a block in.', + 'pipe.offset.2':'Place beneath the upper pipe with its side opening toward the wall.', + 'pipe.offset.3':'Place beside the outer elbow, one cell closer to the wall.', + 'pipe.cycle':'Craft alone to switch to the next offset shape.', + 'pipe.shoe':'Craft a downpipe alone. Fit beneath the last pipe to let rain spill out above the ground.' +})) lang[`hint.${ns}.${key}`] = label; lang[`hint.${ns}.window.use`]='Right-click to open the casement; redstone operates the whole column.'; lang[`hint.${ns}.window.place`]='Stack to share a hinge; sneak to choose a different alignment.'; lang[`hint.${ns}.window.glaze`]='Craft alone to swap leading. Shape the wall before glazing.'; diff --git a/scripts/invoke-client-preview.ps1 b/scripts/invoke-client-preview.ps1 index d2ba03d..cb57ada 100644 --- a/scripts/invoke-client-preview.ps1 +++ b/scripts/invoke-client-preview.ps1 @@ -1,6 +1,6 @@ param( - [string]$Runtime='D:\IronAgeArchitecture\manual-testing\rye-roof-trim-0.1.10\native-client', - [string]$PreviewName='Rye Roof Trim Preview', + [string]$Runtime='D:\IronAgeArchitecture\manual-testing\rye-downpipe-shoe-0.1.11\native-client-final', + [string]$PreviewName='Rye Downpipe Shoe Preview', [string]$ServerTemplate='D:\IronAgeArchitecture\manual-testing\jetties-0.1.2-final\core', [string]$GradleHome='D:\GradleCaches\minecraft-mods-shared', [string]$AssetsDirectory='', diff --git a/scripts/invoke-runtime-probe.ps1 b/scripts/invoke-runtime-probe.ps1 index 4dd21e1..7872792 100644 --- a/scripts/invoke-runtime-probe.ps1 +++ b/scripts/invoke-runtime-probe.ps1 @@ -1,6 +1,6 @@ param( - [ValidateSet('core','reload','furniture','disabled','legacy','legacy-daub','legacy-jetties','iron-age','late-medieval','mixed-eras','no-eras','invalid-era','windows-disabled','clay-disabled','clay-only')][string]$Mode='core', - [string]$RuntimeRoot='D:\IronAgeArchitecture\manual-testing\rye-roof-trim-0.1.10', + [ValidateSet('core','reload','furniture','disabled','legacy','legacy-daub','legacy-jetties','legacy-fascia','iron-age','late-medieval','mixed-eras','no-eras','invalid-era','windows-disabled','clay-disabled','clay-only')][string]$Mode='core', + [string]$RuntimeRoot='D:\IronAgeArchitecture\manual-testing\rye-downpipe-shoe-0.1.11', [string]$ServerTemplate='D:\IronAgeArchitecture\manual-testing\jetties-0.1.2-final\core', [string]$FurnitureJar='D:\IronAgeArchitecture\manual-testing\jetties-0.1.2-final\furniture\mods\iron-age-furniture-0.3.0.110021.jar', [string]$Java8='C:\Users\johnb\.gradle\jdks\temurin-8-amd64-windows.2\bin\java.exe' @@ -42,6 +42,13 @@ if($Mode -eq 'legacy-jetties') { [System.IO.Compression.ZipFile]::ExtractToDirectory('D:\IronAgeArchitecture\candidates\0.1.2.110021-jettied-storeys\Jettied Street Preview.zip',$staging) Copy-Item -LiteralPath (Join-Path $staging 'Jettied Street Preview') -Destination (Join-Path $folder 'world') -Recurse } +if($Mode -eq 'legacy-fascia') { + if(Test-Path -LiteralPath (Join-Path $folder 'world')) {throw 'Legacy fascia probe requires a fresh disposable destination'} + Add-Type -AssemblyName System.IO.Compression.FileSystem + $staging=Join-Path $folder 'fascia-preview-source' + [System.IO.Compression.ZipFile]::ExtractToDirectory('D:\IronAgeArchitecture\manual-testing\rye-roof-trim-0.1.10\Rye Roof Trim Preview.zip',$staging) + Copy-Item -LiteralPath (Join-Path $staging 'Rye Roof Trim Preview') -Destination (Join-Path $folder 'world') -Recurse +} $eraModes=@{'iron-age'=@('iron_age');'late-medieval'=@('late_medieval_tudor');'mixed-eras'=@('iron_age','late_medieval_tudor');'no-eras'=@();'invalid-era'=@('misspelled_era')} if($eraModes.ContainsKey($Mode)) { New-Item -ItemType Directory -Force -Path (Join-Path $folder 'config') | Out-Null @@ -91,9 +98,10 @@ if($Mode -eq 'reload') {$args+='-Diaa.reload=true'} if($Mode -eq 'legacy') {$args+='-Diaa.legacy=true'} if($Mode -eq 'legacy-daub') {$args+='-Diaa.legacyDaub=true'} if($Mode -eq 'legacy-jetties') {$args+='-Diaa.legacyJetties=true'} +if($Mode -eq 'legacy-fascia') {$args+='-Diaa.legacyFascia=true'} if($Mode -eq 'core') {$args+=@('-Diaa.eraMatrix=true','-Diaa.recipeMatrix=true')} if($Mode -eq 'disabled') {$args+='-Diaa.expectedVariants=0'} -if($Mode -eq 'clay-only') {$args+=@('-Diaa.expectedVariants=7','-Diaa.clayOnly=true','-Diaa.recipeMatrix=true')} +if($Mode -eq 'clay-only') {$args+=@('-Diaa.expectedVariants=15','-Diaa.clayOnly=true','-Diaa.recipeMatrix=true')} $args+=@('-jar',$forge,'nogui') Push-Location $folder try { diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureBlocks.java b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureBlocks.java index 1a84454..58e023e 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureBlocks.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureBlocks.java @@ -38,11 +38,22 @@ public final class ArchitectureBlocks { public static final List WINDOWS = new ArrayList<>(); public static WindowLeafBlock WINDOW_LEAF; public static LeadedPaneItem LEADED_PANE; + public static GutterItem GUTTER, GUTTER_OUTLET; + public static DownpipeBlock DOWNPIPE, DOWNPIPE_OFFSET, DOWNPIPE_SHOE; + + public static int variants(Block block) { + if (block instanceof FrameBlock) return ((FrameBlock)block).variants(); + return block == DOWNPIPE_OFFSET ? 4 : 1; + } public static List visibleStacks() { List result=new ArrayList<>(); - for(Block block:VISIBLE) for(int meta=0;meta<(block instanceof FrameBlock?((FrameBlock)block).variants():1);meta++) result.add(new ItemStack(block,1,meta)); + for(Block block:VISIBLE) for(int meta=0;meta meshes() { @@ -51,7 +62,8 @@ public static List meshes() { public static FrameBlock DAUB; public static final List ROOFS = new ArrayList<>(); public static TileHangingBlock CLAY_HANGING, CLAY_HANGING_THIN; - public static TrimmedRoofBlock CLAY_FASCIA, CLAY_VERGE, CLAY_RIDGE_END; + public static FasciaBlock CLAY_FASCIA; + public static TrimmedRoofBlock CLAY_VERGE, CLAY_RIDGE_END; private ArchitectureBlocks() { } public static T block(T block, String name) { block.setRegistryName(new ResourceLocation(IronAgeArchitecture.MODID, name)); @@ -97,12 +109,28 @@ public static void register() { CLAY_HANGING_THIN = block(new TileHangingBlock(true), "clay_tile_hanging_thin"); item(CLAY_HANGING, new TileHangingItem(CLAY_HANGING)); item(CLAY_HANGING_THIN, new TileHangingItem(CLAY_HANGING_THIN)); - CLAY_FASCIA = roofTrim("clay_tile_fascia", TrimmedRoofBlock.Kind.FASCIA); + CLAY_FASCIA = block(new FasciaBlock(), "clay_tile_fascia"); + item(CLAY_FASCIA, new EraItemBlock(CLAY_FASCIA)); + ROOFS.add(CLAY_FASCIA); CLAY_VERGE = roofTrim("clay_tile_verge", TrimmedRoofBlock.Kind.VERGE); CLAY_RIDGE_END = roofTrim("clay_tile_ridge_end", TrimmedRoofBlock.Kind.RIDGE_END); + GUTTER = gutter("iron_gutter", false); + GUTTER_OUTLET = gutter("iron_gutter_outlet", true); + DOWNPIPE = block(new DownpipeBlock(DownpipeBlock.Kind.STRAIGHT), "iron_downpipe"); + item(DOWNPIPE, new DownpipeItem(DOWNPIPE)); + DOWNPIPE_OFFSET = block(new DownpipeBlock(DownpipeBlock.Kind.OFFSET), "iron_downpipe_offset"); + item(DOWNPIPE_OFFSET, new DownpipeItem(DOWNPIPE_OFFSET)); + DOWNPIPE_SHOE = block(new DownpipeBlock(DownpipeBlock.Kind.SHOE), "iron_downpipe_shoe"); + item(DOWNPIPE_SHOE, new DownpipeItem(DOWNPIPE_SHOE)); } EraCatalog.assignPrimaryTabs(); } + private static GutterItem gutter(String name, boolean outlet) { + GutterItem item = new GutterItem(outlet); + item.setRegistryName(new ResourceLocation(IronAgeArchitecture.MODID, name)); + item.setUnlocalizedName(IronAgeArchitecture.MODID + "." + name); + return GameRegistry.register(item); + } private static void registerWindows() { WINDOW_LEAF=block(new WindowLeafBlock(),"open_window_leaf"); LEADED_PANE=new LeadedPaneItem();LEADED_PANE.setRegistryName(new ResourceLocation(IronAgeArchitecture.MODID,"leaded_pane")); diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureRecipes.java b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureRecipes.java index 40904c0..3313c83 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureRecipes.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureRecipes.java @@ -26,6 +26,7 @@ public static void register() { } } if(ArchitectureConfig.windows) registerWindows(); + if (ArchitectureConfig.clayTiles) registerGuttering(); for(RoofBlock roof:ArchitectureBlocks.ROOFS) if(!roof.isRidge() && !(roof instanceof TrimmedRoofBlock)) { Object material; switch (roof.materialKey) { @@ -52,6 +53,18 @@ public static void register() { } } } + private static void registerGuttering() { + shaped(new ItemStack(ArchitectureBlocks.GUTTER, 6), "III", 'I', Items.IRON_INGOT); + shapeless(new ItemStack(ArchitectureBlocks.GUTTER_OUTLET), ArchitectureBlocks.GUTTER, Items.IRON_INGOT); + shaped(new ItemStack(ArchitectureBlocks.DOWNPIPE, 6), "I", "I", "I", 'I', Items.IRON_INGOT); + shapeless(new ItemStack(ArchitectureBlocks.DOWNPIPE_OFFSET), ArchitectureBlocks.DOWNPIPE, Items.STICK); + shapeless(new ItemStack(ArchitectureBlocks.DOWNPIPE_SHOE), ArchitectureBlocks.DOWNPIPE); + for (int profile = 0; profile < 4; profile++) { + shapeless(new ItemStack(ArchitectureBlocks.DOWNPIPE_OFFSET, 1, (profile + 1) & 3), + new ItemStack(ArchitectureBlocks.DOWNPIPE_OFFSET, 1, profile)); + } + } + private static void registerCob() { shapeless(new ItemStack(ArchitectureBlocks.COB.get(0),8),Blocks.DIRT,Blocks.DIRT,Blocks.DIRT,Blocks.DIRT, Items.CLAY_BALL,Items.CLAY_BALL,Items.CLAY_BALL,Items.CLAY_BALL,Items.WHEAT); diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/EraCatalog.java b/src/main/java/zone/moddev/mc/ironagearchitecture/EraCatalog.java index 10b8451..7a1bd89 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/EraCatalog.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/EraCatalog.java @@ -10,7 +10,7 @@ public final class EraCatalog { private EraCatalog() { } public static EnumSet eras(Item item) { if(item==null||item.getRegistryName()==null||!IronAgeArchitecture.MODID.equals(item.getRegistryName().getResourceDomain())) return EnumSet.noneOf(Era.class); - if(item instanceof JettyBracketItem||item instanceof LeadedPaneItem) return EnumSet.of(Era.LATE_MEDIEVAL_TUDOR); + if(item instanceof JettyBracketItem||item instanceof LeadedPaneItem||item instanceof GutterItem) return EnumSet.of(Era.LATE_MEDIEVAL_TUDOR); if(!(item instanceof ItemBlock)) return EnumSet.noneOf(Era.class); Block block=((ItemBlock)item).getBlock(); boolean shared=ArchitectureBlocks.COB.contains(block)||ArchitectureBlocks.SLABS.contains(block)||ArchitectureBlocks.STAIRS.contains(block) diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/IronAgeArchitecture.java b/src/main/java/zone/moddev/mc/ironagearchitecture/IronAgeArchitecture.java index e7bebcd..c86966d 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/IronAgeArchitecture.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/IronAgeArchitecture.java @@ -7,7 +7,7 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; /** Permanent building components in the Iron Age series. */ -@Mod(modid = IronAgeArchitecture.MODID, name = "Iron Age Architecture", version = "0.1.10.110021", +@Mod(modid = IronAgeArchitecture.MODID, name = "Iron Age Architecture", version = "0.1.11.110021", acceptedMinecraftVersions = "[1.10.2]", dependencies = "required-after:Forge@[12.18.3.2511,)") public final class IronAgeArchitecture { public static final String MODID = "ironagearchitecture"; diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/DownpipeBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/DownpipeBlock.java new file mode 100644 index 0000000..a67cf5a --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/DownpipeBlock.java @@ -0,0 +1,132 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import java.util.List; +import java.util.Random; +import javax.annotation.Nullable; +import net.minecraft.block.BlockHorizontal; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public final class DownpipeBlock extends BlockHorizontal { + public static final PropertyInteger PROFILE = PropertyInteger.create("profile", 0, 3); + public enum Kind { STRAIGHT, OFFSET, SHOE } + + private final Kind kind; + + public DownpipeBlock(Kind kind) { + // The shoe discharges inside its own cell. Vanilla drips expire inside solid materials. + super(kind == Kind.SHOE ? Material.CIRCUITS : Material.IRON); + this.kind = kind; + setDefaultState(blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH).withProperty(PROFILE, 0)); + setHardness(1.5F); + setSoundType(SoundType.METAL); + setLightOpacity(0); + } + + public boolean isOffset() { return kind == Kind.OFFSET; } + public boolean isShoe() { return kind == Kind.SHOE; } + + @Override protected BlockStateContainer createBlockState() { return new BlockStateContainer(this, FACING, PROFILE); } + @Override public IBlockState getStateFromMeta(int meta) { + int profile = meta >> 2 & 3; + return getDefaultState().withProperty(FACING, EnumFacing.getHorizontal(meta & 3)) + .withProperty(PROFILE, !isOffset() && profile == 3 ? 0 : profile); + } + @Override public int getMetaFromState(IBlockState state) { + return state.getValue(FACING).getHorizontalIndex() | state.getValue(PROFILE) << 2; + } + @Override public int damageDropped(IBlockState state) { return isOffset() ? state.getValue(PROFILE) : 0; } + @Override public ItemStack getPickBlock(IBlockState state, RayTraceResult hit, World world, BlockPos pos, EntityPlayer player) { + return new ItemStack(this, 1, damageDropped(state)); + } + @Override public IBlockState withRotation(IBlockState state, Rotation rotation) { + return state.withProperty(FACING, rotation.rotate(state.getValue(FACING))); + } + @Override public IBlockState withMirror(IBlockState state, Mirror mirror) { + return state.withProperty(FACING, mirror.mirror(state.getValue(FACING))); + } + + @Override public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing side, float x, float y, + float z, int meta, EntityLivingBase placer) { + int profile = isOffset() ? meta & 3 : 0; + EnumFacing facing = placer.getHorizontalFacing(); + BlockPos clicked = pos.offset(side.getOpposite()); + IBlockState against = world.getBlockState(clicked); + if (!placer.isSneaking()) { + for (boolean upstream : new boolean[]{true, false}) { + DrainRun.Port port = DrainRun.port(against, upstream); + if (port == null || port.side != side) continue; + EnumFacing aligned = against.getValue(FACING); + int candidate = isOffset() ? profile : port.lane(); + if (candidate < 0) continue; + IBlockState chosen = state(aligned, candidate); + DrainRun.Port own = DrainRun.port(chosen, !upstream); + if (own != null && port.matches(own)) return chosen; + } + if (!isOffset() && !(against.getBlock() instanceof DownpipeBlock) && side.getAxis().isHorizontal()) + facing = side.getOpposite(); + } else if (!isOffset()) { + EnumFacing inverse = facing.getAxis() == EnumFacing.Axis.X ? facing.getOpposite() : facing; + double depth = RoofGeometry.rotate(x, y, z, inverse)[2]; + profile = depth < .25 ? 0 : depth < .69 ? 1 : 2; + } + return state(facing, profile); + } + + public IBlockState state(EnumFacing facing, int profile) { + return getDefaultState().withProperty(FACING, facing).withProperty(PROFILE, profile); + } + + public double[][] path(IBlockState state) { + int profile = state.getValue(PROFILE); + if (isOffset()) return DrainGeometry.Offset.values()[profile].path; + return isShoe() ? DrainGeometry.shoe(profile) : DrainGeometry.straight(profile); + } + + public List boxes(IBlockState state) { + return DrainGeometry.pipeBoxes(path(state), state.getValue(FACING)); + } + @Override public boolean isOpaqueCube(IBlockState state) { return false; } + @Override public boolean isFullCube(IBlockState state) { return false; } + @Override public boolean isSideSolid(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing side) { return false; } + @Override public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess world, BlockPos pos) { + return WindowBlock.union(boxes(state)); + } + @Nullable @Override public AxisAlignedBB getCollisionBoundingBox(IBlockState state, World world, BlockPos pos) { + return NULL_AABB; + } + @Override public void addCollisionBoxToList(IBlockState state, World world, BlockPos pos, + AxisAlignedBB mask, List result, @Nullable Entity entity) { + for (AxisAlignedBB box : boxes(state)) addCollisionBoxToList(pos, mask, result, box); + } + @Nullable @Override public RayTraceResult collisionRayTrace(IBlockState state, World world, BlockPos pos, Vec3d from, Vec3d to) { + RayTraceResult nearest = null; + for (AxisAlignedBB box : boxes(state)) { + RayTraceResult hit = rayTrace(pos, from, to, box); + if (hit != null && (nearest == null || from.squareDistanceTo(hit.hitVec) < from.squareDistanceTo(nearest.hitVec))) nearest = hit; + } + return nearest; + } + @SideOnly(Side.CLIENT) + @Override public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random random) { + DrainRun.showDrip(state, world, pos); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/DownpipeItem.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/DownpipeItem.java new file mode 100644 index 0000000..69841c2 --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/DownpipeItem.java @@ -0,0 +1,43 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import java.util.List; +import net.minecraft.block.state.IBlockState; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public final class DownpipeItem extends EraItemBlock { + private final DownpipeBlock pipe; + + public DownpipeItem(DownpipeBlock pipe) { + super(pipe); + this.pipe = pipe; + setHasSubtypes(pipe.isOffset()); + } + + @Override public int getMetadata(int damage) { return pipe.isOffset() ? damage & 3 : 0; } + @Override public String getUnlocalizedName(ItemStack stack) { + return super.getUnlocalizedName(stack) + (pipe.isOffset() ? "." + getMetadata(stack.getMetadata()) : ""); + } + @Override public void getSubItems(Item item, CreativeTabs tab, List result) { + for (int i = 0; i < (pipe.isOffset() ? 4 : 1); i++) result.add(new ItemStack(item, 1, i)); + } + @Override public boolean placeBlockAt(ItemStack stack, EntityPlayer player, World world, BlockPos pos, + EnumFacing side, float x, float y, float z, IBlockState state) { + for (AxisAlignedBB box : pipe.boxes(state)) if (!world.checkNoEntityCollision(box.offset(pos))) return false; + return super.placeBlockAt(stack, player, world, pos, side, x, y, z, state); + } + @Override public void addInformation(ItemStack stack, EntityPlayer player, List tooltip, boolean advanced) { + if (pipe.isShoe()) { + hint(tooltip, "pipe.shoe"); + return; + } + hint(tooltip, pipe.isOffset() ? "pipe.offset." + getMetadata(stack.getMetadata()) : "pipe.place"); + hint(tooltip, pipe.isOffset() ? "pipe.cycle" : "pipe.craft"); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/DrainGeometry.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/DrainGeometry.java new file mode 100644 index 0000000..5f142cd --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/DrainGeometry.java @@ -0,0 +1,136 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; + +/** Dimensions are in pixels, with +Z pointing out from the wall. */ +public final class DrainGeometry { + public static final double OUTER = 14.5, HALF = 6.5, WALL = 1.5; + public static final double RADIUS = .88, COLLAR = 1.15; + public static final int FACETS = 8; + + public enum Offset { + EAVE(new double[][]{{16, OUTER}, {13, OUTER}, {3, WALL}, {0, WALL}}, EnumFacing.UP, EnumFacing.DOWN), + HALF_BLOCK(new double[][]{{16, OUTER}, {12, OUTER}, {4, HALF}, {0, HALF}}, EnumFacing.UP, EnumFacing.DOWN), + JETTY_OUT(new double[][]{{16, WALL}, {10, WALL}, {8, .9}, {8, 0}}, EnumFacing.UP, EnumFacing.NORTH), + JETTY_IN(new double[][]{{8, 16}, {8, 14.5}, {2, WALL}, {0, WALL}}, EnumFacing.SOUTH, EnumFacing.DOWN); + + public final double[][] path; + public final EnumFacing inlet, outlet; + + Offset(double[][] path, EnumFacing inlet, EnumFacing outlet) { + this.path = path; + this.inlet = inlet; + this.outlet = outlet; + } + } + + private DrainGeometry() { } + + public static double[][] straight(int lane) { + double depth = laneDepth(lane); + return new double[][]{{16, depth}, {0, depth}}; + } + + public static double[][] shoe(int lane) { + double depth = laneDepth(lane); + // The outside lane has very little room left before the edge of the cell. + double mouth = Math.min(depth + 5.5, 15.1); + double heel = lane == 2 ? 3.5 : 7.25; + return new double[][]{{16, depth}, {heel, depth}, {1.75, mouth}}; + } + + private static double laneDepth(int lane) { + return lane == 0 ? WALL : lane == 1 ? HALF : OUTER; + } + + public static EnumFacing rotateSide(EnumFacing side, EnumFacing facing) { + if (side.getAxis().isVertical()) return side; + return side == EnumFacing.NORTH ? facing : facing.getOpposite(); + } + + public static double[] gutterPoint(int index, double radius) { + double angle = Math.PI * index / FACETS; + return new double[]{2.7 - radius * Math.sin(angle), OUTER + radius * Math.cos(angle)}; + } + + public static boolean outletOpening(int facet) { return facet >= 2 && facet <= 5; } + + public static double[][] ring(double[][] path, int index, double radius) { + double[] prev = path[Math.max(0, index - 1)]; + double[] next = path[Math.min(path.length - 1, index + 1)]; + double dy = next[0] - prev[0], dz = next[1] - prev[1]; + double length = Math.hypot(dy, dz); + double[][] points = new double[FACETS][]; + for (int i = 0; i < FACETS; i++) { + double angle = Math.PI * 2 * i / FACETS; + double radial = radius * Math.sin(angle); + points[i] = new double[]{8 + radius * Math.cos(angle), path[index][0] + radial * dz / length, + path[index][1] - radial * dy / length}; + } + return points; + } + + public static List gutterBoxes(boolean outlet, int ends, EnumFacing facing) { + List boxes = new ArrayList<>(); + for (int i = 0; i < FACETS; i++) { + double[] a = gutterPoint(i, 1.5), b = gutterPoint(i+1, 1.5); + double[] c = gutterPoint(i, 1.13), d = gutterPoint(i+1, 1.13); + double lowY = min(a,b,c,d,0), lowZ = min(a,b,c,d,1); + double highY = max(a,b,c,d,0), highZ = max(a,b,c,d,1); + if (outlet && outletOpening(i)) { + boxes.add(box(0, lowY, lowZ, 7.2, highY, highZ, facing)); + boxes.add(box(8.8, lowY, lowZ, 16, highY, highZ, facing)); + } else boxes.add(box(0, lowY, lowZ, 16, highY, highZ, facing)); + double capY = Math.min(c[0], d[0]), capZ0 = Math.min(c[1], d[1]), capZ1 = Math.max(c[1], d[1]); + if ((ends & 1) != 0) boxes.add(box(0, capY, capZ0, .22, 2.7, capZ1, facing)); + if ((ends & 2) != 0) boxes.add(box(15.78, capY, capZ0, 16, 2.7, capZ1, facing)); + } + if (outlet) boxes.add(box(8-COLLAR, 0, OUTER-COLLAR, 8+COLLAR, 1.36, OUTER+COLLAR, facing)); + return boxes; + } + + private static double min(double[] a, double[] b, double[] c, double[] d, int index) { + return Math.min(Math.min(a[index], b[index]), Math.min(c[index], d[index])); + } + + private static double max(double[] a, double[] b, double[] c, double[] d, int index) { + return Math.max(Math.max(a[index], b[index]), Math.max(c[index], d[index])); + } + + public static List pipeBoxes(double[][] path, EnumFacing facing) { + List boxes = new ArrayList<>(); + for (int i = 0; i < path.length - 1; i++) { + double[] start = path[i], end = path[i+1]; + int steps = Math.max(1, (int)Math.ceil(Math.abs(end[1] - start[1]))); + for (int step = 0; step < steps; step++) { + double a = step / (double)steps, b = (step + 1) / (double)steps; + double y0 = start[0] + (end[0] - start[0]) * a, y1 = start[0] + (end[0] - start[0]) * b; + double z0 = start[1] + (end[1] - start[1]) * a, z1 = start[1] + (end[1] - start[1]) * b; + double rise = start[1] == end[1] ? 0 : RADIUS; + boxes.add(box(8-RADIUS, Math.min(y0,y1)-rise, Math.min(z0,z1)-RADIUS, + 8+RADIUS, Math.max(y0,y1)+rise, Math.max(z0,z1)+RADIUS, facing)); + } + } + if (path[0][0] == 16) boxes.add(box(8-COLLAR, 14.5, path[0][1]-COLLAR, + 8+COLLAR, 15.3, path[0][1]+COLLAR, facing)); + return boxes; + } + + private static double clip(double value) { return Math.max(0, Math.min(16, value)) / 16; } + + private static AxisAlignedBB box(double x0, double y0, double z0, double x1, double y1, double z1, EnumFacing facing) { + double[] a = RoofGeometry.rotate(clip(x0), clip(y0), clip(z0), facing); + double[] b = RoofGeometry.rotate(clip(x1), clip(y1), clip(z1), facing); + return new AxisAlignedBB(Math.min(a[0], b[0]), a[1], Math.min(a[2], b[2]), + Math.max(a[0], b[0]), b[1], Math.max(a[2], b[2])); + } + + public static Vec3d point(double y, double z, EnumFacing facing) { + double[] p = RoofGeometry.rotate(.5, y / 16, z / 16, facing); + return new Vec3d(p[0], p[1], p[2]); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/DrainRun.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/DrainRun.java new file mode 100644 index 0000000..218a9f4 --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/DrainRun.java @@ -0,0 +1,104 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import javax.annotation.Nullable; +import net.minecraft.block.BlockHorizontal; +import net.minecraft.block.state.IBlockState; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public final class DrainRun { + private DrainRun() { } + + static final class Port { + final EnumFacing side; + final EnumFacing facing; + final double y, depth; + + Port(EnumFacing side, EnumFacing facing, double y, double depth) { + this.side = DrainGeometry.rotateSide(side, facing); + this.facing = facing; + this.y = y; + this.depth = depth; + } + + int lane() { + if (depth == DrainGeometry.WALL) return 0; + if (depth == DrainGeometry.HALF) return 1; + if (depth == DrainGeometry.OUTER) return 2; + return -1; + } + + boolean matches(Port other) { + if (facing != other.facing || side != other.side.getOpposite()) return false; + return side.getAxis().isVertical() ? depth == other.depth : y == other.y; + } + } + + static Port port(IBlockState state, boolean output) { + if (state.getBlock() instanceof FasciaBlock) { + return output && state.getValue(FasciaBlock.FITTING) == 2 + ? new Port(EnumFacing.DOWN, state.getValue(BlockHorizontal.FACING), 0, DrainGeometry.OUTER) : null; + } + if (!(state.getBlock() instanceof DownpipeBlock)) return null; + DownpipeBlock pipe = (DownpipeBlock)state.getBlock(); + if (output && pipe.isShoe()) return null; + double[][] path = pipe.path(state); + double[] end = path[output ? path.length - 1 : 0]; + EnumFacing side = output ? EnumFacing.DOWN : EnumFacing.UP; + if (pipe.isOffset()) { + DrainGeometry.Offset offset = DrainGeometry.Offset.values()[state.getValue(DownpipeBlock.PROFILE)]; + side = output ? offset.outlet : offset.inlet; + } + return new Port(side, state.getValue(BlockHorizontal.FACING), end[0], end[1]); + } + + public static boolean canDrip(IBlockState state, World world, BlockPos pos) { + if (!world.isRaining()) return false; + BlockPos inlet = exposedInletForDrip(state, world, pos); + return inlet != null && world.isRainingAt(inlet.up()); + } + + @Nullable + public static BlockPos exposedInletForDrip(IBlockState state, World world, BlockPos pos) { + if (dripPoint(state, world, pos) == null) return null; + for (int count = 0; count < 32; count++) { + if (state.getBlock() instanceof FasciaBlock) + return world.isBlockLoaded(pos.up()) && world.canSeeSky(pos.up()) ? pos : null; + Port inlet = port(state, false); + if (inlet == null) return null; + pos = pos.offset(inlet.side); + if (!world.isBlockLoaded(pos)) return null; + state = world.getBlockState(pos); + Port previous = port(state, true); + if (previous == null || !inlet.matches(previous)) return null; + } + return null; + } + + @Nullable + private static Vec3d dripPoint(IBlockState state, World world, BlockPos pos) { + if (state.getBlock() instanceof DownpipeBlock && ((DownpipeBlock)state.getBlock()).isShoe()) { + double[][] path = ((DownpipeBlock)state.getBlock()).path(state); + double[] mouth = path[path.length - 1]; + return DrainGeometry.point(mouth[0] - .65, mouth[1] + .3, state.getValue(BlockHorizontal.FACING)); + } + Port end = port(state, true); + if (end == null || end.side != EnumFacing.DOWN || !world.isBlockLoaded(pos.down()) || !world.isAirBlock(pos.down())) + return null; + return DrainGeometry.point(-.48, end.depth, end.facing); + } + + @SideOnly(Side.CLIENT) + static void showDrip(IBlockState state, World world, BlockPos pos) { + if (!canDrip(state, world, pos)) return; + Vec3d point = dripPoint(state, world, pos); + if (point == null) return; + world.spawnParticle(EnumParticleTypes.DRIP_WATER, pos.getX()+point.xCoord, pos.getY()+point.yCoord, + pos.getZ()+point.zCoord, 0, 0, 0); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/FasciaBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/FasciaBlock.java new file mode 100644 index 0000000..9a11351 --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/FasciaBlock.java @@ -0,0 +1,108 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import java.util.List; +import java.util.Random; +import javax.annotation.Nullable; +import net.minecraft.block.SoundType; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zone.moddev.mc.ironagearchitecture.ArchitectureBlocks; + +public final class FasciaBlock extends TrimmedRoofBlock { + public static final PropertyInteger FITTING = PropertyInteger.create("fitting", 0, 2); + public static final PropertyInteger ENDS = PropertyInteger.create("ends", 0, 3); + + public FasciaBlock() { + super(Kind.FASCIA); + setDefaultState(getDefaultState().withProperty(FITTING, 0).withProperty(ENDS, 3)); + } + + @Override protected BlockStateContainer createBlockState() { + return new BlockStateContainer(this, FACING, FITTING, ENDS); + } + @Override public IBlockState getStateFromMeta(int meta) { + int fitting = meta >> 2 & 3; + return getDefaultState().withProperty(FACING, EnumFacing.getHorizontal(meta & 3)) + .withProperty(FITTING, fitting == 3 ? 0 : fitting); + } + @Override public int getMetaFromState(IBlockState state) { + return state.getValue(FACING).getHorizontalIndex() | state.getValue(FITTING) << 2; + } + @Override public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing side, + float x, float y, float z, int meta, EntityLivingBase player) { + return getDefaultState().withProperty(FACING, player.getHorizontalFacing()); + } + @Override public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos) { + if (state.getValue(FITTING) == 0) return state; + EnumFacing facing = state.getValue(FACING); + int ends = connects(world, pos.offset(facing.rotateYCCW()), facing) ? 0 : 1; + if (!connects(world, pos.offset(facing.rotateY()), facing)) ends |= 2; + return state.withProperty(ENDS, ends); + } + + private boolean connects(IBlockAccess world, BlockPos pos, EnumFacing facing) { + if (world instanceof World && !((World)world).isBlockLoaded(pos)) return false; + IBlockState other = world.getBlockState(pos); + return other.getBlock() == this && other.getValue(FACING) == facing && other.getValue(FITTING) != 0; + } + + @Override public List collision(IBlockState state) { + List boxes = RoofTrimGeometry.boxes(Kind.FASCIA, false, state.getValue(FACING)); + if (state.getValue(FITTING) != 0) + boxes.addAll(DrainGeometry.gutterBoxes(state.getValue(FITTING) == 2, state.getValue(ENDS), state.getValue(FACING))); + return boxes; + } + + @Override public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, + EnumHand hand, @Nullable ItemStack held, EnumFacing side, float x, float y, float z) { + int fitting = state.getValue(FITTING); + EnumFacing facing = state.getValue(FACING); + if (!player.isSneaking() || hand != EnumHand.MAIN_HAND || held != null || player.getHeldItemOffhand() != null || fitting == 0) + return false; + EnumFacing inverse = facing.getAxis() == EnumFacing.Axis.X ? facing.getOpposite() : facing; + if (RoofGeometry.rotate(x, y, z, inverse)[2] < RoofTrimGeometry.EAVE + 1e-4 || y > 3.0 / 16) + return false; + if (!world.isBlockModifiable(player, pos) || !player.canPlayerEdit(pos, side, fittingStack(fitting))) return false; + if (!world.isRemote && world.setBlockState(pos, state.withProperty(FITTING, 0), 3)) { + if (!player.capabilities.isCreativeMode) { + ItemStack recovered = fittingStack(fitting); + if (!player.inventory.addItemStackToInventory(recovered)) spawnAsEntity(world, pos, recovered); + } + playFit(world, player, pos); + } + return true; + } + + public static void playFit(World world, EntityPlayer player, BlockPos pos) { + world.playSound(player, pos, SoundType.METAL.getPlaceSound(), SoundCategory.BLOCKS, .6F, .9F); + } + + private static ItemStack fittingStack(int fitting) { + return new ItemStack(fitting == 2 ? ArchitectureBlocks.GUTTER_OUTLET : ArchitectureBlocks.GUTTER); + } + + @Override public List getDrops(IBlockAccess world, BlockPos pos, IBlockState state, int fortune) { + List drops = super.getDrops(world, pos, state, fortune); + int fitting = state.getValue(FITTING); + if (fitting != 0) drops.add(fittingStack(fitting)); + return drops; + } + + @SideOnly(Side.CLIENT) + @Override public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random random) { + DrainRun.showDrip(state, world, pos); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/GutterItem.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/GutterItem.java new file mode 100644 index 0000000..95d2236 --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/GutterItem.java @@ -0,0 +1,46 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import java.util.List; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import zone.moddev.mc.ironagearchitecture.Era; +import zone.moddev.mc.ironagearchitecture.EraTabs; + +public final class GutterItem extends Item { + public final boolean outlet; + + public GutterItem(boolean outlet) { + this.outlet = outlet; + setCreativeTab(EraTabs.tab(Era.LATE_MEDIEVAL_TUDOR)); + } + + @Override public EnumActionResult onItemUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, + EnumHand hand, EnumFacing side, float x, float y, float z) { + IBlockState state = world.getBlockState(pos); + if (!(state.getBlock() instanceof FasciaBlock) || side != state.getValue(FasciaBlock.FACING).getOpposite()) + return EnumActionResult.PASS; + if (state.getValue(FasciaBlock.FITTING) != 0 || stack.stackSize <= 0 || !world.isBlockModifiable(player, pos) + || !player.canPlayerEdit(pos, side, stack)) return EnumActionResult.FAIL; + for (AxisAlignedBB box : DrainGeometry.gutterBoxes(outlet, 3, state.getValue(FasciaBlock.FACING))) + if (!world.checkNoEntityCollision(box.offset(pos))) return EnumActionResult.FAIL; + if (!world.isRemote) { + if (!world.setBlockState(pos, state.withProperty(FasciaBlock.FITTING, outlet ? 2 : 1), 3)) return EnumActionResult.FAIL; + FasciaBlock.playFit(world, player, pos); + if (!player.capabilities.isCreativeMode) stack.stackSize--; + } + return EnumActionResult.SUCCESS; + } + + @Override public void addInformation(ItemStack stack, EntityPlayer player, List tooltip, boolean advanced) { + EraItemBlock.hint(tooltip, "gutter.fit"); + EraItemBlock.hint(tooltip, outlet ? "gutter.outlet" : "gutter.craft"); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofTrimGeometry.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofTrimGeometry.java index f4401fc..41616c4 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofTrimGeometry.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofTrimGeometry.java @@ -9,7 +9,7 @@ /** Shared section dimensions for the tile surface, its thin overhang and the timber edging. */ public final class RoofTrimGeometry { private static final double DEPTH = 3.0 / 16; - private static final double EAVE = 1 - DEPTH; + public static final double EAVE = 1 - DEPTH; private RoofTrimGeometry() { } diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/TrimmedRoofBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/TrimmedRoofBlock.java index 587b01f..2038731 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/block/TrimmedRoofBlock.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/TrimmedRoofBlock.java @@ -13,7 +13,7 @@ import net.minecraft.world.World; /** Straight clay edge pieces. The ridge end faces outwards and the verge has two hands. */ -public final class TrimmedRoofBlock extends RoofBlock { +public class TrimmedRoofBlock extends RoofBlock { public enum Kind { FASCIA, VERGE, RIDGE_END } public static final PropertyBool RIGHT = PropertyBool.create("right"); @@ -22,7 +22,7 @@ public enum Kind { FASCIA, VERGE, RIDGE_END } public TrimmedRoofBlock(Kind kind) { super(kind == Kind.RIDGE_END, "clay_tile"); this.kind = kind; - setDefaultState(getDefaultState().withProperty(RIGHT, false)); + if (getDefaultState().getPropertyKeys().contains(RIGHT)) setDefaultState(getDefaultState().withProperty(RIGHT, false)); } @Override protected BlockStateContainer createBlockState() { diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/client/ClientProxy.java b/src/main/java/zone/moddev/mc/ironagearchitecture/client/ClientProxy.java index 8f9d2fb..8624ae0 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/client/ClientProxy.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/client/ClientProxy.java @@ -16,6 +16,8 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import zone.moddev.mc.ironagearchitecture.ArchitectureBlocks; import zone.moddev.mc.ironagearchitecture.CommonProxy; +import zone.moddev.mc.ironagearchitecture.block.DownpipeBlock; +import zone.moddev.mc.ironagearchitecture.block.GutterItem; import zone.moddev.mc.ironagearchitecture.block.FrameBlock; import zone.moddev.mc.ironagearchitecture.block.RoofBlock; import zone.moddev.mc.ironagearchitecture.block.JettyBracketItem; @@ -30,12 +32,14 @@ public final class ClientProxy extends CommonProxy { super.preInit(); MinecraftForge.EVENT_BUS.register(this); for (Block block : ArchitectureBlocks.VISIBLE) { - int count = block instanceof FrameBlock ? ((FrameBlock)block).variants() : 1; + int count = ArchitectureBlocks.variants(block); for (int meta = 0; meta < count; meta++) { - String name = block.getRegistryName().toString() + (block instanceof FrameBlock ? "_" + meta : ""); + String name = block.getRegistryName().toString() + (block instanceof FrameBlock || block == ArchitectureBlocks.DOWNPIPE_OFFSET ? "_" + meta : ""); ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(block),meta,new ModelResourceLocation(name,"inventory")); } } + if (ArchitectureBlocks.GUTTER != null) for (GutterItem item : gutterItems()) + ModelLoader.setCustomModelResourceLocation(item, 0, new ModelResourceLocation(item.getRegistryName(), "inventory")); for(JettyBracketItem item:ArchitectureBlocks.BRACKETS) ModelLoader.setCustomModelResourceLocation(item,0,new ModelResourceLocation(item.getRegistryName(),"inventory")); if(ArchitectureBlocks.LEADED_PANE!=null) for(int meta=0;meta<2;meta++) ModelLoader.setCustomModelResourceLocation(ArchitectureBlocks.LEADED_PANE,meta,new ModelResourceLocation("ironagearchitecture:leaded_pane_"+meta,"inventory")); } @@ -60,6 +64,24 @@ public final class ClientProxy extends CommonProxy { event.getModelRegistry().putObject(key,new WallBakedModel(event.getModelRegistry().getObject(key),block,state)); } } + if (ArchitectureBlocks.GUTTER != null) { + for (GutterItem item : gutterItems()) { + ModelResourceLocation key = new ModelResourceLocation(item.getRegistryName(), "inventory"); + event.getModelRegistry().putObject(key, RoofBakedModel.gutter(event.getModelRegistry().getObject(key), item.outlet)); + } + for (DownpipeBlock pipe : new DownpipeBlock[]{ArchitectureBlocks.DOWNPIPE, ArchitectureBlocks.DOWNPIPE_OFFSET, ArchitectureBlocks.DOWNPIPE_SHOE}) { + for (IBlockState state : pipe.getBlockState().getValidStates()) { + ModelResourceLocation key = new ModelResourceLocation(pipe.getRegistryName(), variant(state)); + event.getModelRegistry().putObject(key, RoofBakedModel.pipe(event.getModelRegistry().getObject(key), pipe, state)); + } + for (int i = 0; i < ArchitectureBlocks.variants(pipe); i++) { + String name = pipe.getRegistryName().toString() + (pipe.isOffset() ? "_" + i : ""); + ModelResourceLocation key = new ModelResourceLocation(name, "inventory"); + IBlockState state = pipe.getDefaultState().withProperty(DownpipeBlock.PROFILE, i); + event.getModelRegistry().putObject(key, RoofBakedModel.pipe(event.getModelRegistry().getObject(key), pipe, state)); + } + } + } for (RoofBlock block : ArchitectureBlocks.ROOFS) { String texture = "ironagearchitecture:blocks/" + block.textureKey(); for (IBlockState state : block.getBlockState().getValidStates()) { @@ -73,6 +95,10 @@ public final class ClientProxy extends CommonProxy { block.getDefaultState(),Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(texture))); } } + private static GutterItem[] gutterItems() { + return new GutterItem[]{ArchitectureBlocks.GUTTER, ArchitectureBlocks.GUTTER_OUTLET}; + } + @SubscribeEvent public void stitch(TextureStitchEvent.Pre event) { event.getMap().registerSprite(new ResourceLocation("minecraft:blocks/glass_silver"));event.getMap().registerSprite(new ResourceLocation("minecraft:blocks/iron_block")); for(String wood:ArchitectureBlocks.WOODS) { diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/client/DrainMesh.java b/src/main/java/zone/moddev/mc/ironagearchitecture/client/DrainMesh.java new file mode 100644 index 0000000..165e547 --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/client/DrainMesh.java @@ -0,0 +1,93 @@ +package zone.moddev.mc.ironagearchitecture.client; + +import zone.moddev.mc.ironagearchitecture.block.DrainGeometry; + +/** Hollow iron profiles, shared by the fascia mesh and the inventory fittings. */ +final class DrainMesh { + interface Face { void add(double[] a, double[] b, double[] c, double[] d); } + + private DrainMesh() { } + + static void gutter(boolean outlet, int ends, Face face) { + for (int i = 0; i < DrainGeometry.FACETS; i++) { + double[] a = DrainGeometry.gutterPoint(i, 1.5), b = DrainGeometry.gutterPoint(i+1, 1.5); + double[] c = DrainGeometry.gutterPoint(i, 1.13), d = DrainGeometry.gutterPoint(i+1, 1.13); + if (outlet && DrainGeometry.outletOpening(i)) { + trough(face, 0, 7.2, a, b, c, d); + trough(face, 8.8, 16, a, b, c, d); + face.add(gutterPoint(7.2,b), gutterPoint(7.2,a), gutterPoint(7.2,c), gutterPoint(7.2,d)); + face.add(gutterPoint(8.8,a), gutterPoint(8.8,b), gutterPoint(8.8,d), gutterPoint(8.8,c)); + } else trough(face, 0, 16, a, b, c, d); + if ((ends & 1) != 0) face.add(gutterPoint(0,a), gutterPoint(0,b), gutterPoint(0,d), gutterPoint(0,c)); + if ((ends & 2) != 0) face.add(gutterPoint(16,b), gutterPoint(16,a), gutterPoint(16,c), gutterPoint(16,d)); + } + for (int i : new int[]{0, DrainGeometry.FACETS}) { + double[] a = DrainGeometry.gutterPoint(i, 1.5), b = DrainGeometry.gutterPoint(i, 1.13); + if (i == 0) face.add(gutterPoint(0,a), gutterPoint(16,a), gutterPoint(16,b), gutterPoint(0,b)); + else face.add(gutterPoint(16,a), gutterPoint(0,a), gutterPoint(0,b), gutterPoint(16,b)); + } + if ((ends & 1) != 0) cap(0, .22, face); + if ((ends & 2) != 0) cap(16, 15.78, face); + if (outlet) tube(new double[][]{{1.54, DrainGeometry.OUTER}, {0, DrainGeometry.OUTER}}, + DrainGeometry.COLLAR, false, face); + } + + private static void trough(Face face, double start, double end, double[] a, double[] b, double[] c, double[] d) { + face.add(gutterPoint(start,a), gutterPoint(start,b), gutterPoint(end,b), gutterPoint(end,a)); + face.add(gutterPoint(start,d), gutterPoint(start,c), gutterPoint(end,c), gutterPoint(end,d)); + } + + private static void cap(double outer, double inner, Face face) { + double[] center = {2.7, DrainGeometry.OUTER}; + for (int i = 0; i < DrainGeometry.FACETS; i++) { + double[] a = DrainGeometry.gutterPoint(i, 1.13), b = DrainGeometry.gutterPoint(i+1, 1.13); + if (outer == 0) { + face.add(gutterPoint(outer,a), gutterPoint(outer,center), gutterPoint(outer,b), gutterPoint(outer,b)); + face.add(gutterPoint(inner,b), gutterPoint(inner,center), gutterPoint(inner,a), gutterPoint(inner,a)); + } else { + face.add(gutterPoint(outer,b), gutterPoint(outer,center), gutterPoint(outer,a), gutterPoint(outer,a)); + face.add(gutterPoint(inner,a), gutterPoint(inner,center), gutterPoint(inner,b), gutterPoint(inner,b)); + } + } + double[] a = DrainGeometry.gutterPoint(0,1.13), b = DrainGeometry.gutterPoint(DrainGeometry.FACETS,1.13); + face.add(gutterPoint(Math.min(outer,inner),a), gutterPoint(Math.max(outer,inner),a), + gutterPoint(Math.max(outer,inner),b), gutterPoint(Math.min(outer,inner),b)); + } + + private static double[] gutterPoint(double x, double[] point) { return new double[]{x, point[0], point[1]}; } + + static void pipe(double[][] path, Face face) { + tube(path, DrainGeometry.RADIUS, true, face); + if (path[0][0] == 16) { + double z = path[0][1]; + tube(new double[][]{{15.3,z}, {14.5,z}}, DrainGeometry.COLLAR, false, face); + } + } + + private static void tube(double[][] path, double radius, boolean hollow, Face face) { + double[][] outside = DrainGeometry.ring(path, 0, radius); + double[][] inside = DrainGeometry.ring(path, 0, .61); + if (hollow) rim(outside, inside, true, face); + for (int i = 1; i < path.length; i++) { + double[][] next = DrainGeometry.ring(path, i, radius); + double[][] nextInside = DrainGeometry.ring(path, i, .61); + for (int j = 0; j < outside.length; j++) { + int k = (j + 1) % outside.length; + face.add(outside[j], outside[k], next[k], next[j]); + if (hollow) face.add(inside[k], inside[j], nextInside[j], nextInside[k]); + } + outside = next; + inside = nextInside; + } + rim(outside, inside, false, face); + if (!hollow) rim(DrainGeometry.ring(path, 0, radius), DrainGeometry.ring(path, 0, .61), true, face); + } + + private static void rim(double[][] outside, double[][] inside, boolean top, Face face) { + for (int j = 0; j < outside.length; j++) { + int k = (j + 1) % outside.length; + if (top) face.add(outside[k], outside[j], inside[j], inside[k]); + else face.add(outside[j], outside[k], inside[k], inside[j]); + } + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/client/RoofBakedModel.java b/src/main/java/zone/moddev/mc/ironagearchitecture/client/RoofBakedModel.java index 31750b6..89544a4 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/client/RoofBakedModel.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/client/RoofBakedModel.java @@ -15,6 +15,8 @@ import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.util.EnumFacing; import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad; +import zone.moddev.mc.ironagearchitecture.block.DownpipeBlock; +import zone.moddev.mc.ironagearchitecture.block.FasciaBlock; import zone.moddev.mc.ironagearchitecture.block.RoofBlock; import zone.moddev.mc.ironagearchitecture.block.RoofGeometry; import zone.moddev.mc.ironagearchitecture.block.RoofShape; @@ -27,14 +29,16 @@ public final class RoofBakedModel implements IBakedModel { private final TextureAtlasSprite sprite; private final IBakedModel base; public RoofBakedModel(IBakedModel base, RoofBlock block, IBlockState state, TextureAtlasSprite sprite) { - this.base = base; this.sprite = sprite; + this(base, sprite); RoofShape shape = block.geometryShape(state); EnumFacing facing = state.getValue(BlockHorizontal.FACING); if (block instanceof TrimmedRoofBlock) { TrimmedRoofBlock.Kind kind = ((TrimmedRoofBlock) block).kind; TextureAtlasSprite wood = net.minecraft.client.Minecraft.getMinecraft().getTextureMapBlocks() .getAtlasSprite("ironagearchitecture:blocks/beam_dark_oak"); - addTrim(kind, state.getValue(TrimmedRoofBlock.RIGHT), facing, wood); + addTrim(kind, kind == TrimmedRoofBlock.Kind.VERGE && state.getValue(TrimmedRoofBlock.RIGHT), facing, wood); + if (block instanceof FasciaBlock && state.getValue(FasciaBlock.FITTING) != 0) + DrainMesh.gutter(state.getValue(FasciaBlock.FITTING) == 2, state.getValue(FasciaBlock.ENDS), ironFaces(facing)); return; } for (double[][] face : RoofGeometry.top(shape,block.isRidge())) add(face,facing,true); @@ -54,6 +58,45 @@ public RoofBakedModel(IBakedModel base, RoofBlock block, IBlockState state, Text } } + private RoofBakedModel(IBakedModel base, TextureAtlasSprite sprite) { + this.base = base; + this.sprite = sprite; + } + + static RoofBakedModel gutter(IBakedModel base, boolean outlet) { + RoofBakedModel model = new RoofBakedModel(base, iron()); + DrainMesh.gutter(outlet, 3, (a,b,c,d) -> { + // Raise the standalone fitting into the centre of the inventory model. + model.ironFaces(EnumFacing.NORTH).add(inventoryPoint(a), inventoryPoint(b), inventoryPoint(c), inventoryPoint(d)); + }); + return model; + } + + private static double[] inventoryPoint(double[] point) { + return new double[]{point[0], point[1] + 6, point[2] - 6.5}; + } + + static RoofBakedModel pipe(IBakedModel base, DownpipeBlock block, IBlockState state) { + RoofBakedModel model = new RoofBakedModel(base, iron()); + DrainMesh.pipe(block.path(state), model.ironFaces(state.getValue(DownpipeBlock.FACING))); + return model; + } + + private static TextureAtlasSprite iron() { + return net.minecraft.client.Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite("minecraft:blocks/iron_block"); + } + + private DrainMesh.Face ironFaces(EnumFacing facing) { + TextureAtlasSprite metal = iron(); + return (a,b,c,d) -> { + double[][] pixels = {a,b,c,d}; + double[][] points = new double[4][3]; + for (int i = 0; i < 4; i++) + for (int axis = 0; axis < 3; axis++) points[i][axis] = pixels[i][axis] / 16; + add(points, facing, false, metal, null, .43F, .46F, .47F); + }; + } + private void addTrim(TrimmedRoofBlock.Kind kind, boolean right, EnumFacing facing, TextureAtlasSprite wood) { RoofTrimGeometry.Section[] sections = RoofTrimGeometry.sections(kind, right); for (RoofTrimGeometry.Section section : sections) { @@ -112,6 +155,11 @@ private void add(double[][] points, EnumFacing facing, boolean top) { private void add(double[][] points, EnumFacing facing, boolean top, TextureAtlasSprite texture, @Nullable TrimmedRoofBlock.Kind board) { + add(points, facing, top, texture, board, 1, 1, 1); + } + + private void add(double[][] points, EnumFacing facing, boolean top, TextureAtlasSprite texture, + @Nullable TrimmedRoofBlock.Kind board, float red, float green, float blue) { if (java.util.Arrays.equals(points[0],points[1])) points = new double[][]{points[0],points[2],points[3],points[3]}; double[][] rotated = new double[4][]; @@ -137,7 +185,7 @@ private void add(double[][] points, EnumFacing facing, boolean top, TextureAtlas for (int element = 0; element < format.getElementCount(); element++) { switch (format.getElement(element).getUsage()) { case POSITION: builder.put(element,(float)p[0],(float)p[1],(float)p[2],1); break; - case COLOR: builder.put(element,1,1,1,1); break; + case COLOR: builder.put(element,red,green,blue,1); break; case UV: builder.put(element,texture.getInterpolatedU(textureU),texture.getInterpolatedV(textureV),0,1); break; case NORMAL: builder.put(element,(float)(nx/length),(float)(ny/length),(float)(nz/length),0); break; default: builder.put(element); break; diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_fascia.json b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_fascia.json index bbcc653..70092dc 100644 --- a/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_fascia.json +++ b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_fascia.json @@ -1,27 +1,147 @@ { "variants": { - "facing=north,right=false": { + "ends=0,facing=north,fitting=0": { "model": "ironagearchitecture:clay_tile_mesh" }, - "facing=north,right=true": { + "ends=0,facing=north,fitting=1": { "model": "ironagearchitecture:clay_tile_mesh" }, - "facing=east,right=false": { + "ends=0,facing=north,fitting=2": { "model": "ironagearchitecture:clay_tile_mesh" }, - "facing=east,right=true": { + "ends=1,facing=north,fitting=0": { "model": "ironagearchitecture:clay_tile_mesh" }, - "facing=south,right=false": { + "ends=1,facing=north,fitting=1": { "model": "ironagearchitecture:clay_tile_mesh" }, - "facing=south,right=true": { + "ends=1,facing=north,fitting=2": { "model": "ironagearchitecture:clay_tile_mesh" }, - "facing=west,right=false": { + "ends=2,facing=north,fitting=0": { "model": "ironagearchitecture:clay_tile_mesh" }, - "facing=west,right=true": { + "ends=2,facing=north,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=north,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=north,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=north,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=north,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=east,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=east,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=east,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=east,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=east,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=east,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=east,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=east,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=east,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=east,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=east,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=east,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=south,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=south,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=south,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=south,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=south,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=south,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=south,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=south,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=south,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=south,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=south,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=south,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=west,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=west,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=0,facing=west,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=west,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=west,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=1,facing=west,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=west,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=west,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=2,facing=west,fitting=2": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=west,fitting=0": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=west,fitting=1": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "ends=3,facing=west,fitting=2": { "model": "ironagearchitecture:clay_tile_mesh" } } diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe.json b/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe.json new file mode 100644 index 0000000..1b59c0e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe.json @@ -0,0 +1,52 @@ +{ + "variants": { + "facing=north,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=3": { + "model": "ironagearchitecture:drain_mesh" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe_offset.json b/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe_offset.json new file mode 100644 index 0000000..1b59c0e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe_offset.json @@ -0,0 +1,52 @@ +{ + "variants": { + "facing=north,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=3": { + "model": "ironagearchitecture:drain_mesh" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe_shoe.json b/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe_shoe.json new file mode 100644 index 0000000..1b59c0e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/iron_downpipe_shoe.json @@ -0,0 +1,52 @@ +{ + "variants": { + "facing=north,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=north,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=east,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=south,profile=3": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=0": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=1": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=2": { + "model": "ironagearchitecture:drain_mesh" + }, + "facing=west,profile=3": { + "model": "ironagearchitecture:drain_mesh" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/lang/en_GB.lang b/src/main/resources/assets/ironagearchitecture/lang/en_GB.lang index 1f2e1c4..ffbc1ce 100644 --- a/src/main/resources/assets/ironagearchitecture/lang/en_GB.lang +++ b/src/main/resources/assets/ironagearchitecture/lang/en_GB.lang @@ -490,6 +490,14 @@ tile.ironagearchitecture.clay_tile_ridge.name=Clay Tile Ridge Cap tile.ironagearchitecture.clay_tile_fascia.name=Clay Tile Fascia tile.ironagearchitecture.clay_tile_verge.name=Clay Tile Verge tile.ironagearchitecture.clay_tile_ridge_end.name=Clay Tile Ridge End +item.ironagearchitecture.iron_gutter.name=Iron Gutter +item.ironagearchitecture.iron_gutter_outlet.name=Iron Gutter Outlet +tile.ironagearchitecture.iron_downpipe.name=Iron Downpipe +tile.ironagearchitecture.iron_downpipe_shoe.name=Iron Downpipe Shoe +tile.ironagearchitecture.iron_downpipe_offset.0.name=Eave Offset +tile.ironagearchitecture.iron_downpipe_offset.1.name=Half-block Offset +tile.ironagearchitecture.iron_downpipe_offset.2.name=Full-block Outer Elbow +tile.ironagearchitecture.iron_downpipe_offset.3.name=Full-block Inner Elbow tile.ironagearchitecture.clay_tile_hanging.name=Hanging Clay Tiles tile.ironagearchitecture.clay_tile_hanging_thin.name=Hanging Clay Tiles (Thin) tile.ironagearchitecture.leaded_window_full_cob_diamond_oak.name=Oak Diamond Leaded Window @@ -542,6 +550,17 @@ tile.ironagearchitecture.leaded_window_thin_limewashed_cob_diamond_dark_oak.name tile.ironagearchitecture.leaded_window_thin_limewashed_cob_rectangle_dark_oak.name=Limewashed Dark Oak Rectangular Leaded Window (Thin) item.ironagearchitecture.leaded_pane.0.name=Diamond Leaded Pane item.ironagearchitecture.leaded_pane.1.name=Rectangular Leaded Pane +hint.ironagearchitecture.gutter.fit=Fit to the outside face of a bare clay fascia. Sneak and use an empty hand to recover it. +hint.ironagearchitecture.gutter.outlet=Place a downpipe beneath the outlet to carry rain down. +hint.ironagearchitecture.gutter.craft=Three iron ingots in a row make six gutters. Add an ingot to make an outlet. +hint.ironagearchitecture.pipe.place=Place against a pipe end to line up. Sneak, face the wall and click to pick the depth. +hint.ironagearchitecture.pipe.craft=Three iron ingots in a column make six pipes. Add a stick to make an offset. +hint.ironagearchitecture.pipe.offset.0=Fits under an outlet and brings the pipe back to the wall. +hint.ironagearchitecture.pipe.offset.1=Fits under an outside pipe and brings it half a block in. +hint.ironagearchitecture.pipe.offset.2=Place beneath the upper pipe with its side opening toward the wall. +hint.ironagearchitecture.pipe.offset.3=Place beside the outer elbow, one cell closer to the wall. +hint.ironagearchitecture.pipe.cycle=Craft alone to switch to the next offset shape. +hint.ironagearchitecture.pipe.shoe=Craft a downpipe alone. Fit beneath the last pipe to let rain spill out above the ground. hint.ironagearchitecture.window.use=Right-click to open the casement; redstone operates the whole column. hint.ironagearchitecture.window.place=Stack to share a hinge; sneak to choose a different alignment. hint.ironagearchitecture.window.glaze=Craft alone to swap leading. Shape the wall before glazing. diff --git a/src/main/resources/assets/ironagearchitecture/lang/en_US.lang b/src/main/resources/assets/ironagearchitecture/lang/en_US.lang index 1f2e1c4..ffbc1ce 100644 --- a/src/main/resources/assets/ironagearchitecture/lang/en_US.lang +++ b/src/main/resources/assets/ironagearchitecture/lang/en_US.lang @@ -490,6 +490,14 @@ tile.ironagearchitecture.clay_tile_ridge.name=Clay Tile Ridge Cap tile.ironagearchitecture.clay_tile_fascia.name=Clay Tile Fascia tile.ironagearchitecture.clay_tile_verge.name=Clay Tile Verge tile.ironagearchitecture.clay_tile_ridge_end.name=Clay Tile Ridge End +item.ironagearchitecture.iron_gutter.name=Iron Gutter +item.ironagearchitecture.iron_gutter_outlet.name=Iron Gutter Outlet +tile.ironagearchitecture.iron_downpipe.name=Iron Downpipe +tile.ironagearchitecture.iron_downpipe_shoe.name=Iron Downpipe Shoe +tile.ironagearchitecture.iron_downpipe_offset.0.name=Eave Offset +tile.ironagearchitecture.iron_downpipe_offset.1.name=Half-block Offset +tile.ironagearchitecture.iron_downpipe_offset.2.name=Full-block Outer Elbow +tile.ironagearchitecture.iron_downpipe_offset.3.name=Full-block Inner Elbow tile.ironagearchitecture.clay_tile_hanging.name=Hanging Clay Tiles tile.ironagearchitecture.clay_tile_hanging_thin.name=Hanging Clay Tiles (Thin) tile.ironagearchitecture.leaded_window_full_cob_diamond_oak.name=Oak Diamond Leaded Window @@ -542,6 +550,17 @@ tile.ironagearchitecture.leaded_window_thin_limewashed_cob_diamond_dark_oak.name tile.ironagearchitecture.leaded_window_thin_limewashed_cob_rectangle_dark_oak.name=Limewashed Dark Oak Rectangular Leaded Window (Thin) item.ironagearchitecture.leaded_pane.0.name=Diamond Leaded Pane item.ironagearchitecture.leaded_pane.1.name=Rectangular Leaded Pane +hint.ironagearchitecture.gutter.fit=Fit to the outside face of a bare clay fascia. Sneak and use an empty hand to recover it. +hint.ironagearchitecture.gutter.outlet=Place a downpipe beneath the outlet to carry rain down. +hint.ironagearchitecture.gutter.craft=Three iron ingots in a row make six gutters. Add an ingot to make an outlet. +hint.ironagearchitecture.pipe.place=Place against a pipe end to line up. Sneak, face the wall and click to pick the depth. +hint.ironagearchitecture.pipe.craft=Three iron ingots in a column make six pipes. Add a stick to make an offset. +hint.ironagearchitecture.pipe.offset.0=Fits under an outlet and brings the pipe back to the wall. +hint.ironagearchitecture.pipe.offset.1=Fits under an outside pipe and brings it half a block in. +hint.ironagearchitecture.pipe.offset.2=Place beneath the upper pipe with its side opening toward the wall. +hint.ironagearchitecture.pipe.offset.3=Place beside the outer elbow, one cell closer to the wall. +hint.ironagearchitecture.pipe.cycle=Craft alone to switch to the next offset shape. +hint.ironagearchitecture.pipe.shoe=Craft a downpipe alone. Fit beneath the last pipe to let rain spill out above the ground. hint.ironagearchitecture.window.use=Right-click to open the casement; redstone operates the whole column. hint.ironagearchitecture.window.place=Stack to share a hinge; sneak to choose a different alignment. hint.ironagearchitecture.window.glaze=Craft alone to swap leading. Shape the wall before glazing. diff --git a/src/main/resources/assets/ironagearchitecture/models/block/drain_mesh.json b/src/main/resources/assets/ironagearchitecture/models/block/drain_mesh.json new file mode 100644 index 0000000..4df4d04 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/block/drain_mesh.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/block", + "textures": { + "particle": "minecraft:blocks/iron_block" + } +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe.json b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe.json new file mode 100644 index 0000000..96bb26e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/drain_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_0.json b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_0.json new file mode 100644 index 0000000..96bb26e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_0.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/drain_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_1.json b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_1.json new file mode 100644 index 0000000..96bb26e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_1.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/drain_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_2.json b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_2.json new file mode 100644 index 0000000..96bb26e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_2.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/drain_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_3.json b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_3.json new file mode 100644 index 0000000..96bb26e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_offset_3.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/drain_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_shoe.json b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_shoe.json new file mode 100644 index 0000000..96bb26e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/iron_downpipe_shoe.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/drain_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/iron_gutter.json b/src/main/resources/assets/ironagearchitecture/models/item/iron_gutter.json new file mode 100644 index 0000000..96bb26e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/iron_gutter.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/drain_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/iron_gutter_outlet.json b/src/main/resources/assets/ironagearchitecture/models/item/iron_gutter_outlet.json new file mode 100644 index 0000000..96bb26e --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/iron_gutter_outlet.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/drain_mesh" +} diff --git a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ArchitectureProbe.java b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ArchitectureProbe.java index 6d6dd84..cfe0bf7 100644 --- a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ArchitectureProbe.java +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ArchitectureProbe.java @@ -45,7 +45,7 @@ /** Disposable packaged-runtime verification mod. Never part of the player jar. */ @Mod(modid="ironagearchitectureprobe",name="Architecture Verification",version="1",acceptableRemoteVersions="*", - dependencies="required-after:ironagearchitecture@[0.1.10.110021]") + dependencies="required-after:ironagearchitecture@[0.1.11.110021]") public final class ArchitectureProbe { @SidedProxy(clientSide="zone.moddev.mc.ironagearchitecture.probe.ClientProbe",serverSide="zone.moddev.mc.ironagearchitecture.probe.ProbeProxy") public static ProbeProxy proxy; @@ -54,8 +54,14 @@ public final class ArchitectureProbe { @Mod.EventHandler public void started(FMLServerStartedEvent event) throws Exception { if(!Boolean.getBoolean("iaa.probe")) return; MinecraftServer server=FMLCommonHandler.instance().getMinecraftServerInstance(); - verifyConfigMatrix(); WorldServer world=server.worldServerForDimension(0); + if (Boolean.getBoolean("iaa.legacyFascia")) { + int bareFascias = GutterProbe.verifyOldPreview(world); + finish(server, "{\"passed\":true,\"bareFascias\":" + bareFascias + + ",\"runtimeSha256\":\"" + System.getProperty("iaa.runtimeSha256", "development") + "\"}\n"); + return; + } + verifyConfigMatrix(); if(Boolean.getBoolean("iaa.eraMatrix")) EraProbe.verify(world); if(Boolean.getBoolean("iaa.recipeMatrix")) RecipeProbe.verify(world); if(Boolean.getBoolean("iaa.legacy")) verifyLegacy(world); @@ -79,7 +85,6 @@ public final class ArchitectureProbe { require(index==4448,"0.1.2 legacy matrix incomplete"); } for(Block block:savedBlocks) { - int count=block instanceof FrameBlock?((FrameBlock)block).variants():1; for(int meta=0;meta<16;meta++) { IBlockState state=block.getStateFromMeta(meta); require(block.getStateFromMeta(block.getMetaFromState(state)).equals(state),"Metadata "+block+" "+meta); @@ -89,8 +94,10 @@ public final class ArchitectureProbe { List drops=block.getDrops(world,pos,state,0); Item expected=block instanceof PartialWallBlock?Item.getItemFromBlock(((PartialWallBlock)block).canonical()): block instanceof JettyFloorBlock?Item.getItemFromBlock(((JettyFloorBlock)block).canonical()):Item.getItemFromBlock(block); - require(drops.size()==(block instanceof JettyFloorBlock&&((JettyFloorBlock)block).bracket!=null?2:1)&&drops.get(0).getItem()==expected,"Drop item "+block); - require(drops.get(0).getMetadata()==(block instanceof FrameBlock?block.damageDropped(state):0),"Drop identity "+block); + boolean attachment = block instanceof JettyFloorBlock && ((JettyFloorBlock)block).bracket != null + || block instanceof FasciaBlock && state.getValue(FasciaBlock.FITTING) != 0; + require(drops.size()==(attachment?2:1)&&drops.get(0).getItem()==expected,"Drop item "+block); + require(drops.get(0).getMetadata()==(block instanceof FrameBlock || block instanceof DownpipeBlock ? block.damageDropped(state) : 0),"Drop identity "+block); states++; } } @@ -135,9 +142,10 @@ public final class ArchitectureProbe { } } ClayTileProbe.verify(world); + GutterProbe.verify(world); if (!reload && clayClient) RyeTilePreview.build(world); if (Boolean.getBoolean("iaa.clayOnly")) { - require(variants == 7 && recipes == 10 && RecipeProbe.verifyReachability() == 7, "Standalone clay tiles"); + require(variants == 15 && recipes == 19 && RecipeProbe.verifyReachability() == 15, "Standalone clay tiles"); } world.setWorldTime(1000);world.getGameRules().setOrCreateGameRule("doDaylightCycle","false"); world.getGameRules().setOrCreateGameRule("doFireTick","false");world.getGameRules().setOrCreateGameRule("doMobSpawning","false"); @@ -148,6 +156,10 @@ public final class ArchitectureProbe { +",\"legacy\":"+Boolean.getBoolean("iaa.legacy")+",\"legacyDaub\":"+Boolean.getBoolean("iaa.legacyDaub") +",\"legacyJetties\":"+Boolean.getBoolean("iaa.legacyJetties")+",\"eras\":"+new com.google.gson.Gson().toJson(zone.moddev.mc.ironagearchitecture.ArchitectureConfig.survivalEras.allowed) +",\"eraMatrix\":"+Boolean.getBoolean("iaa.eraMatrix")+",\"recipeMatrix\":"+Boolean.getBoolean("iaa.recipeMatrix")+",\"runtimeSha256\":\""+System.getProperty("iaa.runtimeSha256","development")+"\"}\n"; + finish(server, result); + } + + private static void finish(MinecraftServer server, String result) throws java.io.IOException { Files.write(Paths.get(System.getProperty("iaa.receipt","architecture-probe.json")),result.getBytes(StandardCharsets.UTF_8)); System.out.println("IAA_PROBE_PASS "+result.trim()); if(!Boolean.getBoolean("iaa.keepRunning")) server.initiateShutdown(); diff --git a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClayTileProbe.java b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClayTileProbe.java index 97825ee..881e0e9 100644 --- a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClayTileProbe.java +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClayTileProbe.java @@ -21,7 +21,7 @@ final class ClayTileProbe { private static final String PREFIX = "ironagearchitecture:clay_tile_"; static boolean isClay(Block block) { - return block instanceof TileHangingBlock || block instanceof RoofBlock && "clay_tile".equals(((RoofBlock) block).materialKey); + return block instanceof DownpipeBlock || block instanceof TileHangingBlock || block instanceof RoofBlock && "clay_tile".equals(((RoofBlock) block).materialKey); } static void verify(WorldServer world) { diff --git a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClientProbe.java b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClientProbe.java index 5b2bc4a..f166945 100644 --- a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClientProbe.java +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClientProbe.java @@ -83,6 +83,7 @@ public final class ClientProbe extends ProbeProxy { throw new IllegalStateException("Wrong hanging tile sprite " + state); } } + checkDrainModels(mc); phase=60;ticks=0; return; } @@ -114,14 +115,23 @@ public final class ClientProbe extends ProbeProxy { if(phase<57){double[] c=cameras[phase-50];lookAt(mc,c[0],c[1],c[2],c[3],c[4],c[5]);phase++;ticks=0;return;} checkPalettes(mc); mc.displayGuiScreen(new PaletteGui(mc,Era.IRON_AGE,true));phase=8;ticks=0; - } else if (phase>=60&&phase<=62) { - double[][] cameras={{-62,25,-16,-46,17,1},{-32,9.1,-7,-46,16,0},{-44,14.4,-7,-45.5,18.7,-.3}}; + } else if (phase>=60&&phase<=65) { + double[][] cameras={{-62,25,-16,-46,17,1},{-32,9.1,-7,-46,16,0}, + {-51.8,15.5,-7,-55.2,18.3,-.5},{-31.8,10.4,-6,-34.5,13.5,-.3}, + {-32.7,8.9,-2.8,-34.5,10.55,.5},{-32.7,8.9,-2.8,-34.5,10.55,.5}}; double[] camera=cameras[phase-60]; lookAt(mc,camera[0],camera[1],camera[2],camera[3],camera[4],camera[5]); if (ticks<=130) return; - String[] names={"rye-roof-trim-overview.png","rye-roof-trim-street.png","rye-roof-trim-gable.png"}; + String[] names={"rye-guttering-overview.png","rye-guttering-street.png", + "rye-guttering-half-jetty.png","rye-guttering-full-jetty.png","rye-downpipe-shoe-closeup.png","rye-downpipe-shoe-rain.png"}; + if (phase == 65 && !rainReady(mc)) { + if (ticks > 300) throw new IllegalStateException("Preview rain did not reach the pipe foot"); + return; + } capture(mc,names[phase-60]); - if (phase<62) {phase++;ticks=0;return;} + if (phase == 64) setPreviewRain(mc, true); + if (phase < 65) {phase++;ticks=0;return;} + setPreviewRain(mc, false); checkPalettes(mc); mc.displayGuiScreen(new PaletteGui(mc,Era.IRON_AGE,true));phase=8;ticks=0; } else if(phase==8) { @@ -136,6 +146,35 @@ public final class ClientProbe extends ProbeProxy { } } } + private static void checkDrainModels(Minecraft mc) { + for (ItemStack stack : ArchitectureBlocks.visibleStacks()) { + String key = stack.getItem().getRegistryName().getResourcePath(); + if (!key.startsWith("iron_gutter") && !key.startsWith("iron_downpipe")) continue; + IBakedModel model = mc.getRenderItem().getItemModelMesher().getItemModel(stack); + if (!(model instanceof RoofBakedModel) || model.getQuads(null, null, 0).isEmpty() + || !"minecraft:blocks/iron_block".equals(model.getParticleTexture().getIconName())) + throw new IllegalStateException("Missing drain item model " + stack); + } + } + + private static void setPreviewRain(Minecraft mc, boolean rain) { + mc.getIntegratedServer().addScheduledTask(() -> { + net.minecraft.world.storage.WorldInfo info = mc.getIntegratedServer().worldServerForDimension(0).getWorldInfo(); + info.setCleanWeatherTime(rain ? 0 : 12000); + info.setRainTime(12000); + info.setRaining(rain); + }); + } + + private static boolean rainReady(Minecraft mc) { + net.minecraft.util.math.BlockPos foot = RyeTilePreview.RAIN_FOOT; + if (zone.moddev.mc.ironagearchitecture.block.DrainRun.canDrip(mc.world.getBlockState(foot), mc.world, foot)) { + System.out.println("IAA_GUTTER_RAIN_PREVIEW_PASS"); + return true; + } + return false; + } + private void checkPalettes(Minecraft mc) { for(Era era:Era.values()) { java.util.List items=new java.util.ArrayList<>();EraTabs.tab(era).displayAllRelevantItems(items); diff --git a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/EraProbe.java b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/EraProbe.java index ee2f40c..7927d17 100644 --- a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/EraProbe.java +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/EraProbe.java @@ -15,10 +15,10 @@ final class EraProbe { private static void require(boolean condition,String message) {if(!condition) throw new IllegalStateException(message);} static int expectedRecipes() { - return ArchitectureConfig.survivalEras.allowed.contains(Era.LATE_MEDIEVAL_TUDOR)?(ArchitectureConfig.windows?2010:1863)+(ArchitectureConfig.clayTiles?10:0): + return ArchitectureConfig.survivalEras.allowed.contains(Era.LATE_MEDIEVAL_TUDOR)?(ArchitectureConfig.windows?2010:1863)+(ArchitectureConfig.clayTiles?19:0): ArchitectureConfig.survivalEras.allowed.contains(Era.IRON_AGE)?36:0; } - static int expectedVariants() { return (ArchitectureConfig.windows ? 510 : 460) + (ArchitectureConfig.clayTiles ? 7 : 0); } + static int expectedVariants() { return (ArchitectureConfig.windows ? 510 : 460) + (ArchitectureConfig.clayTiles ? 15 : 0); } private static boolean ours(IRecipe recipe) { ItemStack output=recipe.getRecipeOutput(); return output!=null&&"ironagearchitecture".equals(output.getItem().getRegistryName().getResourceDomain()); diff --git a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/GutterProbe.java b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/GutterProbe.java new file mode 100644 index 0000000..3f53a2f --- /dev/null +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/GutterProbe.java @@ -0,0 +1,190 @@ +package zone.moddev.mc.ironagearchitecture.probe; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.common.util.FakePlayerFactory; +import zone.moddev.mc.ironagearchitecture.ArchitectureBlocks; +import zone.moddev.mc.ironagearchitecture.block.DownpipeBlock; +import zone.moddev.mc.ironagearchitecture.block.DrainGeometry; +import zone.moddev.mc.ironagearchitecture.block.DrainRun; +import zone.moddev.mc.ironagearchitecture.block.FasciaBlock; + +final class GutterProbe { + private GutterProbe() { } + + static int verifyOldPreview(WorldServer world) { + FasciaBlock fascia = ArchitectureBlocks.CLAY_FASCIA; + int count = 0; + for (int[] run : new int[][]{{-58,-51,20}, {-41,-34,15}}) { + for (int x = run[0]; x <= run[1]; x++) { + for (int z : new int[]{-1,7}) { + EnumFacing facing = z == -1 ? EnumFacing.SOUTH : EnumFacing.NORTH; + IBlockState old = world.getBlockState(new BlockPos(x, run[2], z)); + require(old.equals(fascia.getDefaultState().withProperty(FasciaBlock.FACING, facing)), + "Original fascia changed at " + x + "," + run[2] + "," + z); + count++; + } + } + } + System.out.println("IAA_LEGACY_FASCIA_PASS bareFascias=" + count); + return count; + } + + static void verify(WorldServer world) { + if (ArchitectureBlocks.GUTTER == null) { + for (String id : new String[]{"iron_gutter", "iron_gutter_outlet", "iron_downpipe", "iron_downpipe_offset", "iron_downpipe_shoe"}) + require(Item.getByNameOrId("ironagearchitecture:" + id) == null, "Disabled drain item " + id); + return; + } + BlockPos pos = new BlockPos(95, 100, 95); + FakePlayer player = FakePlayerFactory.getMinecraft(world); + try { + for (EnumFacing facing : EnumFacing.HORIZONTALS) verifyFacing(world, player, pos, facing); + } finally { + player.setSneaking(false); + player.setHeldItem(EnumHand.MAIN_HAND, null); + } + System.out.println("IAA_GUTTER_PASS facings=4 chunkJoin=true bothOffsets=true rainGuards=true shoeAbovePaving=true"); + } + + private static void verifyFacing(WorldServer world, FakePlayer player, BlockPos pos, EnumFacing facing) { + FasciaBlock fascia = ArchitectureBlocks.CLAY_FASCIA; + EnumFacing outside = facing.getOpposite(); + IBlockState bare = fascia.getStateFromMeta(facing.getHorizontalIndex()); + require(bare.getValue(FasciaBlock.FITTING) == 0, "Legacy fascia gained a fitting"); + world.setBlockState(pos, bare, 2); + player.setSneaking(false); + player.setPosition(pos.getX()-4, pos.getY(), pos.getZ()-4); + player.rotationYaw = outside.getHorizontalIndex() * 90; + ItemStack outlet = new ItemStack(ArchitectureBlocks.GUTTER_OUTLET, 2); + require(use(world, player, pos, outlet, facing) == EnumActionResult.PASS && outlet.stackSize == 2, + "Fitting attached to the inner face"); + require(use(world, player, pos, outlet, outside) == EnumActionResult.SUCCESS && outlet.stackSize == 1, + "Fitting did not attach from outside"); + IBlockState fitted = world.getBlockState(pos); + require(fitted.getValue(FasciaBlock.FITTING) == 2 && pos.equals(DrainRun.exposedInletForDrip(fitted, world, pos)), + "Exposed outlet did not drip"); + require(use(world, player, pos, outlet, outside) == EnumActionResult.FAIL && outlet.stackSize == 1, + "Fitting attached twice"); + + BlockPos left = pos.offset(facing.rotateYCCW()), right = pos.offset(facing.rotateY()); + world.setBlockState(left, bare.withProperty(FasciaBlock.FITTING, 1), 2); + world.setBlockState(right, bare.withProperty(FasciaBlock.FITTING, 1), 2); + require(fascia.getActualState(fitted, world, pos).getValue(FasciaBlock.ENDS) == 0, "Joined gutter has end caps"); + world.setBlockState(right, bare.withProperty(FasciaBlock.FITTING, 1) + .withProperty(FasciaBlock.FACING, facing.getOpposite()), 2); + require(fascia.getActualState(fitted, world, pos).getValue(FasciaBlock.ENDS) == 2, "Reversed gutter joined"); + world.setBlockToAir(left); + world.setBlockToAir(right); + + DownpipeBlock pipe = ArchitectureBlocks.DOWNPIPE, offset = ArchitectureBlocks.DOWNPIPE_OFFSET; + BlockPos first = pos.down(), elbow = first.down(), inner = elbow.offset(facing), foot = inner.down(); + require(use(world, player, pos, new ItemStack(pipe), EnumFacing.DOWN) == EnumActionResult.SUCCESS + && world.getBlockState(first).equals(pipe.state(facing, 2)), "Pipe failed to inherit the outlet port"); + require(DrainRun.exposedInletForDrip(fitted, world, pos) == null + && pos.equals(DrainRun.exposedInletForDrip(world.getBlockState(first), world, first)), + "Drip did not move to the bottom of the pipe"); + require(use(world, player, first, new ItemStack(offset, 1, 1), EnumFacing.DOWN) == EnumActionResult.SUCCESS + && world.getBlockState(elbow).equals(offset.state(facing, 1)), "Half offset failed to inherit the outer port"); + require(use(world, player, elbow, new ItemStack(pipe), EnumFacing.DOWN) == EnumActionResult.SUCCESS + && world.getBlockState(elbow.down()).equals(pipe.state(facing, 1)), "Half lane did not line up"); + require(pos.equals(DrainRun.exposedInletForDrip(world.getBlockState(elbow.down()), world, elbow.down())), "Half route is disconnected"); + world.setBlockToAir(elbow.down()); + + world.setBlockState(first, offset.state(facing, 0), 2); + world.setBlockState(elbow, offset.state(facing, 2), 2); + require(use(world, player, elbow, new ItemStack(offset, 1, 3), facing) == EnumActionResult.SUCCESS + && world.getBlockState(inner).equals(offset.state(facing, 3)), "Full offset failed at its side port"); + require(use(world, player, inner, new ItemStack(pipe), EnumFacing.DOWN) == EnumActionResult.SUCCESS + && world.getBlockState(foot).equals(pipe.state(facing, 0)), "Wall lane did not line up"); + IBlockState footState = world.getBlockState(foot); + require(pos.equals(DrainRun.exposedInletForDrip(footState, world, foot)), "Full offset lost its rain path"); + + Vec3d start = DrainGeometry.point(7, 0, facing).addVector(foot.getX(), foot.getY(), foot.getZ()); + Vec3d end = DrainGeometry.point(7, 16, facing).addVector(foot.getX(), foot.getY(), foot.getZ()); + require(pipe.collisionRayTrace(footState, world, foot, end, start) != null + && pipe.collisionRayTrace(footState, world, foot, end.addVector(.3,0,.3), start.addVector(.3,0,.3)) == null, + "Pipe ray covered empty space or missed the pipe"); + for (int profile = 0; profile < 4; profile++) + for (AxisAlignedBB box : offset.boxes(offset.state(facing, profile))) + require(box.minX >= 0 && box.minY >= 0 && box.minZ >= 0 + && box.maxX <= 1 && box.maxY <= 1 && box.maxZ <= 1, "Elbow collision escaped its cell"); + + verifyShoe(world, player, pos, foot, facing); + + world.setBlockState(foot.down(), Blocks.STONE.getDefaultState(), 2); + require(DrainRun.exposedInletForDrip(footState, world, foot) == null, "Blocked pipe dripped"); + world.setBlockToAir(foot.down()); + world.setBlockState(pos.up(), Blocks.STONE.getDefaultState(), 2); + require(DrainRun.exposedInletForDrip(footState, world, foot) == null, "Sheltered inlet dripped"); + world.setBlockToAir(pos.up()); + if (!world.isRaining()) require(!DrainRun.canDrip(footState, world, foot), "Dry inlet dripped"); + world.setBlockToAir(inner); + require(DrainRun.exposedInletForDrip(footState, world, foot) == null, "Disconnected pipe dripped"); + world.setBlockToAir(foot); + world.setBlockToAir(elbow); + world.setBlockToAir(first); + + require(fascia.getDrops(world, pos, fitted, 0).stream().anyMatch(s -> s.getItem() == ArchitectureBlocks.GUTTER_OUTLET), + "Harvest lost its outlet"); + player.setSneaking(true); + player.setHeldItem(EnumHand.MAIN_HAND, null); + player.setHeldItem(EnumHand.OFF_HAND, null); + Vec3d hit = DrainGeometry.point(2, 15.8, facing); + require(fascia.onBlockActivated(world, pos, fitted, player, EnumHand.MAIN_HAND, null, outside, + (float)hit.xCoord, (float)hit.yCoord, (float)hit.zCoord) && world.getBlockState(pos).equals(bare), + "Empty hand could not recover the fitting"); + world.setBlockToAir(pos); + } + + private static void verifyShoe(WorldServer world, FakePlayer player, BlockPos inlet, BlockPos pipePos, EnumFacing facing) { + DownpipeBlock shoe = ArchitectureBlocks.DOWNPIPE_SHOE; + BlockPos at = pipePos.down(); + world.setBlockState(at.down(), Blocks.COBBLESTONE.getDefaultState(), 2); + IBlockState original = world.getBlockState(pipePos); + try { + for (int lane = 0; lane < 3; lane++) { + world.setBlockState(pipePos, ArchitectureBlocks.DOWNPIPE.state(facing, lane), 2); + require(use(world, player, pipePos, new ItemStack(shoe), EnumFacing.DOWN) == EnumActionResult.SUCCESS, + "Could not place shoe above paving"); + IBlockState state = world.getBlockState(at); + require(state.equals(shoe.state(facing, lane)), "Shoe did not inherit its pipe lane"); + double[][] path = shoe.path(state); + double[] mouth = path[path.length - 1]; + Vec3d from = DrainGeometry.point(mouth[0], 16, facing).addVector(at.getX(), at.getY(), at.getZ()); + Vec3d to = DrainGeometry.point(mouth[0], mouth[1] - .5, facing).addVector(at.getX(), at.getY(), at.getZ()); + require(shoe.collisionRayTrace(state, world, at, from, to) != null, "Angled shoe mouth could not be targeted"); + require(shoe.getBoundingBox(state, world, at).minY > 0 && !state.getMaterial().isSolid(), + "Shoe has no space for drips above paving"); + if (lane == 0) { + require(inlet.equals(DrainRun.exposedInletForDrip(state, world, at)), + "Connected shoe could not discharge over paving"); + world.setBlockToAir(pipePos); + require(DrainRun.exposedInletForDrip(state, world, at) == null, "Disconnected shoe dripped"); + } + world.setBlockToAir(at); + } + } finally { + world.setBlockState(pipePos, original, 2); + world.setBlockToAir(at.down()); + } + } + + private static EnumActionResult use(WorldServer world, FakePlayer player, BlockPos pos, ItemStack stack, EnumFacing side) { + return stack.getItem().onItemUse(stack, player, world, pos, EnumHand.MAIN_HAND, side, .5F, .1F, .5F); + } + + private static void require(boolean ok, String message) { + if (!ok) throw new IllegalStateException(message); + } +} diff --git a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/RecipeProbe.java b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/RecipeProbe.java index 00bf471..8d76b6e 100644 --- a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/RecipeProbe.java +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/RecipeProbe.java @@ -112,6 +112,7 @@ private static String material(ItemStack stack) { Block block=Block.getBlockFromItem(item); if(block instanceof JettyFloorBlock) return "floor:"+((JettyFloorBlock)block).wood; if(block instanceof FrameBlock) {FrameBlock wall=(FrameBlock)block;return wall.family==FrameFamily.INFILL?"daub:"+wall.finish(stack.getMetadata()):"frame:"+wall.wood+":"+wall.finish(stack.getMetadata());} + if(item instanceof GutterItem || block instanceof DownpipeBlock) return "iron_drain"; if(block instanceof RoofBlock) return "roof:"+((RoofBlock)block).materialKey; if(block instanceof TileHangingBlock) return "roof:clay_tile"; for(int finish=0;finish