diff --git a/CHANGELOG.txt b/CHANGELOG.txt index bd3c20a..dabdb6c 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,16 @@ +0.1.10.110021 - Alpha + +I've added the clay tiles and timber roof trim I've been trying out on the Rye cottages. + +- Build with weathered clay tile slopes and matching ridge caps. The slopes turn hips + and valleys like the existing roofs. +- Hang the same tiles on gables and upper walls. Thin panels line up with half-depth + timber walls and leaded windows. +- Finish straight eaves with dark oak fascia boards. Matching verge and ridge end pieces + give a projecting gable a continuous timber edge and a thin underside. +- Start with bricks and sticks, then turn the slopes into ridge caps, trim or hanging + panels. The clay pieces are in the Late Medieval & Tudor palette. + 0.1.8.110021 - Alpha This update adds openable leaded windows and tidies up the timber framing. The village diff --git a/README.md b/README.md index 2b93490..e3f9290 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ close studding and diagonal braces. You choose the spacing and pattern, from a m to a richly framed townhouse. Matching timbers join around corners and through intersections. All six vanilla wood types are available. Cob comes in blocks, slabs and stairs, and smooth -thatch and slate roofs have automatic corners and separate ridge caps. +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. 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. @@ -35,7 +37,7 @@ floor edges, and matching corner pieces let the overhang continue around the sid ## Getting started -The **0.1.8.110021 alpha** is for **Minecraft 1.10.2 with Forge**. Everything is crafted from vanilla +Iron Age Architecture targets **Minecraft 1.10.2 with Forge**. Everything is crafted from vanilla ingredients, and the main content families can be enabled or disabled in the configuration. See the [CurseForge files](https://www.curseforge.com/minecraft/mc-mods/ironagearchitecture/files/all) diff --git a/art/README.md b/art/README.md index 38533dd..5d7955d 100644 --- a/art/README.md +++ b/art/README.md @@ -18,6 +18,10 @@ Per-material prompt additions: The historical modeltemplates/ directory retains the 2019 drafts and their authorship. The new runtime geometry uses local templates and the shared Java roof mesh. +The two 32×32 clay tile sprites were drawn on a pixel grid. The main sprite staggers +four overlapping courses and is shared by the roof and hanging tiles. The cap +sprite shades across the ridge and repeats the joints along its length. + ## Timber beam revision Six new beam-side textures were generated with the built-in image-generation tool for the diff --git a/docs/BUILDING-GUIDE.md b/docs/BUILDING-GUIDE.md index 36563eb..dec792b 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.8.110021 contains raw and limewashed cob blocks, +Version 0.1.10.110021 contains raw and limewashed cob blocks, stairs and slabs; modular wattle-and-daub walls in all six vanilla woods; and smooth -thatch/slate roofs with automatic corners and separate ridge caps, plus jettied-storey walls, -timber floors, support brackets and openable leaded windows. There are 510 visible +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 variants including the wood and finish choices. Architecture remains standalone. ## Wattle and daub @@ -65,6 +65,16 @@ faces that direction. Adjacent matching slopes form hips and valleys using stair Ridges run along the direction the player looks; place them one row above the high ends of the last slopes. Their half-block apex completes the 45-degree roof profile. +For a timber fascia along a straight clay eave, craft a slope with a stick and +place it facing uphill. At a gable, craft a slope with a dark oak plank to make +a verge. Face uphill and click the half that will overhang the wall. Its thin +underside covers a half-block projection. Craft a clay ridge cap with a dark oak +plank for the matching ridge end and place it while looking inward along the ridge. + +Use full hanging tiles to fill the stepped gable beneath a clay roof. Thin hanging +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. + ## Leaded windows Use full-depth surrounds in full walls and half-depth surrounds in thin walls. Both come @@ -160,8 +170,11 @@ or six from L-shaped corners. Either inside/outside pattern can be recovered thi - Three quarter floors in an L: one L-shaped floor. Craft an L-shaped floor alone to recover three quarters. - One full timber floor plus one stick, shapeless: two full-reach brackets in that wood. One full-reach bracket alone makes two half-reach brackets; two halves side by side make one full bracket. -- Three wheat above three sticks: six thatch roof slopes. Use stone instead of wheat for slate. +- Three wheat above three sticks: six thatch roof slopes. Use stone for slate or bricks for clay tile. - Two matching slopes vertically: four ridge caps. Two ridge caps side by side: one slope. +- Craft a clay tile slope alone to turn it into a full hanging panel, or craft the full panel + alone to get the slope back. Two full panels vertically make four thin panels. + Two thin panels side by side rejoin into one full panel. ### Limewashing @@ -174,7 +187,7 @@ shaped corners follow this rule too. The creative tabs follow building eras. **Iron Age** currently holds the shared cob, plain daub and thatch pieces. **Late Medieval & Tudor** holds the full timber-framed -village kit, including leaded windows, jetties, brackets and slate roofs, along with those shared materials. +village kit, including leaded windows, jetties, brackets, slate and clay tiles, along with those shared materials. The Iron Age palette is a starting selection; a dedicated roundhouse kit is still to come. Choose which eras can be crafted in survival in `config/ironagearchitecture.cfg`: @@ -200,9 +213,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. 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 current test world is Horizontal Rails Preview; earlier preview saves are retained separately. -See [the current candidate handoff](HORIZONTAL-RAILS-CANDIDATE.md) for the verification record. +The Rye Roof Trim Preview shows how the fascia and gable trim fit on three houses stepping down a cobbled street. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 2c3318e..a1b9045 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -52,7 +52,8 @@ rail search joins structural intersections; vertical/diagonal neighbours supply The client listener invalidates the dependent render range after updates without polling/ticking. Walls use full-cell lighting classification with opaque light blocking and non-opaque face culling, so recessed plaster samples the lit adjacent cell rather than its own dark interior in Forge 1.10. -Roof blocks save facing only. Inner/outer corners and ridge caps derive from same-material neighbours. +Roof blocks save facing. Clay verges also save which half overhangs the gable. +Inner/outer corners and ridge caps derive from same-material neighbours. Slopes rise one block per block; ridge caps rise half a block and belong one row above the adjacent slopes. The roof mesh is smooth; collision is a conservative approximation within 1/16 of the surface. No architectural block needs a tile entity or scheduled ticking. @@ -131,7 +132,7 @@ bracket face winding; the native check covers both reaches and every saved direc EraCatalog supplies both creative membership and survival recipe eligibility. Basic cob, plain daub and thatch are shared by Iron Age and Late Medieval & Tudor; timber framing, -jetties, brackets and slate belong to the latter. Add future palettes to Era and assign membership +jetties, brackets, slate and clay tiles belong to the latter. Add future palettes to Era and assign membership in the catalog. Shared items expose every relevant creative tab through their Item implementations. ArchitectureConfig parses recipes.survivalEras at startup. All recipe registration passes through @@ -163,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 and invalid-era. +mixed-eras, no-eras, invalid-era, clay-disabled and clay-only. 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. @@ -181,11 +182,14 @@ Known starter recipes also reject mixed wood. Receipts bind both matrices to the Window checks cover 2,304 metadata states, 576 directional columns, 144 saved columns, placement/alignment, independent columns, blocked bridges, hand/power transitions, swept entity clearance, owner/helper rays, player passage, harvesting, explosions and link repair. -Use a fresh runtime root for each candidate; current default is manual-testing/horizontal-rails-0.1.8. +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. The optional client probe requires iaa.probe=true, iaa.clientProbe=true and iaa.keepRunning=true. -It creates its own Horizontal Rails Preview, checks all mesh/material variants and both -actual creative-tab contents, captures twenty native screenshots, then shuts down. +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. 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 68ae5db..60aed6c 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.8.110021 +mod_version=0.1.10.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 c8e0b46..a7085a0 100644 --- a/scripts/generate-resources.cjs +++ b/scripts/generate-resources.cjs @@ -34,6 +34,11 @@ for (const [key, text] of Object.entries({ 'bracket.reach':'One full bracket makes two halves; two halves rejoin.', 'roof.ridge':'Cut two slopes vertically for four ridge caps.', 'roof.slope':'Two ridge caps side by side make one roof slope.', + 'clay.fascia':'Place facing uphill for a timber edge along the low side.', + 'clay.verge':'Face uphill and click the left or right half that projects past the gable.', + 'clay.ridge_end':'Look along the ridge from outside to finish its exposed end.', + 'clay.swap':'Craft alone to swap a clay slope and a full hanging tile panel.', + 'clay.place':'Match the neighbouring wall half, or sneak to choose it yourself.', 'cob':'Craft alone to make plain wattle and daub.', 'slab':'Two slabs side by side recover one cob.', 'wall.short':'Two short walls side by side make one thin wall.', @@ -118,16 +123,38 @@ for(const wood of woods) { item(name,name);lang[`item.${ns}.${name}.name`]=`${names[woods.indexOf(wood)]} ${reach==='half'?'Half':'Full'}-reach Jetty Bracket`; } } -for (const material of ['thatch','slate']) { +for (const [material,label] of [['thatch','Thatch'],['slate','Slate'],['clay_tile','Clay Tile']]) { model(`${material}_mesh`,{parent:'minecraft:block/block',textures:{particle:`${ns}:blocks/${material}`},elements:[]}); for (const ridge of [false,true]) { const name = `${material}_${ridge?'ridge':'roof'}`, variants = {}; + const mesh = material === 'clay_tile' && ridge ? 'clay_tile_cap_mesh' : `${material}_mesh`; + if (mesh === 'clay_tile_cap_mesh') model(mesh,{parent:'minecraft:block/block',textures:{particle:`${ns}:blocks/clay_tile_cap`},elements:[]}); for (const facing of Object.keys(facings)) for (const shape of ridge?['single','start','middle','end']:shapes) - variants[ridge?`ends=${shape},facing=${facing}`:`facing=${facing},shape=${shape}`] = {model:`${ns}:${material}_mesh`}; - state(name,variants); item(name,`${material}_mesh`); - lang[`tile.${ns}.${name}.name`] = `${material==='thatch'?'Thatch':'Slate'} ${ridge?'Ridge Cap':'Roof'}`; + variants[ridge?`ends=${shape},facing=${facing}`:`facing=${facing},shape=${shape}`] = {model:`${ns}:${mesh}`}; + state(name,variants); item(name,mesh); + lang[`tile.${ns}.${name}.name`] = `${label} ${ridge?'Ridge Cap':'Roof'}`; } } +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}`}; + state(name,variants); item(name,mesh); + lang[`tile.${ns}.${name}.name`] = `Clay Tile ${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}])); +model('clay_tile_hanging_thin',{parent:'minecraft:block/block', + textures:{tile:`${ns}:blocks/clay_tile`,particle:`${ns}:blocks/clay_tile`}, + elements:[{from:[0,0,0],to:[16,16,8],faces:tileFaces}]}); +for (const [name,label] of [['clay_tile_hanging','Hanging Clay Tiles'],['clay_tile_hanging_thin','Hanging Clay Tiles (Thin)']]) { + state(name,Object.fromEntries(Object.entries(facings).map(([facing,y]) => + [`facing=${facing}`,{model:`${ns}:${name}`,...(name.endsWith('_thin')&&y?{y}:{})}]))); + item(name,name); + lang[`tile.${ns}.${name}.name`] = label; +} model('window_mesh',{parent:'minecraft:block/block',textures:{particle:'minecraft:blocks/glass_silver'}}); for(const [w,wood] of woods.entries()) for(const finish of [0,1]) for(const thin of [false,true]) for(const diamond of [true,false]) for(const right of [false,true]) for(const back of [false,true]) { if(back&&!thin) continue; @@ -145,7 +172,7 @@ lang[`hint.${ns}.pane.use`]='Add to a matching post panel; craft alone to swap l lang[`message.${ns}.window.blocked`]='There is not enough clear space to open this window.'; lang[`message.${ns}.window.join`]='Close the adjoining windows before joining these sections.'; for (const locale of ['en_US','en_GB']) expected.set(`${assets}/lang/${locale}.lang`,Object.entries(lang).map(([k,v])=>`${k}=${v}`).join('\n')+'\n'); -expected.set('src/main/resources/mcmod.info',JSON.stringify([{modid:ns,name:'Iron Age Architecture',description:'Cob walls, timber frames, thatch and slate roofs for the Iron Age series.',version:'${version}',mcversion:'${mcversion}',url:'https://github.com/MinecraftModDevelopmentMods/IronAgeArchitecture',authorList:['SkyBlade1978','FoxNekitsune'],credits:'Original 2019 scaffold and model drafts retained in repository history.',dependencies:[]}],null,2)+'\n'); +expected.set('src/main/resources/mcmod.info',JSON.stringify([{modid:ns,name:'Iron Age Architecture',description:'Cob walls, timber frames, leaded windows and traditional roofs for the Iron Age series.',version:'${version}',mcversion:'${mcversion}',url:'https://github.com/MinecraftModDevelopmentMods/IronAgeArchitecture',authorList:['SkyBlade1978','FoxNekitsune'],credits:'Original 2019 scaffold and model drafts retained in repository history.',dependencies:[]}],null,2)+'\n'); expected.set('src/main/resources/pack.mcmeta',JSON.stringify({pack:{pack_format:2,description:'Iron Age Architecture resources'}},null,2)+'\n'); let failures=[]; // Remove only the exact generated frame meshes superseded by the shared wall model. @@ -158,10 +185,10 @@ for(const name of obsolete) { else fs.unlinkSync(file); } } -for(const wood of woods) { - const file=path.join(root,assets,'textures/blocks',`beam_${wood}.png`); - if(!fs.existsSync(file)) failures.push('missing beam texture '+wood); - else {const png=fs.readFileSync(file);if(png.readUInt32BE(16)!==32||png.readUInt32BE(20)!==32) failures.push('beam texture must be 32x32: '+wood);} +for(const name of [...woods.map(wood=>`beam_${wood}`),'clay_tile','clay_tile_cap']) { + const file=path.join(root,assets,'textures/blocks',`${name}.png`); + if(!fs.existsSync(file)) failures.push('missing material texture '+name); + else {const png=fs.readFileSync(file);if(png.readUInt32BE(16)!==32||png.readUInt32BE(20)!==32) failures.push('material texture must be 32x32: '+name);} } for (const [file,content] of expected) { const dest=path.join(root,file); diff --git a/scripts/invoke-client-preview.ps1 b/scripts/invoke-client-preview.ps1 index fb77121..d2ba03d 100644 --- a/scripts/invoke-client-preview.ps1 +++ b/scripts/invoke-client-preview.ps1 @@ -1,12 +1,15 @@ param( - [string]$Runtime='D:\IronAgeArchitecture\manual-testing\horizontal-rails-0.1.8\native-client', - [string]$PreviewName='Horizontal Rails Preview', + [string]$Runtime='D:\IronAgeArchitecture\manual-testing\rye-roof-trim-0.1.10\native-client', + [string]$PreviewName='Rye Roof Trim Preview', [string]$ServerTemplate='D:\IronAgeArchitecture\manual-testing\jetties-0.1.2-final\core', [string]$GradleHome='D:\GradleCaches\minecraft-mods-shared', [string]$AssetsDirectory='', [string]$Java8='C:\Users\johnb\.gradle\jdks\temurin-8-amd64-windows.2\bin\java.exe' ) $ErrorActionPreference='Stop' +if (Test-Path -LiteralPath (Join-Path $Runtime "saves/$PreviewName")) { + throw 'Use a fresh runtime or preview name to keep the earlier save.' +} $repo=Split-Path $PSScriptRoot -Parent $version=((Select-String -LiteralPath (Join-Path $repo 'gradle.properties') -Pattern '^mod_version=').Line -split '=',2)[1] $runtimeJar='iron-age-architecture-'+$version+'.jar' @@ -48,6 +51,7 @@ foreach($library in $metadata.libraries) { } @' pauseOnLostFocus:false +showInventoryAchievementHint:false renderDistance:8 guiScale:2 fancyGraphics:true diff --git a/scripts/invoke-runtime-probe.ps1 b/scripts/invoke-runtime-probe.ps1 index dd692e5..4dd21e1 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')][string]$Mode='core', - [string]$RuntimeRoot='D:\IronAgeArchitecture\manual-testing\horizontal-rails-0.1.8', + [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', [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' @@ -51,16 +51,22 @@ if($Mode -eq 'windows-disabled') { New-Item -ItemType Directory -Force -Path (Join-Path $folder 'config') | Out-Null "blocks {`n B:leadedWindows=false`n}" | Set-Content -LiteralPath (Join-Path $folder 'config/ironagearchitecture.cfg') -Encoding ascii } -if($Mode -eq 'disabled') { +if($Mode -eq 'clay-disabled') { New-Item -ItemType Directory -Force -Path (Join-Path $folder 'config') | Out-Null - @' + "blocks {`n B:clayTiles=false`n}" | Set-Content -LiteralPath (Join-Path $folder 'config/ironagearchitecture.cfg') -Encoding ascii +} +if($Mode -in @('disabled','clay-only')) { + New-Item -ItemType Directory -Force -Path (Join-Path $folder 'config') | Out-Null + $clayTiles=($Mode -eq 'clay-only').ToString().ToLowerInvariant() + @" blocks { B:cob=false B:timberFrames=false B:thatch=false B:slate=false + B:clayTiles=$clayTiles } -'@ | Set-Content -LiteralPath (Join-Path $folder 'config/ironagearchitecture.cfg') -Encoding ascii +"@ | Set-Content -LiteralPath (Join-Path $folder 'config/ironagearchitecture.cfg') -Encoding ascii } 'eula=true' | Set-Content -LiteralPath (Join-Path $folder 'eula.txt') -Encoding ascii @' @@ -87,6 +93,7 @@ if($Mode -eq 'legacy-daub') {$args+='-Diaa.legacyDaub=true'} if($Mode -eq 'legacy-jetties') {$args+='-Diaa.legacyJetties=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')} $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 74cb1cc..1a84454 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureBlocks.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureBlocks.java @@ -50,6 +50,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; private ArchitectureBlocks() { } public static T block(T block, String name) { block.setRegistryName(new ResourceLocation(IronAgeArchitecture.MODID, name)); @@ -89,6 +91,16 @@ public static void register() { } if (ArchitectureConfig.thatch) roofFamily("thatch", true); if (ArchitectureConfig.slate) roofFamily("slate", false); + if (ArchitectureConfig.clayTiles) { + roofFamily("clay_tile", false); + CLAY_HANGING = block(new TileHangingBlock(false), "clay_tile_hanging"); + 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_VERGE = roofTrim("clay_tile_verge", TrimmedRoofBlock.Kind.VERGE); + CLAY_RIDGE_END = roofTrim("clay_tile_ridge_end", TrimmedRoofBlock.Kind.RIDGE_END); + } EraCatalog.assignPrimaryTabs(); } private static void registerWindows() { @@ -167,12 +179,18 @@ public static JettyBracketItem findBracket(String wood,boolean full) { } private static void roofFamily(String name, boolean flammable) { for (boolean ridge : new boolean[]{false, true}) { - RoofBlock roof = block(ridge ? new RidgeBlock(flammable) : new RoofBlock(false, flammable), name + (ridge ? "_ridge" : "_roof")); + RoofBlock roof = block(ridge ? new RidgeBlock(name) : new RoofBlock(false, name), name + (ridge ? "_ridge" : "_roof")); item(roof, new EraItemBlock(roof)); ROOFS.add(roof); if (flammable) Blocks.FIRE.setFireInfo(roof, 60, 20); } } + private static TrimmedRoofBlock roofTrim(String name, TrimmedRoofBlock.Kind kind) { + TrimmedRoofBlock trim = block(new TrimmedRoofBlock(kind), name); + item(trim, new EraItemBlock(trim)); + ROOFS.add(trim); + return trim; + } private static final class CobBlock extends Block { CobBlock() { super(Material.GROUND); setHardness(1.2F); setResistance(4F); setSoundType(SoundType.GROUND); setHarvestLevel("shovel", 0); } } diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureConfig.java b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureConfig.java index 7422286..06fe2a9 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureConfig.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureConfig.java @@ -5,7 +5,7 @@ /** Complete content-family switches. Framed panels require the cob family. */ public final class ArchitectureConfig { - public static boolean cob, frames, thatch, slate,windows; + public static boolean cob, frames, thatch, slate, windows, clayTiles; public static EraSelection survivalEras=EraSelection.parse("all"); private ArchitectureConfig() { } public static void load(File file) { @@ -16,6 +16,7 @@ public static void load(File file) { windows = config.getBoolean("leadedWindows", "blocks", true, "Openable leaded windows; requires timberFrames and cob.") && frames; thatch = config.getBoolean("thatch", "blocks", true, "Thatch slopes and ridge caps."); slate = config.getBoolean("slate", "blocks", true, "Crafted slate slopes and ridge caps."); + clayTiles = config.getBoolean("clayTiles", "blocks", true, "Clay tile roofs, ridge caps and hanging tiles."); net.minecraftforge.common.config.Property eras=config.get("recipes","survivalEras",new String[]{"all"}, "Allowed survival palettes: all, iron_age, late_medieval_tudor. Select one or more IDs. An empty list disables Architecture recipes. Shared materials stay craftable when any selected era uses them. Does not hide creative items or remove existing blocks. Restart required."); eras.setRequiresMcRestart(true); diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureRecipes.java b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureRecipes.java index 22bc536..40904c0 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureRecipes.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/ArchitectureRecipes.java @@ -26,11 +26,30 @@ public static void register() { } } if(ArchitectureConfig.windows) registerWindows(); - for(RoofBlock roof:ArchitectureBlocks.ROOFS) if(!roof.isRidge()) { - boolean thatch=roof.getRegistryName().getResourcePath().startsWith("thatch"); - shaped(new ItemStack(roof,6),"MMM","SSS",'M',thatch?Items.WHEAT:Blocks.STONE,'S',Items.STICK); - Block ridge=Block.getBlockFromName(IronAgeArchitecture.MODID+":"+(thatch?"thatch_ridge":"slate_ridge")); + for(RoofBlock roof:ArchitectureBlocks.ROOFS) if(!roof.isRidge() && !(roof instanceof TrimmedRoofBlock)) { + Object material; + switch (roof.materialKey) { + case "thatch": material = Items.WHEAT; break; + case "clay_tile": material = Items.BRICK; break; + case "slate": material = Blocks.STONE; break; + default: throw new IllegalStateException("Unknown roof material " + roof.materialKey); + } + shaped(new ItemStack(roof,6),"MMM","SSS",'M',material,'S',Items.STICK); + Block ridge=Block.getBlockFromName(IronAgeArchitecture.MODID+":"+roof.materialKey+"_ridge"); cut(new ItemStack(roof),new ItemStack(ridge));join(new ItemStack(ridge),new ItemStack(roof)); + if ("clay_tile".equals(roof.materialKey)) { + ItemStack slope = new ItemStack(roof); + ItemStack full = new ItemStack(ArchitectureBlocks.CLAY_HANGING); + ItemStack thin = new ItemStack(ArchitectureBlocks.CLAY_HANGING_THIN); + shapeless(full, slope); + shapeless(slope, full); + cut(full, thin); + join(thin, full); + ItemStack board = new ItemStack(Blocks.PLANKS, 1, 5); + shapeless(new ItemStack(ArchitectureBlocks.CLAY_FASCIA), slope, Items.STICK); + shapeless(new ItemStack(ArchitectureBlocks.CLAY_VERGE), slope, board); + shapeless(new ItemStack(ArchitectureBlocks.CLAY_RIDGE_END), new ItemStack(ridge), board); + } } } private static void registerCob() { diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/EraCatalog.java b/src/main/java/zone/moddev/mc/ironagearchitecture/EraCatalog.java index eb5816a..10b8451 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/EraCatalog.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/EraCatalog.java @@ -15,7 +15,7 @@ public static EnumSet eras(Item item) { Block block=((ItemBlock)item).getBlock(); boolean shared=ArchitectureBlocks.COB.contains(block)||ArchitectureBlocks.SLABS.contains(block)||ArchitectureBlocks.STAIRS.contains(block) ||block instanceof FrameBlock&&((FrameBlock)block).family==FrameFamily.INFILL - ||block instanceof RoofBlock&&block.getRegistryName().getResourcePath().startsWith("thatch"); + ||block instanceof RoofBlock&&"thatch".equals(((RoofBlock) block).materialKey); return shared?EnumSet.allOf(Era.class):EnumSet.of(Era.LATE_MEDIEVAL_TUDOR); } public static boolean canCraft(ItemStack output) {return output!=null&&ArchitectureConfig.survivalEras.permits(eras(output.getItem()));} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/IronAgeArchitecture.java b/src/main/java/zone/moddev/mc/ironagearchitecture/IronAgeArchitecture.java index e7b4425..e7bebcd 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.8.110021", +@Mod(modid = IronAgeArchitecture.MODID, name = "Iron Age Architecture", version = "0.1.10.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/EraItemBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/EraItemBlock.java index a1f1d93..20ec532 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/block/EraItemBlock.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/EraItemBlock.java @@ -14,7 +14,13 @@ public class EraItemBlock extends ItemBlock { @Override public CreativeTabs[] getCreativeTabs() {return EraTabs.forItem(this);} protected static void hint(List tooltip,String key) {tooltip.add(net.minecraft.util.text.translation.I18n.translateToLocal("hint.ironagearchitecture."+key));} @Override public void addInformation(ItemStack stack,EntityPlayer player,List tooltip,boolean advanced) { + if (block instanceof TrimmedRoofBlock) { + hint(tooltip, "clay." + ((TrimmedRoofBlock) block).kind.name().toLowerCase(java.util.Locale.ROOT)); + return; + } if(block instanceof RoofBlock) hint(tooltip,((RoofBlock)block).isRidge()?"roof.slope":"roof.ridge"); + if (block instanceof RoofBlock && "clay_tile".equals(((RoofBlock) block).materialKey) + && !((RoofBlock) block).isRidge()) hint(tooltip, "clay.swap"); if(ArchitectureBlocks.COB.contains(block)) hint(tooltip,"cob"); if(ArchitectureBlocks.STAIRS.contains(block)) hint(tooltip,"stair"); if(ArchitectureBlocks.COB.indexOf(block)==0||ArchitectureBlocks.STAIRS.indexOf(block)==0) hint(tooltip,"wash"); diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/JettyFloorBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/JettyFloorBlock.java index 62fb32b..ac42eaf 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/block/JettyFloorBlock.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/JettyFloorBlock.java @@ -90,7 +90,8 @@ public boolean covers(IBlockState state,double x,double z) { } IBlockState below=world.getBlockState(pos.down()); int support=0; - if(below.getBlock() instanceof FrameBlock&&!(below.getBlock() instanceof JettyFloorBlock)||below.getBlock() instanceof WindowBlock) { + if(below.getBlock() instanceof FrameBlock&&!(below.getBlock() instanceof JettyFloorBlock)||below.getBlock() instanceof WindowBlock + ||below.getBlock() instanceof TileHangingBlock&&((TileHangingBlock)below.getBlock()).isThin()) { for(int q=0;q<4;q++) { double[] point=WallGeometry.rotate(q%2*8+4,0,q/2*8+4,facing); boolean occupied=true; @@ -104,6 +105,11 @@ public boolean covers(IBlockState state,double x,double z) { double[] local=WallGeometry.rotate(point[0],0,point[2],inverse); occupied=((PartialWallBlock)below.getBlock()).profile.contains(local[0],local[2]); } + if (below.getBlock() instanceof TileHangingBlock) { + net.minecraft.util.math.AxisAlignedBB bounds = below.getBoundingBox(world, pos.down()); + double x = point[0] / 16, z = point[2] / 16; + occupied = x >= bounds.minX && x <= bounds.maxX && z >= bounds.minZ && z <= bounds.maxZ; + } if(occupied) support|=1< ENDS = PropertyEnum.create("ends",End.class); - public RidgeBlock(boolean thatch) { super(true,thatch); setDefaultState(getDefaultState().withProperty(ENDS,End.SINGLE)); } + public RidgeBlock(String materialKey) { super(true,materialKey); setDefaultState(getDefaultState().withProperty(ENDS,End.SINGLE)); } @Override protected BlockStateContainer createBlockState() { return new BlockStateContainer(this,FACING,ENDS); } private boolean connects(IBlockAccess world, BlockPos pos, EnumFacing facing) { IBlockState other = world.getBlockState(pos); - return other.getBlock() == this && other.getValue(FACING).getAxis() == facing.getAxis(); + if (!(other.getBlock() instanceof RoofBlock)) return false; + RoofBlock roof = (RoofBlock) other.getBlock(); + EnumFacing direction = other.getValue(FACING); + return roof.isRidge() && materialKey.equals(roof.materialKey) + && (roof instanceof TrimmedRoofBlock ? direction == facing : direction.getAxis() == facing.getAxis()); } @Override public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos) { EnumFacing facing = state.getValue(FACING); boolean north = connects(world,pos.offset(facing),facing); - boolean south = connects(world,pos.offset(facing.getOpposite()),facing); + boolean south = connects(world,pos.offset(facing.getOpposite()),facing.getOpposite()); return state.withProperty(ENDS,north ? (south ? End.MIDDLE : End.END) : (south ? End.START : End.SINGLE)); } @Override public RoofShape geometryShape(IBlockState state) { return RoofShape.STRAIGHT; } diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofBlock.java index 802122f..2189fcf 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofBlock.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofBlock.java @@ -25,17 +25,21 @@ public class RoofBlock extends BlockHorizontal { public static final PropertyEnum SHAPE = PropertyEnum.create("shape", RoofShape.class); private final boolean ridge; - public RoofBlock(boolean ridge, boolean thatch) { - super(thatch ? Material.GRASS : Material.ROCK); + public final String materialKey; + public RoofBlock(boolean ridge, String materialKey) { + super("thatch".equals(materialKey) ? Material.GRASS : Material.ROCK); + this.materialKey = materialKey; + boolean thatch = "thatch".equals(materialKey); this.ridge = ridge; setHardness(thatch ? .8F : 2F); setResistance(thatch ? 1F : 6F); setSoundType(thatch ? SoundType.PLANT : SoundType.STONE); setHarvestLevel(thatch ? "axe" : "pickaxe", 0); IBlockState state = blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH); - setDefaultState(ridge ? state : state.withProperty(SHAPE, RoofShape.STRAIGHT)); + setDefaultState(state.getPropertyKeys().contains(SHAPE) ? state.withProperty(SHAPE, RoofShape.STRAIGHT) : state); setLightOpacity(0); } public boolean isRidge() { return ridge; } + public String textureKey() { return ridge && "clay_tile".equals(materialKey) ? "clay_tile_cap" : materialKey; } @Override protected BlockStateContainer createBlockState() { return new BlockStateContainer(this, FACING, SHAPE); } @Override public IBlockState getStateFromMeta(int meta) { return getDefaultState().withProperty(FACING, EnumFacing.getHorizontal(meta & 3)); } @Override public int getMetaFromState(IBlockState state) { return state.getValue(FACING).getHorizontalIndex(); } diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofTrimGeometry.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofTrimGeometry.java new file mode 100644 index 0000000..f4401fc --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/RoofTrimGeometry.java @@ -0,0 +1,72 @@ +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 zone.moddev.mc.ironagearchitecture.block.TrimmedRoofBlock.Kind; + +/** 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; + + private RoofTrimGeometry() { } + + public static final class Section { + public final double x0, x1, z0, z1; + public final boolean thin; + + private Section(double x0, double x1, double z0, double z1, boolean thin) { + this.x0 = x0; + this.x1 = x1; + this.z0 = z0; + this.z1 = z1; + this.thin = thin; + } + } + + public static Section[] sections(Kind kind, boolean right) { + switch (kind) { + case FASCIA: return new Section[]{new Section(0, 1, 0, EAVE, false)}; + case VERGE: return new Section[]{new Section(0, .5, 0, 1, !right), new Section(.5, 1, 0, 1, right)}; + case RIDGE_END: return new Section[]{ + new Section(0, .5, 0, .5, true), new Section(.5, 1, 0, .5, true), + new Section(0, .5, .5, 1, false), new Section(.5, 1, .5, 1, false)}; + default: throw new AssertionError(kind); + } + } + + public static double height(Kind kind, double x, double z) { + return RoofGeometry.height(RoofShape.STRAIGHT, kind == Kind.RIDGE_END, x, z); + } + + public static double underside(Section section, double height) { + return section.thin ? height - DEPTH : 0; + } + + public static List boxes(Kind kind, boolean right, EnumFacing facing) { + List boxes = new ArrayList<>(); + double step = 1.0 / RoofGeometry.STEPS; + for (Section section : sections(kind, right)) { + boolean ridge = kind == Kind.RIDGE_END; + double start = ridge ? section.x0 : section.z0; + double end = ridge ? section.x1 : section.z1; + for (double at = start; at < end; at += step) { + double x0 = ridge ? at : section.x0; + double x1 = ridge ? at + step : section.x1; + double z0 = ridge ? section.z0 : at; + double z1 = ridge ? section.z1 : at + step; + double first = height(kind, x0, z0); + double last = height(kind, x1, z1); + double low = Math.max(0, underside(section, Math.min(first, last))); + double high = Math.max(first, last); + double[] a = RoofGeometry.rotate(x0, low, z0, facing); + double[] b = RoofGeometry.rotate(x1, high, z1, facing); + boxes.add(new AxisAlignedBB(Math.min(a[0], b[0]), low, Math.min(a[2], b[2]), + Math.max(a[0], b[0]), high, Math.max(a[2], b[2]))); + } + } + return boxes; + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/TileHangingBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/TileHangingBlock.java new file mode 100644 index 0000000..37a1b89 --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/TileHangingBlock.java @@ -0,0 +1,78 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import net.minecraft.block.BlockHorizontal; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +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.world.IBlockAccess; +import net.minecraft.world.World; + +/** Fired clay wall panels with the same occupied half as a thin timber wall. */ +public final class TileHangingBlock extends BlockHorizontal { + private final boolean thin; + + public TileHangingBlock(boolean thin) { + super(Material.ROCK); + this.thin = thin; + setDefaultState(blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH)); + setHardness(2F); + setResistance(6F); + setSoundType(SoundType.STONE); + setHarvestLevel("pickaxe", 0); + if (thin) { + setLightOpacity(0); + useNeighborBrightness = true; + } + } + + public boolean isThin() { return thin; } + @Override protected BlockStateContainer createBlockState() { return new BlockStateContainer(this, FACING); } + @Override public boolean isOpaqueCube(IBlockState state) { return !thin; } + @Override public boolean isFullCube(IBlockState state) { return !thin; } + @Override public int damageDropped(IBlockState state) { return 0; } + @Override public int getMetaFromState(IBlockState state) { return thin ? state.getValue(FACING).getHorizontalIndex() : 0; } + @Override public IBlockState getStateFromMeta(int meta) { + return thin ? getDefaultState().withProperty(FACING, EnumFacing.getHorizontal(meta & 3)) : getDefaultState(); + } + @Override public IBlockState withRotation(IBlockState state, Rotation rotation) { + return thin ? state.withProperty(FACING, rotation.rotate(state.getValue(FACING))) : state; + } + @Override public IBlockState withMirror(IBlockState state, Mirror mirror) { + return thin ? state.withProperty(FACING, mirror.mirror(state.getValue(FACING))) : state; + } + @Override public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing side, + float x, float y, float z, int meta, EntityLivingBase placer) { + if (!thin) return getDefaultState(); + EnumFacing facing = side.getOpposite(); + if (side.getAxis().isVertical()) { + facing = placer.getHorizontalFacing().getAxis() == EnumFacing.Axis.X + ? (x < .5F ? EnumFacing.WEST : EnumFacing.EAST) + : (z < .5F ? EnumFacing.NORTH : EnumFacing.SOUTH); + } + if (!placer.isSneaking()) { + EnumFacing inherited = WallRuns.straightHalfFacing(world.getBlockState(pos.offset(side.getOpposite()))); + if (inherited != null) facing = inherited; + } + return getDefaultState().withProperty(FACING, facing); + } + + @Override public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess world, BlockPos pos) { + if (!thin) return FULL_BLOCK_AABB; + EnumFacing facing = state.getValue(FACING); + double[] a = WallGeometry.rotate(0, 0, 0, facing); + double[] b = WallGeometry.rotate(16, 16, 8, facing); + return new AxisAlignedBB(Math.min(a[0], b[0]) / 16, 0, Math.min(a[2], b[2]) / 16, + Math.max(a[0], b[0]) / 16, 1, Math.max(a[2], b[2]) / 16); + } + + @Override public boolean isSideSolid(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing side) { + return !thin || side == state.getValue(FACING); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/TileHangingItem.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/TileHangingItem.java new file mode 100644 index 0000000..54dc41c --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/TileHangingItem.java @@ -0,0 +1,39 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import java.util.List; +import net.minecraft.block.Block; +import net.minecraft.block.SoundType; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public final class TileHangingItem extends EraItemBlock { + public TileHangingItem(TileHangingBlock block) { super(block); } + + @Override public EnumActionResult onItemUse(ItemStack stack, EntityPlayer player, World world, + BlockPos pos, EnumHand hand, EnumFacing side, float x, float y, float z) { + if (!((TileHangingBlock) block).isThin()) return super.onItemUse(stack, player, world, pos, hand, side, x, y, z); + Block clicked = world.getBlockState(pos).getBlock(); + if (!clicked.isReplaceable(world, pos)) pos = pos.offset(side); + if (stack.stackSize == 0 || !player.canPlayerEdit(pos, side, stack) + || !world.canBlockBePlaced(block, pos, true, side, null, stack)) return EnumActionResult.FAIL; + IBlockState state = block.getStateForPlacement(world, pos, side, x, y, z, 0, player, stack); + if (!world.checkNoEntityCollision(state.getBoundingBox(world, pos).offset(pos)) + || !placeBlockAt(stack, player, world, pos, side, x, y, z, state)) return EnumActionResult.FAIL; + SoundType sound = block.getSoundType(state, world, pos, player); + world.playSound(player, pos, sound.getPlaceSound(), SoundCategory.BLOCKS, (sound.getVolume() + 1) / 2, sound.getPitch() * .8F); + if (!player.capabilities.isCreativeMode) stack.stackSize--; + return EnumActionResult.SUCCESS; + } + + @Override public void addInformation(ItemStack stack, EntityPlayer player, List tooltip, boolean advanced) { + hint(tooltip, "cut"); + hint(tooltip, ((TileHangingBlock) block).isThin() ? "clay.place" : "clay.swap"); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/TrimmedRoofBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/TrimmedRoofBlock.java new file mode 100644 index 0000000..587b01f --- /dev/null +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/TrimmedRoofBlock.java @@ -0,0 +1,62 @@ +package zone.moddev.mc.ironagearchitecture.block; + +import java.util.List; +import net.minecraft.block.properties.PropertyBool; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.Mirror; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +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 enum Kind { FASCIA, VERGE, RIDGE_END } + + public static final PropertyBool RIGHT = PropertyBool.create("right"); + public final Kind kind; + + public TrimmedRoofBlock(Kind kind) { + super(kind == Kind.RIDGE_END, "clay_tile"); + this.kind = kind; + setDefaultState(getDefaultState().withProperty(RIGHT, false)); + } + + @Override protected BlockStateContainer createBlockState() { + return new BlockStateContainer(this, FACING, RIGHT); + } + + @Override public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos) { return state; } + @Override public RoofShape geometryShape(IBlockState state) { return RoofShape.STRAIGHT; } + + @Override public IBlockState getStateFromMeta(int meta) { + return super.getStateFromMeta(meta).withProperty(RIGHT, kind == Kind.VERGE && (meta & 4) != 0); + } + + @Override public int getMetaFromState(IBlockState state) { + return super.getMetaFromState(state) | (kind == Kind.VERGE && state.getValue(RIGHT) ? 4 : 0); + } + + @Override public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing side, + float x, float y, float z, int meta, EntityLivingBase player) { + EnumFacing facing = player.getHorizontalFacing(); + if (kind == Kind.RIDGE_END) facing = facing.getOpposite(); + EnumFacing inverse = facing.getAxis() == EnumFacing.Axis.X ? facing.getOpposite() : facing; + boolean right = kind == Kind.VERGE && RoofGeometry.rotate(x, y, z, inverse)[0] >= .5; + return getDefaultState().withProperty(FACING, facing).withProperty(RIGHT, right); + } + + @Override public IBlockState withMirror(IBlockState state, Mirror mirror) { + IBlockState mirrored = super.withMirror(state, mirror); + return kind == Kind.VERGE && mirror != Mirror.NONE ? mirrored.cycleProperty(RIGHT) : mirrored; + } + + @Override public boolean isSideSolid(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing side) { return false; } + + @Override public List collision(IBlockState state) { + return RoofTrimGeometry.boxes(kind, state.getValue(RIGHT), state.getValue(FACING)); + } +} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/WallRuns.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/WallRuns.java index 1af827b..4acb6b6 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/block/WallRuns.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/WallRuns.java @@ -38,6 +38,11 @@ private static void add(List list,BlockPos pos,EnumFacing facing,double x0, public static List of(IBlockState state,BlockPos pos) { List list=new ArrayList<>(); if(state.getBlock() instanceof WindowBlock) {WindowBlock window=(WindowBlock)state.getBlock();add(list,pos,state.getValue(WindowBlock.FACING),0,16,window.back?8:0,window.thin?(window.back?16:8):16);return list;} + if (state.getBlock() instanceof TileHangingBlock) { + TileHangingBlock panel = (TileHangingBlock) state.getBlock(); + if (panel.isThin()) add(list, pos, state.getValue(TileHangingBlock.FACING), 0, 16, 0, 8); + return list; + } if(!(state.getBlock() instanceof FrameBlock)||state.getBlock() instanceof JettyFloorBlock) return list; EnumFacing facing=state.getValue(FrameBlock.FACING); if(state.getBlock() instanceof PartialWallBlock) { @@ -65,4 +70,19 @@ public static boolean touches(IBlockState a,BlockPos pa,IBlockState b,BlockPos p for(Run x:of(a,pa)) for(Run y:of(b,pb)) if(x.touches(y,direction)) return true; return false; } + + /** Normal pointing towards the occupied half, independent of a casement's outward facing. */ + static EnumFacing straightHalfFacing(IBlockState state) { + if (state.getBlock() instanceof TileHangingBlock && ((TileHangingBlock) state.getBlock()).isThin()) { + return state.getValue(TileHangingBlock.FACING); + } + if (state.getBlock() instanceof PartialWallBlock && ((PartialWallBlock) state.getBlock()).profile == WallProfile.THIN) { + return state.getValue(FrameBlock.FACING); + } + if (state.getBlock() instanceof WindowBlock && ((WindowBlock) state.getBlock()).thin) { + EnumFacing facing = state.getValue(WindowBlock.FACING); + return ((WindowBlock) state.getBlock()).back ? facing.getOpposite() : facing; + } + return null; + } } diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/WindowBlock.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/WindowBlock.java index e26e6ef..65da8f1 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/block/WindowBlock.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/WindowBlock.java @@ -60,9 +60,12 @@ RayTraceResult trace(BlockPos pos,Vec3d start,Vec3d end,List boxe } @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) { ItemStack main=player.getHeldItemMainhand(),off=player.getHeldItemOffhand(); - if(player.isSneaking()||main!=null&&main.getItem() instanceof WindowItem||off!=null&&off.getItem() instanceof WindowItem) return false; + if(player.isSneaking()||allowsPlacement(main)||allowsPlacement(off)) return false; if(!world.isRemote) WindowController.toggle(world,pos,player);return true; } + private static boolean allowsPlacement(@Nullable ItemStack held) { + return held != null && (held.getItem() instanceof WindowItem || held.getItem() instanceof TileHangingItem); + } @Override public void neighborChanged(IBlockState state,World world,BlockPos pos,Block changed){if(!world.isRemote) WindowController.neighbor(world,pos);} @Override public void onBlockAdded(World world,BlockPos pos,IBlockState state){if(!world.isRemote) WindowController.neighbor(world,pos);} @Override public void breakBlock(World world,BlockPos pos,IBlockState state){WindowController.removeLeaf(world,pos,state);super.breakBlock(world,pos,state);} diff --git a/src/main/java/zone/moddev/mc/ironagearchitecture/block/WindowItem.java b/src/main/java/zone/moddev/mc/ironagearchitecture/block/WindowItem.java index dd55439..59a760a 100644 --- a/src/main/java/zone/moddev/mc/ironagearchitecture/block/WindowItem.java +++ b/src/main/java/zone/moddev/mc/ironagearchitecture/block/WindowItem.java @@ -18,8 +18,8 @@ public final class WindowItem extends EraItemBlock { IBlockState previous=world.getBlockState(pos.offset(side.getOpposite()));Boolean right=null;Boolean back=null; if(!player.isSneaking()) { if(previous.getBlock() instanceof WindowBlock&&((WindowBlock)previous.getBlock()).thin==base.thin) {facing=previous.getValue(WindowBlock.FACING);right=((WindowBlock)previous.getBlock()).right;back=((WindowBlock)previous.getBlock()).back;} - else if(base.thin&&previous.getBlock() instanceof PartialWallBlock&&((PartialWallBlock)previous.getBlock()).profile==WallProfile.THIN) { - EnumFacing plane=previous.getValue(FrameBlock.FACING);BlockPos clicked=pos.offset(side.getOpposite()); + else if(base.thin&&WallRuns.straightHalfFacing(previous)!=null) { + EnumFacing plane=WallRuns.straightHalfFacing(previous);BlockPos clicked=pos.offset(side.getOpposite()); double[] centre=WallGeometry.rotate(8,0,4,plane); facing=plane.getAxis()==EnumFacing.Axis.Z?(player.posZ 1e-8; float textureU = (float)((top || alongX ? source[0] : source[2]) * 16); - float textureV = (float)(top ? source[2] * 16 : (1-source[1]) * 16); + double v = top ? source[2] : 1-source[1]; + if (board != null) v = RoofTrimGeometry.height(board, source[0], source[2]) - source[1]; + float textureV = (float)(Math.max(0,Math.min(1,v)) * 16); 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 UV: builder.put(element,sprite.getInterpolatedU(textureU),sprite.getInterpolatedV(textureV),0,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 new file mode 100644 index 0000000..bbcc653 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_fascia.json @@ -0,0 +1,28 @@ +{ + "variants": { + "facing=north,right=false": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=north,right=true": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,right=false": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,right=true": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,right=false": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,right=true": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,right=false": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,right=true": { + "model": "ironagearchitecture:clay_tile_mesh" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_hanging.json b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_hanging.json new file mode 100644 index 0000000..e819741 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_hanging.json @@ -0,0 +1,16 @@ +{ + "variants": { + "facing=north": { + "model": "ironagearchitecture:clay_tile_hanging" + }, + "facing=east": { + "model": "ironagearchitecture:clay_tile_hanging" + }, + "facing=south": { + "model": "ironagearchitecture:clay_tile_hanging" + }, + "facing=west": { + "model": "ironagearchitecture:clay_tile_hanging" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_hanging_thin.json b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_hanging_thin.json new file mode 100644 index 0000000..05d21d5 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_hanging_thin.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=north": { + "model": "ironagearchitecture:clay_tile_hanging_thin" + }, + "facing=east": { + "model": "ironagearchitecture:clay_tile_hanging_thin", + "y": 90 + }, + "facing=south": { + "model": "ironagearchitecture:clay_tile_hanging_thin", + "y": 180 + }, + "facing=west": { + "model": "ironagearchitecture:clay_tile_hanging_thin", + "y": 270 + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_ridge.json b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_ridge.json new file mode 100644 index 0000000..aac47bc --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_ridge.json @@ -0,0 +1,52 @@ +{ + "variants": { + "ends=single,facing=north": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=start,facing=north": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=middle,facing=north": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=end,facing=north": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=single,facing=east": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=start,facing=east": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=middle,facing=east": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=end,facing=east": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=single,facing=south": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=start,facing=south": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=middle,facing=south": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=end,facing=south": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=single,facing=west": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=start,facing=west": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=middle,facing=west": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "ends=end,facing=west": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_ridge_end.json b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_ridge_end.json new file mode 100644 index 0000000..bcccd19 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_ridge_end.json @@ -0,0 +1,28 @@ +{ + "variants": { + "facing=north,right=false": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "facing=north,right=true": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "facing=east,right=false": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "facing=east,right=true": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "facing=south,right=false": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "facing=south,right=true": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "facing=west,right=false": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + }, + "facing=west,right=true": { + "model": "ironagearchitecture:clay_tile_cap_mesh" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_roof.json b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_roof.json new file mode 100644 index 0000000..4562e70 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_roof.json @@ -0,0 +1,64 @@ +{ + "variants": { + "facing=north,shape=straight": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=north,shape=inner_left": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=north,shape=inner_right": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=north,shape=outer_left": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=north,shape=outer_right": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,shape=straight": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,shape=inner_left": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,shape=inner_right": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,shape=outer_left": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,shape=outer_right": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,shape=straight": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,shape=inner_left": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,shape=inner_right": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,shape=outer_left": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,shape=outer_right": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,shape=straight": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,shape=inner_left": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,shape=inner_right": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,shape=outer_left": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,shape=outer_right": { + "model": "ironagearchitecture:clay_tile_mesh" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_verge.json b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_verge.json new file mode 100644 index 0000000..bbcc653 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/blockstates/clay_tile_verge.json @@ -0,0 +1,28 @@ +{ + "variants": { + "facing=north,right=false": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=north,right=true": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,right=false": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=east,right=true": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,right=false": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=south,right=true": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,right=false": { + "model": "ironagearchitecture:clay_tile_mesh" + }, + "facing=west,right=true": { + "model": "ironagearchitecture:clay_tile_mesh" + } + } +} diff --git a/src/main/resources/assets/ironagearchitecture/lang/en_GB.lang b/src/main/resources/assets/ironagearchitecture/lang/en_GB.lang index 917c25c..1f2e1c4 100644 --- a/src/main/resources/assets/ironagearchitecture/lang/en_GB.lang +++ b/src/main/resources/assets/ironagearchitecture/lang/en_GB.lang @@ -16,6 +16,11 @@ hint.ironagearchitecture.floor.l=Craft alone to recover three quarter floors. hint.ironagearchitecture.bracket.reach=One full bracket makes two halves; two halves rejoin. hint.ironagearchitecture.roof.ridge=Cut two slopes vertically for four ridge caps. hint.ironagearchitecture.roof.slope=Two ridge caps side by side make one roof slope. +hint.ironagearchitecture.clay.fascia=Place facing uphill for a timber edge along the low side. +hint.ironagearchitecture.clay.verge=Face uphill and click the left or right half that projects past the gable. +hint.ironagearchitecture.clay.ridge_end=Look along the ridge from outside to finish its exposed end. +hint.ironagearchitecture.clay.swap=Craft alone to swap a clay slope and a full hanging tile panel. +hint.ironagearchitecture.clay.place=Match the neighbouring wall half, or sneak to choose it yourself. hint.ironagearchitecture.cob=Craft alone to make plain wattle and daub. hint.ironagearchitecture.slab=Two slabs side by side recover one cob. hint.ironagearchitecture.wall.short=Two short walls side by side make one thin wall. @@ -480,6 +485,13 @@ tile.ironagearchitecture.thatch_roof.name=Thatch Roof tile.ironagearchitecture.thatch_ridge.name=Thatch Ridge Cap tile.ironagearchitecture.slate_roof.name=Slate Roof tile.ironagearchitecture.slate_ridge.name=Slate Ridge Cap +tile.ironagearchitecture.clay_tile_roof.name=Clay Tile Roof +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 +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 tile.ironagearchitecture.leaded_window_full_cob_rectangle_oak.name=Oak Rectangular Leaded Window tile.ironagearchitecture.leaded_window_thin_cob_diamond_oak.name=Oak Diamond Leaded Window (Thin) diff --git a/src/main/resources/assets/ironagearchitecture/lang/en_US.lang b/src/main/resources/assets/ironagearchitecture/lang/en_US.lang index 917c25c..1f2e1c4 100644 --- a/src/main/resources/assets/ironagearchitecture/lang/en_US.lang +++ b/src/main/resources/assets/ironagearchitecture/lang/en_US.lang @@ -16,6 +16,11 @@ hint.ironagearchitecture.floor.l=Craft alone to recover three quarter floors. hint.ironagearchitecture.bracket.reach=One full bracket makes two halves; two halves rejoin. hint.ironagearchitecture.roof.ridge=Cut two slopes vertically for four ridge caps. hint.ironagearchitecture.roof.slope=Two ridge caps side by side make one roof slope. +hint.ironagearchitecture.clay.fascia=Place facing uphill for a timber edge along the low side. +hint.ironagearchitecture.clay.verge=Face uphill and click the left or right half that projects past the gable. +hint.ironagearchitecture.clay.ridge_end=Look along the ridge from outside to finish its exposed end. +hint.ironagearchitecture.clay.swap=Craft alone to swap a clay slope and a full hanging tile panel. +hint.ironagearchitecture.clay.place=Match the neighbouring wall half, or sneak to choose it yourself. hint.ironagearchitecture.cob=Craft alone to make plain wattle and daub. hint.ironagearchitecture.slab=Two slabs side by side recover one cob. hint.ironagearchitecture.wall.short=Two short walls side by side make one thin wall. @@ -480,6 +485,13 @@ tile.ironagearchitecture.thatch_roof.name=Thatch Roof tile.ironagearchitecture.thatch_ridge.name=Thatch Ridge Cap tile.ironagearchitecture.slate_roof.name=Slate Roof tile.ironagearchitecture.slate_ridge.name=Slate Ridge Cap +tile.ironagearchitecture.clay_tile_roof.name=Clay Tile Roof +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 +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 tile.ironagearchitecture.leaded_window_full_cob_rectangle_oak.name=Oak Rectangular Leaded Window tile.ironagearchitecture.leaded_window_thin_cob_diamond_oak.name=Oak Diamond Leaded Window (Thin) diff --git a/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_cap_mesh.json b/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_cap_mesh.json new file mode 100644 index 0000000..03c038d --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_cap_mesh.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/block", + "textures": { + "particle": "ironagearchitecture:blocks/clay_tile_cap" + }, + "elements": [] +} diff --git a/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_hanging.json b/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_hanging.json new file mode 100644 index 0000000..7033016 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_hanging.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "ironagearchitecture:blocks/clay_tile" + } +} diff --git a/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_hanging_thin.json b/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_hanging_thin.json new file mode 100644 index 0000000..a41bac4 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_hanging_thin.json @@ -0,0 +1,46 @@ +{ + "parent": "minecraft:block/block", + "textures": { + "tile": "ironagearchitecture:blocks/clay_tile", + "particle": "ironagearchitecture:blocks/clay_tile" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 8 + ], + "faces": { + "down": { + "texture": "#tile", + "cullface": "down" + }, + "up": { + "texture": "#tile", + "cullface": "up" + }, + "north": { + "texture": "#tile", + "cullface": "north" + }, + "south": { + "texture": "#tile" + }, + "west": { + "texture": "#tile", + "cullface": "west" + }, + "east": { + "texture": "#tile", + "cullface": "east" + } + } + } + ] +} diff --git a/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_mesh.json b/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_mesh.json new file mode 100644 index 0000000..c9caf3b --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/block/clay_tile_mesh.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/block", + "textures": { + "particle": "ironagearchitecture:blocks/clay_tile" + }, + "elements": [] +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_fascia.json b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_fascia.json new file mode 100644 index 0000000..612eeaf --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_fascia.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/clay_tile_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_hanging.json b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_hanging.json new file mode 100644 index 0000000..5df25fd --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_hanging.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/clay_tile_hanging" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_hanging_thin.json b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_hanging_thin.json new file mode 100644 index 0000000..1ac92ba --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_hanging_thin.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/clay_tile_hanging_thin" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_ridge.json b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_ridge.json new file mode 100644 index 0000000..dd68ff1 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_ridge.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/clay_tile_cap_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_ridge_end.json b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_ridge_end.json new file mode 100644 index 0000000..dd68ff1 --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_ridge_end.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/clay_tile_cap_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_roof.json b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_roof.json new file mode 100644 index 0000000..612eeaf --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_roof.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/clay_tile_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_verge.json b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_verge.json new file mode 100644 index 0000000..612eeaf --- /dev/null +++ b/src/main/resources/assets/ironagearchitecture/models/item/clay_tile_verge.json @@ -0,0 +1,3 @@ +{ + "parent": "ironagearchitecture:block/clay_tile_mesh" +} diff --git a/src/main/resources/assets/ironagearchitecture/textures/blocks/clay_tile.png b/src/main/resources/assets/ironagearchitecture/textures/blocks/clay_tile.png new file mode 100644 index 0000000..fcc61e0 Binary files /dev/null and b/src/main/resources/assets/ironagearchitecture/textures/blocks/clay_tile.png differ diff --git a/src/main/resources/assets/ironagearchitecture/textures/blocks/clay_tile_cap.png b/src/main/resources/assets/ironagearchitecture/textures/blocks/clay_tile_cap.png new file mode 100644 index 0000000..ed34a27 Binary files /dev/null and b/src/main/resources/assets/ironagearchitecture/textures/blocks/clay_tile_cap.png differ diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 1c79244..3616290 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -2,7 +2,7 @@ { "modid": "ironagearchitecture", "name": "Iron Age Architecture", - "description": "Cob walls, timber frames, thatch and slate roofs for the Iron Age series.", + "description": "Cob walls, timber frames, leaded windows and traditional roofs for the Iron Age series.", "version": "${version}", "mcversion": "${mcversion}", "url": "https://github.com/MinecraftModDevelopmentMods/IronAgeArchitecture", 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 48904b7..6d6dd84 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.8.110021]") + dependencies="required-after:ironagearchitecture@[0.1.10.110021]") public final class ArchitectureProbe { @SidedProxy(clientSide="zone.moddev.mc.ironagearchitecture.probe.ClientProbe",serverSide="zone.moddev.mc.ironagearchitecture.probe.ProbeProxy") public static ProbeProxy proxy; @@ -62,15 +62,19 @@ public final class ArchitectureProbe { if(Boolean.getBoolean("iaa.legacyDaub")) verifyLegacyDaub(world); int variants=0, recipes=0, states=0; boolean reload=Boolean.getBoolean("iaa.reload"); + boolean clayClient = Boolean.getBoolean("iaa.clientProbe") && ArchitectureConfig.clayTiles && ArchitectureConfig.frames; variants=ArchitectureBlocks.visibleStacks().size(); List savedBlocks=new ArrayList<>(ArchitectureBlocks.VISIBLE); savedBlocks.removeIf(block->block instanceof WindowBlock); for(FrameBlock block:ArchitectureBlocks.meshes()) if(!savedBlocks.contains(block)) savedBlocks.add(block); if(Boolean.getBoolean("iaa.legacyJetties")) { int index=0; - for(Block block:savedBlocks) for(int meta=0;meta<16;meta++) { - require(world.getBlockState(new BlockPos(100+index,70,100)).equals(block.getStateFromMeta(meta)),"0.1.2 saved state "+index); - index++; + for(Block block:savedBlocks) { + if (ClayTileProbe.isClay(block)) continue; + for(int meta=0;meta<16;meta++) { + require(world.getBlockState(new BlockPos(100+index,70,100)).equals(block.getStateFromMeta(meta)),"0.1.2 saved state "+index); + index++; + } } require(index==4448,"0.1.2 legacy matrix incomplete"); } @@ -90,7 +94,7 @@ public final class ArchitectureProbe { states++; } } - require(variants==Integer.getInteger("iaa.expectedVariants",ArchitectureConfig.windows?510:460),"Visible variants "+variants); + require(variants==Integer.getInteger("iaa.expectedVariants",EraProbe.expectedVariants()),"Visible variants "+variants); for(IRecipe recipe:CraftingManager.getInstance().getRecipeList()) { ItemStack output=recipe.getRecipeOutput(); if(output==null||!"ironagearchitecture".equals(output.getItem().getRegistryName().getResourceDomain())) continue; @@ -118,7 +122,11 @@ public final class ArchitectureProbe { verifyWallHeads(world); HorizontalRailProbe.verify(world); if(ArchitectureConfig.windows) WindowProbe.verify(world,reload); - if(!reload) {new JettyPreview().build(world);if(ArchitectureConfig.windows) WindowPreview.build(world);TimberJoinProbe.preview(world);} + if (!reload && !clayClient) { + new JettyPreview().build(world); + if (ArchitectureConfig.windows) WindowPreview.build(world); + TimberJoinProbe.preview(world); + } for(int finish=0;finish<2;finish++) { Block doubled=Block.getBlockFromName("ironagearchitecture:"+ArchitectureBlocks.FINISHES[finish]+"_double_slab"); List drops=doubled.getDrops(world,new BlockPos(90,70,100),doubled.getDefaultState(),0); @@ -126,10 +134,15 @@ public final class ArchitectureProbe { require(total==2,"Double slab quantity"); } } + ClayTileProbe.verify(world); + if (!reload && clayClient) RyeTilePreview.build(world); + if (Boolean.getBoolean("iaa.clayOnly")) { + require(variants == 7 && recipes == 10 && RecipeProbe.verifyReachability() == 7, "Standalone clay tiles"); + } world.setWorldTime(1000);world.getGameRules().setOrCreateGameRule("doDaylightCycle","false"); world.getGameRules().setOrCreateGameRule("doFireTick","false");world.getGameRules().setOrCreateGameRule("doMobSpawning","false"); world.getGameRules().setOrCreateGameRule("spawnRadius","0"); - world.setSpawnPoint(new BlockPos(7,64,-9)); + world.setSpawnPoint(clayClient ? RyeTilePreview.SPAWN : new BlockPos(7,64,-9)); server.saveAllWorlds(false); String result="{\"passed\":true,\"variants\":"+variants+",\"recipes\":"+recipes+",\"states\":"+states+",\"reload\":"+reload +",\"legacy\":"+Boolean.getBoolean("iaa.legacy")+",\"legacyDaub\":"+Boolean.getBoolean("iaa.legacyDaub") @@ -141,18 +154,21 @@ public final class ArchitectureProbe { } private void verifyConfigMatrix() throws Exception { boolean cob=ArchitectureConfig.cob,frames=ArchitectureConfig.frames,thatch=ArchitectureConfig.thatch,slate=ArchitectureConfig.slate,windows=ArchitectureConfig.windows; + boolean clayTiles = ArchitectureConfig.clayTiles; zone.moddev.mc.ironagearchitecture.EraSelection eras=ArchitectureConfig.survivalEras; java.nio.file.Path directory=Paths.get("config-matrix");Files.createDirectories(directory); try { - for(int mask=0;mask<32;mask++) { + for(int mask=0;mask<64;mask++) { java.nio.file.Path file=directory.resolve(mask+".cfg"); String text="blocks {\n B:cob="+((mask&1)!=0)+"\n B:timberFrames="+((mask&2)!=0) - +"\n B:thatch="+((mask&4)!=0)+"\n B:slate="+((mask&8)!=0)+"\n B:leadedWindows="+((mask&16)!=0)+"\n}\n"; + +"\n B:thatch="+((mask&4)!=0)+"\n B:slate="+((mask&8)!=0)+"\n B:leadedWindows="+((mask&16)!=0) + +"\n B:clayTiles="+((mask&32)!=0)+"\n}\n"; Files.write(file,text.getBytes(StandardCharsets.UTF_8));ArchitectureConfig.load(file.toFile()); require(ArchitectureConfig.cob==((mask&1)!=0)&&ArchitectureConfig.frames==((mask&3)==3) - &&ArchitectureConfig.thatch==((mask&4)!=0)&&ArchitectureConfig.slate==((mask&8)!=0)&&ArchitectureConfig.windows==((mask&19)==19),"Config mask "+mask); + &&ArchitectureConfig.thatch==((mask&4)!=0)&&ArchitectureConfig.slate==((mask&8)!=0)&&ArchitectureConfig.windows==((mask&19)==19) + &&ArchitectureConfig.clayTiles==((mask&32)!=0),"Config mask "+mask); } - } finally {ArchitectureConfig.cob=cob;ArchitectureConfig.frames=frames;ArchitectureConfig.thatch=thatch;ArchitectureConfig.slate=slate;ArchitectureConfig.windows=windows;ArchitectureConfig.survivalEras=eras;} + } finally {ArchitectureConfig.cob=cob;ArchitectureConfig.frames=frames;ArchitectureConfig.thatch=thatch;ArchitectureConfig.slate=slate;ArchitectureConfig.windows=windows;ArchitectureConfig.clayTiles=clayTiles;ArchitectureConfig.survivalEras=eras;} } private void verifyWallHeads(WorldServer world) { BlockPos pos=new BlockPos(95,90,95); @@ -200,7 +216,7 @@ private void verifyPlacementAndWalking(WorldServer world) { world.setBlockToAir(pos); for(int x=-3;x<=3;x++) for(int z=-3;z<=3;z++) world.setBlockState(pos.add(x,-1,z),Blocks.STONE.getDefaultState(),2); for(RoofBlock roof:ArchitectureBlocks.ROOFS) { - if(roof.isRidge()) continue; + if(roof.isRidge() || roof instanceof TrimmedRoofBlock) continue; for(EnumFacing facing:EnumFacing.HORIZONTALS) { world.setBlockState(pos,roof.getDefaultState().withProperty(BlockHorizontal.FACING,facing),2); double[] start=RoofGeometry.rotate(.5,0,1.6,facing); @@ -226,7 +242,8 @@ private void verifyLegacy(WorldServer world) { private void verifyLegacyDaub(WorldServer world) { List blocks=new ArrayList<>(); for(int finish=0;finish<2;finish++) {blocks.add(ArchitectureBlocks.COB.get(finish));blocks.add(ArchitectureBlocks.SLABS.get(finish));blocks.add(ArchitectureBlocks.STAIRS.get(finish));} - blocks.addAll(ArchitectureBlocks.WALLS);blocks.addAll(ArchitectureBlocks.ROOFS); + blocks.addAll(ArchitectureBlocks.WALLS); + for (RoofBlock roof : ArchitectureBlocks.ROOFS) if (!ClayTileProbe.isClay(roof)) blocks.add(roof); int index=0; for(Block block:blocks) for(int meta=0;meta<16;meta++) { require(world.getBlockState(new BlockPos(100+index,70,100)).equals(block.getStateFromMeta(meta)),"0.1.1 saved wall/roof state "+index); @@ -366,7 +383,7 @@ private void verifyWalls(WorldServer world) { private void verifyRoofs(WorldServer world) { BlockPos pos=new BlockPos(90,70,90); for(RoofBlock roof:ArchitectureBlocks.ROOFS) { - if(roof.isRidge()) continue; + if(roof.isRidge() || roof instanceof TrimmedRoofBlock) continue; for(EnumFacing facing:EnumFacing.HORIZONTALS) { for(EnumFacing turn:new EnumFacing[]{facing.rotateY(),facing.rotateYCCW()}) { for(EnumFacing side:EnumFacing.HORIZONTALS) world.setBlockToAir(pos.offset(side)); diff --git a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClayTileProbe.java b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClayTileProbe.java new file mode 100644 index 0000000..97825ee --- /dev/null +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClayTileProbe.java @@ -0,0 +1,194 @@ +package zone.moddev.mc.ironagearchitecture.probe; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.item.EntityArmorStand; +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.ArchitectureConfig; +import zone.moddev.mc.ironagearchitecture.block.*; + +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); + } + + static void verify(WorldServer world) { + if (!ArchitectureConfig.clayTiles) { + for (String name : new String[]{"roof", "ridge", "hanging", "hanging_thin", "fascia", "verge", "ridge_end"}) { + require(Block.getBlockFromName(PREFIX + name) == null, "Disabled tile block registered: " + name); + } + return; + } + TileHangingBlock thin = ArchitectureBlocks.CLAY_HANGING_THIN; + RidgeBlock ridge = (RidgeBlock) Block.getBlockFromName(PREFIX + "ridge"); + BlockPos pos = new BlockPos(87, 100, -84); + FakePlayer player = FakePlayerFactory.getMinecraft(world); + for (EnumFacing facing : EnumFacing.HORIZONTALS) { + IBlockState panel = thin.getDefaultState().withProperty(TileHangingBlock.FACING, facing); + world.setBlockState(pos, panel, 2); + Vec3d solid = point(pos, 8, .5, 4, facing); + Vec3d empty = point(pos, 8, .5, 12, facing); + require(!world.getCollisionBoxes(player, around(solid)).isEmpty(), "Missing tile collision " + facing); + require(world.getCollisionBoxes(player, around(empty)).isEmpty(), "Collision in empty tile half " + facing); + require(thin.collisionRayTrace(panel, world, pos, solid.addVector(0, 1, 0), solid.addVector(0, -1, 0)) != null, + "Tile cannot be targeted " + facing); + require(thin.collisionRayTrace(panel, world, pos, empty.addVector(0, 1, 0), empty.addVector(0, -1, 0)) == null, + "Empty tile half is targetable " + facing); + + IBlockState cap = ridge.getDefaultState().withProperty(RidgeBlock.FACING, facing); + BlockPos capPos = pos.up(4); + world.setBlockState(capPos, cap, 2); + require(ridge.getActualState(cap, world, capPos).getValue(RidgeBlock.ENDS) == RidgeBlock.End.SINGLE, "Single clay cap"); + world.setBlockState(capPos.offset(facing), cap.withProperty(RidgeBlock.FACING, facing.getOpposite()), 2); + require(ridge.getActualState(cap, world, capPos).getValue(RidgeBlock.ENDS) == RidgeBlock.End.END, "Clay cap end"); + world.setBlockState(capPos.offset(facing.getOpposite()), cap, 2); + require(ridge.getActualState(cap, world, capPos).getValue(RidgeBlock.ENDS) == RidgeBlock.End.MIDDLE, "Joined clay caps"); + world.setBlockToAir(capPos.offset(facing)); + world.setBlockToAir(capPos.offset(facing.getOpposite())); + + if (ArchitectureConfig.frames) { + JettyFloorBlock floor = ArchitectureBlocks.findFloor("oak", FloorShape.FULL, null); + IBlockState deck = floor.getDefaultState().withProperty(FrameBlock.FACING, facing); + require((floor.connections(deck, world, pos.up()) >> 16) == 3, "Tile wall supports wrong deck quarters " + facing); + PartialWallBlock wall = ArchitectureBlocks.findPartial("oak", FrameFamily.POST, WallProfile.THIN); + IBlockState placed = wall.getStateForPlacement(world, pos.up(), EnumFacing.UP, .5F, 1, .5F, 0, player); + require(placed.getValue(FrameBlock.FACING) == facing, "Timber did not inherit the tile plane"); + world.setBlockState(pos, placed, 2); + require(thin.getStateForPlacement(world, pos.up(), EnumFacing.UP, .5F, 1, .5F, 0, player) + .getValue(TileHangingBlock.FACING) == facing, "Tile did not inherit the timber plane"); + } + if (ArchitectureConfig.windows) verifyWindowPlacement(world, player, pos, panel, facing); + world.setBlockToAir(pos); + world.setBlockToAir(capPos); + } + verifyRoofTrim(world, player, pos); + verifyEntityPlacement(world, player, pos); + System.out.println("IAA_CLAY_TILE_PASS fourFacings=true collision=true ray=true ridge=true windowAlignment=" + ArchitectureConfig.windows); + } + + private static void verifyRoofTrim(WorldServer world, FakePlayer player, BlockPos pos) { + TrimmedRoofBlock verge = ArchitectureBlocks.CLAY_VERGE; + TrimmedRoofBlock fascia = ArchitectureBlocks.CLAY_FASCIA; + TrimmedRoofBlock end = ArchitectureBlocks.CLAY_RIDGE_END; + RidgeBlock ridge = (RidgeBlock) Block.getBlockFromName(PREFIX + "ridge"); + for (EnumFacing facing : EnumFacing.HORIZONTALS) { + player.rotationYaw = facing.getHorizontalIndex() * 90; + for (boolean right : new boolean[]{false, true}) { + double lip = right ? .75 : .25; + double[] hit = RoofGeometry.rotate(lip, 1, .25, facing); + IBlockState state = verge.getStateForPlacement(world, pos, EnumFacing.UP, + (float)hit[0], 1, (float)hit[2], 0, player); + require(state.getValue(RoofBlock.FACING) == facing && state.getValue(TrimmedRoofBlock.RIGHT) == right, + "Verge lost its placement hand " + facing); + require(verge.getStateFromMeta(verge.getMetaFromState(state)).equals(state), "Verge metadata " + facing); + world.setBlockState(pos, state, 2); + Vec3d below = roofPoint(pos, lip, .25, .25, facing); + Vec3d support = roofPoint(pos, 1-lip, .25, .25, facing); + Vec3d board = roofPoint(pos, lip, .67, .25, facing); + require(world.getCollisionBoxes(player, around(below)).isEmpty(), "Verge blocks clear space beneath lip"); + require(!world.getCollisionBoxes(player, around(support)).isEmpty(), "Verge lost its solid half"); + require(verge.collisionRayTrace(state, world, pos, board.addVector(0, .02, 0), board.addVector(0, -.3, 0)) != null, + "Verge board is not targetable"); + } + IBlockState eave = fascia.getDefaultState().withProperty(RoofBlock.FACING, facing); + world.setBlockState(pos, eave, 2); + require(world.getCollisionBoxes(player, around(roofPoint(pos, .5, .07, .94, facing))).isEmpty(), + "Fascia margin is not clear"); + require(!world.getCollisionBoxes(player, around(roofPoint(pos, .5, .1, .75, facing))).isEmpty(), + "Fascia board has no collision"); + + IBlockState cap = ridge.getDefaultState().withProperty(RoofBlock.FACING, facing); + world.setBlockState(pos, cap, 2); + world.setBlockState(pos.offset(facing), end.getDefaultState().withProperty(RoofBlock.FACING, facing), 2); + require(ridge.getActualState(cap, world, pos).getValue(RidgeBlock.ENDS) == RidgeBlock.End.END, + "Ridge does not meet trim end"); + world.setBlockToAir(pos.offset(facing)); + world.setBlockToAir(pos); + } + System.out.println("IAA_ROOF_TRIM_PASS facings=4 vergeHands=2 eaveClearance=true ridgeJoin=true"); + } + + private static Vec3d roofPoint(BlockPos pos, double x, double y, double z, EnumFacing facing) { + double[] p = RoofGeometry.rotate(x, y, z, facing); + return new Vec3d(pos.getX()+p[0], pos.getY()+p[1], pos.getZ()+p[2]); + } + + private static void verifyWindowPlacement(WorldServer world, FakePlayer player, BlockPos pos, + IBlockState panel, EnumFacing facing) { + for (boolean inside : new boolean[]{false, true}) { + world.setBlockState(pos, panel, 2); + Vec3d standing = point(pos, 8, 0, inside ? 48 : -32, facing); + player.setPosition(standing.xCoord, standing.yCoord, standing.zCoord); + player.rotationYaw = facing.getOpposite().getHorizontalIndex() * 90; + ItemStack glazing = new ItemStack(ArchitectureBlocks.findWindow("oak", 1, true, true, false)); + require(place(glazing, player, world, pos, EnumFacing.UP, .5F, .5F), "Window could not be placed against tiles"); + IBlockState window = world.getBlockState(pos.up()); + WindowBlock block = (WindowBlock) window.getBlock(); + require(window.getValue(WindowBlock.FACING) == (inside ? facing.getOpposite() : facing) + && block.back == inside, "Window lost tile plane or outward facing"); + ItemStack tile = new ItemStack(panel.getBlock()); + player.setHeldItem(EnumHand.MAIN_HAND, tile); + require(!block.onBlockActivated(world, pos.up(), window, player, EnumHand.MAIN_HAND, tile, facing.rotateY(), .5F, .5F, .5F), + "Tile item toggles the window"); + EnumFacing lateral = facing.rotateY(); + require(place(tile, player, world, pos.up(), lateral, .5F, .5F), "Could not place beside the thin window"); + BlockPos adjacent = pos.up().offset(lateral); + require(world.getBlockState(adjacent).equals(panel), "Tile failed to inherit window half " + facing + " inside=" + inside); + world.setBlockToAir(adjacent); + world.setBlockToAir(pos.up()); + player.setHeldItem(EnumHand.MAIN_HAND, null); + } + } + + private static void verifyEntityPlacement(WorldServer world, FakePlayer player, BlockPos pos) { + TileHangingBlock panel = ArchitectureBlocks.CLAY_HANGING_THIN; + world.setBlockState(pos.down(), panel.getDefaultState(), 2); + player.setPosition(pos.getX() + 2, pos.getY(), pos.getZ() + 2); + player.rotationYaw = 0; + player.setSneaking(true); + EntityArmorStand stand = new EntityArmorStand(world, pos.getX() + .5, pos.getY(), pos.getZ() + .25); + require(world.spawnEntity(stand), "Could not spawn tile placement obstruction"); + try { + ItemStack stack = new ItemStack(panel, 2); + require(!place(stack, player, world, pos.down(), EnumFacing.UP, .5F, .25F) && stack.stackSize == 2, + "Thin tile overlaps an entity in its chosen half"); + require(place(stack, player, world, pos.down(), EnumFacing.UP, .5F, .75F) + && world.getBlockState(pos).getValue(TileHangingBlock.FACING) == EnumFacing.SOUTH, + "Sneaking failed to choose the free half"); + } finally { + world.removeEntityDangerously(stand); + world.setBlockToAir(pos); + world.setBlockToAir(pos.down()); + player.setSneaking(false); + } + } + + private static boolean place(ItemStack stack, FakePlayer player, WorldServer world, BlockPos pos, EnumFacing side, float x, float z) { + return stack.getItem().onItemUse(stack, player, world, pos, EnumHand.MAIN_HAND, side, x, 1, z) == EnumActionResult.SUCCESS; + } + + private static AxisAlignedBB around(Vec3d p) { + return new AxisAlignedBB(p.xCoord-.05, p.yCoord-.05, p.zCoord-.05, p.xCoord+.05, p.yCoord+.05, p.zCoord+.05); + } + + private static Vec3d point(BlockPos pos, double x, double y, double z, EnumFacing facing) { + double[] p = WallGeometry.rotate(x, 0, z, facing); + return new Vec3d(pos.getX()+p[0]/16, pos.getY()+y, pos.getZ()+p[2]/16); + } + + 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/ClientProbe.java b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClientProbe.java index 14e410e..5b2bc4a 100644 --- a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClientProbe.java +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/ClientProbe.java @@ -18,6 +18,7 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import zone.moddev.mc.ironagearchitecture.ArchitectureBlocks; +import zone.moddev.mc.ironagearchitecture.ArchitectureConfig; import zone.moddev.mc.ironagearchitecture.block.RoofBlock; import zone.moddev.mc.ironagearchitecture.client.RoofBakedModel; import zone.moddev.mc.ironagearchitecture.block.FrameBlock; @@ -40,17 +41,21 @@ public final class ClientProbe extends ProbeProxy { if(phase==0&&mc.currentScreen instanceof GuiMainMenu&&ticks>60) { mc.gameSettings.pauseOnLostFocus=false; phase=1;ticks=0; - String preview=System.getProperty("iaa.previewName","Horizontal Rails Preview"); + String preview=System.getProperty("iaa.previewName",RyeTilePreview.NAME); mc.launchIntegratedServer(preview,preview,new WorldSettings(137,GameType.CREATIVE,false,false,WorldType.FLAT)); } else if(phase==1&&mc.player!=null) { phase=2;ticks=0; mc.displayGuiScreen(null);mc.gameSettings.hideGUI=true;mc.gameSettings.renderDistanceChunks=8; + mc.gameSettings.showInventoryAchievementHint=false; + mc.setIngameNotInFocus(); mc.player.capabilities.allowFlying=true;mc.player.capabilities.isFlying=true; lookAt(mc,48,102,-85,48,73,4); } else if(phase==2&&ticks>260) { for(RoofBlock roof:ArchitectureBlocks.ROOFS) for(IBlockState state:roof.getBlockState().getValidStates()) { IBakedModel model=mc.getBlockRendererDispatcher().getModelForState(state); if(!(model instanceof RoofBakedModel)||model.getQuads(state,null,0).isEmpty()) throw new IllegalStateException("Roof model missing "+state); + if (!model.getParticleTexture().getIconName().equals("ironagearchitecture:blocks/" + roof.textureKey())) + throw new IllegalStateException("Wrong roof sprite " + state); } for(FrameBlock wall:ArchitectureBlocks.meshes()) { for(IBlockState state:wall.getBlockState().getValidStates()) { @@ -70,6 +75,17 @@ public final class ClientProbe extends ProbeProxy { if(!(model instanceof WindowBakedModel)||model.getQuads(state,null,0).isEmpty()) throw new IllegalStateException("Missing window model "+state); for(net.minecraft.client.renderer.block.model.BakedQuad quad:model.getQuads(state,null,0)) if(quad.getSprite().getIconName().contains("missingno")) throw new IllegalStateException("Missing window texture "+state); } + if (ArchitectureConfig.clayTiles) { + for (net.minecraft.block.Block panel : new net.minecraft.block.Block[]{ArchitectureBlocks.CLAY_HANGING, ArchitectureBlocks.CLAY_HANGING_THIN}) { + for (IBlockState state : panel.getBlockState().getValidStates()) { + IBakedModel model = mc.getBlockRendererDispatcher().getModelForState(state); + if (!model.getParticleTexture().getIconName().equals("ironagearchitecture:blocks/clay_tile")) + throw new IllegalStateException("Wrong hanging tile sprite " + state); + } + } + phase=60;ticks=0; + return; + } capture(mc,"jettied-street-overview.png"); lookAt(mc,-5,74,-7,3,69.5,2);phase=3;ticks=0; } else if(phase==3&&ticks>120) { @@ -96,17 +112,17 @@ public final class ClientProbe extends ProbeProxy { double[][] cameras={{3,66,-3,3,66.7,.15},{4.5,71,-3,3.5,70.8,-.3},{3,70,3,3,70.7,-.25},{15,68,18,15,65,23},{19,66,26,15.5,65,27.5},{-2,70,-3,1.3,70.9,-.35},{1.8,70,3,1,70.9,-.3}}; capture(mc,names[phase-50]); 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;} - for(Era era:Era.values()) { - java.util.List items=new java.util.ArrayList<>();EraTabs.tab(era).displayAllRelevantItems(items); - int expected=era==Era.IRON_AGE?14:510; - if(items.size()!=expected) throw new IllegalStateException("Creative tab "+era+" expected "+expected+" got "+items.size()); - for(ItemStack item:items) if(!zone.moddev.mc.ironagearchitecture.EraCatalog.eras(item.getItem()).contains(era)) throw new IllegalStateException("Wrong era item "+item); - for(ItemStack item:items) { - java.util.List tooltip=new java.util.ArrayList<>();item.getItem().addInformation(item,mc.player,tooltip,false); - if(tooltip.isEmpty()) throw new IllegalStateException("Missing crafting hint "+item); - for(String line:tooltip) if(line.startsWith("hint.ironagearchitecture.")) throw new IllegalStateException("Untranslated crafting hint "+line); - } - } + 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}}; + 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"}; + capture(mc,names[phase-60]); + if (phase<62) {phase++;ticks=0;return;} + checkPalettes(mc); mc.displayGuiScreen(new PaletteGui(mc,Era.IRON_AGE,true));phase=8;ticks=0; } else if(phase==8) { if(!ensurePalette(mc,Era.IRON_AGE)) {ticks=0;return;} @@ -115,11 +131,24 @@ public final class ClientProbe extends ProbeProxy { if(!ensurePalette(mc,Era.LATE_MEDIEVAL_TUDOR)) {ticks=0;return;} if(ticks>80) { capture(mc,"late-medieval-tudor-palette.png");mc.displayGuiScreen(null); - System.out.println("IAA_CLIENT_PROBE_PASS creativeIronAge=14 creativeLateMedievalTudor=510");phase=10; + System.out.println("IAA_CLIENT_PROBE_PASS creativeIronAge=14 creativeLateMedievalTudor="+EraProbe.expectedVariants());phase=10; mc.shutdown(); } } } + private void checkPalettes(Minecraft mc) { + for(Era era:Era.values()) { + java.util.List items=new java.util.ArrayList<>();EraTabs.tab(era).displayAllRelevantItems(items); + int expected=era==Era.IRON_AGE?14:EraProbe.expectedVariants(); + if(items.size()!=expected) throw new IllegalStateException("Creative tab "+era+" expected "+expected+" got "+items.size()); + for(ItemStack item:items) if(!zone.moddev.mc.ironagearchitecture.EraCatalog.eras(item.getItem()).contains(era)) throw new IllegalStateException("Wrong era item "+item); + for(ItemStack item:items) { + java.util.List tooltip=new java.util.ArrayList<>();item.getItem().addInformation(item,mc.player,tooltip,false); + if(tooltip.isEmpty()) throw new IllegalStateException("Missing crafting hint "+item); + for(String line:tooltip) if(line.startsWith("hint.ironagearchitecture.")) throw new IllegalStateException("Untranslated crafting hint "+line); + } + } + } private void verifyBracketShell(JettyFloorBlock floor,IBlockState state,IBakedModel model) { if(floor.bracket==null) return; java.util.List parts=floor.parts(state,0); 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 412678f..ee2f40c 100644 --- a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/EraProbe.java +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/EraProbe.java @@ -15,9 +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): + return ArchitectureConfig.survivalEras.allowed.contains(Era.LATE_MEDIEVAL_TUDOR)?(ArchitectureConfig.windows?2010:1863)+(ArchitectureConfig.clayTiles?10:0): ArchitectureConfig.survivalEras.allowed.contains(Era.IRON_AGE)?36:0; } + static int expectedVariants() { return (ArchitectureConfig.windows ? 510 : 460) + (ArchitectureConfig.clayTiles ? 7 : 0); } private static boolean ours(IRecipe recipe) { ItemStack output=recipe.getRecipeOutput(); return output!=null&&"ironagearchitecture".equals(output.getItem().getRegistryName().getResourceDomain()); @@ -40,6 +41,7 @@ static void verify(World world) throws Exception { List original=new ArrayList<>(recipes); EraSelection selected=ArchitectureConfig.survivalEras; boolean cob=ArchitectureConfig.cob,frames=ArchitectureConfig.frames,thatch=ArchitectureConfig.thatch,slate=ArchitectureConfig.slate,windows=ArchitectureConfig.windows; + boolean clayTiles = ArchitectureConfig.clayTiles; String[][] cases={{"all"},{"iron_age"},{"late_medieval_tudor"},{"iron_age","late_medieval_tudor"},{},{"typo"},{"iron_age","typo"},{" IRON_AGE ","iron_age"}}; try { recipes.removeIf(EraProbe::ours);ArchitectureConfig.survivalEras=EraSelection.parse("all");ArchitectureRecipes.register(); @@ -48,7 +50,7 @@ static void verify(World world) throws Exception { Files.createDirectories(Paths.get("era-matrix")); for(int c=0;c\n}\n"; + String text="blocks {\n B:leadedWindows="+windows+"\n B:clayTiles="+clayTiles+"\n}\nrecipes {\n S:survivalEras <\n"+String.join("\n",cases[c])+"\n >\n}\n"; Files.write(file,text.getBytes(StandardCharsets.UTF_8));ArchitectureConfig.load(file.toFile()); recipes.removeIf(EraProbe::ours);ArchitectureRecipes.register(); int count=0;for(IRecipe recipe:recipes) if(ours(recipe)) {require(EraCatalog.canCraft(recipe.getRecipeOutput()),"Prohibited registered output");count++;} @@ -63,15 +65,16 @@ static void verify(World world) throws Exception { if(EraCatalog.eras(stack.getItem()).contains(Era.IRON_AGE)) iron++; if(EraCatalog.eras(stack.getItem()).contains(Era.LATE_MEDIEVAL_TUDOR)) late++; } - require(iron==14&&late==(windows?510:460),"Creative palette changed with recipe filter"); - int expected=ArchitectureConfig.survivalEras.allowed.contains(Era.LATE_MEDIEVAL_TUDOR)?(windows?510:460): + require(iron==14&&late==expectedVariants(),"Creative palette changed with recipe filter"); + int expected=ArchitectureConfig.survivalEras.allowed.contains(Era.LATE_MEDIEVAL_TUDOR)?expectedVariants(): ArchitectureConfig.survivalEras.allowed.contains(Era.IRON_AGE)?14:0; require(RecipeProbe.verifyReachability()==expected,"Era selection leaves unreachable content case "+c); } - System.out.println("IAA_ERA_MATRIX_PASS cases="+cases.length+" sharedVariants=14 lateVariants="+(windows?510:460)); + System.out.println("IAA_ERA_MATRIX_PASS cases="+cases.length+" sharedVariants=14 lateVariants="+expectedVariants()); } finally { recipes.clear();recipes.addAll(original);ArchitectureConfig.survivalEras=selected; ArchitectureConfig.cob=cob;ArchitectureConfig.frames=frames;ArchitectureConfig.thatch=thatch;ArchitectureConfig.slate=slate;ArchitectureConfig.windows=windows; + ArchitectureConfig.clayTiles = clayTiles; } } } 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 696c6c9..00bf471 100644 --- a/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/RecipeProbe.java +++ b/src/probe/java/zone/moddev/mc/ironagearchitecture/probe/RecipeProbe.java @@ -31,7 +31,7 @@ private static void crafts(World world,ItemStack expected,ItemStack... slots) { require(ItemStack.areItemStacksEqual(expected,actual),"Crafting contract expected "+expected+" got "+actual); } static void verify(World world) { - for(int wood=0;wood<6;wood++) for(int finish=0;finish<2;finish++) { + if(ArchitectureConfig.frames) for(int wood=0;wood<6;wood++) for(int finish=0;finish<2;finish++) { ItemStack plank=new ItemStack(Blocks.PLANKS,1,wood),infill=new ItemStack(ArchitectureBlocks.DAUB,1,finish); crafts(world,new ItemStack(ArchitectureBlocks.FRAMES.get(wood),4,finish*2),null,plank,null,plank,infill,plank,null,plank,null); ItemStack other=new ItemStack(Blocks.PLANKS,1,(wood+1)%6); @@ -112,8 +112,9 @@ 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());} - for(int finish=0;finish<2;finish++) if(ArchitectureBlocks.COB.get(finish)==block||ArchitectureBlocks.SLABS.get(finish)==block||ArchitectureBlocks.STAIRS.get(finish)==block) return "daub:"+finish; - if(block instanceof RoofBlock) return "roof:"+(block.getRegistryName().getResourcePath().startsWith("thatch")?"thatch":"slate"); + if(block instanceof RoofBlock) return "roof:"+((RoofBlock)block).materialKey; + if(block instanceof TileHangingBlock) return "roof:clay_tile"; + for(int finish=0;finish= -7 && z <= -1; + IBlockState top = road ? ((x * 13 + z * 7) % 19 == 0 ? Blocks.MOSSY_COBBLESTONE : Blocks.COBBLESTONE).getDefaultState() + : Blocks.GRASS.getDefaultState(); + set(world, x, street - 1, z, top); + if (road && street < streetHeight(x - 1)) { + set(world, x, street, z, Blocks.STONE_STAIRS.getDefaultState().withProperty(BlockStairs.FACING, EnumFacing.WEST)); + } + } + } + house(world, -57, 14, 7, 7, 7, "spruce", false, false); + house(world, -50, 12, 9, 9, 6, "dark_oak", true, false); + house(world, -41, 10, 7, 7, 6, "oak", false, true); + + // A short garden wall and hedge frame the exposed downhill end of the row. + for (int z = 1; z <= 7; z++) { + int x = -33; + int ground = streetHeight(x); + set(world, x, ground, z, Blocks.COBBLESTONE_WALL.getStateFromMeta(z % 3 == 0 ? 1 : 0)); + if (z >= 4) set(world, x, ground + 1, z, Blocks.LEAVES.getStateFromMeta(4)); + } + } + + private static int streetHeight(int x) { return 14 - Math.floorDiv(x + 57, 4); } + + private static void house(WorldServer world, int start, int base, int width, int depth, int height, + String wood, boolean tileFront, boolean brickBase) { + int top = base + height; + IBlockState fullTiles = ArchitectureBlocks.CLAY_HANGING.getDefaultState(); + // Recess the street gable by half a block under the thin verge. + IBlockState thinTiles = ArchitectureBlocks.CLAY_HANGING_THIN.getDefaultState().withProperty(TileHangingBlock.FACING, EnumFacing.SOUTH); + IBlockState floor = ArchitectureBlocks.findFloor(wood, FloorShape.FULL, null).getDefaultState(); + for (int x = 0; x < width; x++) { + for (int z = 0; z < depth; z++) { + for (int y = base - 1; y >= 3; y--) { + BlockPos at = new BlockPos(start + x, y, z); + if (!world.isAirBlock(at)) break; + world.setBlockState(at, Blocks.STONEBRICK.getDefaultState(), 2); + } + set(world, start + x, base - 1, z, Blocks.STONEBRICK.getDefaultState()); + set(world, start + x, base + 3, z, floor); + boolean front = z == 0; + if (!front && z != depth - 1 && x != 0 && x != width - 1) continue; + EnumFacing facing = front ? EnumFacing.NORTH : z == depth - 1 ? EnumFacing.SOUTH : x == 0 ? EnumFacing.WEST : EnumFacing.EAST; + int along = facing.getAxis() == EnumFacing.Axis.Z ? x : z; + for (int y = 0; y < height; y++) { + if (y == 3) continue; + IBlockState state; + if (front && x == width / 2 && y < 2) { + state = Blocks.SPRUCE_DOOR.getStateFromMeta(y == 0 ? 3 : 8); + } else if (front && ArchitectureConfig.windows && windowBay(x, y, width, height, tileFront)) { + boolean recessed = tileFront && y >= 4; + state = ArchitectureBlocks.findWindow(wood, 1, recessed, tileFront, x < width / 2, recessed) + .getDefaultState().withProperty(WindowBlock.FACING, facing); + } else if (tileFront && front && y >= 4) { + state = thinTiles; + } else if (brickBase && y < 3) { + state = Blocks.BRICK_BLOCK.getDefaultState(); + } else if (y == 0) { + state = ((x + z) % 5 == 0 ? Blocks.MOSSY_COBBLESTONE : Blocks.COBBLESTONE).getDefaultState(); + } else { + FrameFamily family = y == height - 1 ? FrameFamily.RAIL + : along % 3 == 0 ? FrameFamily.POST : FrameFamily.INFILL; + int panel = family == FrameFamily.INFILL ? 1 : y == height - 1 ? 3 : 2; + state = ArchitectureBlocks.findWall(family == FrameFamily.INFILL ? null : wood, family).getDefaultState() + .withProperty(FrameBlock.FACING, facing).withProperty(FrameBlock.PANEL, panel); + } + set(world, start + x, base + y, z, state); + } + } + } + + // The entrance hood uses existing slabs and fence brackets. + if (tileFront) { + for (int x = width / 2 - 1; x <= width / 2 + 1; x++) { + set(world, start + x, base + 2, -1, Blocks.WOODEN_SLAB.getStateFromMeta(5 | 8)); + } + set(world, start + width / 2 - 1, base + 1, -1, Blocks.DARK_OAK_FENCE.getDefaultState()); + set(world, start + width / 2 + 1, base + 1, -1, Blocks.DARK_OAK_FENCE.getDefaultState()); + } + + int span = tileFront ? width : depth; + for (int x = 0; x < width; x++) for (int z = 0; z < depth; z++) { + int rise = roofRise(tileFront ? x : z, span); + boolean gableEnd = tileFront ? z == 0 || z == depth - 1 : x == 0 || x == width - 1; + if (gableEnd) { + IBlockState gable = tileFront && z == 0 ? thinTiles : fullTiles; + for (int y = 0; y < rise; y++) set(world, start + x, top + y, z, gable); + } + } + + RoofBlock slope = (RoofBlock) Block.getBlockFromName("ironagearchitecture:clay_tile_roof"); + RoofBlock cap = (RoofBlock) Block.getBlockFromName("ironagearchitecture:clay_tile_ridge"); + // Extend exposed edges by a row, except the recessed gable and the party walls. + int firstX = tileFront || brickBase ? 0 : -1; + int lastX = brickBase ? width : width - 1; + int firstZ = tileFront ? 0 : -1; + for (int x = firstX; x <= lastX; x++) for (int z = firstZ; z <= depth; z++) { + int across = tileFront ? x : z; + boolean apex = across == span / 2; + EnumFacing facing = apex ? (tileFront ? EnumFacing.NORTH : EnumFacing.EAST) + : tileFront ? (across < span / 2 ? EnumFacing.EAST : EnumFacing.WEST) + : (across < span / 2 ? EnumFacing.SOUTH : EnumFacing.NORTH); + RoofBlock piece = apex ? cap : slope; + if (tileFront && z == 0) piece = apex ? ArchitectureBlocks.CLAY_RIDGE_END : ArchitectureBlocks.CLAY_VERGE; + if (!tileFront && (z == -1 || z == depth)) piece = ArchitectureBlocks.CLAY_FASCIA; + IBlockState roof = piece.getDefaultState().withProperty(RoofBlock.FACING, facing); + if (piece == ArchitectureBlocks.CLAY_VERGE) roof = roof.withProperty(TrimmedRoofBlock.RIGHT, facing == EnumFacing.WEST); + set(world, start + x, top + roofRise(across, span), z, roof); + } + + if (tileFront) chimney(world, start + width / 2 + 1, top, top + span / 2 + 3, depth - 3); + if (brickBase) chimney(world, start + 2, top, top + span / 2 + 2, depth / 2 + 1); + } + + private static boolean windowBay(int x, int y, int width, int height, boolean tileFront) { + if (y == 1 || y == 2) return x == 1 || x == width - 2; + if (y < 4 || y >= height - (tileFront ? 0 : 1)) return false; + return x == 1 || x == 2 || x == width - 3 || x == width - 2; + } + + private static int roofRise(int across, int span) { + return Math.min(across, span - 1 - across); + } + + private static void chimney(WorldServer world, int x, int bottom, int top, int z) { + for (int y = bottom; y < top; y++) set(world, x, y, z, Blocks.BRICK_BLOCK.getDefaultState()); + set(world, x, top, z, Blocks.FLOWER_POT.getDefaultState()); + } + + private static void set(WorldServer world, int x, int y, int z, IBlockState state) { + world.setBlockState(new BlockPos(x, y, z), state, 2); + } +}