Compare commits

..
50 Commits
Author SHA1 Message Date
Curle b0a68b8c79 Mark 1.19.3 Recommended Build 2023-01-02 03:37:31 +00:00
Brennan Ward f225d1f3fb [1.19.3] Allow Item and Elements models to specify static color, sky light, and block light values. (#9106) 2022-12-31 10:36:56 +01:00
bconlon 15214a6e9a Fix StemBlock not checking canSustainPlant for the correct block, it now checks for Melons/Pumpkins instead of the stem itself. (#9270) 2022-12-30 11:13:42 -08:00
matyrobbrt dfce6b62d4 Add github shared actions for automation purposes. (#9251) 2022-12-30 11:12:49 -08:00
Mysterious_Dev 0232ad7424 Add translate key for Forge pack.mcmeta description (#9260) 2022-12-30 11:12:11 -08:00
Mysterious_Dev e6ff2e0c24 Fix broken link for update checker docs in mdk (#9271) 2022-12-30 11:11:43 -08:00
sciwhiz12 72980d2b03 Remove duplicate updateNeighbourForOutputSignal call Fixes #9169 (#9234) 2022-12-30 11:11:23 -08:00
Minecraftschurli 6e3b51d81f Add helper methods to access the set of loaded sprite locations (#9223) 2022-12-30 11:11:02 -08:00
Dennis C 6b1c628326 Disable guiLight3d for generated item models (#9230) 2022-12-30 17:59:36 +01:00
matyrobbrt 85f9b1012c Remove resource caching (#9254) 2022-12-27 11:40:06 -08:00
failall 7569de7004 Add TradeWithVillagerEvent (#9244) 2022-12-26 13:20:01 -08:00
Mysterious_Dev 5ccde7422c Update link for Parchment "Getting Started" (#9243) 2022-12-26 13:19:44 -08:00
bconlon ad6bf9428e Allows DatapackBuiltinEntriesProvider to datagen LevelStems (#9247) 2022-12-26 13:19:36 -08:00
noeppi_noeppi d176bab28e Add a method to LootContext.Builder that allows changing the queried loot table id (#9084) 2022-12-19 15:18:52 -08:00
ChampionAsh5357 58d6f072e4 [1.19.3] Fix Datagen Tests and Providers (#9212) 2022-12-19 16:28:19 +00:00
matyrobbrt cc393818e4 Fix concrete powder not being hydrated by singular water sources (#9236) 2022-12-19 16:05:22 +00:00
Girafi e84098a011 [1.19.3] Fix LootTableLoadEvent not getting fired (#9239) 2022-12-19 16:05:08 +00:00
matyrobbrt f2db3941ea Allow using custom factories in button builders (#9238) 2022-12-19 16:04:41 +00:00
Dennis C c2bfe12068 Fix logspam when a root resource is requested from DelegatingPackResources, fixes #9197 (#9227) 2022-12-19 16:04:27 +00:00
matyrobbrt d9e3a0081e [1.19.3] Fix retrieveRegistryLookup attempting to get the registry lookup from a HolderGetter (#9225) 2022-12-19 16:03:56 +00:00
Minecraftschurliandsciwhiz12 84d7a0facb [1.19.3] Add ability to datagen forge specific values in pack.mcmeta (#9221)
Co-authored-by: sciwhiz12 <[email protected]>
2022-12-16 16:52:54 +00:00
Dennis C 9c66928854 Add block atlas config to register forge:white texture (#9187) 2022-12-16 16:48:20 +00:00
bl4ckscor3 4b494451db Fix ExtendedButton not being highlighted when focused (#9144) 2022-12-16 16:46:58 +00:00
matyrobbrt e9ea4ca169 Separate checkAndFix from the check* tasks. (#9213) 2022-12-16 16:44:56 +00:00
Dennis C c5ca054936 Fix forge resources overriding vanilla ones (#9222) 2022-12-16 16:43:46 +00:00
Exopandora 7d2c7c0f91 Fix tooltip customization not working for creative inventory (#9218) 2022-12-16 16:42:51 +00:00
sciwhiz12 54b872af69 Fix glowing item frame entity's texture (#9126)
Fixes #9123
2022-12-16 16:41:45 +00:00
matyrobbrt ae954bea17 Fix datapack registries not being synced to clients (#9219) 2022-12-15 11:53:53 -08:00
Dennis C ad59985a9b Fix creatives tabs rendering overlapping tabs if the selected tab isn't on the current page. (#9214) 2022-12-15 11:41:29 -08:00
Technici4n f8fdd43552 Fix SidedInvWrapper not accounting for vanilla stacking special cases in brewing stands and furnaces (#9189) 2022-12-15 11:40:25 -08:00
Marc Hermans b269f7dbfd Update to the latest JarJar. (#9217) 2022-12-15 11:39:37 -08:00
ChampionAsh5357 e688f136dd Specify NetworkHooks#getEntitySpawningPacket Generic Return Type (#9220) 2022-12-15 11:39:29 -08:00
LexManos 81276b341a Fix using a DeferredRegister on a non-forge wrapped registry. Closes #9199 2022-12-12 12:00:23 -08:00
Dennis C f050617b47 Add support for custom CreativeModeTab implementations (#9210) 2022-12-12 10:23:48 -08:00
eerussianguy e6c7958456 Simplify tree grower patches (#9209) 2022-12-12 10:23:32 -08:00
ChampionAsh5357 4802ceeccc Replace AdvancementProvider patch with Forge helper (#9188) 2022-12-12 10:23:10 -08:00
matyrobbrt 2ea16d89ca Allow using PackOutputs in Forge-added datagen classes (#9182) 2022-12-12 10:22:48 -08:00
J.T. McQuigg e541201129 Add simpleBlockWithItem for data gens (#9170) 2022-12-12 10:22:32 -08:00
Technici4n 09aac3a4d3 Fix running test mods (#9211) 2022-12-12 10:22:09 -08:00
Dennis C dccb1ab3a4 [1.19.3] Fix models nested in custom geometries not resolving parents (#9200) 2022-12-11 21:25:14 +01:00
gigaherz 8bf96d8738 Fix OBJ Loader caches not being thread-safe. (#9204) 2022-12-11 20:36:17 +01:00
Dennis C 069a94f205 [1.19.3] Add event before baked models are cached by the BlockModelShaper (#9190) 2022-12-11 20:35:45 +01:00
sciwhiz12 332ed4ab76 Fix compatibility checker task configuration (#9202) 2022-12-11 20:24:47 +01:00
matyrobbrt 030f2dbd38 Fix chat offset (#9184) 2022-12-11 16:46:17 +01:00
LexManos ec3abdea14 Redesign CreativeTab collection event to be a lot more straight forward. (#9198) 2022-12-10 15:03:23 -08:00
LexManos 4cf5508e04 Move ICondition patch placement to before MC throws an error.
Disable the explicitly erroring test biome modifier.
2022-12-09 19:52:30 -08:00
shartte d861f647d7 Fix BlockStateProvider not waiting for models before finishing. (#9196) Fixes #9195: 2022-12-09 12:50:55 -08:00
LexManos 992f793695 Fix tooltips not rendering on screens. Closes #9191 2022-12-09 12:48:59 -08:00
LexManos 69eb940764 Fix merged mod resource pack not returning all resources with the same name when asked. Closes #9194 2022-12-09 12:44:40 -08:00
Marc Hermans c1f26d544a Fix searching using the wrong prefix for items or tags. Fixes #9176 Fixes #9179 (#9177) 2022-12-08 09:11:18 -08:00
156 changed files with 5511 additions and 2615 deletions

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
name: 'Comment actions'
on: issue_comment
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/comment_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+16
View File
@@ -0,0 +1,16 @@
name: 'Issue actions'
on: issues
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/issue_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+16
View File
@@ -0,0 +1,16 @@
name: 'Pull Request Review actions'
on: pull_request_review
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/pr_review_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+16
View File
@@ -0,0 +1,16 @@
name: 'Pull Request actions'
on: pull_request
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/pr_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
+16
View File
@@ -0,0 +1,16 @@
name: 'Push actions'
on: push
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
labels:
uses: MinecraftForge/SharedActions/.github/workflows/push_actions.yml@main
secrets:
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
-42
View File
@@ -1,42 +0,0 @@
name: 'Spam label'
on:
issues:
types: [labeled]
pull_request:
types: [labeled]
discussion:
types: [labeled]
permissions:
contents: read
issues: write
pull-requests: write
discussions: write
jobs:
lock:
if: ${{ (github.event.label.name == 'spam') && (github.event.action == 'labeled') }}
runs-on: ubuntu-latest
steps:
- uses: peter-evans/close-issue@v2
name: Close Issue
with:
close-reason: 'not_planned'
if: ${{ github.event_name != 'pull_request' }}
- run: gh pr close ${{ github.event.pull_request.html_url }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Close PR
if: ${{ github.event_name == 'pull_request' }}
- uses: maxkomarychev/[email protected]
name: Lock PR
if: ${{ github.event_name == 'pull_request' }}
with:
token: "${{ secrets.GITHUB_TOKEN }}"
issue_number: ${{ github.event.number }}
- uses: OSDKDev/[email protected]
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
name: Lock issue
if: ${{ github.event_name != 'pull_request' }}
-27
View File
@@ -1,27 +0,0 @@
name: Support Requests
on:
issues:
types:
- labeled
- unlabeled
- reopened
permissions:
issues: write
jobs:
support:
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v2
with:
support-label: Forum
issue-comment: >
:wave: We use the issue tracker exclusively for final bug reports and feature requests.
However, this issue appears to be better suited for the
[Forge Support Forums](https://forums.minecraftforge.net/) or
[Forge Discord](https://discord.gg/UvedJ9m). Please create a new topic on the support forum
with this issue or ask in the `#tech-support` channel in the Discord server, and the
conversation can continue there.
lock-issue: true
+39 -16
View File
@@ -1,4 +1,10 @@
import groovy.json.JsonBuilder
import net.minecraftforge.forge.tasks.checks.CheckATs
import net.minecraftforge.forge.tasks.checks.CheckExcs
import net.minecraftforge.forge.tasks.checks.CheckPatches
import net.minecraftforge.forge.tasks.checks.CheckSAS
import net.minecraftforge.forge.tasks.checks.CheckTask
import net.minecraftforge.forge.tasks.checks.CheckMode
import java.nio.file.Files
import java.util.Date
@@ -56,7 +62,7 @@ ext {
JETBRAINS_ANNOTATIONS_VERSION = '23.0.0'
SLF4J_API_VERSION = '1.8.0-beta4'
APACHE_MAVEN_ARTIFACT_VERSION = '3.8.5'
JARJAR_VERSION = '0.3.18'
JARJAR_VERSION = '0.3.19'
// These versions should be kept in sync with the version manifest JSON
// To use a version newer than the version manifest JSON, the dependency must be added to the installer configuration
@@ -1168,34 +1174,45 @@ project(':forge') {
}
}
task checkATs(type: CheckATs, dependsOn: [extractInheritance, createSrg2Mcp]) {
inheritance = extractInheritance.output
tasks.register('checkAll') {
dependsOn 'checkLicenses'
group = 'checks'
}
tasks.register('checkAllAndFix') {
dependsOn 'findFieldInstanceChecks', 'checkLicenses'
group = 'checks'
}
CheckTask.registerTask(tasks, 'ATs', CheckATs) {
dependsOn extractInheritance, createSrg2Mcp
ats.from patcher.accessTransformers
inheritance = extractInheritance.output
mappings = createSrg2Mcp.output
}
task checkSAS(type: CheckSAS, dependsOn: extractInheritance) {
inheritance = extractInheritance.output
CheckTask.registerTask(tasks, 'SAS', CheckSAS) {
dependsOn extractInheritance
sass.from patcher.sideAnnotationStrippers
inheritance = extractInheritance.output
}
task checkExcs(type: CheckExcs, dependsOn: jar) {
CheckTask.registerTask(tasks, 'Excs', CheckExcs) {
dependsOn jar
binary = jar.archiveFile.get().asFile
excs.from patcher.excs
}
task checkPatchesAndFix(type: CheckPatches, dependsOn: genPatches) {
CheckTask.registerTask(tasks, 'Patches', CheckPatches) {
dependsOn genPatches
patchDir = file("$rootDir/patches")
autoFix = true
}
task checkPatches(type: CheckPatches, dependsOn: genPatches) {
patchDir = file("$rootDir/patches")
autoFix = false
patchesWithS2SArtifact = [
'minecraft/net/minecraft/client/renderer/ViewArea.java.patch',
'minecraft/net/minecraft/data/models/blockstates/Variant.java.patch',
]
}
genPatches {
finalizedBy checkPatches
finalizedBy checkAndFixPatches
autoHeader true
lineEnding = '\n'
}
@@ -1241,9 +1258,15 @@ project(':forge') {
dependsOn 'setupCheckJarCompatibility'
baseJar = project.tasks.named('mergeBaseForgeJar').flatMap { it.output }
}
baseLibraries.from(project.tasks.named('createJoinedSRG').flatMap { it.output })
task checkAll(dependsOn: [checkATs, checkSAS, checkExcs, findFieldInstanceChecks, checkPatches, checkLicenses]){}
inputJar = project.tasks.named('reobfJar').flatMap { it.output }
concreteLibraries.from(project.PACKED_DEPS.collect { project.rootProject.tasks.getByPath(it).archiveFile })
commonLibraries.from(project.configurations.minecraftImplementation)
commonLibraries.from(project.configurations.installer)
commonLibraries.from(project.configurations.moduleonly)
}
task launcherJson(type: LauncherJson) {
packedDependencies = [':fmlloader:jar']
+1
View File
@@ -8,4 +8,5 @@ dependencies {
implementation 'org.ow2.asm:asm-tree:9.3'
implementation 'net.minecraftforge:srgutils:0.4.+'
implementation 'commons-io:commons-io:2.8.0'
implementation 'com.google.code.gson:gson:2.10'
}
@@ -1,267 +0,0 @@
package net.minecraftforge.forge.tasks
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.Opcodes
abstract class CheckATs extends DefaultTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@InputFile @Optional abstract RegularFileProperty getMappings()
@TaskAction
protected void exec() {
def mappings = mappings.map { IMappingFile.load(it.asFile) }.getOrNull()
Util.init()
def parse = { line ->
def idx = line.indexOf('#')
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
def (modifier, cls, desc) = (line.trim() + ' ').split(' ', -1)
def key = cls + (desc.isEmpty() ? '' : ' ' + desc)
return [modifier, cls, desc, comment, key]
}
def accessLevel = { access ->
if ((access & Opcodes.ACC_PUBLIC) != 0) return 3
if ((access & Opcodes.ACC_PROTECTED) != 0) return 2
if ((access & Opcodes.ACC_PRIVATE) != 0) return 0
return 1
}
def accessStr = { access ->
if (access.endsWith('-f') || access.endsWith('+f'))
return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
def json = inheritance.get().asFile.json()
ats.each { f ->
TreeMap lines = [:]
def group = null
for (def line : f.readLines()) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
def (modifier, cls, desc, comment, key) = parse.call(line.substring(7))
if (desc != '*' && desc != '*()' && desc != '<init>')
throw new IllegalStateException('Invalid group: ' + line)
group = [modifier: modifier, cls: cls, desc: desc, comment: comment,
existing: [] as Set,
children: [] as TreeSet,
group: true
]
if (lines.containsKey(key))
throw new IllegalStateException('Duplicate group: ' + line)
lines[key] = group
} else if (group != null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
def (modifier, cls, desc, comment, key) = parse.call(line)
group['existing'].add(key)
}
} else if (line.startsWith('#endgroup')) {
throw new IllegalStateException('Invalid group ending: ' + line)
} else if (line.startsWith('#')) {
//Nom
} else {
def (modifier, cls, desc, comment, key) = parse.call(line)
if (lines.containsKey(key)) {
println('Duplicate: ' + line)
continue
}
lines[key] = [modifier: modifier, cls: cls, desc: desc, comment: comment, group: false]
}
}
// Process Groups, this will remove any entries outside the group that is covered by the group
for (def key : new ArrayList<>(lines.keySet())) {
def entry = lines.get(key)
if (entry != null && entry['group']) {
def cls = entry['cls']
def jcls = json.get(cls.replaceAll('\\.', '/'))
if (jcls == null) {
lines.remove(key)
println('Invalid Group: ' + key)
} else if ('*' == entry['desc']) {
if (!jcls.containsKey('fields')) {
lines.remove(key)
println('Invalid Group, Class has no fields: ' + key)
} else {
jcls['fields'].each { field, value ->
def fkey = cls + ' ' + field
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
if (lines.containsKey(fkey)) {
lines.remove(fkey)
} else if (!entry['existing'].contains(fkey)) {
println('Added: ' + fkey)
}
entry['children'].add(fkey)
} else if (lines.containsKey(fkey)) {
lines.remove(fkey)
println('Removed: ' + fkey)
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
} else if ('*()' == entry['desc']) {
if (!jcls.containsKey('methods')) {
lines.remove(key)
println('Invalid Group, Class has no methods: ' + key)
} else {
jcls['methods'].each{ mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$'))
return
key = cls + ' ' + mtd.replace(' ', '')
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
if (lines.containsKey(key)) {
lines.remove(key)
} else if (!entry['existing'].contains(key)) {
println('Added: ' + key)
}
entry['children'].add(key)
} else if (lines.containsKey(key)) {
lines.remove(key)
println('Removed: ' + key)
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
} else if ('<init>' == entry['desc']) { //Make all public non-abstract subclasses
json.each{ tcls,value ->
if (!value.containsKey('methods') || ((value['access'] & Opcodes.ACC_ABSTRACT) != 0))
return
def parents = [] as Set
def parent = tcls
while (parent != null && json.containsKey(parent)) {
parents.add(parent)
def p = json[parent]
parent = p == null ? null : p['superName']
}
if (parents.contains(cls.replaceAll('\\.', '/'))) {
value['methods'].each{ mtd, v ->
if (mtd.startsWith('<init>')) {
def child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel.call(v['access']) < 3) {
if (lines.containsKey(child)) {
lines.remove(child)
} else if (!entry['existing'].contains(child)) {
println('Added: ' + child)
}
entry['children'].add(child)
} else if (lines.containsKey(child)) {
lines.remove(child)
println('Removed: ' + child)
}
}
}
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
}
}
// Process normal lines, remove invalid and remove narrowing
for (def key : new ArrayList<>(lines.keySet())) {
def entry = lines.get(key)
if (entry != null && !entry['group']) {
def cls = entry['cls']
def jcls = json.get(cls.replaceAll('\\.', '/'))
if (jcls == null) {
lines.remove(key)
println('Invalid: ' + key)
} else if (entry['desc'] == '') {
if (accessLevel.call(jcls['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
}
} else if (!entry['desc'].contains('(')) {
if (!jcls.containsKey('fields') || !jcls['fields'].containsKey(entry['desc'])) {
lines.remove(key)
println('Invalid: ' + key)
} else {
def value = jcls['fields'][entry['desc']]
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
println(entry.comment)
}
}
} else {
def jdesc = entry['desc'].replace('(', ' (')
if (!jcls.containsKey('methods') || !jcls['methods'].containsKey(jdesc)) {
lines.remove(key)
println('Invalid: ' + key)
} else {
def value = jcls['methods'][jdesc]
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
}
}
}
}
}
def data = []
def remapComment = { entry ->
if (!mappings || !entry || !entry.desc) return null
def comment = entry.comment?.substring(1)?.trim()
def jsonCls = json.get(entry.cls.replaceAll('\\.', '/'))
def mappingsClass = mappings?.getClass(jsonCls.name)
if (!mappingsClass) return entry.comment
def idx = entry.desc.indexOf('(')
def mappedName = idx == -1
? mappingsClass.remapField(entry.desc)
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
if (!mappedName) return entry.comment
if (mappedName == '<init>')
mappedName = 'constructor'
if (comment?.startsWith(mappedName))
return '# ' + comment
if (comment && comment.indexOf(' ') != -1) {
def split = comment.split(" - ").toList()
if (split[0].indexOf(' ') != -1)
// The first string is more than one word, so append before it
return "# ${mappedName} - ${comment}"
split.remove(0)
return "# ${mappedName} - ${String.join(' - ', split)}"
}
return '# ' + mappedName
}
lines.each { key,value ->
if (!value.group) {
def comment = remapComment.call(value)
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add('#group ' + value.modifier + ' ' + key + (value.comment == null ? '' : ' ' + value.comment))
value.children.each {
def line = value.modifier + ' ' + it
def (modifier, cls, desc, comment) = parse.call(line)
comment = remapComment.call([modifier: modifier, cls: cls, desc: desc, comment: comment])
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
f.text = data.join('\n')
}
}
}
@@ -1,94 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends DefaultTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@TaskAction
protected void exec() {
Util.init()
def known = []
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
def visitor = new ClassVisitor(Opcodes.ASM9) {
private String cls
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) != null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
excs.each { f ->
def lines = []
f.eachLine { line ->
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
println('Invalid: ' + line)
return
}
def (key, value) = line.split('=', 2)
if (!known.contains(key)) {
println('Unknown: ' + line)
return
}
def (cls, desc) = key.split('\\.', 2)
if (!desc.contains('(')) {
println('Invalid: ' + line)
return
}
def name = desc.split('\\(', 2)[0]
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length != Type.getArgumentTypes(desc).length) {
println('Invalid: ' + line)
return
}
lines.add(line)
}
f.text = lines.sort().join('\n')
}
}
}
@@ -1,219 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.Paths
import java.util.regex.Pattern
abstract class CheckPatches extends DefaultTask {
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input boolean autoFix = false
@TaskAction
protected void exec() {
def hasS2SArtifact = [
Paths.get("patches/minecraft/net/minecraft/client/renderer/ViewArea.java.patch"),
Paths.get("patches/minecraft/net/minecraft/data/models/blockstates/Variant.java.patch")
]
def verified = true;
patchDir.get().asFileTree.each { patch ->
def patchPath = project.rootDir.toPath().relativize(patch.toPath())
verified &= verifyPatch(patch, autoFix, patchPath.toString(), hasS2SArtifact.contains(patchPath))
}
if (!verified)
throw new RuntimeException('One or more patches failed verification. Check the log for errors.')
}
def verifyPatch(patch, fix, patchPath, hasS2SArtifact) {
def hunk_start_pattern = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
def white_space_pattern = Pattern.compile('^[+\\-]\\s*$')
def import_pattern = Pattern.compile('^[+\\-]\\s*import.*')
def field_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
def method_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
def class_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
def accessMap = [("private"):0, (null):1, ("protected"):2, ("public"):3]
def hasProblem = false;
def lines = patch.readLines()
def hunksStart = 0
def onlyWhiteSpace = false
def didFix = false
def newLines = []
def whiteSpaceErrors = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i = 2
for (; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (hunk_start_pattern.matcher(line).find()) {
if (onlyWhiteSpace) {
if (fix || hasS2SArtifact) {
logger.lifecycle("Removing white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
didFix = true
}
else {
logger.lifecycle("Patch contains only white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
hasProblem = true
}
}
else {
if (!whiteSpaceErrors.empty)
hasProblem = true
whiteSpaceErrors.each { error -> logger.lifecycle(error) }
whiteSpaceErrors.clear()
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim.equals(currTrim)) {
prefixChange = true
}
def pMatcher = field_pattern.matcher(prevLine)
def cMatcher = field_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
pMatcher = method_pattern.matcher(prevLine)
cMatcher = method_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
pMatcher = class_pattern.matcher(prevLine)
cMatcher = class_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
def nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
def nextTrim = nextLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim.equals(currTrim)) {
prefixChange = true
}
}
}
def isWhiteSpaceChange = white_space_pattern.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && import_pattern.matcher(line).find()
}
else if (isWhiteSpaceChange) {
def prevLineChange = prevLine.startsWithAny('+','-')
def nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
whiteSpaceErrors.add(String.format("Patch contains white space change in valid hunk at line %d (cannot auto fix), file: %s", i + 1, patchPath))
}
}
if (line.contains("\t")) {
if (!fix) {
logger.lifecycle("Patch contains tabs on line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
else {
logger.lifecycle("Fixing tabs on line {}, file: {}", i + 1, patchPath)
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
didFix = true
}
}
if (import_pattern.matcher(line).find() && !hasS2SArtifact) {
logger.lifecycle("Patch contains import change on line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
}
}
if (onlyWhiteSpace) {
if (fix || hasS2SArtifact) {
logger.lifecycle("Removing white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
didFix = true
}
else {
logger.lifecycle("Patch contains only white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
hasProblem = true
}
}
else {
if (!whiteSpaceErrors.empty)
hasProblem = true
whiteSpaceErrors.each { error -> logger.lifecycle(error) }
}
if (didFix) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch.toPath())
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
patch.withWriter('UTF-8') { writer ->
newLines.each { l -> writer.write(l) }
}
}
}
return !hasProblem
}
}
@@ -1,107 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.TaskAction
abstract class CheckSAS extends DefaultTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@TaskAction
protected void exec() {
Util.init()
def json = inheritance.get().asFile.json()
sass.each { f ->
def lines = []
f.eachLine { line ->
if (line[0] == '\t') return //Skip any tabbed lines, those are ones we add
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
def (cls, name, desc) = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
cls = cls.replaceAll('\\.', '/')
if (json[cls] == null) {
println('Invalid: ' + line)
} else if (name.isEmpty()) { //Class SAS
def toAdd = []
def clsSided = isSided(json[cls])
def sided = clsSided
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null) {
for (entry in json[cls]['fields']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key)
}
}
}
*/
if (json[cls]['methods'] != null) {
for (entry in json[cls]['methods']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
findChildMethods(json, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(json, cls, entry.key).each { println(line + ' -- ' + it) }
} else if (clsSided) {
findChildMethods(json, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(json, cls, entry.key).each { println(line + ' -- ' + it) }
}
}
}
if (sided) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
lines.addAll(toAdd.sort())
} else
println('Invalid: ' + line)
} else if (desc.isEmpty()) { // Fields
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
else
*/
println('Invalid: ' + line)
} else { // Methods
if (json[cls]['methods'] == null || !isSided(json[cls]['methods'][name + ' ' + desc]))
println('Invalid: ' + line)
else {
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
findChildMethods(json, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
findChildMethods(json, cls, name + ' ' + desc).each { lines.add('\t' + it) }
}
}
}
f.text = lines.join('\n')
}
}
protected static isSided(json) {
if (json == null || json['annotations'] == null)
return false
for (ann in json['annotations']) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;'.equals(ann.desc))
return true
}
return false
}
protected static findChildMethods(json, cls, desc)
{
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -0,0 +1,49 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
@CompileStatic
@TupleConstructor
final class InheritanceData implements Annotatable {
String name
int access
String superName
List<String> interfaces = []
Map<String, Method> methods = [:]
Map<String, Field> fields = [:]
List<Annotation> annotations = []
@TupleConstructor
static final class Method implements Annotatable {
int access
String override
List<Annotation> annotations = []
}
@TupleConstructor
static final class Field implements Annotatable {
int access
String desc
List<Annotation> annotations = []
}
@TupleConstructor
static final class Annotation {
String desc
}
private static final Gson GSON = new Gson()
static Map<String, InheritanceData> parse(File file) {
try (final reader = file.newReader()) {
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
}
}
}
@CompileStatic
interface Annotatable {
List<InheritanceData.Annotation> getAnnotations()
}
@@ -18,6 +18,7 @@ abstract class SetupCheckJarCompatibility extends DefaultTask {
onlyIf {
inputVersion.getOrNull() != null
}
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
}
@@ -31,17 +32,9 @@ abstract class SetupCheckJarCompatibility extends DefaultTask {
}.toArray(Dependency[]::new))
project.tasks.named('checkJarCompatibility') {
baseLibraries.from(project.tasks.createJoinedSRG.output)
baseLibraries.from(project.provider {
fmlLibs.resolvedConfiguration.lenientConfiguration.files
})
inputJar = project.tasks.reobfJar.output
concreteLibraries.from(project.PACKED_DEPS.collect { project.rootProject.tasks.getByPath(it).archiveFile })
commonLibraries.from(project.configurations.minecraftImplementation)
commonLibraries.from(project.configurations.installer)
commonLibraries.from(project.configurations.moduleonly)
}
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
@@ -0,0 +1,310 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
import net.minecraftforge.forge.tasks.InheritanceData
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.objectweb.asm.Opcodes
@CompileStatic
abstract class CheckATs extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@InputFile @Optional abstract RegularFileProperty getMappings()
@Override
void check(Reporter reporter, boolean fix) {
final IMappingFile mappings = mappings.map { RegularFile it -> IMappingFile.load(it.asFile) }.getOrNull()
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
ats.each {
final lines = process(it, reporter, inheritance)
if (fix) {
it.text = joinBack(lines, inheritance, mappings).join('\n')
}
}
}
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
final Map<String, ATParser.Entry> constructorGroups = [:]
final itr = lines.entrySet().iterator()
final toRemove = []
while (itr.hasNext()) {
final next = itr.next()
String key = next.key
final entry = next.value
if (entry === null) continue
final binaryName = entry.cls.replaceAll('\\.', '/')
// Process Groups, this will remove any entries outside the group that is covered by the group
if (entry.group) {
final jcls = inheritance[binaryName]
if (jcls === null) {
itr.remove()
reporter.report("Invalid group: $key")
} else if ('*' == entry.desc) {
if (!jcls.fields) {
itr.remove()
reporter.report("Invalid group, class has no fields: $key")
} else {
jcls.fields.each { field, value ->
final fkey = entry.cls + ' ' + field
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(fkey)) {
toRemove.add(fkey)
} else if (!entry.existing.contains(fkey)) {
reporter.report("Missing group entry: $fkey")
}
entry.children.add(fkey)
} else if (lines.containsKey(fkey)) {
toRemove.add(fkey)
reporter.report("Found invalid group entry: $fkey")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('*()' == entry.desc) {
if (!jcls.methods) {
itr.remove()
reporter.report("Invalid group, class has no methods: $key")
} else {
jcls.methods.each { mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
key = entry.cls + ' ' + mtd.replace(' ', '')
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(key)) {
toRemove.add(key)
} else if (!entry.existing.contains(key)) {
reporter.report("Missing group entry: $key")
}
entry.children.add(key)
} else if (lines.containsKey(key)) {
toRemove.add(key)
reporter.report("Found invalid group entry: $key")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
constructorGroups.put(binaryName, entry)
}
}
// Process normal lines, remove invalid and remove narrowing
else {
def jcls = inheritance.get(binaryName)
if (jcls === null) {
itr.remove()
reporter.report("Invalid: $key")
} else if (entry.desc == '') {
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
} else if (!entry.desc.contains('(')) {
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.fields[entry.desc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
}
}
} else {
final jdesc = entry.desc.replace('(', ' (')
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.methods[jdesc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
}
}
}
}
inheritance.each { tcls, value ->
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
String parent = tcls
while (parent !== null) {
constructorGroups[parent]?.tap { entry ->
value.methods.each { mtd, v ->
if (mtd.startsWith('<init>')) {
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel(v.access) < 3) {
if (lines.containsKey(child)) {
toRemove.add(child)
} else if (child !in entry.existing) {
reporter.report("Missing group entry: $child")
}
entry.children.add(child)
} else if (lines.containsKey(child)) {
toRemove.add(child)
reporter.report("Found invalid group entry: $child")
}
}
}
}
parent = inheritance[parent]?.superName
}
}
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
toRemove.each(lines.&remove)
return lines
}
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
final data = []
final remapComment = { ATParser.Entry entry ->
if (!mappings || !entry || !entry.desc) return null
final comment = entry.comment?.substring(1)?.trim()
final jsonCls = inheritance.get(entry.cls.replaceAll('\\.', '/'))
final mappingsClass = mappings?.getClass(jsonCls.name)
if (mappingsClass === null) return entry.comment
final idx = entry.desc.indexOf('(')
String mappedName = idx == -1
? mappingsClass.remapField(entry.desc)
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
if (!mappedName) return entry.comment
if (mappedName == '<init>')
mappedName = 'constructor'
if (comment?.startsWith(mappedName))
return '# ' + comment
if (comment && comment.indexOf(' ') !== -1) {
def split = comment.split(' - ').toList()
if (split[0].indexOf(' ') !== -1)
// The first string is more than one word, so append before it
return "# ${mappedName} - ${comment}"
split.remove(0)
return "# ${mappedName} - ${String.join(' - ', split)}"
}
return '# ' + mappedName
}
lines.each { key, value ->
if (!value.group) {
def comment = remapComment.call(value)
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
value.children.each {
final line = value.modifier + ' ' + it
final entry = ATParser.parseEntry(line)
final comment = remapComment(entry)
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
return data
}
static int accessStr(String access) {
if (access.endsWith('-f') || access.endsWith('+f')) return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
static int accessLevel(int access) {
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
return 1
}
}
@CompileStatic
class ATParser {
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
TreeMap<String, Entry> outLines = new TreeMap<>()
Entry group = null
for (final line : lines) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
final entry = parseEntry(line.substring(7))
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
reporter.report("Invalid group: $line", false)
}
entry.group = true
entry.children = []
entry.existing = []
group = entry
if (outLines.containsKey(entry.key)) {
reporter.report("Duplicate group: $line", false)
}
outLines[entry.key] = group
} else if (group !== null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
final key = parseEntry(line).key
group.existing.add(key)
}
} else if (line.startsWith('#endgroup')) {
reporter.report("Invalid group ending: $line", false)
} else if (line.startsWith('#')) {
//Nom
} else {
final entry = parseEntry(line)
if (outLines.containsKey(entry.key)) {
reporter.report("Found duplicate: $line")
continue
}
outLines[entry.key] = entry
}
}
return outLines
}
static Entry parseEntry(String line) {
final idx = line.indexOf('#')
final String comment = idx === -1 ? null : line.substring(idx)
if (idx !== -1) line = line.substring(0, idx - 1)
final data = (line.trim() + ' ').split(' ', -1)
new Entry(data[0], data[1], data[2], comment)
}
@TupleConstructor
static final class Entry {
String modifier, cls, desc, comment
Set<String> existing
TreeSet<String> children
boolean group = false
@Lazy
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
Object getAt(String key) {
return getProperty(key)
}
}
}
@@ -0,0 +1,93 @@
package net.minecraftforge.forge.tasks.checks
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends CheckTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@Override
void check(Reporter reporter, boolean fix) {
final Set<String> known = []
collectKnown(known)
excs.each { f ->
final lines = []
f.eachLine { line ->
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
return
}
if (idx != -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
reporter.report("Invalid: $line")
return
}
def (String key, String value) = line.split('=', 2)
if (!known.contains(key)) {
reporter.report("Unknown: $line")
return
}
String desc = key.split('\\.', 2)[1]
if (!desc.contains('(')) {
reporter.report("Invalid: $line")
return
}
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
reporter.report("Invalid: $line")
return
}
lines.add(line)
}
if (fix) f.text = lines.sort().join('\n')
}
}
private void collectKnown(Collection<String> known) {
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
final visitor = new ClassVisitor(Opcodes.ASM9) {
private String cls
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
}
}
@@ -0,0 +1,9 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
@CompileStatic
enum CheckMode {
CHECK,
FIX
}
@@ -0,0 +1,177 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
@CompileStatic
abstract class CheckPatches extends CheckTask {
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
@Override
void check(Reporter reporter, boolean fix) {
final patchDir = getPatchDir().get().asFile.toPath()
Files.walk(patchDir).withCloseable {
it.filter(Files.&isRegularFile).forEach { path ->
final String relativeName = patchDir.relativize(path).toString()
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
}
}
}
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
final oldFixedErrors = reporter.fixed.size()
final lines = Files.readAllLines(patch)
int hunksStart = 0
boolean onlyWhiteSpace = false
final List<String> newLines = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i
for (i = 2; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (HUNK_START_PATTERN.matcher(line).find()) {
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
int toRemove = i - hunksStart
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim == currTrim) {
prefixChange = true
}
def pMatcher = FIELD_PATTERN.matcher(prevLine)
def cMatcher = FIELD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = METHOD_PATTERN.matcher(prevLine)
cMatcher = METHOD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = CLASS_PATTERN.matcher(prevLine)
cMatcher = CLASS_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
final nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
final currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim == currTrim) {
prefixChange = true
}
}
}
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
} else if (isWhiteSpaceChange) {
final prevLineChange = prevLine.startsWithAny('+','-')
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath", false)
}
}
if (line.contains('\t')) {
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
}
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
}
}
}
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch)
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
Files.newBufferedWriter(patch).withCloseable {
newLines.each { l -> it.write(l) }
}
}
}
}
}
@@ -0,0 +1,110 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Annotatable
import net.minecraftforge.forge.tasks.InheritanceData
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
@CompileStatic
abstract class CheckSAS extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@Override
void check(Reporter reporter, boolean fix) {
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
sass.each { f ->
final lines = []
f.eachLine { line ->
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
final idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
cls = cls.replaceAll('\\.', '/')
if (inheritance[cls] === null) {
reporter.report("Invalid: $line")
} else if (name.isEmpty()) { //Class SAS
final toAdd = []
final clsSided = isSided(inheritance[cls])
boolean sided = clsSided
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null) {
for (entry in json[cls]['fields']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key)
}
}
} */
final clsInh = inheritance[cls]
if (clsInh.methods) {
for (entry in clsInh.methods) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
} else if (clsSided) {
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
}
}
}
if (sided) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
lines.addAll(toAdd.sort())
} else {
reporter.report("Invalid: $line")
}
} else if (desc.isEmpty()) { // Fields
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
else */
reporter.report("Invalid: $line")
} else { // Methods
final clsInh = inheritance[cls]
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
reporter.report("Invalid: $line")
else {
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
}
}
}
if (fix) f.text = lines.join('\n')
}
}
protected static isSided(Annotatable annotatable) {
if (annotatable === null) return false
for (ann in annotatable.annotations) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
return true
}
return false
}
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -0,0 +1,109 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.VerificationTask
@CompileStatic
abstract class CheckTask extends DefaultTask implements VerificationTask {
@Input
abstract Property<CheckMode> getMode()
private boolean ignoreFailures = false
@Input
@Override
boolean getIgnoreFailures() {
return ignoreFailures
}
@Override
void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures
}
@TaskAction
void run() {
Util.init()
final doFix = getMode().get() === CheckMode.FIX
final Reporter reporter = new Reporter(doFix)
check(reporter, doFix)
if (reporter.messages) {
if (getMode().get() === CheckMode.CHECK) {
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
}
} else {
if (logger.isEnabled(LogLevel.DEBUG)) {
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
} else {
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
}
if (reporter.notFixed) {
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
}
}
}
}
}
abstract void check(Reporter reporter, boolean fix)
@CompileStatic
static final class Reporter {
final boolean trackFixed
Reporter(boolean trackFixed) {
this.trackFixed = trackFixed
}
public final List<String> messages = []
public final List<String> fixed = []
public final List<String> notFixed = []
void report(String message, boolean canBeFixed = true) {
messages.add(message)
if (trackFixed) {
if (canBeFixed) {
fixed.add(message)
} else {
notFixed.add(message)
}
}
}
}
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
taskName = taskName.capitalize()
tasks.register("check$taskName", clazz) { CheckTask task ->
configuration.setDelegate((T) task)
configuration.call(task)
task.mode.set(CheckMode.CHECK)
task.group = 'checks'
}
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
configuration.setDelegate((T) task)
configuration.call(task)
task.mode.set(CheckMode.FIX)
task.group = 'checks'
}
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
}
}
+2 -2
View File
@@ -2,8 +2,8 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.19-&query=$.promos["1.19.2-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)](https://files.minecraftforge.net)
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.19.2-&query=$.promos["1.19.2-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.19.3-&query=$.promos["1.19.3-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)](https://files.minecraftforge.net)
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.19.3-&query=$.promos["1.19.3-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)](https://files.minecraftforge.net) [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)](https://discord.gg/UvedJ9m) [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)](https://www.patreon.com/LexManos)
Forge is a free, open-source modding API all of your favourite mods use!
@@ -31,7 +31,6 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
// The extra jar is on the classpath, so try and pull it out of the legacy classpath
var legacyCP = Objects.requireNonNull(System.getProperty("legacyClassPath"), "Missing legacyClassPath, cannot find client-extra").split(File.pathSeparator);
var extra = findJarOnClasspath(legacyCP, "client-extra");
mcstream.add(extra);
// The MC code/Patcher edits are in exploded directories
final var modstream = Stream.<List<Path>>builder();
@@ -42,6 +41,7 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
minecraft.stream().distinct().forEach(mcstream::add);
mods.values().forEach(modstream::add);
mcstream.add(extra);
var mcFilter = getMcFilter(extra, minecraft, modstream);
return new LocatedPaths(mcstream.build().toList(), mcFilter, modstream.build().toList(), getFmlStuff(legacyCP));
}
+1 -1
View File
@@ -22,7 +22,7 @@ minecraft {
// See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
//
// Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge
// Additional setup is needed to use their mappings: https://github.com/ParchmentMC/Parchment/wiki/Getting-Started
// Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started
//
// Use non-default mappings at your own risk. They may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
@@ -58,8 +58,7 @@ public class ExampleMod
MinecraftForge.EVENT_BUS.register(this);
// Register the item to a creative tab
modEventBus.addListener((CreativeModeTabEvent.BuildContents event) -> event.registerSimple(CreativeModeTabs.BUILDING_BLOCKS,
EXAMPLE_BLOCK_ITEM.get()));
modEventBus.addListener(this::addCreative);
}
private void commonSetup(final FMLCommonSetupEvent event)
@@ -69,6 +68,12 @@ public class ExampleMod
LOGGER.info("DIRT BLOCK >> {}", ForgeRegistries.BLOCKS.getKey(Blocks.DIRT));
}
private void addCreative(CreativeModeTabEvent.BuildContents event)
{
if (event.getTab() == CreativeModeTabs.BUILDING_BLOCKS)
event.accept(EXAMPLE_BLOCK_ITEM);
}
// You can use SubscribeEvent and let the Event Bus discover methods to call
@SubscribeEvent
public void onServerStarting(ServerStartingEvent event)
+1 -1
View File
@@ -22,7 +22,7 @@ modId="examplemod" #mandatory
version="${file.jarVersion}" #mandatory
# A display name for the mod
displayName="Example Mod" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
@@ -0,0 +1,29 @@
--- a/net/minecraft/client/gui/components/Button.java
+++ b/net/minecraft/client/gui/components/Button.java
@@ -29,6 +_,11 @@
this.f_252416_ = p_259552_;
}
+ protected Button(Builder builder) {
+ this(builder.f_252538_, builder.f_252462_, builder.f_252510_, builder.f_252447_, builder.f_252401_, builder.f_252468_, builder.f_252431_);
+ m_257544_(builder.f_256855_); // Forge: Make use of the Builder tooltip
+ }
+
public void m_5691_() {
this.f_93717_.m_93750_(this);
}
@@ -92,9 +_,11 @@
}
public Button m_253136_() {
- Button button = new Button(this.f_252538_, this.f_252462_, this.f_252510_, this.f_252447_, this.f_252401_, this.f_252468_, this.f_252431_);
- button.m_257544_(this.f_256855_);
- return button;
+ return build(Button::new);
+ }
+
+ public Button build(java.util.function.Function<Builder, Button> builder) {
+ return builder.apply(this);
}
}
@@ -18,7 +18,7 @@
this.m_98630_();
} else {
(this.f_97732_).f_98639_.addAll(p_261591_);
@@ -273,6 +_,30 @@
@@ -273,6 +_,34 @@
protected void m_7856_() {
if (this.f_96541_.f_91072_.m_105290_()) {
super.m_7856_();
@@ -45,6 +45,10 @@
+ if (this.pages.size() > 1) {
+ m_142416_(net.minecraft.client.gui.components.Button.m_253074_(Component.m_237113_("<"), b -> setCurrentPage(this.pages.get(Math.max(this.pages.indexOf(this.currentPage) - 1, 0)))).m_252794_(f_97735_, f_97736_ - 50).m_253046_(20, 20).m_253136_());
+ m_142416_(net.minecraft.client.gui.components.Button.m_253074_(Component.m_237113_(">"), b -> setCurrentPage(this.pages.get(Math.min(this.pages.indexOf(this.currentPage) + 1, this.pages.size() - 1)))).m_252794_(f_97735_ + f_97726_ - 20, f_97736_ - 50).m_253046_(20, 20).m_253136_());
+ }
+ this.currentPage = this.pages.stream().filter(page -> page.getVisibleTabs().contains(f_98507_)).findFirst().orElse(this.currentPage);
+ if (!this.currentPage.getVisibleTabs().contains(f_98507_)) {
+ f_98507_ = this.currentPage.getVisibleTabs().get(0);
+ }
this.f_98510_ = new EditBox(this.f_96547_, this.f_97735_ + 82, this.f_97736_ + 6, 80, 9, Component.m_237115_("itemGroup.search"));
this.f_98510_.m_94199_(50);
@@ -80,11 +84,11 @@
if (s.startsWith("#")) {
s = s.substring(1);
- searchtree = this.f_96541_.m_231372_(SearchRegistry.f_119942_);
+ searchtree = this.f_96541_.m_231372_(net.minecraftforge.client.CreativeModeTabSearchRegistry.getNameSearchKey(f_98507_));
+ searchtree = this.f_96541_.m_231372_(net.minecraftforge.client.CreativeModeTabSearchRegistry.getTagSearchKey(f_98507_));
this.m_98619_(s);
} else {
- searchtree = this.f_96541_.m_231372_(SearchRegistry.f_119941_);
+ searchtree = this.f_96541_.m_231372_(net.minecraftforge.client.CreativeModeTabSearchRegistry.getTagSearchKey(f_98507_));
+ searchtree = this.f_96541_.m_231372_(net.minecraftforge.client.CreativeModeTabSearchRegistry.getNameSearchKey(f_98507_));
}
(this.f_97732_).f_98639_.addAll(searchtree.m_6293_(s.toLowerCase(Locale.ROOT)));
@@ -174,7 +178,7 @@
TooltipFlag.Default tooltipflag$default = this.f_96541_.f_91066_.f_92125_ ? TooltipFlag.Default.f_256730_ : TooltipFlag.Default.f_256752_;
TooltipFlag tooltipflag = flag ? tooltipflag$default.m_257777_() : tooltipflag$default;
List<Component> list = p_98591_.m_41651_(this.f_96541_.f_91074_, tooltipflag);
@@ -641,8 +_,8 @@
@@ -641,20 +_,20 @@
int i = 1;
@@ -185,7 +189,12 @@
list1.add(i++, creativemodetab.m_40786_().m_6881_().m_130940_(ChatFormatting.BLUE));
}
}
@@ -654,7 +_,7 @@
}
- this.m_169388_(p_98590_, list1, p_98591_.m_150921_(), p_98592_, p_98593_);
+ this.renderTooltip(p_98590_, list1, p_98591_.m_150921_(), p_98592_, p_98593_, null, p_98591_);
}
protected void m_7286_(PoseStack p_98572_, float p_98573_, int p_98574_, int p_98575_) {
RenderSystem.m_157429_(1.0F, 1.0F, 1.0F, 1.0F);
@@ -203,7 +212,7 @@
this.m_93228_(p_98572_, this.f_97735_, this.f_97736_, 0, 0, this.f_97726_, this.f_97727_);
this.f_98510_.m_86412_(p_98572_, p_98574_, p_98575_, p_98573_);
RenderSystem.m_157429_(1.0F, 1.0F, 1.0F, 1.0F);
@@ -671,7 +_,7 @@
@@ -671,11 +_,12 @@
int k = this.f_97736_ + 18;
int i = k + 112;
RenderSystem.m_157427_(GameRenderer::m_172817_);
@@ -212,6 +221,11 @@
if (f_98507_.m_40791_()) {
this.m_93228_(p_98572_, j, k + (int)((float)(i - k - 17) * this.f_98508_), 232 + (this.m_98631_() ? 0 : 12), 0, 12, 15);
}
+ if (currentPage.getVisibleTabs().contains(f_98507_)) //Forge: only display tab selection when the selected tab is on the current page
this.m_98581_(p_98572_, f_98507_);
if (f_98507_.m_257962_() == CreativeModeTab.Type.INVENTORY) {
InventoryScreen.m_98850_(this.f_97735_ + 88, this.f_97736_ + 45, 20, (float)(this.f_97735_ + 88 - p_98574_), (float)(this.f_97736_ + 45 - 30 - p_98575_), this.f_96541_.f_91074_);
@@ -684,7 +_,7 @@
}
@@ -249,7 +263,7 @@
this.m_93228_(p_98582_, l, i1, j, k, 26, 32);
this.f_96542_.f_115093_ = 100.0F;
l += 5;
@@ -784,6 +_,15 @@
@@ -784,6 +_,14 @@
}
@@ -259,7 +273,6 @@
+
+ public void setCurrentPage(net.minecraftforge.client.gui.CreativeTabsScreenPage currentPage) {
+ this.currentPage = currentPage;
+ this.m_98560_(currentPage.getDefaultTab());
+ }
+
@OnlyIn(Dist.CLIENT)
@@ -0,0 +1,34 @@
--- a/net/minecraft/client/renderer/block/model/BlockElement.java
+++ b/net/minecraft/client/renderer/block/model/BlockElement.java
@@ -28,14 +_,21 @@
public final Map<Direction, BlockElementFace> f_111310_;
public final BlockElementRotation f_111311_;
public final boolean f_111312_;
+ public net.minecraftforge.client.model.ForgeFaceData faceData;
public BlockElement(Vector3f p_253626_, Vector3f p_254426_, Map<Direction, BlockElementFace> p_254454_, @Nullable BlockElementRotation p_254229_, boolean p_253661_) {
+ this(p_253626_, p_254426_, p_254454_, p_254229_, p_253661_, net.minecraftforge.client.model.ForgeFaceData.DEFAULT);
+ }
+
+ public BlockElement(Vector3f p_253626_, Vector3f p_254426_, Map<Direction, BlockElementFace> p_254454_, @Nullable BlockElementRotation p_254229_, boolean p_253661_, net.minecraftforge.client.model.ForgeFaceData faceData) {
this.f_111308_ = p_253626_;
this.f_111309_ = p_254426_;
this.f_111310_ = p_254454_;
this.f_111311_ = p_254229_;
this.f_111312_ = p_253661_;
this.m_111319_();
+ this.faceData = faceData;
+ this.f_111310_.values().forEach(face -> face.parent = this);
}
private void m_111319_() {
@@ -78,7 +_,8 @@
throw new JsonParseException("Expected shade to be a Boolean");
} else {
boolean flag = GsonHelper.m_13855_(jsonobject, "shade", true);
- return new BlockElement(vector3f, vector3f1, map, blockelementrotation, flag);
+ var faceData = net.minecraftforge.client.model.ForgeFaceData.read(jsonobject.get("forge_data"), net.minecraftforge.client.model.ForgeFaceData.DEFAULT);
+ return new BlockElement(vector3f, vector3f1, map, blockelementrotation, flag, faceData);
}
}
@@ -1,40 +1,43 @@
--- a/net/minecraft/client/renderer/block/model/BlockElementFace.java
+++ b/net/minecraft/client/renderer/block/model/BlockElementFace.java
@@ -19,12 +_,24 @@
@@ -19,12 +_,30 @@
public final int f_111355_;
public final String f_111356_;
public final BlockFaceUV f_111357_;
+ public final int emissivity;
+ public final boolean hasAmbientOcclusion;
+ @Nullable
+ private final net.minecraftforge.client.model.ForgeFaceData faceData; // If null, we defer to the parent BlockElement's ForgeFaceData, which is not nullable.
+ @Nullable
+ BlockElement parent; // Parent canot be set by the constructor due to instantiation ordering. This shouldn't really ever be null, but it could theoretically be.
public BlockElementFace(@Nullable Direction p_111359_, int p_111360_, String p_111361_, BlockFaceUV p_111362_) {
+ this(p_111359_, p_111360_, p_111361_, p_111362_, 0, true);
+ this(p_111359_, p_111360_, p_111361_, p_111362_, null);
+ }
+
+ public BlockElementFace(@Nullable Direction p_111359_, int p_111360_, String p_111361_, BlockFaceUV p_111362_, int emissivity) {
+ this(p_111359_, p_111360_, p_111361_, p_111362_, emissivity, true);
+ }
+
+ public BlockElementFace(@Nullable Direction p_111359_, int p_111360_, String p_111361_, BlockFaceUV p_111362_, int emissivity, boolean hasAmbientOcclusion) {
+ public BlockElementFace(@Nullable Direction p_111359_, int p_111360_, String p_111361_, BlockFaceUV p_111362_, @Nullable net.minecraftforge.client.model.ForgeFaceData faceData) {
this.f_111354_ = p_111359_;
this.f_111355_ = p_111360_;
this.f_111356_ = p_111361_;
this.f_111357_ = p_111362_;
+ this.emissivity = emissivity;
+ this.hasAmbientOcclusion = hasAmbientOcclusion;
+ this.faceData = faceData;
+ }
+
+ public net.minecraftforge.client.model.ForgeFaceData getFaceData() {
+ if(this.faceData != null) {
+ return this.faceData;
+ } else if(this.parent != null) {
+ return this.parent.faceData;
+ }
+ return net.minecraftforge.client.model.ForgeFaceData.DEFAULT;
}
@OnlyIn(Dist.CLIENT)
@@ -37,7 +_,11 @@
@@ -37,7 +_,8 @@
int i = this.m_111368_(jsonobject);
String s = this.m_111370_(jsonobject);
BlockFaceUV blockfaceuv = p_111367_.deserialize(jsonobject, BlockFaceUV.class);
- return new BlockElementFace(direction, i, s, blockfaceuv);
+ int emissivity = GsonHelper.m_13824_(jsonobject, "emissivity", 0);
+ if (emissivity != net.minecraft.util.Mth.m_14045_(emissivity, 0, 15))
+ throw new JsonParseException("The emissivity value must be between 0 and 15. Found: " + emissivity);
+ boolean hasAmbientOcclusion = GsonHelper.m_13855_(jsonobject, "ambientocclusion", true);
+ return new BlockElementFace(direction, i, s, blockfaceuv, emissivity, hasAmbientOcclusion);
+ var faceData = net.minecraftforge.client.model.ForgeFaceData.read(jsonobject.get("forge_data"), null);
+ return new BlockElementFace(direction, i, s, blockfaceuv, faceData);
}
protected int m_111368_(JsonObject p_111369_) {
@@ -39,6 +39,17 @@
public Collection<ResourceLocation> m_7970_() {
Set<ResourceLocation> set = Sets.newHashSet();
@@ -127,6 +_,10 @@
}
public void m_5500_(Function<ResourceLocation, UnbakedModel> p_249059_) {
+ if (customData.hasCustomGeometry()) {
+ customData.getCustomGeometry().resolveParents(p_249059_, customData);
+ }
+
Set<UnbakedModel> set = Sets.newLinkedHashSet();
for(BlockModel blockmodel = this; blockmodel.f_111419_ != null && blockmodel.f_111418_ == null; blockmodel = blockmodel.f_111418_) {
@@ -161,11 +_,24 @@
});
}
@@ -1,13 +1,17 @@
--- a/net/minecraft/client/renderer/block/model/FaceBakery.java
+++ b/net/minecraft/client/renderer/block/model/FaceBakery.java
@@ -50,7 +_,10 @@
@@ -50,7 +_,14 @@
this.m_111630_(aint, direction);
}
- return new BakedQuad(aint, p_111603_.f_111355_, direction, p_111604_, p_111608_);
+ net.minecraftforge.client.ForgeHooksClient.fillNormal(aint, direction);
+ var quad = new BakedQuad(aint, p_111603_.f_111355_, direction, p_111604_, p_111608_, p_111603_.hasAmbientOcclusion);
+ net.minecraftforge.client.model.QuadTransformers.settingEmissivity(p_111603_.emissivity).processInPlace(quad);
+ var data = p_111603_.getFaceData();
+ var quad = new BakedQuad(aint, p_111603_.f_111355_, direction, p_111604_, p_111608_, data.ambientOcclusion());
+ if (!net.minecraftforge.client.model.ForgeFaceData.DEFAULT.equals(data)) {
+ net.minecraftforge.client.model.QuadTransformers.applyingLightmap(data.blockLight(), data.skyLight()).processInPlace(quad);
+ net.minecraftforge.client.model.QuadTransformers.applyingColor(data.color()).processInPlace(quad);
+ }
+ return quad;
}
@@ -1,10 +1,11 @@
--- a/net/minecraft/client/renderer/block/model/ItemModelGenerator.java
+++ b/net/minecraft/client/renderer/block/model/ItemModelGenerator.java
@@ -40,6 +_,7 @@
@@ -40,6 +_,8 @@
map.put("particle", p_111672_.m_111477_("particle") ? Either.left(p_111672_.m_111480_("particle")) : map.get("layer0"));
BlockModel blockmodel = new BlockModel((ResourceLocation)null, list, map, false, p_111672_.m_111479_(), p_111672_.m_111491_(), p_111672_.m_111484_());
blockmodel.f_111416_ = p_111672_.f_111416_;
+ blockmodel.customData.copyFrom(p_111672_.customData);
+ blockmodel.customData.setGui3d(false);
return blockmodel;
}
@@ -1,15 +1,6 @@
--- a/net/minecraft/client/renderer/entity/ItemFrameRenderer.java
+++ b/net/minecraft/client/renderer/entity/ItemFrameRenderer.java
@@ -62,7 +_,7 @@
ItemStack itemstack = p_115076_.m_31822_();
if (!flag) {
ModelManager modelmanager = this.f_234645_.m_110907_().m_110881_();
- ModelResourceLocation modelresourcelocation = this.m_174212_(p_115076_, itemstack);
+ ModelResourceLocation modelresourcelocation = p_115076_.m_31822_().m_41720_() instanceof MapItem ? f_115045_ : f_115044_;
p_115079_.m_85836_();
p_115079_.m_252880_(-0.5F, -0.5F, -0.5F);
this.f_234645_.m_110937_().m_111067_(p_115079_.m_85850_(), p_115080_.m_6299_(Sheets.m_110789_()), (BlockState)null, modelmanager.m_119422_(modelresourcelocation), 1.0F, 1.0F, 1.0F, p_115081_, OverlayTexture.f_118083_);
@@ -70,30 +_,31 @@
@@ -70,31 +_,32 @@
}
if (!itemstack.m_41619_()) {
@@ -42,7 +33,17 @@
int k = this.m_174208_(p_115076_, 15728880, p_115081_);
p_115079_.m_85841_(0.5F, 0.5F, 0.5F);
this.f_115047_.m_174269_(itemstack, ItemTransforms.TransformType.FIXED, k, OverlayTexture.f_118083_, p_115079_, p_115080_, p_115076_.m_19879_());
+ }
}
+ }
}
p_115079_.m_85849_();
@@ -106,7 +_,7 @@
private ModelResourceLocation m_174212_(T p_174213_, ItemStack p_174214_) {
boolean flag = p_174213_.m_6095_() == EntityType.f_147033_;
- if (p_174214_.m_150930_(Items.f_42573_)) {
+ if (p_174214_.m_41720_() instanceof MapItem) {
return flag ? f_174202_ : f_115045_;
} else {
return flag ? f_174201_ : f_115044_;
@@ -9,3 +9,16 @@
}
private void m_246472_(int p_252243_, int p_251110_, int p_249203_) {
@@ -145,5 +_,12 @@
public void m_247255_(SpriteLoader.Preparations p_251993_) {
this.m_117960_(false, p_251993_.f_244353_() > 0);
+ }
+
+ /**
+ * {@return the set of sprites in this atlas.}
+ */
+ public java.util.Set<ResourceLocation> getTextureLocations() {
+ return java.util.Collections.unmodifiableSet(f_118264_.keySet());
}
}
@@ -35,6 +35,15 @@
CompletableFuture<Map<ResourceLocation, BlockModel>> completablefuture = m_246704_(p_251134_, p_250550_);
CompletableFuture<Map<ResourceLocation, List<ModelBakery.LoadedJson>>> completablefuture1 = m_246899_(p_251134_, p_250550_);
CompletableFuture<ModelBakery> completablefuture2 = completablefuture.thenCombineAsync(completablefuture1, (p_251201_, p_251281_) -> {
@@ -172,6 +_,8 @@
return " " + p_248692_.m_119193_() + ":" + p_248692_.m_119203_();
}).collect(Collectors.joining("\n")));
});
+ p_252136_.m_6182_("forge_modify_baking_result");
+ net.minecraftforge.client.ForgeHooksClient.onModifyBakingResult(p_248945_.m_119251_(), p_248945_);
p_252136_.m_6182_("dispatch");
Map<ResourceLocation, BakedModel> map = p_248945_.m_119251_();
BakedModel bakedmodel = map.get(ModelBakery.f_119230_);
@@ -201,6 +_,8 @@
this.f_119397_ = modelbakery.m_119251_();
this.f_119404_ = modelbakery.m_119355_();
@@ -17,7 +17,16 @@
this.m_205921_(p_256594_);
Validate.notNull(p_256594_);
Validate.notNull(p_256374_);
@@ -300,6 +_,12 @@
@@ -145,6 +_,8 @@
reference = this.f_205842_.computeIfAbsent(p_256594_, (p_258168_) -> {
return Holder.Reference.m_254896_(this.m_255331_(), p_258168_);
});
+ // Forge: Bind the value immediately so it can be queried while the registry is not frozen
+ reference.m_247654_(p_256374_);
}
this.f_205842_.put(p_256594_, reference);
@@ -300,14 +_,17 @@
return this.f_205842_.containsKey(p_175392_);
}
@@ -30,6 +39,14 @@
public Registry<T> m_203521_() {
if (this.f_205845_) {
return this;
} else {
this.f_205845_ = true;
- this.f_205843_.forEach((p_247989_, p_247990_) -> {
- p_247990_.m_247654_(p_247989_);
- });
List<ResourceLocation> list = this.f_205842_.entrySet().stream().filter((p_211055_) -> {
return !p_211055_.getValue().m_203633_();
}).map((p_211794_) -> {
@@ -321,7 +_,8 @@
throw new IllegalStateException("Some intrusive holders were not registered: " + this.f_244282_.values());
}
@@ -11,3 +11,24 @@
private RegistrySetBuilder.BuildState m_254900_(RegistryAccess p_256400_) {
RegistrySetBuilder.BuildState registrysetbuilder$buildstate = RegistrySetBuilder.BuildState.m_255369_(p_256400_, this.f_254732_.stream().map(RegistrySetBuilder.RegistryStub::f_254738_));
this.f_254732_.forEach((p_255629_) -> {
@@ -81,7 +_,7 @@
RegistrySetBuilder.UniversalLookup registrysetbuilder$universallookup = new RegistrySetBuilder.UniversalLookup(registrysetbuilder$compositeowner);
ImmutableMap.Builder<ResourceLocation, HolderGetter<?>> builder = ImmutableMap.builder();
p_255995_.m_206193_().forEach((p_258197_) -> {
- builder.put(p_258197_.f_206233_().m_135782_(), RegistrySetBuilder.m_254882_(p_258197_.f_206234_().m_255303_()));
+ builder.put(p_258197_.f_206233_().m_135782_(), net.minecraftforge.common.ForgeHooks.wrapRegistryLookup(p_258197_.f_206234_().m_255303_()));
});
p_256495_.forEach((p_256603_) -> {
builder.put(p_256603_.m_135782_(), registrysetbuilder$universallookup);
@@ -102,6 +_,11 @@
public <S> HolderGetter<S> m_255420_(ResourceKey<? extends Registry<? extends S>> p_255961_) {
return (HolderGetter<S>) BuildState.this.f_254690_.getOrDefault(p_255961_.m_135782_(), BuildState.this.f_254749_);
+ }
+
+ @Override
+ public <S> Optional<HolderLookup.RegistryLookup<S>> registryLookup(ResourceKey<? extends Registry<? extends S>> registry) {
+ return Optional.ofNullable((HolderLookup.RegistryLookup<S>) BuildState.this.f_254690_.get(registry.m_135782_()));
}
};
}
@@ -0,0 +1,11 @@
--- a/net/minecraft/core/RegistrySynchronization.java
+++ b/net/minecraft/core/RegistrySynchronization.java
@@ -23,7 +_,7 @@
m_245912_(builder, Registries.f_256952_, Biome.f_47430_);
m_245912_(builder, Registries.f_256873_, ChatType.f_237005_);
m_245912_(builder, Registries.f_256787_, DimensionType.f_63843_);
- return builder.build();
+ return net.minecraftforge.registries.DataPackRegistriesHooks.grabNetworkableRegistries(builder); // FORGE: Keep the map so custom registries can be added later
});
public static final Codec<RegistryAccess> f_244380_ = m_247146_();
@@ -16,7 +16,7 @@
stopwatch1.start();
hashcache.m_253116_(hashcache.m_253234_(p_254418_, p_253750_::m_213708_).join());
stopwatch1.stop();
@@ -56,6 +_,30 @@
@@ -56,6 +_,34 @@
public DataGenerator.PackGenerator m_253030_(boolean p_253826_, String p_254134_) {
Path path = this.f_244116_.m_247566_(PackOutput.Target.DATA_PACK).resolve("minecraft").resolve("datapacks").resolve(p_254134_);
return new DataGenerator.PackGenerator(p_253826_, p_254134_, new PackOutput(path));
@@ -30,6 +30,10 @@
+ return this.f_244116_;
+ }
+
+ public PackOutput getPackOutput(String path) {
+ return new PackOutput(f_243753_.resolve(path));
+ }
+
+ public <T extends DataProvider> T addProvider(boolean run, DataProvider.Factory<T> factory) {
+ return addProvider(run, factory.m_253034_(this.f_244116_));
+ }
@@ -1,53 +1,14 @@
--- a/net/minecraft/data/advancements/AdvancementProvider.java
+++ b/net/minecraft/data/advancements/AdvancementProvider.java
@@ -18,8 +_,19 @@
@@ -14,6 +_,11 @@
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
+/**
+ * @deprecated Forge: Use {@link net.minecraftforge.common.data.ForgeAdvancementProvider} instead,
+ * provides ease of access for the {@link net.minecraftforge.common.data.ExistingFileHelper} in the generator
+ */
+@Deprecated
public class AdvancementProvider implements DataProvider {
private final PackOutput.PathProvider f_236156_;
private final List<AdvancementSubProvider> f_244266_;
private final CompletableFuture<HolderLookup.Provider> f_254664_;
+ @org.jetbrains.annotations.Nullable
+ protected final net.minecraftforge.common.data.ExistingFileHelper existingFileHelper;
+ /**
+ * @deprecated Forge: Use the {@linkplain #AdvancementProvider(PackOutput, CompletableFuture, List, net.minecraftforge.common.data.ExistingFileHelper) existing file helper variant}
+ */
+ @Deprecated
public AdvancementProvider(PackOutput p_256529_, CompletableFuture<HolderLookup.Provider> p_255722_, List<AdvancementSubProvider> p_255883_) {
+ this(p_256529_, p_255722_, p_255883_, null);
+ }
+
+ public AdvancementProvider(PackOutput p_256529_, CompletableFuture<HolderLookup.Provider> p_255722_, List<AdvancementSubProvider> p_255883_, @org.jetbrains.annotations.Nullable net.minecraftforge.common.data.ExistingFileHelper existingFileHelper) {
+ this.existingFileHelper = existingFileHelper;
this.f_236156_ = p_256529_.m_245269_(PackOutput.Target.DATA_PACK, "advancements");
this.f_244266_ = p_255883_;
this.f_254664_ = p_255722_;
@@ -38,14 +_,27 @@
}
};
- for(AdvancementSubProvider advancementsubprovider : this.f_244266_) {
- advancementsubprovider.m_245571_(p_255484_, consumer);
- }
+ // Forge: Allow mods to take control of advancement registration
+ this.registerAdvancements(p_255484_, consumer, this.existingFileHelper);
return CompletableFuture.allOf(list.toArray((p_253393_) -> {
return new CompletableFuture[p_253393_];
}));
});
+ }
+
+ /**
+ * Registers all {@linkplain Advancement advancements}.
+ *
+ * @param registries the registries available for lookup including all registries in {@link net.minecraft.core.registries.Registries}
+ * @param saver a {@link Consumer} which saves any advancements provided
+ * @param existingFileHelper the existing file helper to check for the existence of files like parent advancements
+ * @see Advancement.Builder#save(Consumer, ResourceLocation, net.minecraftforge.common.data.ExistingFileHelper)
+ */
+ protected void registerAdvancements(HolderLookup.Provider registries, Consumer<Advancement> saver, net.minecraftforge.common.data.ExistingFileHelper existingFileHelper) {
+ for (AdvancementSubProvider subProvider : this.f_244266_) {
+ subProvider.m_245571_(registries, saver);
+ }
}
public final String m_6055_() {
@@ -1,11 +1,46 @@
--- a/net/minecraft/data/registries/RegistriesDatapackGenerator.java
+++ b/net/minecraft/data/registries/RegistriesDatapackGenerator.java
@@ -18,12 +_,23 @@
import net.minecraft.resources.ResourceKey;
import org.slf4j.Logger;
+/**
+ * @deprecated Forge: Use {@link net.minecraftforge.common.data.DatapackBuiltinEntriesProvider} instead
+ */
+@Deprecated
public class RegistriesDatapackGenerator implements DataProvider {
private static final Logger f_254654_ = LogUtils.getLogger();
private final PackOutput f_254743_;
private final CompletableFuture<HolderLookup.Provider> f_254747_;
+ private final java.util.function.Predicate<String> namespacePredicate;
+ @Deprecated
public RegistriesDatapackGenerator(PackOutput p_256643_, CompletableFuture<HolderLookup.Provider> p_255780_) {
+ this(p_256643_, p_255780_, null);
+ }
+
+ public RegistriesDatapackGenerator(PackOutput p_256643_, CompletableFuture<HolderLookup.Provider> p_255780_, @org.jetbrains.annotations.Nullable java.util.Set<String> modIds) {
+ this.namespacePredicate = modIds == null ? namespace -> true : modIds::contains;
this.f_254747_ = p_255780_;
this.f_254743_ = p_256643_;
}
@@ -31,7 +_,7 @@
public CompletableFuture<?> m_213708_(CachedOutput p_255785_) {
return this.f_254747_.thenCompose((p_256533_) -> {
DynamicOps<JsonElement> dynamicops = RegistryOps.m_255058_(JsonOps.INSTANCE, p_256533_);
- return CompletableFuture.allOf(RegistryDataLoader.f_243803_.stream().flatMap((p_256552_) -> {
+ return CompletableFuture.allOf(net.minecraftforge.registries.DataPackRegistriesHooks.getDataPackRegistries().stream().flatMap((p_256552_) -> {
+ return CompletableFuture.allOf(net.minecraftforge.registries.DataPackRegistriesHooks.getDataPackRegistriesWithDimensions().flatMap((p_256552_) -> {
return this.m_254918_(p_255785_, p_256533_, dynamicops, p_256552_).stream();
}).toArray((p_255809_) -> {
return new CompletableFuture[p_255809_];
@@ -42,8 +_,8 @@
private <T> Optional<CompletableFuture<?>> m_254918_(CachedOutput p_256502_, HolderLookup.Provider p_256492_, DynamicOps<JsonElement> p_256000_, RegistryDataLoader.RegistryData<T> p_256449_) {
ResourceKey<? extends Registry<T>> resourcekey = p_256449_.f_243794_();
return p_256492_.m_254861_(resourcekey).map((p_255847_) -> {
- PackOutput.PathProvider packoutput$pathprovider = this.f_254743_.m_245269_(PackOutput.Target.DATA_PACK, resourcekey.m_135782_().m_135815_());
- return CompletableFuture.allOf(p_255847_.m_214062_().map((p_256105_) -> {
+ PackOutput.PathProvider packoutput$pathprovider = this.f_254743_.m_245269_(PackOutput.Target.DATA_PACK, net.minecraftforge.common.ForgeHooks.prefixNamespace(resourcekey.m_135782_()));
+ return CompletableFuture.allOf(p_255847_.m_214062_().filter(holder -> this.namespacePredicate.test(holder.m_205785_().m_135782_().m_135827_())).map((p_256105_) -> {
return m_255283_(packoutput$pathprovider.m_245731_(p_256105_.m_205785_().m_135782_()), p_256502_, p_256000_, p_256449_.f_244580_(), p_256105_.m_203334_());
}).toArray((p_256279_) -> {
return new CompletableFuture[p_256279_];
@@ -0,0 +1,9 @@
--- a/net/minecraft/data/worldgen/BootstapContext.java
+++ b/net/minecraft/data/worldgen/BootstapContext.java
@@ -14,4 +_,6 @@
}
<S> HolderGetter<S> m_255420_(ResourceKey<? extends Registry<? extends S>> p_256410_);
+
+ default <S> java.util.Optional<net.minecraft.core.HolderLookup.RegistryLookup<S>> registryLookup(ResourceKey<? extends Registry<? extends S>> registry) { return java.util.Optional.empty(); }
}
@@ -9,11 +9,11 @@
}
static <E> void m_255048_(RegistryOps.RegistryInfoLookup p_256369_, ResourceManager p_256349_, ResourceKey<? extends Registry<E>> p_255792_, WritableRegistry<E> p_256211_, Decoder<E> p_256232_, Map<ResourceKey<?>, Exception> p_255884_) {
@@ -138,6 +_,7 @@
@@ -136,6 +_,7 @@
try (Reader reader = resource.m_215508_()) {
JsonElement jsonelement = JsonParser.parseReader(reader);
DataResult<E> dataresult = p_256232_.parse(registryops, jsonelement);
+ if (!net.minecraftforge.common.crafting.conditions.ICondition.shouldRegisterEntry(jsonelement)) continue;
E e = dataresult.getOrThrow(false, (p_248715_) -> {
});
+ if (net.minecraftforge.common.crafting.conditions.ICondition.shouldRegisterEntry(jsonelement))
p_256211_.m_255290_(resourcekey, e, resource.m_247137_() ? Lifecycle.stable() : dataresult.lifecycle());
}
@@ -10,7 +10,7 @@
+ return DataResult.error("Not a registry ops");
+
+ return registryOps.f_254668_.m_254838_(resourceKey).map(registryInfo -> {
+ if (!(registryInfo.f_254724_() instanceof HolderLookup.RegistryLookup<E> registryLookup))
+ if (!(registryInfo.f_254675_() instanceof HolderLookup.RegistryLookup<E> registryLookup))
+ return DataResult.<HolderLookup.RegistryLookup<E>>error("Found holder getter but was not a registry lookup for " + resourceKey);
+
+ return DataResult.success(registryLookup, registryInfo.f_254751_());
@@ -1,58 +0,0 @@
--- a/net/minecraft/server/packs/VanillaPackResources.java
+++ b/net/minecraft/server/packs/VanillaPackResources.java
@@ -32,6 +_,19 @@
this.f_10314_ = p_250468_;
this.f_244169_ = p_248798_;
this.f_244459_ = p_251106_;
+ this.cacheManager = new net.minecraftforge.resource.ResourceCacheManager(false, "indexVanillaPackCachesOnThread",
+ (packType, namespace) -> {
+ List<Path> validPaths = new java.util.ArrayList<>();
+
+ for (Path basePath : this.f_244459_.getOrDefault(packType, List.of())) {
+ Path path = basePath.resolve(namespace);
+ if (Files.exists(path) && PathPackResources.m_246877_(path)) {
+ validPaths.add(path);
+ }
+ }
+
+ return validPaths;
+ });
}
@Nullable
@@ -66,6 +_,14 @@
public void m_8031_(PackType p_248974_, String p_248703_, String p_250848_, PackResources.ResourceOutput p_249668_) {
FileUtil.m_245538_(p_250848_).get().ifLeft((p_248228_) -> {
List<Path> list = this.f_244459_.get(p_248974_);
+ // TODO 1.19.3: Fix resource caching to actually work and produce valid results -SS
+ // if (net.minecraftforge.resource.ResourceCacheManager.shouldUseCache() && this.cacheManager.hasCached(p_248974_, p_248703_)) {
+ // for (Path rootPath : list) {
+ // this.cacheManager.listResources(p_248974_, p_248703_, FileUtil.resolvePath(rootPath, p_248228_), this::getResource, p_249668_);
+ // }
+ // return;
+ // }
+
int i = list.size();
if (i == 1) {
m_246310_(p_249668_, p_248703_, list.get(0), p_248228_);
@@ -161,5 +_,20 @@
return new Resource(this, p_248221_);
});
};
+ }
+
+ private final net.minecraftforge.resource.ResourceCacheManager cacheManager;
+
+ @Override
+ public void initForNamespace(final String namespace) {
+ if (net.minecraftforge.resource.ResourceCacheManager.shouldUseCache())
+ this.cacheManager.index(namespace);
+ }
+
+ @Override
+ public void init(final PackType packType) {
+ // Forge: Init vanilla namespaces
+ initForNamespace("minecraft");
+ initForNamespace("realms");
}
}
@@ -9,3 +9,11 @@
}
}
@@ -21,6 +_,7 @@
JsonObject jsonobject = new JsonObject();
jsonobject.add("description", Component.Serializer.m_130716_(p_250206_.m_10373_()));
jsonobject.addProperty("pack_format", p_250206_.m_10374_());
+ net.minecraftforge.common.ForgeHooks.writeTypedPackFormats(jsonobject, p_250206_);
return jsonobject;
}
@@ -0,0 +1,24 @@
--- a/net/minecraft/server/packs/resources/FallbackResourceManager.java
+++ b/net/minecraft/server/packs/resources/FallbackResourceManager.java
@@ -98,8 +_,11 @@
for(int i = this.f_10599_.size() - 1; i >= 0; --i) {
FallbackResourceManager.PackEntry fallbackresourcemanager$packentry = this.f_10599_.get(i);
- PackResources packresources = fallbackresourcemanager$packentry.f_215433_;
- if (packresources != null) {
+ PackResources pack = fallbackresourcemanager$packentry.f_215433_;
+ if (pack != null) {
+ var children = pack.getChildren();
+ var packs = children == null ? List.of(pack) : children;
+ for (final PackResources packresources : packs) {
IoSupplier<InputStream> iosupplier = packresources.m_214146_(this.f_10601_, p_215367_);
if (iosupplier != null) {
IoSupplier<ResourceMetadata> iosupplier1;
@@ -113,6 +_,7 @@
}
list.add(new Resource(packresources, iosupplier, iosupplier1));
+ }
}
}
@@ -1,10 +0,0 @@
--- a/net/minecraft/server/packs/resources/MultiPackResourceManager.java
+++ b/net/minecraft/server/packs/resources/MultiPackResourceManager.java
@@ -24,6 +_,7 @@
public MultiPackResourceManager(PackType p_203797_, List<PackResources> p_203798_) {
this.f_203795_ = List.copyOf(p_203798_);
Map<String, FallbackResourceManager> map = new HashMap<>();
+ p_203798_.forEach(resourcePack -> resourcePack.init(p_203797_));
List<String> list = p_203798_.stream().flatMap((p_215471_) -> {
return p_215471_.m_5698_(p_203797_).stream();
}).distinct().toList();
@@ -1,5 +1,13 @@
--- a/net/minecraft/world/entity/npc/AbstractVillager.java
+++ b/net/minecraft/world/entity/npc/AbstractVillager.java
@@ -116,6 +_,7 @@
CriteriaTriggers.f_10585_.m_70959_((ServerPlayer)this.f_35263_, this, p_35274_.m_45368_());
}
+ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.player.TradeWithVillagerEvent(this.f_35263_, p_35274_, this));
}
protected abstract void m_8058_(MerchantOffer p_35299_);
@@ -164,9 +_,9 @@
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/world/item/CreativeModeTab.java
+++ b/net/minecraft/world/item/CreativeModeTab.java
@@ -28,14 +_,26 @@
@@ -28,14 +_,30 @@
private Consumer<List<ItemStack>> f_256965_;
private final Supplier<ItemStack> f_256912_;
private final CreativeModeTab.DisplayItemsGenerator f_256824_;
@@ -25,6 +25,10 @@
+ this.tabsImage = tabsImage;
+ this.labelColor = labelColor;
+ this.slotColor = slotColor;
+ }
+
+ protected CreativeModeTab(CreativeModeTab.Builder builder) {
+ this(builder.f_256796_, builder.f_256977_, builder.f_256847_, builder.f_256856_, builder.f_256981_, builder.f_256953_, builder.backgroundLocation, builder.hasSearchBar, builder.searchBarWidth, builder.tabsImage, builder.labelColor, builder.slotColor);
}
public static CreativeModeTab.Builder m_257815_(CreativeModeTab.Row p_259342_, int p_260312_) {
@@ -44,7 +48,7 @@
public void m_258021_(FeatureFlagSet p_260125_, boolean p_259855_) {
CreativeModeTab.ItemDisplayBuilder creativemodetab$itemdisplaybuilder = new CreativeModeTab.ItemDisplayBuilder(this, p_260125_);
- this.f_256824_.m_257865_(p_260125_, creativemodetab$itemdisplaybuilder, p_259855_);
+ net.minecraftforge.client.ForgeHooksClient.onCreativeModeTabBuildContents(this, this.f_256824_).m_257865_(p_260125_, creativemodetab$itemdisplaybuilder, p_259855_);
+ net.minecraftforge.client.ForgeHooksClient.onCreativeModeTabBuildContents(this, this.f_256824_, p_260125_, creativemodetab$itemdisplaybuilder, p_259855_);
this.f_243839_ = creativemodetab$itemdisplaybuilder.f_244363_;
this.f_243841_ = creativemodetab$itemdisplaybuilder.f_244585_;
this.m_257466_();
@@ -83,7 +87,7 @@
private final CreativeModeTab.Row f_256796_;
private final int f_256977_;
private Component f_256856_ = Component.m_237119_();
@@ -136,6 +_,13 @@
@@ -136,6 +_,14 @@
private boolean f_256854_ = false;
private CreativeModeTab.Type f_256847_ = CreativeModeTab.Type.CATEGORY;
private String f_257036_ = "items.png";
@@ -94,10 +98,11 @@
+ private net.minecraft.resources.ResourceLocation tabsImage = CREATIVE_INVENTORY_TABS_IMAGE;
+ private int labelColor = 4210752;
+ private int slotColor = -2130706433;
+ private java.util.function.Function<CreativeModeTab.Builder, CreativeModeTab> tabFactory = CreativeModeTab::new;
public Builder(CreativeModeTab.Row p_259171_, int p_259661_) {
this.f_256796_ = p_259171_;
@@ -174,23 +_,57 @@
@@ -174,11 +_,49 @@
protected CreativeModeTab.Builder m_257623_(CreativeModeTab.Type p_259283_) {
this.f_256847_ = p_259283_;
@@ -108,7 +113,6 @@
public CreativeModeTab.Builder m_257609_(String p_259981_) {
- this.f_257036_ = p_259981_;
- return this;
+ return withBackgroundLocation(new net.minecraft.resources.ResourceLocation("textures/gui/container/creative_inventory/tab_" + p_259981_));
+ }
+
@@ -142,14 +146,19 @@
+ public CreativeModeTab.Builder withSlotColor(int slotColor) {
+ this.slotColor = slotColor;
+ return this;
+ }
+
+ public CreativeModeTab.Builder withTabFactory(java.util.function.Function<CreativeModeTab.Builder, CreativeModeTab> tabFactory) {
+ this.tabFactory = tabFactory;
return this;
}
public CreativeModeTab m_257652_() {
@@ -186,11 +_,12 @@
if ((this.f_256847_ == CreativeModeTab.Type.HOTBAR || this.f_256847_ == CreativeModeTab.Type.INVENTORY) && this.f_256953_ != f_256756_) {
throw new IllegalStateException("Special tabs can't have display items");
} else {
- CreativeModeTab creativemodetab = new CreativeModeTab(this.f_256796_, this.f_256977_, this.f_256847_, this.f_256856_, this.f_256981_, this.f_256953_);
+ CreativeModeTab creativemodetab = new CreativeModeTab(this.f_256796_, this.f_256977_, this.f_256847_, this.f_256856_, this.f_256981_, this.f_256953_, this.backgroundLocation, this.hasSearchBar, this.searchBarWidth, this.tabsImage, this.labelColor, this.slotColor);
+ CreativeModeTab creativemodetab = tabFactory.apply(this);
creativemodetab.f_257018_ = this.f_256854_;
creativemodetab.f_40768_ = this.f_256851_;
creativemodetab.f_40767_ = this.f_256992_;
@@ -207,14 +207,6 @@
}
@Nullable
@@ -640,6 +_,7 @@
this.m_46745_(p_151544_).m_8092_(true);
}
+ this.m_46717_(p_151544_, m_8055_(p_151544_).m_60734_()); //Notify neighbors of changes
}
public int m_5736_() {
@@ -684,7 +_,7 @@
public int m_46681_(BlockPos p_46682_, Direction p_46683_) {
BlockState blockstate = this.m_8055_(p_46682_);
@@ -5,16 +5,21 @@
public void m_48792_(Level p_52068_, BlockPos p_52069_, BlockState p_52070_, BlockState p_52071_, FallingBlockEntity p_52072_) {
- if (m_52080_(p_52068_, p_52069_, p_52071_)) {
+ if (m_52080_(p_52068_, p_52069_, p_52070_)) { // Forge: Uses block of falling entity instead of block at current position, checked within shouldSolidify
+ if (shouldSolidify(p_52068_, p_52069_, p_52070_, p_52071_.m_60819_())) { // Forge: Use block of falling entity instead of block at replaced position, and check if shouldSolidify with the FluidState of the replaced block
p_52068_.m_7731_(p_52069_, this.f_52058_, 3);
}
@@ -34,19 +_,19 @@
@@ -33,20 +_,24 @@
return m_52080_(blockgetter, blockpos, blockstate) ? this.f_52058_ : super.m_5573_(p_52063_);
}
+ private static boolean shouldSolidify(BlockGetter p_52081_, BlockPos p_52082_, BlockState p_52083_, net.minecraft.world.level.material.FluidState fluidState) {
+ return p_52083_.canBeHydrated(p_52081_, p_52082_, fluidState, p_52082_) || touchesLiquid(p_52081_, p_52082_, p_52083_);
+ }
+
private static boolean m_52080_(BlockGetter p_52081_, BlockPos p_52082_, BlockState p_52083_) {
- return m_52088_(p_52083_) || m_52064_(p_52081_, p_52082_);
+ return p_52083_.canBeHydrated(p_52081_, p_52082_, p_52081_.m_6425_(p_52082_), p_52082_) || touchesLiquid(p_52081_, p_52082_, p_52083_);
+ return shouldSolidify(p_52081_, p_52082_, p_52083_, p_52081_.m_6425_(p_52082_));
}
- private static boolean m_52064_(BlockGetter p_52065_, BlockPos p_52066_) {
@@ -20,7 +20,7 @@
BlockState blockstate = p_222539_.m_8055_(blockpos.m_7495_());
- if (p_222539_.m_8055_(blockpos).m_60795_() && (blockstate.m_60713_(Blocks.f_50093_) || blockstate.m_204336_(BlockTags.f_144274_))) {
+ Block block = blockstate.m_60734_();
+ if (p_222539_.m_46859_(blockpos) && (blockstate.canSustainPlant(p_222539_, blockpos.m_7495_(), Direction.UP, this) || block == Blocks.f_50093_ || block == Blocks.f_50493_ || block == Blocks.f_50546_ || block == Blocks.f_50599_ || block == Blocks.f_50440_)) {
+ if (p_222539_.m_46859_(blockpos) && (blockstate.canSustainPlant(p_222539_, blockpos.m_7495_(), Direction.UP, this.f_57015_) || block == Blocks.f_50093_ || block == Blocks.f_50493_ || block == Blocks.f_50546_ || block == Blocks.f_50599_ || block == Blocks.f_50440_)) {
p_222539_.m_46597_(blockpos, this.f_57015_.m_49966_());
p_222539_.m_46597_(p_222540_, this.f_57015_.m_7810_().m_49966_().m_61124_(HorizontalDirectionalBlock.f_54117_, direction));
}
@@ -0,0 +1,24 @@
--- a/net/minecraft/world/level/block/StemGrownBlock.java
+++ b/net/minecraft/world/level/block/StemGrownBlock.java
@@ -2,7 +_,7 @@
import net.minecraft.world.level.block.state.BlockBehaviour;
-public abstract class StemGrownBlock extends Block {
+public abstract class StemGrownBlock extends Block implements net.minecraftforge.common.IPlantable {
public StemGrownBlock(BlockBehaviour.Properties p_57058_) {
super(p_57058_);
}
@@ -10,4 +_,12 @@
public abstract StemBlock m_7161_();
public abstract AttachedStemBlock m_7810_();
+
+ //FORGE START
+ @Override
+ public net.minecraft.world.level.block.state.BlockState getPlant(net.minecraft.world.level.BlockGetter world, net.minecraft.core.BlockPos pos) {
+ net.minecraft.world.level.block.state.BlockState state = world.m_8055_(pos);
+ if (state.m_60734_() != this) return m_49966_();
+ return state;
+ }
}
@@ -1,33 +1,12 @@
--- a/net/minecraft/world/level/block/grower/AbstractMegaTreeGrower.java
+++ b/net/minecraft/world/level/block/grower/AbstractMegaTreeGrower.java
@@ -27,6 +_,14 @@
return super.m_213817_(p_222891_, p_222892_, p_222893_, p_222894_, p_222895_);
}
+ /**
+ * Forge: context-sensitive version to be able to use data pack provided features
+ */
+ @Nullable
+ protected Holder<? extends ConfiguredFeature<?, ?>> getConfiguredMegaFeature(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, ResourceKey<ConfiguredFeature<?, ?>> featureKey) {
+ return level.m_8891_().m_175515_(Registries.f_256911_).m_203636_(featureKey).orElse(null);
+ }
+
@Nullable
protected abstract ResourceKey<ConfiguredFeature<?, ?>> m_213566_(RandomSource p_222904_);
@@ -35,11 +_,12 @@
if (resourcekey == null) {
@@ -36,6 +_,9 @@
return false;
} else {
- Holder<ConfiguredFeature<?, ?>> holder = p_222897_.m_8891_().m_175515_(Registries.f_256911_).m_203636_(resourcekey).orElse((Holder.Reference<ConfiguredFeature<?, ?>>)null);
- if (holder == null) {
+ Holder<? extends ConfiguredFeature<?, ?>> holder = this.getConfiguredMegaFeature(p_222897_, p_222898_, p_222899_, p_222900_, p_222901_, resourcekey);
+ net.minecraftforge.event.level.SaplingGrowTreeEvent event = net.minecraftforge.event.ForgeEventFactory.blockGrowFeature(p_222897_, p_222901_, p_222899_, holder);
+ if (event.getResult().equals(net.minecraftforge.eventbus.api.Event.Result.DENY) || event.getFeature() == null) {
Holder<ConfiguredFeature<?, ?>> holder = p_222897_.m_8891_().m_175515_(Registries.f_256911_).m_203636_(resourcekey).orElse((Holder.Reference<ConfiguredFeature<?, ?>>)null);
+ var event = net.minecraftforge.event.ForgeEventFactory.blockGrowFeature(p_222897_, p_222901_, p_222899_, holder);
+ holder = event.getFeature();
+ if (event.getResult() == net.minecraftforge.eventbus.api.Event.Result.DENY) return false;
if (holder == null) {
return false;
} else {
- ConfiguredFeature<?, ?> configuredfeature = holder.m_203334_();
+ ConfiguredFeature<?, ?> configuredfeature = event.getFeature().m_203334_();
BlockState blockstate = Blocks.f_50016_.m_49966_();
p_222897_.m_7731_(p_222899_.m_7918_(p_222902_, 0, p_222903_), blockstate, 4);
p_222897_.m_7731_(p_222899_.m_7918_(p_222902_ + 1, 0, p_222903_), blockstate, 4);
@@ -1,33 +1,12 @@
--- a/net/minecraft/world/level/block/grower/AbstractTreeGrower.java
+++ b/net/minecraft/world/level/block/grower/AbstractTreeGrower.java
@@ -14,6 +_,14 @@
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
public abstract class AbstractTreeGrower {
+ /**
+ * Forge: context-sensitive version to be able to use data pack provided features
+ */
+ @Nullable
+ protected Holder<? extends ConfiguredFeature<?, ?>> getConfiguredFeature(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, boolean hasFlowers, ResourceKey<ConfiguredFeature<?, ?>> featureKey) {
+ return level.m_8891_().m_175515_(Registries.f_256911_).m_203636_(featureKey).orElse(null);
+ }
+
@Nullable
protected abstract ResourceKey<ConfiguredFeature<?, ?>> m_213888_(RandomSource p_222910_, boolean p_222911_);
@@ -22,11 +_,12 @@
if (resourcekey == null) {
@@ -23,6 +_,9 @@
return false;
} else {
- Holder<ConfiguredFeature<?, ?>> holder = p_222905_.m_8891_().m_175515_(Registries.f_256911_).m_203636_(resourcekey).orElse((Holder.Reference<ConfiguredFeature<?, ?>>)null);
- if (holder == null) {
+ Holder<? extends ConfiguredFeature<?, ?>> holder = this.getConfiguredFeature(p_222905_, p_222906_, p_222907_, p_222908_, p_222909_, this.m_60011_(p_222905_, p_222907_), resourcekey);
+ net.minecraftforge.event.level.SaplingGrowTreeEvent event = net.minecraftforge.event.ForgeEventFactory.blockGrowFeature(p_222905_, p_222909_, p_222907_, holder);
+ if (event.getResult().equals(net.minecraftforge.eventbus.api.Event.Result.DENY) || event.getFeature() == null) {
Holder<ConfiguredFeature<?, ?>> holder = p_222905_.m_8891_().m_175515_(Registries.f_256911_).m_203636_(resourcekey).orElse((Holder.Reference<ConfiguredFeature<?, ?>>)null);
+ var event = net.minecraftforge.event.ForgeEventFactory.blockGrowFeature(p_222905_, p_222909_, p_222907_, holder);
+ holder = event.getFeature();
+ if (event.getResult() == net.minecraftforge.eventbus.api.Event.Result.DENY) return false;
if (holder == null) {
return false;
} else {
- ConfiguredFeature<?, ?> configuredfeature = holder.m_203334_();
+ ConfiguredFeature<?, ?> configuredfeature = event.getFeature().m_203334_();
BlockState blockstate = p_222905_.m_6425_(p_222907_).m_76188_();
p_222905_.m_7731_(p_222907_, blockstate, 4);
if (configuredfeature.m_224953_(p_222905_, p_222906_, p_222909_, p_222907_)) {
@@ -48,8 +48,15 @@
public LootContext.Builder m_230911_(RandomSource p_230912_) {
this.f_78958_ = p_230912_;
return this;
@@ -191,7 +_,7 @@
@@ -189,9 +_,14 @@
return (T)this.f_78956_.get(p_78983_);
}
+ public LootContext.Builder withQueriedLootTableId(ResourceLocation queriedLootTableId) {
+ this.queriedLootTableId = queriedLootTableId;
+ return this;
+ }
+
public LootContext m_78975_(LootContextParamSet p_78976_) {
Set<LootContextParam<?>> set = Sets.difference(this.f_78956_.keySet(), p_78976_.m_81398_());
- if (!set.isEmpty()) {
@@ -6,7 +6,7 @@
try {
- LootTable loottable = f_79189_.fromJson(p_79199_, LootTable.class);
+ net.minecraft.server.packs.resources.Resource res = p_79215_.m_213713_(getPreparedPath(p_79198_)).orElse(null);
+ LootTable loottable = net.minecraftforge.common.ForgeHooks.loadLootTable(f_79189_, p_79198_, p_79199_, res == null || !res.m_215506_().equals("Default"), this);
+ LootTable loottable = net.minecraftforge.common.ForgeHooks.loadLootTable(f_79189_, p_79198_, p_79199_, res == null || !res.m_215506_().equals(net.minecraft.server.packs.repository.BuiltInPackSource.f_243761_), this);
builder.put(p_79198_, loottable);
} catch (Exception exception) {
f_79188_.error("Couldn't parse loot table {}", p_79198_, exception);
@@ -0,0 +1,8 @@
{
"sources": [
{
"type": "minecraft:single",
"resource": "forge:white"
}
]
}
@@ -0,0 +1,10 @@
{
"type": "minecraft:the_end",
"generator": {
"type": "minecraft:noise",
"biome_source": {
"type": "minecraft:the_end"
},
"settings": "minecraft:end"
}
}
@@ -36,7 +36,6 @@ public class ClientForgeMod
// TODO: Deprecated. To be removed in 1.20
event.register("bucket", DynamicFluidContainerModel.Loader.INSTANCE_DEPRECATED);
event.register("item-layers", ItemLayerModel.Loader.INSTANCE_DEPRECATED);
event.register("separate-perspective", SeparateTransformsModel.Loader.INSTANCE_DEPRECATED);
}
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import java.util.IdentityHashMap;
@@ -6,10 +6,6 @@
package net.minecraftforge.client;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.graph.ElementOrder;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.shaders.FogShape;
@@ -74,7 +70,6 @@ import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.locale.Language;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.ChatType;
import net.minecraft.network.chat.CommonComponents;
@@ -94,9 +89,11 @@ import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ItemStackLinkedSet;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.GameType;
@@ -144,7 +141,7 @@ import net.minecraftforge.client.textures.ForgeTextureMetadata;
import net.minecraftforge.common.ForgeI18n;
import net.minecraftforge.common.ForgeMod;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.ItemStackMap;
import net.minecraftforge.common.util.MutableHashedLinkedMap;
import net.minecraftforge.event.CreativeModeTabEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@@ -153,8 +150,6 @@ import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.VersionChecker;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.loading.toposort.CyclePresentException;
import net.minecraftforge.fml.loading.toposort.TopologicalSort;
import net.minecraftforge.network.NetworkConstants;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.registries.GameData;
@@ -174,9 +169,9 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -435,7 +430,7 @@ public class ForgeHooksClient
private static void drawScreenInternal(Screen screen, PoseStack poseStack, int mouseX, int mouseY, float partialTick)
{
if (!MinecraftForge.EVENT_BUS.post(new ScreenEvent.Render.Pre(screen, poseStack, mouseX, mouseY, partialTick)))
screen.render(poseStack, mouseX, mouseY, partialTick);
screen.renderWithTooltip(poseStack, mouseX, mouseY, partialTick);
MinecraftForge.EVENT_BUS.post(new ScreenEvent.Render.Post(screen, poseStack, mouseX, mouseY, partialTick));
}
@@ -477,9 +472,14 @@ public class ForgeHooksClient
return event;
}
public static void onModifyBakingResult(Map<ResourceLocation, BakedModel> models, ModelBakery modelBakery)
{
ModLoader.get().postEvent(new ModelEvent.ModifyBakingResult(models, modelBakery));
}
public static void onModelBake(ModelManager modelManager, Map<ResourceLocation, BakedModel> models, ModelBakery modelBakery)
{
ModLoader.get().postEvent(new ModelEvent.BakingCompleted(modelManager, models, modelBakery));
ModLoader.get().postEvent(new ModelEvent.BakingCompleted(modelManager, Collections.unmodifiableMap(models), modelBakery));
}
public static BakedModel handleCameraTransforms(PoseStack poseStack, BakedModel model, ItemTransforms.TransformType cameraTransformType, boolean applyLeftHandTransform)
@@ -1189,106 +1189,26 @@ public class ForgeHooksClient
return new ResourceLocation(loc.getNamespace(), normalised);
}
public static CreativeModeTab.DisplayItemsGenerator onCreativeModeTabBuildContents(CreativeModeTab tab, CreativeModeTab.DisplayItemsGenerator originalGenerator) {
Set<CreativeModeTabEvent.DisplayItemsAdapter> customGenerators = new HashSet<>();
CreativeModeTabEvent.BuildContents event = ModLoader.get().postEventWithReturn(new net.minecraftforge.event.CreativeModeTabEvent.BuildContents(tab, customGenerators));
record ModificationEntry(CreativeModeTab.TabVisibility tabVisibility, ItemStack before, ItemStack after) {}
return (featureFlagSet, output, hasPermissions) -> {
final Map<ItemStack, CreativeModeTab.TabVisibility> originalEntries = ItemStackMap.createTypeAndTagLinkedMap();
final Map<ItemStack, ModificationEntry> entries = ItemStackMap.createTypeAndTagMap();
originalGenerator.accept(featureFlagSet, originalEntries::put, hasPermissions);
customGenerators.forEach(generator -> generator.accept(featureFlagSet, (stack, visibility, before, after) -> {
if (stack.getCount() != 1)
throw new IllegalArgumentException("The stack count must be 1");
entries.put(stack, new ModificationEntry(visibility, before, after));
}, hasPermissions));
// We switch to the stacks tag here, as we need to be able to compare and hash them for the graph!
// We could not handle this with an open hashset since the builder does not support this internally.
final MutableGraph<CompoundTag> graph = GraphBuilder.directed().nodeOrder(ElementOrder.insertion()).build();
final Map<CompoundTag, ItemStack> tagToStack = Maps.newHashMap();
CompoundTag lastStackTag = null;
for (ItemStack stack : originalEntries.keySet())
{
final CompoundTag tag = stack.serializeNBT();
tagToStack.put(tag, stack);
if (!stack.isEmpty())
{
graph.addNode(tag);
}
if (lastStackTag != null)
{
graph.putEdge(lastStackTag, tag);
}
lastStackTag = tag;
public static void onCreativeModeTabBuildContents(CreativeModeTab tab, CreativeModeTab.DisplayItemsGenerator originalGenerator, FeatureFlagSet flags, CreativeModeTab.Output output, boolean hasPermisions)
{
final var entries = new MutableHashedLinkedMap<ItemStack, CreativeModeTab.TabVisibility>(ItemStackLinkedSet.TYPE_AND_TAG,
(key, left, right) -> {
//throw new IllegalStateException("Accidentally adding the same item stack twice " + key.getDisplayName().getString() + " to a Creative Mode Tab: " + tab.getDisplayName().getString());
// Vanilla adds enchanting books twice in both visibilities.
// This is just code cleanliness for them. For us lets just increase the visibility and merge the entries.
return CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS;
}
);
CompoundTag finalLastStackTag = lastStackTag;
entries.forEach((stack, modificationEntry) -> {
final CompoundTag tag = stack.serializeNBT();
if (!stack.isEmpty())
{
graph.addNode(tag);
tagToStack.put(tag, stack);
}
if (!modificationEntry.after.isEmpty())
{
final CompoundTag afterTag = modificationEntry.after.serializeNBT();
graph.putEdge(afterTag, tag);
tagToStack.put(afterTag, modificationEntry.after);
}
else if (finalLastStackTag != null)
{
// If the user does not specify an after, we assume it is the last stack and put it after that.
graph.putEdge(finalLastStackTag, tag);
}
if (!modificationEntry.before.isEmpty())
{
final CompoundTag beforeTag = modificationEntry.before.serializeNBT();
graph.putEdge(tag, beforeTag);
tagToStack.put(beforeTag, modificationEntry.before);
}
});
originalGenerator.accept(flags, (stack, vis) -> {
if (stack.getCount() != 1)
throw new IllegalArgumentException("The stack count must be 1");
entries.put(stack, vis);
}, hasPermisions);
try
{
List<CompoundTag> tierList = TopologicalSort.topologicalSort(graph, null);
ModLoader.get().postEvent(new CreativeModeTabEvent.BuildContents(tab, flags, hasPermisions, entries));
// Now we unwind the tags again so that they are sorted neatly.
for (CompoundTag tag : tierList)
{
final ItemStack stack = tagToStack.get(tag);
if (originalEntries.containsKey(stack))
{
output.accept(stack, originalEntries.get(stack));
}
else if (entries.containsKey(stack))
{
output.accept(stack, entries.get(stack).tabVisibility);
}
}
}
catch (CyclePresentException cyclePresentException)
{
LOGGER.error("Failed to inject custom items into creative mode tab {} due to a cycle in the dependency graph", tab, cyclePresentException);
LOGGER.warn("Detected cycles:");
for (Set<Object> cycle : cyclePresentException.getCycles())
{
graph.edges().forEach(edge -> {
if (cycle.contains(edge.nodeU()) && cycle.contains(edge.nodeV()))
{
LOGGER.warn(" - {} -> {}", tagToStack.get(edge.nodeU()), tagToStack.get(edge.nodeV()));
}
});
}
LOGGER.error("Falling back to default!");
originalGenerator.accept(featureFlagSet, output, hasPermissions);
}
};
for (var entry : entries)
output.accept(entry.getKey(), entry.getValue());
}
}
@@ -32,9 +32,56 @@ public abstract class ModelEvent extends Event
{
}
/**
* Fired while the {@link ModelManager} is reloading models, after the model registry is set up, but before it's
* passed to the {@link net.minecraft.client.renderer.block.BlockModelShaper} for caching.
*
* <p>
* This event is fired from a worker thread and it is therefore not safe to access anything outside the
* model registry and {@link ModelBakery} provided in this event.<br>
* The {@link ModelManager} firing this event is not fully set up with the latest data when this event fires and
* must therefore not be accessed in this event.
* </p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class ModifyBakingResult extends ModelEvent implements IModBusEvent
{
private final Map<ResourceLocation, BakedModel> models;
private final ModelBakery modelBakery;
@ApiStatus.Internal
public ModifyBakingResult(Map<ResourceLocation, BakedModel> models, ModelBakery modelBakery)
{
this.models = models;
this.modelBakery = modelBakery;
}
/**
* @return the modifiable registry map of models and their model names
*/
public Map<ResourceLocation, BakedModel> getModels()
{
return models;
}
/**
* @return the model loader
*/
public ModelBakery getModelBakery()
{
return modelBakery;
}
}
/**
* Fired when the {@link ModelManager} is notified of the resource manager reloading.
* Called after model registry is set up, but before it's passed to {@link net.minecraft.client.renderer.block.BlockModelShaper}.
* Called after the model registry is set up and cached in the {@link net.minecraft.client.renderer.block.BlockModelShaper}.<br>
* The model registry given by this event is unmodifiable. To modify the model registry, use
* {@link ModelEvent.ModifyBakingResult} instead.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
@@ -64,7 +111,7 @@ public abstract class ModelEvent extends Event
}
/**
* @return the modifiable registry map of models and their model names
* @return an unmodifiable view of the registry map of models and their model names
*/
public Map<ResourceLocation, BakedModel> getModels()
{
@@ -575,7 +575,7 @@ public class ForgeGui extends Gui
minecraft.getProfiler().push("chat");
Window window = minecraft.getWindow();
var event = new CustomizeGuiOverlayEvent.Chat(window, pStack, minecraft.getFrameTime(), 0, height - 48);
var event = new CustomizeGuiOverlayEvent.Chat(window, pStack, minecraft.getFrameTime(), 0, height - 40);
MinecraftForge.EVENT_BUS.post(event);
pStack.pushPose();
@@ -33,6 +33,11 @@ public class ExtendedButton extends Button
super(xPos, yPos, width, height, displayString, handler, createNarration);
}
public ExtendedButton(Button.Builder builder)
{
super(builder);
}
/**
* Draws this button to the screen.
*/
@@ -40,7 +45,7 @@ public class ExtendedButton extends Button
public void renderButton(PoseStack poseStack, int mouseX, int mouseY, float partialTick)
{
Minecraft mc = Minecraft.getInstance();
int k = this.getYImage(this.isHovered);
int k = this.getYImage(this.isHoveredOrFocused());
ScreenUtils.blitWithBorder(poseStack, WIDGETS_LOCATION, this.getX(), this.getY(), 0, 46 + k * 20, this.width, this.height, 200, 20, 2, 3, 2, 2, this.getBlitOffset());
this.renderBg(poseStack, mc, mouseX, mouseY);
@@ -35,7 +35,7 @@ public class UnicodeGlyphButton extends ExtendedButton
{
Minecraft mc = Minecraft.getInstance();
this.isHovered = mouseX >= this.getX() && mouseY >= this.getY() && mouseX < this.getX() + this.width && mouseY < this.getY() + this.height;
int k = this.getYImage(this.isHovered);
int k = this.getYImage(this.isHoveredOrFocused());
ScreenUtils.blitWithBorder(poseStack, WIDGETS_LOCATION, this.getX(), this.getY(), 0, 46 + k * 20, this.width, this.height, 200, 20, 2, 3, 2, 2, this.getBlitOffset());
this.renderBg(poseStack, mc, mouseX, mouseY);
@@ -30,6 +30,7 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
@@ -112,6 +113,12 @@ public class CompositeModel implements IUnbakedGeometry<CompositeModel>
return new Baked(context.isGui3d(), context.useBlockLight(), context.useAmbientOcclusion(), particle, context.getTransforms(), overrides, bakedParts, itemPassesBuilder.build());
}
@Override
public void resolveParents(Function<ResourceLocation, UnbakedModel> modelGetter, IGeometryBakingContext context)
{
children.values().forEach(child -> child.resolveParents(modelGetter));
}
@Override
public Set<String> getConfigurableComponentNames()
{
@@ -0,0 +1,64 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.model;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.client.renderer.block.model.BlockElement;
import net.minecraft.client.renderer.block.model.BlockElementFace;
import net.minecraft.util.ExtraCodecs;
/**
* Holds extra data that may be injected into a face.<p>
* Used by {@link ItemLayerModel}, {@link BlockElement} and {@link BlockElementFace}
*
* @param color Color in ARGB format
* @param blockLight Block Light for this face from 0-15 (inclusive)
* @param skyLight Sky Light for this face from 0-15 (inclusive)
* @param ambientOcclusion If this face has AO
*/
public record ForgeFaceData(int color, int blockLight, int skyLight, boolean ambientOcclusion)
{
public static final ForgeFaceData DEFAULT = new ForgeFaceData(0xFFFFFFFF, 0, 0, true);
public static final Codec<Integer> COLOR = new ExtraCodecs.EitherCodec<>(Codec.INT, Codec.STRING).xmap(
either -> either.map(Function.identity(), str -> (int) Long.parseLong(str, 16)),
color -> Either.right(Integer.toHexString(color)));
public static final Codec<ForgeFaceData> CODEC = RecordCodecBuilder.create(builder -> builder.group(
COLOR.optionalFieldOf("color", 0xFFFFFFFF).forGetter(ForgeFaceData::color),
Codec.intRange(0, 15).optionalFieldOf("block_light", 0).forGetter(ForgeFaceData::blockLight),
Codec.intRange(0, 15).optionalFieldOf("sky_light", 0).forGetter(ForgeFaceData::skyLight),
Codec.BOOL.optionalFieldOf("ambient_occlusion", true).forGetter(ForgeFaceData::ambientOcclusion))
.apply(builder, ForgeFaceData::new));
/**
* Parses a ForgeFaceData from JSON
* @param obj The JsonObject to parse from, weakly-typed to JsonElement to reduce logic complexity.
* @param fallback What to return if the first parameter is null.
* @return The parsed ForgeFaceData, or the fallback parameter if the first parmeter is null.
* @throws JsonParseException
*/
@Nullable
public static ForgeFaceData read(@Nullable JsonElement obj, @Nullable ForgeFaceData fallback) throws JsonParseException
{
if(obj == null)
{
return fallback;
}
return CODEC.parse(JsonOps.INSTANCE, obj).getOrThrow(false, JsonParseException::new);
}
}
@@ -10,11 +10,9 @@ import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.ItemModelGenerator;
import net.minecraft.client.renderer.block.model.ItemOverrides;
@@ -23,7 +21,6 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.ForgeRenderTypes;
import net.minecraftforge.client.RenderTypeGroup;
@@ -31,13 +28,9 @@ import net.minecraftforge.client.model.geometry.IGeometryBakingContext;
import net.minecraftforge.client.model.geometry.IGeometryLoader;
import net.minecraftforge.client.model.geometry.IUnbakedGeometry;
import net.minecraftforge.client.model.geometry.UnbakedGeometryHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/**
@@ -48,26 +41,16 @@ import java.util.function.Function;
*/
public class ItemLayerModel implements IUnbakedGeometry<ItemLayerModel>
{
private static final Logger LOGGER = LogManager.getLogger();
@Nullable
private ImmutableList<Material> textures;
private final IntSet emissiveLayers;
private final Int2ObjectMap<ForgeFaceData> layerData;
private final Int2ObjectMap<ResourceLocation> renderTypeNames;
private final boolean deprecatedLoader, logWarning;
public ItemLayerModel(@Nullable ImmutableList<Material> textures, IntSet emissiveLayers, Int2ObjectMap<ResourceLocation> renderTypeNames)
{
this(textures, emissiveLayers, renderTypeNames, false, false);
}
private ItemLayerModel(@Nullable ImmutableList<Material> textures, IntSet emissiveLayers, Int2ObjectMap<ResourceLocation> renderTypeNames, boolean deprecatedLoader, boolean logWarning)
private ItemLayerModel(@Nullable ImmutableList<Material> textures, Int2ObjectMap<ForgeFaceData> layerData, Int2ObjectMap<ResourceLocation> renderTypeNames)
{
this.textures = textures;
this.emissiveLayers = emissiveLayers;
this.layerData = layerData;
this.renderTypeNames = renderTypeNames;
this.deprecatedLoader = deprecatedLoader;
this.logWarning = logWarning;
}
@Override
@@ -84,11 +67,6 @@ public class ItemLayerModel implements IUnbakedGeometry<ItemLayerModel>
textures = builder.build();
}
if (deprecatedLoader)
LOGGER.warn("Model \"" + modelLocation + "\" is using the deprecated loader \"forge:item-layers\" instead of \"forge:item_layers\". This loader will be removed in 1.20.");
if (logWarning)
LOGGER.warn("Model \"" + modelLocation + "\" is using the deprecated \"fullbright_layers\" field in its item layer model instead of \"emissive_layers\". This field will be removed in 1.20.");
TextureAtlasSprite particle = spriteGetter.apply(
context.hasMaterial("particle") ? context.getMaterial("particle") : textures.get(0)
);
@@ -101,9 +79,8 @@ public class ItemLayerModel implements IUnbakedGeometry<ItemLayerModel>
for (int i = 0; i < textures.size(); i++)
{
TextureAtlasSprite sprite = spriteGetter.apply(textures.get(i));
var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(i, sprite.contents());
var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(i, sprite.contents(), this.layerData.get(i));
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, modelState, modelLocation);
if (emissiveLayers.contains(i)) QuadTransformers.settingMaxEmissivity().processInPlace(quads);
var renderTypeName = renderTypeNames.get(i);
var renderTypes = renderTypeName != null ? context.getRenderType(renderTypeName) : null;
builder.addQuads(renderTypes != null ? renderTypes : normalRenderTypes, quads);
@@ -114,16 +91,7 @@ public class ItemLayerModel implements IUnbakedGeometry<ItemLayerModel>
public static final class Loader implements IGeometryLoader<ItemLayerModel>
{
public static final Loader INSTANCE = new Loader(false);
@Deprecated(forRemoval = true, since = "1.19")
public static final Loader INSTANCE_DEPRECATED = new Loader(true);
private final boolean deprecated;
private Loader(boolean deprecated)
{
this.deprecated = deprecated;
}
public static final Loader INSTANCE = new Loader();
@Override
public ItemLayerModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext)
@@ -141,25 +109,28 @@ public class ItemLayerModel implements IUnbakedGeometry<ItemLayerModel>
}
}
var emissiveLayers = new IntOpenHashSet();
readUnlit(jsonObject, "emissive_layers", renderTypeNames, emissiveLayers, false);
boolean logWarning = readUnlit(jsonObject, "fullbright_layers", renderTypeNames, emissiveLayers, true); // TODO: Deprecated name. To be removed in 1.20
return new ItemLayerModel(null, emissiveLayers, renderTypeNames, deprecated, logWarning);
var emissiveLayers = new Int2ObjectArrayMap<ForgeFaceData>();
if(jsonObject.has("forge_data"))
{
JsonObject forgeData = jsonObject.get("forge_data").getAsJsonObject();
readLayerData(forgeData, "layers", renderTypeNames, emissiveLayers, false);
}
return new ItemLayerModel(null, emissiveLayers, renderTypeNames);
}
private boolean readUnlit(JsonObject jsonObject, String name, Int2ObjectOpenHashMap<ResourceLocation> renderTypeNames, IntOpenHashSet litLayers, boolean logWarning)
protected void readLayerData(JsonObject jsonObject, String name, Int2ObjectOpenHashMap<ResourceLocation> renderTypeNames, Int2ObjectMap<ForgeFaceData> layerData, boolean logWarning)
{
if (!jsonObject.has(name))
return false;
var fullbrightLayers = jsonObject.getAsJsonArray(name);
var renderType = new ResourceLocation("forge", "item_unlit");
for (var layer : fullbrightLayers)
{
litLayers.add(layer.getAsInt());
renderTypeNames.putIfAbsent(layer.getAsInt(), renderType);
return;
}
var fullbrightLayers = jsonObject.getAsJsonObject(name);
for (var entry : fullbrightLayers.entrySet())
{
int layer = Integer.parseInt(entry.getKey());
var data = ForgeFaceData.read(entry.getValue(), ForgeFaceData.DEFAULT);
layerData.put(layer, data);
}
return logWarning && !fullbrightLayers.isEmpty();
}
}
}
@@ -83,19 +83,27 @@ public final class QuadTransformers {
}
/**
* {@return a new {@link BakedQuad} transformer that applies the specified lightmap}
* @return A new {@link BakedQuad} transformer that applies the specified packed light value.
*/
public static IQuadTransformer applyingLightmap(int lightmap)
public static IQuadTransformer applyingLightmap(int packedLight)
{
return quad -> {
var vertices = quad.getVertices();
for (int i = 0; i < 4; i++)
vertices[i * IQuadTransformer.STRIDE + IQuadTransformer.UV2] = lightmap;
vertices[i * IQuadTransformer.STRIDE + IQuadTransformer.UV2] = packedLight;
};
}
/**
* {@return a {@link BakedQuad} transformer that sets the lightmap to the given emissivity (0-15)}
* @return A new {@link BakedQuad} transformer that applies the specified block and sky light values.
*/
public static IQuadTransformer applyingLightmap(int blockLight, int skyLight)
{
return applyingLightmap(LightTexture.pack(blockLight, skyLight));
}
/**
* @return A {@link BakedQuad} transformer that sets the lightmap to the given emissivity (0-15)
*/
public static IQuadTransformer settingEmissivity(int emissivity)
{
@@ -104,13 +112,64 @@ public final class QuadTransformers {
}
/**
* {@return a {@link BakedQuad} transformer that sets the lightmap to its max value}
* @return A {@link BakedQuad} transformer that sets the lightmap to its max value
*/
public static IQuadTransformer settingMaxEmissivity()
{
return EMISSIVE_TRANSFORMERS[15];
}
/**
* @param color The color in ARGB format.
* @return A {@link BakedQuad} transformer that sets the color to the specified value.
*/
public static IQuadTransformer applyingColor(int color)
{
final int fixedColor = toABGR(color);
return quad -> {
var vertices = quad.getVertices();
for (int i = 0; i < 4; i++)
vertices[i * IQuadTransformer.STRIDE + IQuadTransformer.COLOR] = fixedColor;
};
}
/**
* This method supplies a default alpha value of 255 (no transparency)
* @param red The red value (0-255)
* @param green The green value (0-255)
* @param blue The blue value (0-255)
* @return A {@link BakedQuad} transformer that sets the color to the specified value.
*/
public static IQuadTransformer applyingColor(int red, int green, int blue)
{
return applyingColor(255, red, green, blue);
}
/**
* @param alpha The alpha value (0-255)
* @param red The red value (0-255)
* @param green The green value (0-255)
* @param blue The blue value (0-255)
* @return A {@link BakedQuad} transformer that sets the color to the specified value.
*/
public static IQuadTransformer applyingColor(int alpha, int red, int green, int blue)
{
return applyingColor(alpha << 24 | red << 16 | green << 8 | blue);
}
/**
* Converts an ARGB color to an ABGR color, as the commonly used color format is not the format colors end up packed into.
* This function doubles as its own inverse.
* @param color ARGB color
* @return ABGR color
*/
public static int toABGR(int argb)
{
return (argb & 0xFF00FF00) // alpha and green same spot
| ((argb >> 16) & 0x000000FF) // red moves to blue
| ((argb << 16) & 0x00FF0000); // blue moves to red
}
private QuadTransformers()
{
}
@@ -24,6 +24,7 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
@@ -78,6 +79,13 @@ public class SeparateTransformsModel implements IUnbakedGeometry<SeparateTransfo
);
}
@Override
public void resolveParents(Function<ResourceLocation, UnbakedModel> modelGetter, IGeometryBakingContext context)
{
baseModel.resolveParents(modelGetter);
perspectives.values().forEach(model -> model.resolveParents(modelGetter));
}
public static class Baked implements IDynamicBakedModel
{
private final boolean isAmbientOcclusion;
@@ -5,7 +5,7 @@
package net.minecraftforge.client.model.generators;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraftforge.common.data.ExistingFileHelper;
import org.jetbrains.annotations.NotNull;
@@ -15,8 +15,8 @@ import org.jetbrains.annotations.NotNull;
*/
public abstract class BlockModelProvider extends ModelProvider<BlockModelBuilder> {
public BlockModelProvider(DataGenerator generator, String modid, ExistingFileHelper existingFileHelper) {
super(generator, modid, BLOCK_FOLDER, BlockModelBuilder::new, existingFileHelper);
public BlockModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) {
super(output, modid, BLOCK_FOLDER, BlockModelBuilder::new, existingFileHelper);
}
@NotNull
@@ -14,7 +14,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.LinkedHashMap;
@@ -25,7 +25,6 @@ import java.util.function.Function;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -73,20 +72,20 @@ public abstract class BlockStateProvider implements DataProvider {
@VisibleForTesting
protected final Map<Block, IGeneratedBlockState> registeredBlocks = new LinkedHashMap<>();
private final DataGenerator generator;
private final PackOutput output;
private final String modid;
private final BlockModelProvider blockModels;
private final ItemModelProvider itemModels;
public BlockStateProvider(DataGenerator gen, String modid, ExistingFileHelper exFileHelper) {
this.generator = gen;
public BlockStateProvider(PackOutput output, String modid, ExistingFileHelper exFileHelper) {
this.output = output;
this.modid = modid;
this.blockModels = new BlockModelProvider(gen, modid, exFileHelper) {
this.blockModels = new BlockModelProvider(output, modid, exFileHelper) {
@Override public CompletableFuture<?> run(CachedOutput cache) { return CompletableFuture.allOf(); }
@Override protected void registerModels() {}
};
this.itemModels = new ItemModelProvider(gen, modid, this.blockModels.existingFileHelper) {
this.itemModels = new ItemModelProvider(output, modid, this.blockModels.existingFileHelper) {
@Override protected void registerModels() {}
@Override public CompletableFuture<?> run(CachedOutput cache) { return CompletableFuture.allOf(); }
@@ -99,10 +98,10 @@ public abstract class BlockStateProvider implements DataProvider {
itemModels().clear();
registeredBlocks.clear();
registerStatesAndModels();
models().generateAll(cache);
itemModels().generateAll(cache);
CompletableFuture<?>[] futures = new CompletableFuture<?>[this.registeredBlocks.size()];
CompletableFuture<?>[] futures = new CompletableFuture<?>[2 + this.registeredBlocks.size()];
int i = 0;
futures[i++] = models().generateAll(cache);
futures[i++] = itemModels().generateAll(cache);
for (Map.Entry<Block, IGeneratedBlockState> entry : registeredBlocks.entrySet()) {
futures[i++] = saveBlockState(cache, entry.getValue().toJson(), entry.getKey());
}
@@ -188,6 +187,11 @@ public abstract class BlockStateProvider implements DataProvider {
itemModels().getBuilder(key(block).getPath()).parent(model);
}
public void simpleBlockWithItem(Block block, ModelFile model) {
simpleBlock(block, model);
simpleBlockItem(block, model);
}
public void simpleBlock(Block block, ConfiguredModel... models) {
getVariantBuilder(block)
.partialState().setModels(models);
@@ -857,7 +861,7 @@ public abstract class BlockStateProvider implements DataProvider {
private CompletableFuture<?> saveBlockState(CachedOutput cache, JsonObject stateJson, Block owner) {
ResourceLocation blockName = Preconditions.checkNotNull(key(owner));
Path outputPath = this.generator.getPackOutput().getOutputFolder(PackOutput.Target.RESOURCE_PACK)
Path outputPath = this.output.getOutputFolder(PackOutput.Target.RESOURCE_PACK)
.resolve(blockName.getNamespace()).resolve("blockstates").resolve(blockName.getPath() + ".json");
return DataProvider.saveStable(cache, stateJson, outputPath);
}
@@ -5,7 +5,7 @@
package net.minecraftforge.client.model.generators;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraftforge.common.data.ExistingFileHelper;
@@ -20,8 +20,8 @@ import java.util.Objects;
*/
public abstract class ItemModelProvider extends ModelProvider<ItemModelBuilder> {
public ItemModelProvider(DataGenerator generator, String modid, ExistingFileHelper existingFileHelper) {
super(generator, modid, ITEM_FOLDER, ItemModelBuilder::new, existingFileHelper);
public ItemModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) {
super(output, modid, ITEM_FOLDER, ItemModelBuilder::new, existingFileHelper);
}
public ItemModelBuilder basicItem(Item item)
@@ -20,6 +20,7 @@ import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.serialization.JsonOps;
import net.minecraft.client.renderer.block.model.BlockFaceUV;
import net.minecraft.client.renderer.block.model.BlockModel.GuiLight;
@@ -32,6 +33,7 @@ import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraftforge.client.model.ForgeFaceData;
import net.minecraftforge.common.data.ExistingFileHelper;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
@@ -60,7 +62,7 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
protected final List<ElementBuilder> elements = new ArrayList<>();
protected CustomLoaderBuilder customLoader = null;
protected CustomLoaderBuilder<T> customLoader = null;
protected ModelBuilder(ResourceLocation outputLocation, ExistingFileHelper existingFileHelper) {
super(outputLocation);
@@ -317,11 +319,8 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
if (face.tintIndex != -1) {
faceObj.addProperty("tintindex", face.tintIndex);
}
if (face.emissivity > 0) {
faceObj.addProperty("emissivity", face.emissivity);
}
if (!face.hasAmbientOcclusion) {
faceObj.addProperty("ambientocclusion", face.hasAmbientOcclusion);
if (!face.getFaceData().equals(ForgeFaceData.DEFAULT)) {
faceObj.add("forge_data", ForgeFaceData.CODEC.encodeStart(JsonOps.INSTANCE, face.getFaceData()).result().get());
}
faces.add(dir.getSerializedName(), faceObj);
}
@@ -368,6 +367,9 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
private final Map<Direction, FaceBuilder> faces = new LinkedHashMap<>();
private RotationBuilder rotation;
private boolean shade = true;
private int color = 0xFFFFFFFF;
private int blockLight = 0, skyLight = 0;
private boolean hasAmbientOcclusion = true;
private void validateCoordinate(float coord, char name) {
Preconditions.checkArgument(!(coord < -16.0F) && !(coord > 32.0F), "Position " + name + " out of range, must be within [-16, 32]. Found: %d", coord);
@@ -499,6 +501,42 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
public ElementBuilder cube(String texture) {
return allFaces(addTexture(texture).andThen((dir, f) -> f.cullface(dir)));
}
/**
* Set the block and sky light of the element (0-15).
* Traditional "emissivity" values were set both of these to the same value.
*
* @param blockLight the block light
* @param skyLight the sky light
* @return this builder
*/
public ElementBuilder emissivity(int blockLight, int skyLight) {
this.blockLight = blockLight;
this.skyLight = skyLight;
return this;
}
/**
* Sets the color of the element.
*
* @param color the color in ARGB format.
* @return this builder
*/
public ElementBuilder color(int color) {
this.color = color;
return this;
}
/**
* Set the ambient occlusion of the element.
*
* @param ao the ambient occlusion
* @return this builder
*/
public ElementBuilder ao(boolean ao) {
this.hasAmbientOcclusion = ao;
return this;
}
private BiConsumer<Direction, FaceBuilder> addTexture(String texture) {
return ($, f) -> f.texture(texture);
@@ -507,7 +545,7 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
BlockElement build() {
Map<Direction, BlockElementFace> faces = this.faces.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build(), (k1, k2) -> { throw new IllegalArgumentException(); }, LinkedHashMap::new));
return new BlockElement(from, to, faces, rotation == null ? null : rotation.build(), shade);
return new BlockElement(from, to, faces, rotation == null ? null : rotation.build(), shade, new ForgeFaceData(this.color, this.blockLight, this.skyLight, this.hasAmbientOcclusion));
}
public T end() { return self(); }
@@ -519,7 +557,8 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
private String texture = MissingTextureAtlasSprite.getLocation().toString();
private float[] uvs;
private FaceRotation rotation = FaceRotation.ZERO;
private int emissivity = 0;
private int color = 0xFFFFFFFF;
private int blockLight = 0, skyLight = 0;
private boolean hasAmbientOcclusion = true;
FaceBuilder(Direction dir) {
@@ -568,23 +607,28 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
}
/**
* Set the emissivity of the face (0-15).
* Set the block and sky light of the face (0-15).
* Traditional "emissivity" values set both of these to the same value.
*
* @param emissivity the emissivity
* @param blockLight the block light
* @param skyLight the sky light
* @return this builder
*/
public FaceBuilder emissivity(int emissivity) {
this.emissivity = emissivity;
public FaceBuilder emissivity(int blockLight, int skyLight) {
this.blockLight = blockLight;
this.skyLight = skyLight;
return this;
}
/**
* Make the face emissive (emissivity = 15).
* Sets the color of the face.
*
* @param color the color in ARGB format.
* @return this builder
*/
public FaceBuilder emissive() {
return emissivity(15);
public FaceBuilder color(int color) {
this.color = color;
return this;
}
/**
@@ -602,7 +646,7 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
if (this.texture == null) {
throw new IllegalStateException("A model face must have a texture");
}
return new BlockElementFace(cullface, tintindex, texture, new BlockFaceUV(uvs, rotation.rotation), emissivity, hasAmbientOcclusion);
return new BlockElementFace(cullface, tintindex, texture, new BlockFaceUV(uvs, rotation.rotation), new ForgeFaceData(this.color, this.blockLight, this.skyLight, this.hasAmbientOcclusion));
}
public ElementBuilder end() { return ElementBuilder.this; }
@@ -16,7 +16,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -34,7 +33,7 @@ public abstract class ModelProvider<T extends ModelBuilder<T>> implements DataPr
protected static final ResourceType MODEL_WITH_EXTENSION = new ResourceType(PackType.CLIENT_RESOURCES, "", "models");
private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create();
protected final DataGenerator generator;
protected final PackOutput output;
protected final String modid;
protected final String folder;
protected final Function<ResourceLocation, T> factory;
@@ -45,9 +44,9 @@ public abstract class ModelProvider<T extends ModelBuilder<T>> implements DataPr
protected abstract void registerModels();
public ModelProvider(DataGenerator generator, String modid, String folder, Function<ResourceLocation, T> factory, ExistingFileHelper existingFileHelper) {
Preconditions.checkNotNull(generator);
this.generator = generator;
public ModelProvider(PackOutput output, String modid, String folder, Function<ResourceLocation, T> factory, ExistingFileHelper existingFileHelper) {
Preconditions.checkNotNull(output);
this.output = output;
Preconditions.checkNotNull(modid);
this.modid = modid;
Preconditions.checkNotNull(folder);
@@ -58,8 +57,8 @@ public abstract class ModelProvider<T extends ModelBuilder<T>> implements DataPr
this.existingFileHelper = existingFileHelper;
}
public ModelProvider(DataGenerator generator, String modid, String folder, BiFunction<ResourceLocation, ExistingFileHelper, T> builderFromModId, ExistingFileHelper existingFileHelper) {
this(generator, modid, folder, loc->builderFromModId.apply(loc, existingFileHelper), existingFileHelper);
public ModelProvider(PackOutput output, String modid, String folder, BiFunction<ResourceLocation, ExistingFileHelper, T> builderFromModId, ExistingFileHelper existingFileHelper) {
this(output, modid, folder, loc->builderFromModId.apply(loc, existingFileHelper), existingFileHelper);
}
public T getBuilder(String path) {
@@ -408,6 +407,6 @@ public abstract class ModelProvider<T extends ModelBuilder<T>> implements DataPr
protected Path getPath(T model) {
ResourceLocation loc = model.getLocation();
return this.generator.getPackOutput().getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(loc.getNamespace()).resolve("models").resolve(loc.getPath() + ".json");
return this.output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(loc.getNamespace()).resolve("models").resolve(loc.getPath() + ".json");
}
}
@@ -5,30 +5,37 @@
package net.minecraftforge.client.model.generators.loaders;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.common.base.Preconditions;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.serialization.JsonOps;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.model.ForgeFaceData;
import net.minecraftforge.client.model.generators.CustomLoaderBuilder;
import net.minecraftforge.client.model.generators.ModelBuilder;
import net.minecraftforge.common.data.ExistingFileHelper;
import java.util.*;
public class ItemLayersModelBuilder<T extends ModelBuilder<T>> extends CustomLoaderBuilder<T>
public class ItemLayerModelBuilder<T extends ModelBuilder<T>> extends CustomLoaderBuilder<T>
{
public static <T extends ModelBuilder<T>> ItemLayersModelBuilder<T> begin(T parent, ExistingFileHelper existingFileHelper)
public static <T extends ModelBuilder<T>> ItemLayerModelBuilder<T> begin(T parent, ExistingFileHelper existingFileHelper)
{
return new ItemLayersModelBuilder<>(parent, existingFileHelper);
return new ItemLayerModelBuilder<>(parent, existingFileHelper);
}
private final IntSet emissiveLayers = new IntOpenHashSet();
private final Int2ObjectMap<ForgeFaceData> faceData = new Int2ObjectOpenHashMap<>();
private final Map<ResourceLocation, IntSet> renderTypes = new LinkedHashMap<>();
private final IntSet layersWithRenderTypes = new IntOpenHashSet();
protected ItemLayersModelBuilder(T parent, ExistingFileHelper existingFileHelper)
protected ItemLayerModelBuilder(T parent, ExistingFileHelper existingFileHelper)
{
super(new ResourceLocation("forge:item_layers"), parent, existingFileHelper);
}
@@ -36,18 +43,51 @@ public class ItemLayersModelBuilder<T extends ModelBuilder<T>> extends CustomLoa
/**
* Marks a set of layers to be rendered emissively.
*
* @param blockLight The block light (0-15)
* @param skyLight The sky light (0-15)
* @param layers the layers that will render unlit
* @return this builder
* @throws NullPointerException if {@code layers} is {@code null}
* @throws IllegalArgumentException if {@code layers} is empty
* @throws IllegalArgumentException if any entry in {@code layers} is smaller than 0
*/
public ItemLayersModelBuilder<T> emissive(int... layers)
public ItemLayerModelBuilder<T> emissive(int blockLight, int skyLight, int... layers)
{
Preconditions.checkNotNull(layers, "Layers must not be null");
Preconditions.checkArgument(layers.length > 0, "At least one layer must be specified");
Preconditions.checkArgument(Arrays.stream(layers).allMatch(i -> i >= 0), "All layers must be >= 0");
Arrays.stream(layers).forEach(emissiveLayers::add);
for(int i : layers)
{
faceData.compute(i, (key, value) -> {
ForgeFaceData fallback = value == null ? ForgeFaceData.DEFAULT : value;
return new ForgeFaceData(fallback.color(), blockLight, skyLight, fallback.ambientOcclusion());
});
}
return this;
}
/**
* Marks a set of layers to be rendered with a specific color.
*
* @param color The color, in ARGB.
* @param layers the layers that will render with color
* @return this builder
* @throws NullPointerException if {@code layers} is {@code null}
* @throws IllegalArgumentException if {@code layers} is empty
* @throws IllegalArgumentException if any entry in {@code layers} is smaller than 0
*/
public ItemLayerModelBuilder<T> color(int color, int... layers)
{
Preconditions.checkNotNull(layers, "Layers must not be null");
Preconditions.checkArgument(layers.length > 0, "At least one layer must be specified");
Preconditions.checkArgument(Arrays.stream(layers).allMatch(i -> i >= 0), "All layers must be >= 0");
for(int i : layers)
{
faceData.compute(i, (key, value) -> {
ForgeFaceData fallback = value == null ? ForgeFaceData.DEFAULT : value;
return new ForgeFaceData(color, fallback.blockLight(), fallback.skyLight(), fallback.ambientOcclusion());
});
}
return this;
}
@@ -64,7 +104,7 @@ public class ItemLayersModelBuilder<T extends ModelBuilder<T>> extends CustomLoa
* @throws IllegalArgumentException if any entry in {@code layers} is smaller than 0
* @throws IllegalArgumentException if any entry in {@code layers} already has a render type
*/
public ItemLayersModelBuilder<T> renderType(String renderType, int... layers)
public ItemLayerModelBuilder<T> renderType(String renderType, int... layers)
{
Preconditions.checkNotNull(renderType, "Render type must not be null");
ResourceLocation asLoc;
@@ -88,7 +128,7 @@ public class ItemLayersModelBuilder<T extends ModelBuilder<T>> extends CustomLoa
* @throws IllegalArgumentException if any entry in {@code layers} is smaller than 0
* @throws IllegalArgumentException if any entry in {@code layers} already has a render type
*/
public ItemLayersModelBuilder<T> renderType(ResourceLocation renderType, int... layers)
public ItemLayerModelBuilder<T> renderType(ResourceLocation renderType, int... layers)
{
Preconditions.checkNotNull(renderType, "Render type must not be null");
Preconditions.checkNotNull(layers, "Layers must not be null");
@@ -109,9 +149,16 @@ public class ItemLayersModelBuilder<T extends ModelBuilder<T>> extends CustomLoa
{
json = super.toJson(json);
JsonArray unlitLayers = new JsonArray();
this.emissiveLayers.intStream().sorted().forEach(unlitLayers::add);
json.add("emissive_layers", unlitLayers);
JsonObject forgeData = new JsonObject();
JsonObject layerObj = new JsonObject();
for(Int2ObjectMap.Entry<ForgeFaceData> entry : this.faceData.int2ObjectEntrySet())
{
layerObj.add(String.valueOf(entry.getIntKey()), ForgeFaceData.CODEC.encodeStart(JsonOps.INSTANCE, entry.getValue()).getOrThrow(false, s -> {}));
}
forgeData.add("layers", layerObj);
json.add("forge_data", forgeData);
JsonObject renderTypes = new JsonObject();
this.renderTypes.forEach((renderType, layers) -> {
@@ -41,6 +41,7 @@ public class BlockGeometryBakingContext implements IGeometryBakingContext
private Transformation rootTransform;
@Nullable
private ResourceLocation renderTypeHint;
private boolean gui3d = true;
@ApiStatus.Internal
public BlockGeometryBakingContext(BlockModel owner)
@@ -93,7 +94,7 @@ public class BlockGeometryBakingContext implements IGeometryBakingContext
@Override
public boolean isGui3d()
{
return true;
return gui3d;
}
@Override
@@ -141,12 +142,18 @@ public class BlockGeometryBakingContext implements IGeometryBakingContext
this.renderTypeHint = renderTypeHint;
}
public void setGui3d(boolean gui3d)
{
this.gui3d = gui3d;
}
public void copyFrom(BlockGeometryBakingContext other)
{
this.customGeometry = other.customGeometry;
this.rootTransform = other.rootTransform;
this.visibilityData.copyFrom(other.visibilityData);
this.renderTypeHint = other.renderTypeHint;
this.gui3d = other.gui3d;
}
public BakedModel bake(ModelBaker baker, Function<Material, TextureAtlasSprite> bakedTextureGetter, ModelState modelTransform, ItemOverrides overrides, ResourceLocation modelLocation)
@@ -7,6 +7,8 @@ package net.minecraftforge.client.model.geometry;
import java.util.Set;
import java.util.function.Function;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.ItemOverrides;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
@@ -28,6 +30,16 @@ public interface IUnbakedGeometry<T extends IUnbakedGeometry<T>>
{
BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState modelState, ItemOverrides overrides, ResourceLocation modelLocation);
/**
* Resolve parents of nested {@link BlockModel}s which are later used in
* {@link IUnbakedGeometry#bake(IGeometryBakingContext, ModelBaker, Function, ModelState, ItemOverrides, ResourceLocation)}
* via {@link BlockModel#resolveParents(Function)}
*/
default void resolveParents(Function<ResourceLocation, UnbakedModel> modelGetter, IGeometryBakingContext context)
{
}
/**
* {@return a set of all the components whose visibility may be configured via {@link IGeometryBakingContext}}
*/
@@ -27,6 +27,7 @@ import net.minecraft.client.resources.model.ModelState;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.model.ElementsModel;
import net.minecraftforge.client.model.ForgeFaceData;
import net.minecraftforge.client.model.IModelBuilder;
import net.minecraftforge.client.model.SimpleModelState;
import org.jetbrains.annotations.ApiStatus;
@@ -127,15 +128,33 @@ public class UnbakedGeometryHelper
return elementsModel.bake(blockModel.customData, modelBaker, spriteGetter, modelState, blockModel.getOverrides(modelBaker, owner, spriteGetter), modelLocation);
}
/**
* @see #createUnbakedItemElements(int, SpriteContents, ForgeFaceData)
*/
public static List<BlockElement> createUnbakedItemElements(int layerIndex, SpriteContents spriteContents)
{
return createUnbakedItemElements(layerIndex, spriteContents, null);
}
/**
* Creates a list of {@linkplain BlockElement block elements} in the shape of the specified sprite contents.
* These can later be baked using the same, or another texture.
* <p>
* The {@link Direction#NORTH} and {@link Direction#SOUTH} faces take up the whole surface.
*/
public static List<BlockElement> createUnbakedItemElements(int layerIndex, SpriteContents spriteContents)
public static List<BlockElement> createUnbakedItemElements(int layerIndex, SpriteContents spriteContents, @Nullable ForgeFaceData faceData)
{
return ITEM_MODEL_GENERATOR.processFrames(layerIndex, "layer" + layerIndex, spriteContents);
var elements = ITEM_MODEL_GENERATOR.processFrames(layerIndex, "layer" + layerIndex, spriteContents);
elements.forEach(element -> element.faceData = faceData);
return elements;
}
/**
* @see #createUnbakedItemMaskElements(int, SpriteContents, ForgeFaceData)
*/
public static List<BlockElement> createUnbakedItemMaskElements(int layerIndex, SpriteContents spriteContents)
{
return createUnbakedItemMaskElements(layerIndex, spriteContents, null);
}
/**
@@ -144,9 +163,9 @@ public class UnbakedGeometryHelper
* <p>
* The {@link Direction#NORTH} and {@link Direction#SOUTH} faces take up only the pixels the texture uses.
*/
public static List<BlockElement> createUnbakedItemMaskElements(int layerIndex, SpriteContents spriteContents)
public static List<BlockElement> createUnbakedItemMaskElements(int layerIndex, SpriteContents spriteContents, @Nullable ForgeFaceData faceData)
{
var elements = createUnbakedItemElements(layerIndex, spriteContents);
var elements = createUnbakedItemElements(layerIndex, spriteContents, faceData);
elements.remove(0); // Remove north and south faces
int width = spriteContents.width(), height = spriteContents.height();
@@ -31,8 +31,8 @@ public class ObjLoader implements IGeometryLoader<ObjModel>, ResourceManagerRelo
{
public static ObjLoader INSTANCE = new ObjLoader();
private final Map<ObjModel.ModelSettings, ObjModel> modelCache = Maps.newHashMap();
private final Map<ResourceLocation, ObjMaterialLibrary> materialCache = Maps.newHashMap();
private final Map<ObjModel.ModelSettings, ObjModel> modelCache = Maps.newConcurrentMap();
private final Map<ResourceLocation, ObjMaterialLibrary> materialCache = Maps.newConcurrentMap();
private ResourceManager manager = Minecraft.getInstance().getResourceManager();
@@ -24,6 +24,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
@@ -46,6 +47,8 @@ import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.HolderSet;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.ChatDecorator;
import net.minecraft.network.chat.ClickEvent;
@@ -55,6 +58,7 @@ import net.minecraft.network.chat.TextColor;
import net.minecraft.network.chat.contents.LiteralContents;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.metadata.pack.PackMetadataSection;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.TagEntry;
import net.minecraft.tags.TagKey;
@@ -1520,7 +1524,7 @@ public class ForgeHooks
for (PackType packType : PackType.values())
{
String key = "forge:" + packType.bridgeType.name().toLowerCase(Locale.ROOT) + "_pack_format";
String key = makePackFormatKey(packType);
if (json.has(key))
{
map.put(packType, GsonHelper.getAsInt(json, key));
@@ -1530,6 +1534,24 @@ public class ForgeHooks
return map.buildOrThrow();
}
public static void writeTypedPackFormats(JsonObject json, PackMetadataSection section)
{
int packFormat = section.getPackFormat();
for (PackType packType : PackType.values())
{
int format = section.getPackFormat(packType);
if (format != packFormat)
{
json.addProperty(makePackFormatKey(packType), format);
}
}
}
private static String makePackFormatKey(PackType packType)
{
return "forge:" + packType.bridgeType.name().toLowerCase(Locale.ROOT) + "_pack_format";
}
/**
* <p>
* This method is used to prefix the path, where elements of the associated registry are stored, with their namespace, if it is not minecraft
@@ -1567,4 +1589,15 @@ public class ForgeHooks
}
return false;
}
@ApiStatus.Internal
public static <T> HolderLookup.RegistryLookup<T> wrapRegistryLookup(final HolderLookup.RegistryLookup<T> lookup)
{
return new HolderLookup.RegistryLookup.Delegate<>()
{
@Override protected RegistryLookup<T> parent() { return lookup; }
@Override public Stream<HolderSet.Named<T>> listTags() { return Stream.empty(); }
@Override public Optional<HolderSet.Named<T>> get(TagKey<T> key) { return Optional.of(HolderSet.emptyNamed(lookup, key)); }
};
}
}
@@ -43,6 +43,7 @@ import net.minecraftforge.common.crafting.IntersectionIngredient;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.common.data.ForgeBiomeTagsProvider;
import net.minecraftforge.common.data.ForgeFluidTagsProvider;
import net.minecraftforge.common.data.ForgeSpriteSourceProvider;
import net.minecraftforge.common.extensions.IForgeEntity;
import net.minecraftforge.common.extensions.IForgePlayer;
import net.minecraftforge.common.loot.CanToolPerformAction;
@@ -488,6 +489,8 @@ public class ForgeMod
gen.addProvider(event.includeServer(), new ForgeRecipeProvider(packOutput));
gen.addProvider(event.includeServer(), new ForgeLootTableProvider(packOutput));
gen.addProvider(event.includeServer(), new ForgeBiomeTagsProvider(packOutput, lookupProvider, existingFileHelper));
gen.addProvider(event.includeClient(), new ForgeSpriteSourceProvider(packOutput, existingFileHelper));
}
public void missingSoundMapping(MissingMappingsEvent event)
@@ -1,28 +1,70 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.data;
import net.minecraft.Util;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.RegistrySetBuilder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.PackOutput;
import net.minecraft.data.registries.RegistriesDatapackGenerator;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import net.minecraftforge.registries.DataPackRegistriesHooks;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
* An extension of the {@link RegistriesDatapackGenerator} which properly handles
* referencing existing dynamic registry objects within another dynamic registry
* object.
*/
public class DatapackBuiltinEntriesProvider extends RegistriesDatapackGenerator
{
public DatapackBuiltinEntriesProvider(PackOutput output, Supplier<HolderLookup.Provider> registriesSupplier)
/**
* Constructs a new datapack provider which generates all registry objects
* from the provided mods using the holder.
*
* @param output the target directory of the data generator
* @param registries a future of a lookup for registries and their objects
* @param modIds a set of mod ids to generate the dynamic registry objects of
*/
public DatapackBuiltinEntriesProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, Set<String> modIds)
{
super(output, CompletableFuture.supplyAsync(registriesSupplier, Util.backgroundExecutor()));
super(output, registries, modIds);
}
public DatapackBuiltinEntriesProvider(PackOutput output, RegistrySetBuilder datapackEntriesBuilder)
/**
* Constructs a new datapack provider which generates all registry objects
* from the provided mods using the holder. All entries that need to be
* bootstrapped are provided within the {@link RegistrySetBuilder}.
*
* @param output the target directory of the data generator
* @param registries a future of a lookup for registries and their objects
* @param datapackEntriesBuilder a builder containing the dynamic registry objects added by this provider
* @param modIds a set of mod ids to generate the dynamic registry objects of
*/
public DatapackBuiltinEntriesProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, RegistrySetBuilder datapackEntriesBuilder, Set<String> modIds)
{
this(output, () -> createLookup(datapackEntriesBuilder));
this(output, registries.thenApply(r -> constructRegistries(r, datapackEntriesBuilder)), modIds);
}
public static HolderLookup.Provider createLookup(RegistrySetBuilder datapackEntriesBuilder) {
return datapackEntriesBuilder.build(RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY));
/**
* A method used to construct empty bootstraps for all registries this provider
* did not touch such that existing dynamic registry objects do not get inlined.
*
* @param original a future of a lookup for registries and their objects
* @param datapackEntriesBuilder a builder containing the dynamic registry objects added by this provider
* @return a new lookup containing the existing and to be generated registries and their objects
*/
private static HolderLookup.Provider constructRegistries(HolderLookup.Provider original, RegistrySetBuilder datapackEntriesBuilder)
{
var builderKeys = new HashSet<>(datapackEntriesBuilder.getEntryKeys());
DataPackRegistriesHooks.getDataPackRegistriesWithDimensions().filter(data -> !builderKeys.contains(data.key())).forEach(data -> datapackEntriesBuilder.add(data.key(), context -> {}));
return datapackEntriesBuilder.buildPatch(RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY), original);
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.data;
import net.minecraft.advancements.Advancement;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.PackOutput;
import net.minecraft.data.advancements.AdvancementProvider;
import net.minecraft.data.advancements.AdvancementSubProvider;
import net.minecraft.resources.ResourceLocation;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* An extension of the {@link AdvancementProvider} to provide a feature-complete
* experience to generate modded advancements.
*/
public class ForgeAdvancementProvider extends AdvancementProvider
{
/**
* Constructs an advancement provider using the generators to write the
* advancements to a file.
*
* @param output the target directory of the data generator
* @param registries a future of a lookup for registries and their objects
* @param existingFileHelper a helper used to find whether a file exists
* @param subProviders the generators used to create the advancements
*/
public ForgeAdvancementProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, ExistingFileHelper existingFileHelper, List<AdvancementGenerator> subProviders)
{
super(output, registries, subProviders.stream().map(generator -> generator.toSubProvider(existingFileHelper)).toList());
}
/**
* An interface used to generated modded advancements. This is parallel to
* vanilla's {@link AdvancementSubProvider} with access to the {@link ExistingFileHelper}.
*
* @see AdvancementSubProvider
*/
public interface AdvancementGenerator
{
/**
* A method used to generate advancements for a mod. Advancements should be
* built via {@link net.minecraftforge.common.extensions.IForgeAdvancementBuilder#save(Consumer, ResourceLocation, ExistingFileHelper)}.
*
* @param registries a lookup for registries and their objects
* @param saver a consumer used to write advancements to a file
* @param existingFileHelper a helper used to find whether a file exists
*/
void generate(HolderLookup.Provider registries, Consumer<Advancement> saver, ExistingFileHelper existingFileHelper);
/**
* Creates an {@link AdvancementSubProvider} from this generator.
*
* @param existingFileHelper a helper used to find whether a file exists
* @return a sub provider wrapping this generator
*/
default AdvancementSubProvider toSubProvider(ExistingFileHelper existingFileHelper)
{
return (registries, saver) -> this.generate(registries, saver, existingFileHelper);
}
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.data;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import java.util.Optional;
public class ForgeSpriteSourceProvider extends SpriteSourceProvider
{
public ForgeSpriteSourceProvider(PackOutput output, ExistingFileHelper fileHelper)
{
super(output, fileHelper, "forge");
}
@Override
protected void addSources()
{
atlas(SpriteSourceProvider.BLOCKS_ATLAS).addSource(new SingleFile(new ResourceLocation("forge:white"), Optional.empty()));
}
}
@@ -13,14 +13,12 @@ import com.google.gson.JsonObject;
import com.mojang.serialization.JsonOps;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.loot.IGlobalLootModifier;
import net.minecraftforge.common.loot.LootModifier;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
@@ -37,14 +35,14 @@ import java.util.stream.Collectors;
public abstract class GlobalLootModifierProvider implements DataProvider
{
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private final DataGenerator gen;
private final PackOutput output;
private final String modid;
private final Map<String, JsonElement> toSerialize = new HashMap<>();
private boolean replace = false;
public GlobalLootModifierProvider(DataGenerator gen, String modid)
public GlobalLootModifierProvider(PackOutput output, String modid)
{
this.gen = gen;
this.output = output;
this.modid = modid;
}
@@ -66,8 +64,8 @@ public abstract class GlobalLootModifierProvider implements DataProvider
{
start();
Path forgePath = this.gen.getPackOutput().getOutputFolder(PackOutput.Target.DATA_PACK).resolve("forge").resolve("loot_modifiers").resolve("global_loot_modifiers.json");
Path modifierFolderPath = this.gen.getPackOutput().getOutputFolder(PackOutput.Target.DATA_PACK).resolve(this.modid).resolve("loot_modifiers");
Path forgePath = this.output.getOutputFolder(PackOutput.Target.DATA_PACK).resolve("forge").resolve("loot_modifiers").resolve("global_loot_modifiers.json");
Path modifierFolderPath = this.output.getOutputFolder(PackOutput.Target.DATA_PACK).resolve(this.modid).resolve("loot_modifiers");
List<ResourceLocation> entries = new ArrayList<>();
ImmutableList.Builder<CompletableFuture<?>> futuresBuilder = new ImmutableList.Builder<>();
@@ -32,7 +32,6 @@ import net.minecraft.server.packs.PackType;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.crafting.conditions.ICondition;
import net.minecraftforge.common.data.ExistingFileHelper.ResourceType;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.registries.DataPackRegistriesHooks;
import org.slf4j.Logger;
@@ -48,7 +47,7 @@ import org.slf4j.Logger;
public class JsonCodecProvider<T> implements DataProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
protected final DataGenerator dataGenerator;
protected final PackOutput output;
protected final ExistingFileHelper existingFileHelper;
protected final String modid;
protected final DynamicOps<JsonElement> dynamicOps;
@@ -59,14 +58,14 @@ public class JsonCodecProvider<T> implements DataProvider
protected Map<ResourceLocation, ICondition[]> conditions = Collections.emptyMap();
/**
* @param dataGenerator DataGenerator provided by {@link GatherDataEvent}.
* @param output {@linkplain PackOutput} provided by the {@link DataGenerator}.
* @param dynamicOps DynamicOps to encode values to jsons with using the provided Codec, e.g. {@link JsonOps#INSTANCE}.
* @param packType PackType specifying whether to generate entries in assets or data.
* @param directory String representing the directory to generate jsons in, e.g. "dimension" or "cheesemod/cheese".
* @param codec Codec to encode values to jsons with using the provided DynamicOps.
* @param entries Map of named entries to serialize to jsons. Paths for values are derived from the ResourceLocation's entryid:entrypath as specified above.
*/
public JsonCodecProvider(DataGenerator dataGenerator, ExistingFileHelper existingFileHelper, String modid, DynamicOps<JsonElement> dynamicOps, PackType packType,
public JsonCodecProvider(PackOutput output, ExistingFileHelper existingFileHelper, String modid, DynamicOps<JsonElement> dynamicOps, PackType packType,
String directory, Codec<T> codec, Map<ResourceLocation, T> entries)
{
// Track generated data so other dataproviders can validate if needed.
@@ -75,7 +74,7 @@ public class JsonCodecProvider<T> implements DataProvider
{
existingFileHelper.trackGenerated(id, resourceType);
}
this.dataGenerator = dataGenerator;
this.output = output;
this.existingFileHelper = existingFileHelper;
this.modid = modid;
this.dynamicOps = dynamicOps;
@@ -85,38 +84,10 @@ public class JsonCodecProvider<T> implements DataProvider
this.entries = entries;
}
/**
* {@return DatapackRegistryProvider that encodes using the registered loading codec for the provided registry key}
* Ensures the correct directory and codec are used.
* Only vanilla datapack registries enumerated in {@link RegistryAccess} and custom forge datapack registries can
* be generated this way.
*
* @param <T> Registry element type, e.g. Biome
* @param dataGenerator DataGenerator provided by {@link GatherDataEvent}.
* @param modid namespace of the mod adding this DataProvider, for logging purposes.
* @param registryOps RegistryOps to encode values to json with.
* @param registryKey ResourceKey identifying the registry and its directory.
* @param entries Map of entries to encode and their ResourceLocations. Paths for values are derived from the ResourceLocation's entryid:entrypath.
*/
@SuppressWarnings("unchecked")
public static <T> JsonCodecProvider<T> forDatapackRegistry(DataGenerator dataGenerator, ExistingFileHelper existingFileHelper, String modid,
RegistryOps<JsonElement> registryOps, ResourceKey<Registry<T>> registryKey, Map<ResourceLocation, T> entries)
{
final ResourceLocation registryId = registryKey.location();
// Minecraft datapack registry folders are in data/json-namespace/registry-name/
// Non-vanilla registry folders are data/json-namespace/registry-namespace/registry-name/
final String registryFolder = registryId.getNamespace().equals("minecraft")
? registryId.getPath()
: registryId.getNamespace() + "/" + registryId.getPath();
RegistryDataLoader.RegistryData<?> registryData = DataPackRegistriesHooks.getDataPackRegistries().stream().filter(data -> data.key() == registryKey).findAny().orElseThrow();
final Codec<T> codec = (Codec<T>) registryData.elementCodec();
return new JsonCodecProvider<>(dataGenerator, existingFileHelper, modid, registryOps, PackType.SERVER_DATA, registryFolder, codec, entries);
}
@Override
public CompletableFuture<?> run(final CachedOutput cache)
{
final Path outputFolder = this.dataGenerator.getPackOutput().getOutputFolder(this.packType == PackType.CLIENT_RESOURCES
final Path outputFolder = this.output.getOutputFolder(this.packType == PackType.CLIENT_RESOURCES
? PackOutput.Target.RESOURCE_PACK
: PackOutput.Target.DATA_PACK);
ImmutableList.Builder<CompletableFuture<?>> futuresBuilder = new ImmutableList.Builder<>();
@@ -165,4 +136,4 @@ public class JsonCodecProvider<T> implements DataProvider
this.conditions = conditions;
return this;
}
}
}
@@ -12,7 +12,6 @@ import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.world.effect.MobEffect;
@@ -24,12 +23,12 @@ import net.minecraft.world.level.block.Block;
public abstract class LanguageProvider implements DataProvider {
private final Map<String, String> data = new TreeMap<>();
private final DataGenerator gen;
private final PackOutput output;
private final String modid;
private final String locale;
public LanguageProvider(DataGenerator gen, String modid, String locale) {
this.gen = gen;
public LanguageProvider(PackOutput output, String modid, String locale) {
this.output = output;
this.modid = modid;
this.locale = locale;
}
@@ -41,7 +40,7 @@ public abstract class LanguageProvider implements DataProvider {
addTranslations();
if (!data.isEmpty())
return save(cache, this.gen.getPackOutput().getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(this.modid).resolve("lang").resolve(this.locale + ".json"));
return save(cache, this.output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(this.modid).resolve("lang").resolve(this.locale + ".json"));
return CompletableFuture.allOf();
}
@@ -13,7 +13,6 @@ import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -30,7 +29,7 @@ import org.apache.logging.log4j.Logger;
public abstract class SoundDefinitionsProvider implements DataProvider
{
private static final Logger LOGGER = LogManager.getLogger();
private final DataGenerator generator;
private final PackOutput output;
private final String modId;
private final ExistingFileHelper helper;
@@ -39,13 +38,13 @@ public abstract class SoundDefinitionsProvider implements DataProvider
/**
* Creates a new instance of this data provider.
*
* @param generator The data generator instance provided by the event you are initializing this provider in.
* @param output The {@linkplain PackOutput} instance provided by the data generator.
* @param modId The mod ID of the current mod.
* @param helper The existing file helper provided by the event you are initializing this provider in.
*/
protected SoundDefinitionsProvider(final DataGenerator generator, final String modId, final ExistingFileHelper helper)
protected SoundDefinitionsProvider(final PackOutput output, final String modId, final ExistingFileHelper helper)
{
this.generator = generator;
this.output = output;
this.modId = modId;
this.helper = helper;
}
@@ -63,7 +62,7 @@ public abstract class SoundDefinitionsProvider implements DataProvider
this.validate();
if (!this.sounds.isEmpty())
{
return this.save(cache, this.generator.getPackOutput().getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(this.modId).resolve("sounds.json"));
return this.save(cache, this.output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(this.modId).resolve("sounds.json"));
}
return CompletableFuture.allOf();
@@ -0,0 +1,82 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.data;
import com.mojang.serialization.JsonOps;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* <p>Data provider for atlas configuration files.<br>
* An atlas configuration is bound to a specific texture atlas such as the {@code minecraft:blocks} atlas and
* allows adding additional textures to the atlas by adding {@link SpriteSource}s to the configuration.</p>
* <p>See {@link SpriteSources} for the available sources and the constants in this class for the
* atlases used in vanilla Minecraft</p>
*/
public abstract class SpriteSourceProvider extends JsonCodecProvider<List<SpriteSource>>
{
protected static final ResourceLocation BLOCKS_ATLAS = new ResourceLocation("blocks");
protected static final ResourceLocation BANNER_PATTERNS_ATLAS = new ResourceLocation("banner_patterns");
protected static final ResourceLocation BEDS_ATLAS = new ResourceLocation("beds");
protected static final ResourceLocation CHESTS_ATLAS = new ResourceLocation("chests");
protected static final ResourceLocation SHIELD_PATTERNS_ATLAS = new ResourceLocation("shield_patterns");
protected static final ResourceLocation SHULKER_BOXES_ATLAS = new ResourceLocation("shulker_boxes");
protected static final ResourceLocation SIGNS_ATLAS = new ResourceLocation("signs");
protected static final ResourceLocation MOB_EFFECTS_ATLAS = new ResourceLocation("mob_effects");
protected static final ResourceLocation PAINTINGS_ATLAS = new ResourceLocation("paintings");
protected static final ResourceLocation PARTICLES_ATLAS = new ResourceLocation("particles");
private final Map<ResourceLocation, SourceList> atlases = new HashMap<>();
public SpriteSourceProvider(PackOutput output, ExistingFileHelper fileHelper, String modid)
{
super(output, fileHelper, modid, JsonOps.INSTANCE, PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, Map.of());
}
@Override
protected final void gather(BiConsumer<ResourceLocation, List<SpriteSource>> consumer)
{
addSources();
atlases.forEach((atlas, srcList) -> consumer.accept(atlas, srcList.sources));
}
protected abstract void addSources();
/**
* Get or create a {@link SourceList} for the given atlas
* @param atlas The texture atlas the sources should be added to, see constants at the top for the format
* and the vanilla atlases
* @return an existing {@code SourceList} for the given atlas or a new one if not present yet
*/
protected final SourceList atlas(ResourceLocation atlas)
{
return atlases.computeIfAbsent(atlas, $ -> new SourceList());
}
protected static final class SourceList
{
private final List<SpriteSource> sources = new ArrayList<>();
/**
* Add the given {@link SpriteSource} to this atlas configuration
* @param source The {@code SpriteSource} to be added
*/
public SourceList addSource(SpriteSource source)
{
sources.add(source);
return this;
}
}
}
@@ -5,7 +5,12 @@
package net.minecraftforge.common.extensions;
import java.util.Collection;
import org.jetbrains.annotations.Nullable;
import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraftforge.resource.DelegatingPackResources;
public interface IForgePackResources
{
@@ -17,7 +22,13 @@ public interface IForgePackResources
return false;
}
default void initForNamespace(final String nameSpace) {}
default void init(final PackType packType) {}
/**
* Gets a collection of {@code PackResource} instances nested inside this pack.
* Used to merge several packs into one entry in the resource pack selection UI without
* losing the ability for each pack to return a resource in
* {@link ResourceManager#getResourceStack(ResourceLocation)}
* @return Collection of nested {@code PackResource}, or null if this pack has no children
* @see DelegatingPackResources
*/
@Nullable default Collection<PackResources> getChildren() { return null; }
}
@@ -0,0 +1,317 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.util;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.Nullable;
import it.unimi.dsi.fastutil.Hash.Strategy;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
/**
* A mutable linked map with a hashing strategy and a merge function.
*
* @param <K> the type of keys
* @param <V> the type of mapped values
*/
public class MutableHashedLinkedMap<K, V> implements Iterable<Map.Entry<K, V>>
{
private final Strategy<? super K> strategy;
private final Map<K, Entry> entries;
private final MergeFunction<K, V> merge;
private Entry head = null;
private Entry last = null;
/**
* Creates a mutable linked map with a default new-value-selecting merge function.
*
* @param strategy the hashing strategy
*/
public MutableHashedLinkedMap(Strategy<? super K> strategy)
{
this(strategy, (k, v1, v2) -> v2);
}
/**
* Creates a mutable linked map with a custom merge function.
*
* @param strategy the hashing strategy
* @param merge the function used when merging an existing value and a new value
*/
public MutableHashedLinkedMap(Strategy<? super K> strategy, MergeFunction<K, V> merge)
{
this.strategy = strategy;
this.entries = new Object2ObjectOpenCustomHashMap<>(strategy);
this.merge = merge;
}
/**
* Inserts the mapping with the specified key and value pair.
*
* <p>If there is a mapping already associated with this key, then the previous value and the specified (new) value
* are merged according to this collection's merge function, and the position of the entry is not modified. If there
* is no such mapping, then the key-value mapping is inserted at the end of this collection.</p>
*
* @param key key to be inserted
* @param value (new) value to be associated with the key
* @return the previous value associated with the specified key, or {@code null} if there was no mapping for the key
*/
@Nullable
public V put(K key, V value)
{
var old = entries.get(key);
if (old != null)
{
V ret = old.value;
old.value = merge.apply(key, ret, value);
return ret;
}
var self = new Entry(key, value);
var l = last;
self.previous = l;
if (l == null)
head = self;
else
l.next = self;
last = self;
entries.put(key, self);
return null;
}
public boolean contains(K key) { return this.entries.containsKey(key); }
public boolean isEmpty() { return this.entries.isEmpty(); }
@Nullable
public V remove(K key)
{
var ret = this.entries.remove(key);
if (ret == null)
return null;
remove(ret);
return ret.getValue();
}
@Nullable
public V get(K key)
{
var entry = entries.get(key);
return entry == null ? null : entry.getValue();
}
@Override
public Iterator<Map.Entry<K, V>> iterator()
{
return new Iterator<>()
{
private Entry current = head;
@Override public boolean hasNext() { return current != null; }
@Override
public Map.Entry<K, V> next()
{
if (!hasNext())
throw new NoSuchElementException();
var ret = current;
current = current.next;
return ret;
}
};
}
/**
* Inserts the mapping with the specified key and value pair at the beginning of this map.
*
* <p>If there is a mapping already associated with this key, then the previous value and the specified (new) value
* are first merged according to this map's merge function, then the entry is moved to the beginning of the map.</p>
*
* @param key key to be inserted at the beginning
* @param value (new) value to be associated with the key
* @return the previous value associated with the specified key, or {@code null} if there was no mapping for the key
*
* @see #putBefore(Object, Object, Object)
*/
@Nullable
public V putFirst(K key, V value)
{
if (head != null)
return putBefore(head.getKey(), key, value);
return put(key, value);
}
/**
* Inserts the mapping with this key and value pair immediately after the entry with the specified positioning key.
*
* <p>If the specified positioning key is not present within this map, then this method behaves like
* {@link #put(Object, Object)}. If there is a mapping already associated with this key, then the previous value
* and the specified (new) value are first merged according to this map's merge function, then the entry is
* moved to directly after the entry with the specified positioning key.</p>
*
* @param after the key to position this new entry afterwards
* @param key key to be inserted at the beginning
* @param value (new) value to be associated with the key
* @return the previous value associated with the specified key, or {@code null} if there was no mapping for the key
*
* @see #putBefore(Object, Object, Object)
*/
@Nullable
public V putAfter(K after, K key, V value)
{
var target = entries.get(after);
if (target == null)
return put(key, value);
V ret = null;
var entry = entries.get(key);
if (entry != null)
{
ret = entry.value;
entry.value = merge.apply(key, ret, value);
remove(entry);
}
else
{
entry = new Entry(key, value);
entries.put(key, entry);
}
entry.previous = target;
if (target.next == null)
last = target;
else
target.next.previous = entry;
entry.next = target.next;
target.next = entry;
return ret;
}
/**
* Inserts the mapping with this key and value pair immediately before the entry with the specified positioning key.
*
* <p>If the specified positioning key is not present within this map, then this method behaves like
* {@link #put(Object, Object)}. If there is a mapping already associated with this key, then the previous value
* and the specified (new) value are first merged according to this map's merge function, then the entry is
* moved to directly before the entry with the specified positioning key.</p>
*
* @param before the key to position this new entry afterwards
* @param key key to be inserted at the beginning
* @param value (new) value to be associated with the key
* @return the previous value associated with the specified key, or {@code null} if there was no mapping for the key
*
* @see #putAfter(Object, Object, Object)
*/
@Nullable
public V putBefore(K before, K key, V value)
{
var target = entries.get(before);
if (target == null)
return put(key, value);
V ret = null;
var entry = entries.get(key);
if (entry != null)
{
ret = entry.value;
entry.value = merge.apply(key, ret, value);
remove(entry);
}
else
{
entry = new Entry(key, value);
entries.put(key, entry);
}
entry.previous = target.previous;
if (target.previous == null)
head = entry;
else
target.previous.next = entry;
entry.next = target;
target.previous = entry;
return ret;
}
private void remove(Entry e)
{
if (head == null)
head = e.next;
else if (e.previous != null) // Should never be null, but just in case.
{
e.previous.next = e.next;
e.previous = null;
}
if (last == e)
last = e.previous;
else if (e.next != null) // Should never be null, but just in case.
{
e.next.previous = e.previous;
e.next = null;
}
}
public static interface MergeFunction<Key, Value>
{
Value apply(Key key, Value left, Value right);
}
private class Entry implements Map.Entry<K, V>
{
private final K key;
private V value;
private Entry previous;
private Entry next;
private Entry(K key, V value)
{
this.key = key;
this.value = value;
}
@Override public K getKey() { return this.key; }
@Override public V getValue() { return this.value; }
@Override
public V setValue(V value)
{
var old = this.value;
this.value = value;
return old;
}
@Override
public String toString()
{
return "Entry[" + this.key + ", " + this.value + "]";
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?, ?>)o;
return (key == null ? e.getKey() == null : key.equals(e.getKey())) &&
(value == null ? e.getValue() == null : value.equals(e.getValue()));
}
@Override
public int hashCode()
{
return (key == null ? 0 : strategy.hashCode(key)) ^
(value == null ? 0 : value.hashCode());
}
}
}
@@ -7,15 +7,18 @@ package net.minecraftforge.event;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTab.Output;
import net.minecraft.world.item.CreativeModeTab.TabVisibility;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import net.minecraftforge.common.CreativeModeTabRegistry;
import net.minecraftforge.common.util.MutableHashedLinkedMap;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
@@ -106,58 +109,20 @@ public class CreativeModeTabEvent extends Event implements IModBusEvent
* This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.
*/
public static final class BuildContents extends CreativeModeTabEvent
public static final class BuildContents extends CreativeModeTabEvent implements Output
{
private final CreativeModeTab tab;
private final Set<DisplayItemsAdapter> customGenerators;
private final FeatureFlagSet flags;
private final boolean hasPermissions;
private final MutableHashedLinkedMap<ItemStack, TabVisibility> entries;
@ApiStatus.Internal
public BuildContents(CreativeModeTab tab, Set<DisplayItemsAdapter> customGenerators)
public BuildContents(CreativeModeTab tab, FeatureFlagSet flags, boolean hasPermissions, MutableHashedLinkedMap<ItemStack, TabVisibility> entries)
{
this.tab = tab;
this.customGenerators = customGenerators;
}
/**
* Registers the given generator.
*/
public void register(DisplayItemsAdapter generator)
{
this.customGenerators.add(generator);
}
/**
* Registers the given generator if the provided creative mode tab matches this event's currently populating tab.
*/
public void register(CreativeModeTab tab, DisplayItemsAdapter generator)
{
if (this.tab == tab)
this.customGenerators.add(generator);
}
/**
* Registers the given generator if the provided tab name matches this event's currently populating tab's name.
*/
public void register(ResourceLocation tabName, DisplayItemsAdapter generator)
{
if (this.tab == CreativeModeTabRegistry.getTab(tabName))
this.customGenerators.add(generator);
}
/**
* Adds the given item if the provided creative mode tab matches this event's currently populating tab.
*/
public void registerSimple(CreativeModeTab tab, ItemLike item)
{
this.register(tab, (enabledFlags, output, permissions) -> output.accept(item));
}
/**
* Adds the given items if the provided creative mode tab matches this event's currently populating tab.
*/
public void registerSimple(CreativeModeTab tab, ItemLike... items)
{
this.register(tab, (enabledFlags, output, permissions) -> output.acceptAll(items));
this.flags = flags;
this.hasPermissions = hasPermissions;
this.entries = entries;
}
/**
@@ -167,70 +132,36 @@ public class CreativeModeTabEvent extends Event implements IModBusEvent
{
return this.tab;
}
}
@FunctionalInterface
public interface DisplayItemsAdapter
{
void accept(FeatureFlagSet enabledFlags, CreativeModeTabPopulator populator, boolean hasPermissions);
}
@FunctionalInterface
public interface CreativeModeTabPopulator
{
void accept(ItemStack stack, CreativeModeTab.TabVisibility visibility, ItemStack before, ItemStack after);
default void accept(ItemStack stack, ItemStack before, ItemStack after)
public FeatureFlagSet getFlags()
{
accept(stack, CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS, before, after);
return this.flags;
}
default void accept(ItemLike stack, CreativeModeTab.TabVisibility visibility, ItemStack before, ItemStack after)
public boolean hasPermissions()
{
accept(new ItemStack(stack), visibility, before, after);
return this.hasPermissions;
}
default void accept(ItemLike stack, ItemStack before, ItemStack after)
public MutableHashedLinkedMap<ItemStack, TabVisibility> getEntries()
{
accept(stack, CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS, before, after);
return this.entries;
}
default void accept(ItemStack stack, CreativeModeTab.TabVisibility visibility)
@Override
public void accept(ItemStack stack, TabVisibility visibility)
{
accept(stack, visibility, ItemStack.EMPTY, ItemStack.EMPTY);
getEntries().put(stack, visibility);
}
default void accept(ItemStack stack)
public void accept(Supplier<? extends ItemLike> item, CreativeModeTab.TabVisibility visibility)
{
this.accept(stack, CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
this.accept(item.get(), visibility);
}
default void accept(ItemLike itemLike, CreativeModeTab.TabVisibility visibility)
public void accept(Supplier<? extends ItemLike> item)
{
this.accept(new ItemStack(itemLike), visibility);
}
default void accept(ItemLike itemLike)
{
this.accept(new ItemStack(itemLike), CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
}
default void acceptAll(Collection<ItemStack> stacks, CreativeModeTab.TabVisibility visibility)
{
stacks.forEach(stack -> this.accept(stack, visibility));
}
default void acceptAll(Collection<ItemStack> stacks)
{
this.acceptAll(stacks, CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
}
default void acceptAll(ItemLike... items)
{
for (ItemLike item : items)
{
this.accept(item);
}
this.accept(item.get());
}
}
}
@@ -624,7 +624,7 @@ public class ForgeEventFactory
return !blockGrowFeature(level, randomSource, pos, null).getResult().equals(Result.DENY);
}
public static SaplingGrowTreeEvent blockGrowFeature(LevelAccessor level, RandomSource randomSource, BlockPos pos, @Nullable Holder<? extends ConfiguredFeature<?, ?>> holder)
public static SaplingGrowTreeEvent blockGrowFeature(LevelAccessor level, RandomSource randomSource, BlockPos pos, @Nullable Holder<ConfiguredFeature<?, ?>> holder)
{
SaplingGrowTreeEvent event = new SaplingGrowTreeEvent(level, randomSource, pos, holder);
MinecraftForge.EVENT_BUS.post(event);
@@ -0,0 +1,51 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.event.entity.player;
import net.minecraft.world.entity.npc.AbstractVillager;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when a player trades with an {@link AbstractVillager}.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain Event.HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#SERVER logical server}.</p>
*/
public class TradeWithVillagerEvent extends PlayerEvent
{
private final MerchantOffer offer;
private final AbstractVillager abstractVillager;
@ApiStatus.Internal
public TradeWithVillagerEvent(Player player, MerchantOffer offer, AbstractVillager abstractVillager)
{
super(player);
this.offer = offer;
this.abstractVillager = abstractVillager;
}
/**
* {@return the {@link MerchantOffer} selected by the player to trade with}
*/
public MerchantOffer getMerchantOffer()
{
return offer;
}
/**
* {@return the villager the player traded with}
*/
public AbstractVillager getAbstractVillager()
{
return abstractVillager;
}
}
@@ -40,7 +40,7 @@ public class SaplingGrowTreeEvent extends LevelEvent
private final RandomSource randomSource;
private final BlockPos pos;
@Nullable
private Holder<? extends ConfiguredFeature<?, ?>> feature;
private Holder<ConfiguredFeature<?, ?>> feature;
@Deprecated(forRemoval = true, since = "1.19.2")
public SaplingGrowTreeEvent(LevelAccessor level, RandomSource randomSource, BlockPos pos)
@@ -48,7 +48,7 @@ public class SaplingGrowTreeEvent extends LevelEvent
this(level, randomSource, pos, null);
}
public SaplingGrowTreeEvent(LevelAccessor level, RandomSource randomSource, BlockPos pos, @Nullable Holder<? extends ConfiguredFeature<?, ?>> feature)
public SaplingGrowTreeEvent(LevelAccessor level, RandomSource randomSource, BlockPos pos, @Nullable Holder<ConfiguredFeature<?, ?>> feature)
{
super(level);
this.randomSource = randomSource;
@@ -76,15 +76,24 @@ public class SaplingGrowTreeEvent extends LevelEvent
* {@return the holder of the feature which will be placed, possibly null}
*/
@Nullable
public Holder<? extends ConfiguredFeature<?, ?>> getFeature() {
public Holder<ConfiguredFeature<?, ?>> getFeature()
{
return feature;
}
public void setFeature(@Nullable Holder<? extends ConfiguredFeature<?, ?>> feature) {
/**
* @param feature a {@linkplain Holder} referencing a tree feature to be placed instead of the current feature.
*/
public void setFeature(@Nullable Holder<ConfiguredFeature<?, ?>> feature)
{
this.feature = feature;
}
public void setFeature(ResourceKey<ConfiguredFeature<?, ?>> featureKey) {
/**
* @param featureKey a {@linkplain ResourceKey} referencing a tree feature to be placed instead of the current feature.
*/
public void setFeature(ResourceKey<ConfiguredFeature<?, ?>> featureKey)
{
this.feature = this.getLevel().registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).getHolder(featureKey).orElse(null);
}
}
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff. Show more