diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..68a16fe
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,227 @@
+# Contributing
+
+When contributing to this repository, please first discuss the change you wish to make via
+[GitHub issues](https://github.com/refinedmods/refinedstorage-jei-integration/issues), [Discord](https://discordapp.com/invite/VYzsydb),
+or any other method with the owners of this repository before making a change.
+
+## Quickstart
+
+These are the most important things to know before contributing (also explained in more detail later in this document):
+
+- Commit messages must adhere to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
+- Branch names must be formatted correctly. The format is `{category}/GH-{issue number}/{lowercase-description}`.
+ Category must match a
+ category [used in our Commitlint config](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional#type-enum).
+- We use [Checkstyle](https://checkstyle.sourceforge.io/) in our build workflow to validate coding style. It is
+ recommended to import the [config/checkstyle/checkstyle.xml](../config/checkstyle/checkstyle.xml)
+ or [config/intellij-code-style.xml](../config/intellij-code-style.xml) file into your
+ IDE, so that formatting rules are respected.
+- Branches are kept up to date by rebasing, not by merging.
+- For non-technical changes, adding a changelog entry is required.
+
+## Pull requests
+
+- Keep your pull request (PR) as small as possible, this makes reviewing easier.
+- Commits serve a clear purpose and have a fitting commit message.
+- Branches are kept up to date by rebasing (updating a branch by merging makes for a confusing Git history).
+- PRs are merged by merging the commits on top of the target branch (which is `develop`).
+- Remember to add your changes in `CHANGELOG.md`. If your changes are merely technical, it's not necessary to update the
+ changelog as it's not relevant for users.
+
+### Commit messages
+
+Commit messages must adhere to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). We
+use [Commitlint](https://commitlint.js.org/) to validate commit messages.
+
+We use
+the [conventional configuration](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional)
+for Commitlint.
+
+It is recommended to install
+the [Conventional Commit plugin](https://plugins.jetbrains.com/plugin/13389-conventional-commit) to make it
+easier to write commit messages.
+
+### Branch names
+
+Because we use merge commits when merging a PR, branch names will be part of the history of the repository. That is why
+branch names must follow a certain standard.
+
+The format is `{category}/GH-{issue number}/{lowercase-description}` and a branch name can be maximum 50 characters of
+length.
+
+Category must match a
+category [used in our Commitlint config](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional#type-enum).
+
+Valid examples are:
+
+- `fix/GH-123/add-branch-linting`
+- `docs/GH-123/cleanup`
+
+## Versioning
+
+This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## Changelog
+
+The changelog is kept in `CHANGELOG.md`.
+
+Keeping a readable, relevant and user-friendly changelog is essential for our end users
+to stay up to date with the project.
+
+Please refrain from using technical terminology or adding entries for technical changes
+that are (generally) not relevant to the end-user.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+
+### Javadoc
+
+Javadoc is available after every release on https://refinedmods.com/javadoc/.
+
+## Gitflow
+
+This project uses [Gitflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow).
+
+## Documentation
+
+Documentation must be kept up to date when adding or changing functionality.
+
+### API annotations
+
+Public APIs must be annotated with an `@API` annotation
+from [API Guardian](https://github.com/apiguardian-team/apiguardian).
+
+## Code style
+
+We use [Checkstyle](https://checkstyle.sourceforge.io/) in our build workflow to validate coding style.
+
+It is recommended to import the [config/checkstyle/checkstyle.xml](../config/checkstyle/checkstyle.xml)
+or [config/intellij-code-style.xml](../config/intellij-code-style.xml) file into your
+IDE, so that formatting rules are respected.
+
+Moreover, the [CheckStyle-IDEA plugin](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea) can be used to check
+if there are no style violations.
+
+## Architecture
+
+## Testing
+
+When adding functionality or fixing a bug, it is important to add tests. Tests are important, if not more important,
+than the implementation code.
+
+That means that they need to be first class citizens in the codebase, and must be readable
+at all times.
+
+They ensure that there are no regressions, act as general documentation for the codebase,
+and ensure that the project can evolve over time.
+
+To avoid brittle tests, tests need to validate behavior. A test cannot rely on the internal code structure, so most
+mocking should be avoided.
+
+### Test coverage
+
+Our [SonarQube quality gate](https://sonarcloud.io/organizations/refinedmods/quality_gates/show/9) requires a minimum
+test coverage percentage of 80%.
+
+### Mutation testing
+
+We also use [Pitest](https://pitest.org/) mutation testing.
+
+Our build workflow requires a minimum test coverage percentage of 80% and a minimum mutation
+coverage percentage of 90%.
+
+## Release process
+
+The release process is automated and follows Gitflow.
+
+Before running the "Draft release" workflow to start the release process make sure `CHANGELOG.md` contains all the
+unreleased changes.
+
+To determine the version number to be released, the workflow will ask you which release type this is (major, minor,
+patch).
+The latest version from `CHANGELOG.md` will be used as a base, and that will be incremented
+depending on the release type.
+
+`CHANGELOG.md` will be updated by this workflow, you can review this in the resulting release PR.
+
+If you merge the release PR, the "Publish release" workflow will automatically publish the release. An additional PR
+will be created to merge the changes in `CHANGELOG.md` back into `develop`.
+
+## Hotfix process
+
+The hotfix process is semi-automated and follows Gitflow:
+
+- Create a hotfix branch off `main`.
+- Commit your changes on this branch.
+- Update `CHANGELOG.md` (with version number and release date) manually on this branch.
+- Push the branch and create a PR for it, merging into `main`.
+
+The "Publish release" workflow will take care of the rest.
+
+## Workflows
+
+We have a few GitHub workflows:
+
+- Build (PRs, pushes to `develop` and `main`)
+- Draft release (manual trigger)
+- Publish release (merging a PR to `main`)
+- Validate changelog (PRs)
+ - To validate if `CHANGELOG.md` is valid and updated.
+ - Not every pull request needs a changelog change, so the `skip-changelog` label can be added to the pull request to
+ ignore this.
+- Validate commit messages (PRs)
+ - Validates whether the commits on a pull request
+ respect [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
+ - We use
+ the [conventional configuration](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional).
+- Validate branch names (PRs)
+- Issue for unsupported version (issues)
+ - Posts a message on a GitHub issue if the issue is about an unsupported version.
+- Lock resolved issues and PRs (every night)
+
+### Build
+
+The build workflow triggers when a pull request is updated or when a commit is pushed to `develop` or `main`.
+
+The build workflow takes care of the following:
+
+- Running a Gradle build, running our tests in the process and generating an aggregated code coverage report for the API
+ modules.
+- Analyzing the code on SonarQube.
+ > Because of
+ > [limitations with SonarQube](https://portal.productboard.com/sonarsource/1-sonarcloud/c/50-sonarcloud-analyzes-external-pull-request),
+ > pull requests originating from a fork aren't analyzed on SonarQube.
+
+- Code style validation with Checkstyle.
+- Mutation and line coverage test with Pitest.
+- Uploading the artifacts on the action.
+
+### Draft release
+
+The draft release workflow is a manual workflow which will create a release branch from `develop`.
+
+To determine the version number to be released, it will extract the latest version number from `CHANGELOG.md` and
+increment it depending on the release type selected.
+
+This workflow takes care of the following:
+
+- Creating the release branch.
+- Updating the changelog on this release branch.
+- Creating a pull request merging the release branch into `main`.
+
+### Publish release
+
+The "publish release" workflow is triggered when a release or hotfix PR is merged to `main`. Usually, this will be the
+PR created earlier in the "Draft release" workflow.
+
+The workflow takes care of the following:
+
+- Extracting the version number from the release or hotfix branch name that is merged in the PR.
+- Extracting the changelog entry for this version number.
+- Running a build.
+- Publishing on [GitHub packages](https://github.com/refinedmods/refinedstorage-jei-integration/packages) and
+ CreeperHost Maven.
+- Publishing Javadoc on [GitHub pages](https://github.com/refinedmods/javadoc).
+- Deploying on [GitHub releases](https://github.com/refinedmods/refinedstorage-jei-integration/releases).
+- Announcing the release on Discord and Twitter.
+- Creating a PR that merges `main` back into `develop` to get the changes to `CHANGELOG.md` and `build.gradle`
+ into `develop` from the draft release workflow.
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..bc4961c
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+patreon: raoulvdberge
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
new file mode 100644
index 0000000..60e28f8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -0,0 +1,59 @@
+name: Bug report
+description: Found a bug or encountered a crash? Please report it here.
+labels: [ bug ]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Provide a summary of the issue in the title above.
+ - type: textarea
+ id: description
+ attributes:
+ label: Describe the bug
+ description: |
+ Be as detailed as possible.
+ If applicable, also tell us what you expected to happen instead.
+ validations:
+ required: true
+ - type: textarea
+ id: reproduce
+ attributes:
+ label: How can we reproduce this bug or crash?
+ description: |
+ Provide us with steps on how to reproduce this issue.
+ placeholder: |
+ 1.
+ 2.
+ 3.
+ validations:
+ required: true
+ - type: dropdown
+ id: minecraft
+ attributes:
+ label: What Minecraft version is this happening on?
+ description: |
+ If your Minecraft version isn't listed here, it means that it's no longer supported. In that case, don't create an issue.
+ options:
+ - Minecraft 1.20.4
+ validations:
+ required: true
+ - type: input
+ id: modloader-version
+ attributes:
+ label: What NeoForge or Fabric version is this happening on?
+ validations:
+ required: true
+ - type: input
+ id: version
+ attributes:
+ label: What version is this happening on?
+ description: |
+ Ensure that you are using the latest version.
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ render: shell
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..9e5add6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+ - name: Discord Community Support
+ url: https://discordapp.com/invite/VYzsydb
+ about: Please ask and answer questions here. Issues should be used for bugs and feature requests.
diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml
new file mode 100644
index 0000000..5509c3a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/enhancement.yml
@@ -0,0 +1,23 @@
+name: Enhancement
+description: Do you have a suggestion for a new feature or improvement? Let us know.
+labels: [ enhancement ]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Provide a summary of the enhancement in the title above.
+
+ Please follow following guidelines before proposing an enchancement:
+ 1) Ensure that you are running on the latest version (to ensure that the enhancement does not exist yet).
+ 2) Ensure that your enhancement hasn't already been posted. Please look in the closed issues as well (for enhancements that have been denied).
+
+ We might close your issue, without explanation, if you do not follow these guidelines.
+ - type: textarea
+ id: describe
+ attributes:
+ label: Describe your enhancement
+ description: |
+ Be as detailed as possible.
+ Tell us how your idea should work. Why should we consider this?
+ validations:
+ required: true
diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md
new file mode 100644
index 0000000..e48eaf6
--- /dev/null
+++ b/.github/SUPPORT.md
@@ -0,0 +1,11 @@
+# Support
+
+If you have a problem and need help, we offer various channels where you can ask for help.
+
+## I have a question
+
+Questions can be asked on [Discord](https://discordapp.com/invite/VYzsydb).
+
+## I have found a bug
+
+If you have found a bug, please report it on [GitHub issues](https://github.com/refinedmods/refinedstorage-jei-integration/issues).
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..74e8b46
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,14 @@
+name: Build
+on:
+ push:
+ branches:
+ - develop
+ - main
+ pull_request:
+ types: [ opened, synchronize, reopened ]
+jobs:
+ build:
+ uses: refinedmods/refinedarchitect/.github/workflows/build.yml@v0.12.1
+ with:
+ mutation-testing: false
+ secrets: inherit
diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml
new file mode 100644
index 0000000..6fad3ac
--- /dev/null
+++ b/.github/workflows/draft-release.yml
@@ -0,0 +1,24 @@
+name: Draft release
+on:
+ workflow_dispatch:
+ inputs:
+ release-type:
+ description: 'Release type'
+ required: true
+ default: 'minor'
+ type: choice
+ options:
+ - major
+ - minor
+ - patch
+ version-number-override:
+ description: 'Version number override'
+ required: false
+ type: string
+jobs:
+ draft:
+ uses: refinedmods/refinedarchitect/.github/workflows/draft-release.yml@v0.12.1
+ with:
+ release-type: ${{ inputs.release-type }}
+ version-number-override: ${{ inputs.version-number-override }}
+ secrets: inherit
diff --git a/.github/workflows/issue-for-unsupported-version.yml b/.github/workflows/issue-for-unsupported-version.yml
new file mode 100644
index 0000000..1664acf
--- /dev/null
+++ b/.github/workflows/issue-for-unsupported-version.yml
@@ -0,0 +1,7 @@
+name: Issue for unsupported version
+on:
+ issues:
+ types: [ labeled, unlabeled, reopened ]
+jobs:
+ unsupported-labeler:
+ uses: refinedmods/refinedarchitect/.github/workflows/issue-for-unsupported-version.yml@v0.12.1
\ No newline at end of file
diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
new file mode 100644
index 0000000..52d0e4d
--- /dev/null
+++ b/.github/workflows/publish-release.yml
@@ -0,0 +1,14 @@
+name: Publish release
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - closed
+jobs:
+ publish-release:
+ uses: refinedmods/refinedarchitect/.github/workflows/publish-release.yml@v0.12.1
+ secrets: inherit
+ with:
+ project-name: 'Refined Storage - JEI Integration'
+ mutation-testing: false
\ No newline at end of file
diff --git a/.github/workflows/resolved-issue-locking.yml b/.github/workflows/resolved-issue-locking.yml
new file mode 100644
index 0000000..6023644
--- /dev/null
+++ b/.github/workflows/resolved-issue-locking.yml
@@ -0,0 +1,7 @@
+name: Lock resolved issues and PRs
+on:
+ schedule:
+ - cron: '0 0 * * *'
+jobs:
+ lock:
+ uses: refinedmods/refinedarchitect/.github/workflows/resolved-issue-locking.yml@v0.12.1
\ No newline at end of file
diff --git a/.github/workflows/validate-branch-name.yml b/.github/workflows/validate-branch-name.yml
new file mode 100644
index 0000000..c862e1c
--- /dev/null
+++ b/.github/workflows/validate-branch-name.yml
@@ -0,0 +1,5 @@
+name: Validate branch name
+on: [ pull_request ]
+jobs:
+ validate-branch-name:
+ uses: refinedmods/refinedarchitect/.github/workflows/validate-branch-name.yml@v0.12.1
\ No newline at end of file
diff --git a/.github/workflows/validate-changelog.yml b/.github/workflows/validate-changelog.yml
new file mode 100644
index 0000000..8c6b2e8
--- /dev/null
+++ b/.github/workflows/validate-changelog.yml
@@ -0,0 +1,7 @@
+name: Validate changelog
+on:
+ pull_request:
+ types: [ opened, synchronize, reopened, ready_for_review, labeled, unlabeled ]
+jobs:
+ validate-changelog:
+ uses: refinedmods/refinedarchitect/.github/workflows/validate-changelog.yml@v0.12.1
\ No newline at end of file
diff --git a/.github/workflows/validate-commit-messages.yml b/.github/workflows/validate-commit-messages.yml
new file mode 100644
index 0000000..306fbb8
--- /dev/null
+++ b/.github/workflows/validate-commit-messages.yml
@@ -0,0 +1,5 @@
+name: Validate commit messages
+on: [ pull_request ]
+jobs:
+ validate-commit-messages:
+ uses: refinedmods/refinedarchitect/.github/workflows/validate-commit-messages.yml@v0.12.1
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..924b02b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+.gradle/
+.nb-gradle/
+.settings/
+build/
+eclipse/
+.classpath
+.nb-gradle-properties
+.project
+*.launch
+*.iml
+*.ipr
+*.iws
+.idea/
+out/
+/bin/
+logs/
+.cache/
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..d0262ca
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
+to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## Unreleased
+
+### Added
+
+- Initial release.
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..e9057d6
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,20 @@
+# The MIT License (MIT)
+
+Copyright © 2024 Refined Mods
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2228b35
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# Refined Storage - JEI Integration [![Build Status](https://github.com/refinedmods/refinedstorage-jei-integration/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/refinedmods/refinedstorage-jei-integration/actions/workflows/build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=refinedmods_refinedstorage-jei-integration&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=refinedmods_refinedstorage-jei-integration) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=refinedmods_refinedstorage-jei-integration&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=refinedmods_refinedstorage-jei-integration) [![Discord](https://img.shields.io/discord/342942776494653441)](https://discordapp.com/invite/VYzsydb) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md)
+
+## About
+
+JEI integration for [Refined Storage](https://github.com/refinedmods/refinedstorage2).
+
+## Links
+
+- [GitHub](https://github.com/refinedmods/refinedstorage-jei-integration)
+ - [Releases](https://github.com/refinedmods/refinedstorage-jei-integration/releases)
+ - [Packages](https://github.com/refinedmods/refinedstorage-jei-integration/packages)
+ - [Issues](https://github.com/refinedmods/refinedstorage-jei-integration/issues)
+ - [Refined Mods on GitHub](https://github.com/refinedmods)
+- [Discord](https://discordapp.com/invite/VYzsydb)
+- [Twitter](https://twitter.com/refinedmods)
+- [Mastodon](https://anvil.social/@refinedmods)
+
+## Building
+
+Clone the repository and import the Gradle project.
+
+## Contributing
+
+See [CONTRIBUTING.md](.github/CONTRIBUTING.md).
+
+## Support
+
+See [SUPPORT.md](.github/SUPPORT.md).
+
+## Changelog
+
+See [CHANGELOG.md](CHANGELOG.md).
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..7f2727d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,7 @@
+apply from: "https://raw.githubusercontent.com/refinedmods/refinedarchitect/v${gradle.ext.refinedArchitectVersion}/helper.gradle"
+
+subprojects {
+ group = 'com.refinedmods.refinedstorage'
+}
+
+enableSonarQube("refinedmods_refinedstorage-jei-integration")
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle-imports.xml b/config/checkstyle/checkstyle-imports.xml
new file mode 100644
index 0000000..6788c49
--- /dev/null
+++ b/config/checkstyle/checkstyle-imports.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml
new file mode 100644
index 0000000..3d00140
--- /dev/null
+++ b/config/checkstyle/checkstyle-suppressions.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000..87191bb
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,275 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/intellij-code-style.xml b/config/intellij-code-style.xml
new file mode 100644
index 0000000..be27458
--- /dev/null
+++ b/config/intellij-code-style.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..c408a30
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,3 @@
+# Gradle
+org.gradle.jvmargs=-Xmx1G
+refinedstorageVersion=2.0.0-milestone.3.7
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..249e583
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b82aa23
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..a69d9cb
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f127cfd
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/refinedstorage-jei-integration-common/build.gradle b/refinedstorage-jei-integration-common/build.gradle
new file mode 100644
index 0000000..7905ef1
--- /dev/null
+++ b/refinedstorage-jei-integration-common/build.gradle
@@ -0,0 +1,27 @@
+archivesBaseName = 'refinedstorage-jei-integration-common'
+
+repositories {
+ maven {
+ name = 'JEI'
+ url = "https://maven.blamejared.com/"
+ }
+ maven {
+ url = uri("https://maven.pkg.github.com/refinedmods/refinedstorage2")
+ credentials {
+ username = "anything"
+ password = "\u0067hp_oGjcDFCn8jeTzIj4Ke9pLoEVtpnZMP4VQgaX"
+ }
+ }
+}
+
+commonProject()
+
+dependencies {
+ compileOnly common.jei.api
+ implementation "com.refinedmods.refinedstorage2:refinedstorage2-platform-common:${refinedstorageVersion}"
+ implementation "com.refinedmods.refinedstorage2:refinedstorage2-platform-api:${refinedstorageVersion}"
+ implementation "com.refinedmods.refinedstorage2:refinedstorage2-resource-api:${refinedstorageVersion}"
+ implementation "com.refinedmods.refinedstorage2:refinedstorage2-grid-api:${refinedstorageVersion}"
+}
+
+enablePublishing()
\ No newline at end of file
diff --git a/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/ClickableIngredient.java b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/ClickableIngredient.java
new file mode 100644
index 0000000..6249589
--- /dev/null
+++ b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/ClickableIngredient.java
@@ -0,0 +1,25 @@
+package com.refinedmods.refinedstorage.jei.common;
+
+import mezz.jei.api.ingredients.ITypedIngredient;
+import mezz.jei.api.runtime.IClickableIngredient;
+import net.minecraft.client.renderer.Rect2i;
+
+class ClickableIngredient implements IClickableIngredient {
+ private final ITypedIngredient ingredient;
+ private final Rect2i area;
+
+ ClickableIngredient(final ITypedIngredient ingredient, final int x, final int y) {
+ this.ingredient = ingredient;
+ area = new Rect2i(x, y, 16, 16);
+ }
+
+ @Override
+ public ITypedIngredient getTypedIngredient() {
+ return ingredient;
+ }
+
+ @Override
+ public Rect2i getArea() {
+ return area;
+ }
+}
diff --git a/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/Common.java b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/Common.java
new file mode 100644
index 0000000..7914423
--- /dev/null
+++ b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/Common.java
@@ -0,0 +1,20 @@
+package com.refinedmods.refinedstorage.jei.common;
+
+import static java.util.Objects.requireNonNull;
+
+public final class Common {
+ public static final String MOD_ID = "refinedstorage_jei_integration";
+
+ private static Platform platform;
+
+ private Common() {
+ }
+
+ public static void setPlatform(final Platform platform) {
+ Common.platform = platform;
+ }
+
+ public static Platform getPlatform() {
+ return requireNonNull(platform, "Platform isn't set yet");
+ }
+}
diff --git a/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/CraftingGridRecipeTransferHandler.java b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/CraftingGridRecipeTransferHandler.java
new file mode 100644
index 0000000..886224d
--- /dev/null
+++ b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/CraftingGridRecipeTransferHandler.java
@@ -0,0 +1,102 @@
+package com.refinedmods.refinedstorage.jei.common;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+import com.refinedmods.refinedstorage2.api.resource.list.ResourceList;
+import com.refinedmods.refinedstorage2.platform.common.content.Menus;
+import com.refinedmods.refinedstorage2.platform.common.grid.CraftingGridContainerMenu;
+import com.refinedmods.refinedstorage2.platform.common.support.resource.ItemResource;
+import mezz.jei.api.constants.RecipeTypes;
+import mezz.jei.api.gui.ingredient.IRecipeSlotView;
+import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
+import mezz.jei.api.recipe.RecipeIngredientRole;
+import mezz.jei.api.recipe.RecipeType;
+import mezz.jei.api.recipe.transfer.IRecipeTransferError;
+import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.CraftingRecipe;
+import net.minecraft.world.item.crafting.RecipeHolder;
+
+class CraftingGridRecipeTransferHandler implements
+ IRecipeTransferHandler> {
+ @Override
+ public Class extends CraftingGridContainerMenu> getContainerClass() {
+ return CraftingGridContainerMenu.class;
+ }
+
+ @Override
+ public Optional> getMenuType() {
+ return Optional.of(Menus.INSTANCE.getCraftingGrid());
+ }
+
+ @Override
+ public RecipeType> getRecipeType() {
+ return RecipeTypes.CRAFTING;
+ }
+
+ @Override
+ @Nullable
+ public IRecipeTransferError transferRecipe(final CraftingGridContainerMenu containerMenu,
+ final RecipeHolder recipe,
+ final IRecipeSlotsView recipeSlots,
+ final Player player,
+ final boolean maxTransfer,
+ final boolean doTransfer) {
+ if (doTransfer) {
+ doTransfer(recipeSlots, containerMenu);
+ return null;
+ }
+ final ResourceList available = containerMenu.getAvailableListForRecipeTransfer();
+ final List missingSlots = findMissingSlots(recipeSlots, available);
+ return missingSlots.isEmpty() ? null : new MissingItemRecipeTransferError(missingSlots);
+ }
+
+ private void doTransfer(final IRecipeSlotsView recipeSlots, final CraftingGridContainerMenu containerMenu) {
+ final List> inputs = getInputs(recipeSlots);
+ containerMenu.transferRecipe(inputs);
+ }
+
+ private List findMissingSlots(final IRecipeSlotsView recipeSlots, final ResourceList available) {
+ return recipeSlots.getSlotViews(RecipeIngredientRole.INPUT).stream().filter(slotView -> {
+ if (slotView.isEmpty()) {
+ return false;
+ }
+ return !isAvailable(available, slotView);
+ }).toList();
+ }
+
+ private boolean isAvailable(final ResourceList available, final IRecipeSlotView slotView) {
+ final List possibilities = slotView.getItemStacks().toList();
+ for (final ItemStack possibility : possibilities) {
+ final ItemResource possibilityResource = ItemResource.ofItemStack(possibility);
+ if (available.remove(possibilityResource, 1).isPresent()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private List> getInputs(final IRecipeSlotsView recipeSlots) {
+ return recipeSlots.getSlotViews(RecipeIngredientRole.INPUT).stream().map(slotView -> {
+ final List stacks = slotView.getItemStacks().collect(Collectors.toList());
+ prioritizeDisplayedStack(slotView, stacks);
+ return stacks.stream().map(ItemResource::ofItemStack).toList();
+ }).toList();
+ }
+
+ private void prioritizeDisplayedStack(final IRecipeSlotView slotView, final List stacks) {
+ slotView.getDisplayedItemStack().ifPresent(displayed -> {
+ final int index = stacks.indexOf(displayed);
+ if (index > 0) {
+ return;
+ }
+ stacks.remove(index);
+ stacks.add(0, displayed);
+ });
+ }
+}
diff --git a/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/ExclusionZonesGuiContainerHandler.java b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/ExclusionZonesGuiContainerHandler.java
new file mode 100644
index 0000000..112a0da
--- /dev/null
+++ b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/ExclusionZonesGuiContainerHandler.java
@@ -0,0 +1,14 @@
+package com.refinedmods.refinedstorage.jei.common;
+
+import java.util.List;
+
+import com.refinedmods.refinedstorage2.platform.common.support.AbstractBaseScreen;
+import mezz.jei.api.gui.handlers.IGuiContainerHandler;
+import net.minecraft.client.renderer.Rect2i;
+
+class ExclusionZonesGuiContainerHandler implements IGuiContainerHandler> {
+ @Override
+ public List getGuiExtraAreas(final AbstractBaseScreen> screen) {
+ return screen.getExclusionZones();
+ }
+}
diff --git a/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/GhostIngredientHandler.java b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/GhostIngredientHandler.java
new file mode 100644
index 0000000..6b4dcae
--- /dev/null
+++ b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/GhostIngredientHandler.java
@@ -0,0 +1,86 @@
+package com.refinedmods.refinedstorage.jei.common;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.refinedmods.refinedstorage2.platform.api.recipemod.IngredientConverter;
+import com.refinedmods.refinedstorage2.platform.api.support.resource.PlatformResourceKey;
+import com.refinedmods.refinedstorage2.platform.common.Platform;
+import com.refinedmods.refinedstorage2.platform.common.support.AbstractBaseScreen;
+import com.refinedmods.refinedstorage2.platform.common.support.containermenu.AbstractResourceContainerMenu;
+import com.refinedmods.refinedstorage2.platform.common.support.containermenu.ResourceSlot;
+import mezz.jei.api.gui.handlers.IGhostIngredientHandler;
+import mezz.jei.api.ingredients.ITypedIngredient;
+import net.minecraft.client.renderer.Rect2i;
+
+@SuppressWarnings("rawtypes")
+class GhostIngredientHandler implements IGhostIngredientHandler {
+ private final IngredientConverter ingredientConverter;
+
+ GhostIngredientHandler(final IngredientConverter ingredientConverter) {
+ this.ingredientConverter = ingredientConverter;
+ }
+
+ @Override
+ public List> getTargetsTyped(final AbstractBaseScreen screen,
+ final ITypedIngredient ingredient,
+ final boolean doStart) {
+ if (screen.getMenu() instanceof AbstractResourceContainerMenu menu) {
+ return getTargets(screen, ingredient.getIngredient(), menu);
+ }
+ return Collections.emptyList();
+ }
+
+ private List> getTargets(final AbstractBaseScreen screen,
+ final I ingredient,
+ final AbstractResourceContainerMenu menu) {
+ final List> targets = new ArrayList<>();
+ ingredientConverter.convertToResource(ingredient).ifPresent(resource -> {
+ for (final ResourceSlot slot : menu.getResourceSlots()) {
+ if (slot.isFilter() && slot.isValid(resource)) {
+ final Rect2i bounds = getBounds(screen, slot);
+ targets.add(new TargetImpl<>(bounds, slot.index));
+ }
+ }
+ });
+ return targets;
+ }
+
+ private Rect2i getBounds(final AbstractBaseScreen screen, final ResourceSlot slot) {
+ return new Rect2i(screen.getLeftPos() + slot.x, screen.getTopPos() + slot.y, 17, 17);
+ }
+
+ @Override
+ public void onComplete() {
+ // no op
+ }
+
+ private class TargetImpl implements Target {
+ private final Rect2i area;
+ private final int slotIndex;
+
+ TargetImpl(final Rect2i area, final int slotIndex) {
+ this.area = area;
+ this.slotIndex = slotIndex;
+ }
+
+ @Override
+ public Rect2i getArea() {
+ return area;
+ }
+
+ @Override
+ public void accept(final I ingredient) {
+ ingredientConverter.convertToResource(ingredient).ifPresent(this::accept);
+ }
+
+ private void accept(final PlatformResourceKey resource) {
+ Platform.INSTANCE.getClientToServerCommunications().sendResourceFilterSlotChange(
+ resource,
+ slotIndex
+ );
+ }
+ }
+}
+
diff --git a/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/GridGuiContainerHandler.java b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/GridGuiContainerHandler.java
new file mode 100644
index 0000000..b4ca9d6
--- /dev/null
+++ b/refinedstorage-jei-integration-common/src/main/java/com/refinedmods/refinedstorage/jei/common/GridGuiContainerHandler.java
@@ -0,0 +1,52 @@
+package com.refinedmods.refinedstorage.jei.common;
+
+import java.util.Optional;
+
+import com.refinedmods.refinedstorage2.platform.api.grid.view.PlatformGridResource;
+import com.refinedmods.refinedstorage2.platform.api.recipemod.IngredientConverter;
+import com.refinedmods.refinedstorage2.platform.api.support.resource.PlatformResourceKey;
+import com.refinedmods.refinedstorage2.platform.common.grid.screen.AbstractGridScreen;
+import mezz.jei.api.gui.handlers.IGuiContainerHandler;
+import mezz.jei.api.ingredients.IIngredientHelper;
+import mezz.jei.api.ingredients.ITypedIngredient;
+import mezz.jei.api.runtime.IClickableIngredient;
+import mezz.jei.api.runtime.IIngredientManager;
+
+class GridGuiContainerHandler implements IGuiContainerHandler> {
+ private final IngredientConverter converter;
+ private final IIngredientManager ingredientManager;
+
+ GridGuiContainerHandler(final IngredientConverter converter, final IIngredientManager ingredientManager) {
+ this.converter = converter;
+ this.ingredientManager = ingredientManager;
+ }
+
+ @Override
+ public Optional> getClickableIngredientUnderMouse(
+ final AbstractGridScreen screen,
+ final double mouseX,
+ final double mouseY
+ ) {
+ final PlatformGridResource resource = screen.getCurrentGridResource();
+ if (resource == null) {
+ return Optional.empty();
+ }
+ final PlatformResourceKey underlyingResource = resource.getUnderlyingResource();
+ if (underlyingResource == null) {
+ return Optional.empty();
+ }
+ return converter.convertToIngredient(underlyingResource).flatMap(
+ ingredient -> convertToClickableIngredient(mouseX, mouseY, ingredient)
+ );
+ }
+
+ private Optional> convertToClickableIngredient(final double x,
+ final double y,
+ final Object ingredient) {
+ final IIngredientHelper