I've managed to hide recipes depending on whether a player has a stage or not, but I've hit a road block where the unhidden recipes only show once you relog.
The code I have right now sends all the gamestages a player has to the client, and updates JEI to unhide some recipes or recipe categories for a given gamestage. Is there a way I can force JEI to re-index it's recipes?
#Dynamically hidden recipes and recipe categories do not update until relog
37 messages · Page 1 of 1 (latest)
Once your ticket has been resolved, please close it with </ticket close:1054771505520717835> command!
const JEIStageHelper = {
/**
* @param {Internal.IRecipeManager} recipeManager
* @param {boolean} hide
* @param {string} recipeType
* @param {string[]} recipeIds
*/
setHideRecipes(recipeManager, hide, recipeType, recipeIds){
console.log('toggled hiding recipes: ' +hide)
console.log(recipeIds)
const recipeTypeMan = recipeManager.getRecipeType(recipeType).get()
const recipeList = recipeManager.createRecipeLookup(recipeTypeMan).get()
.filter(recipe => {
return recipeIds.reduce(recipeId => {recipeId == recipe.id}) > 0
// return recipeIds.includes(recipe.getId().toString())
}).toList()
if(hide) {
return recipeManager.hideRecipes(recipeTypeMan, recipeList)
}
recipeManager.unhideRecipes(recipeTypeMan, recipeList)
},
/**
* @param {Internal.IRecipeManager} recipeManager
* @param {boolean} hide
* @param {string} recipeType
*/
setHideRecipeCategory(recipeManager, hide, recipeType){
console.log('toggled hiding recipe category: ' +hide)
const recipeTypeMan = recipeManager.getRecipeType(recipeType).get()
if(hide) return recipeManager.hideRecipeCategory(recipeTypeMan)
recipeManager.unhideRecipeCategory(recipeTypeMan)
},
/**
*
* @param {Internal.ListTag} stageUpdateData
* @param {string} stage
*/
hasStage(stageUpdateData, stage){
console.log(stageUpdateData)
const stageArr = stageUpdateData
for (let i = 0; i < stageArr.length; i++) {
if(stage == stageArr[i]) return true
}
return false
}
}
const STAGES = global.constants.gamestages
const stages = Object.keys(STAGES)
NetworkEvents.dataReceived('stage_update', event => {
const {data} = event
const playerStagesListTag = data.player_stages
const JEIRecipeManager = global.jeiRuntime.recipeManager
for(let stage in STAGES){
let recipeTypes = STAGES[stage]
let playerHasStage = JEIStageHelper.hasStage(playerStagesListTag, stage)
for(let recipeType in recipeTypes){
let recipes = recipeTypes[recipeType]
recipes === 'hide_category' ?
JEIStageHelper.setHideRecipeCategory(JEIRecipeManager, !playerHasStage, recipeType) :
JEIStageHelper.setHideRecipes(JEIRecipeManager, !playerHasStage, recipeType, recipes)
}
}
})
Just to note, I've been using JEI to interface with EMI before this, so if someone somehow knows how to do this with EMI, that would be extremely helpful, though so far it doesn't seem like EMI can dynamically hide recipes at runtime unless you do it through resource packs
AStages is able to dynamically hide and unhide EMI items but I'm not too sure how they achieve it.
It does have KubeJS support, so you could try to integrate it into your script.
I've gotten that part down, I'm looking for how I can hide recipes, which doesn't quite works on both JEI and EMI, both of those recipe viewers don't work too well when you try to manage recipes dynamically
And I get that but AStages unhides the recipes without a relog
it only prevents using the recipes, they're still visible in recipe viewers as far as I'm aware, not to mention it doesn't work at all with non-vanilla recipe categories
Astages does hide the recipes too, there is an attribute boolean for that and the machine recipe stages mod iirc does other types than vanilla
But i think i have some code with just the jei api somewhere lurking that did recipe hiding and showing, i will come back to this
maybe i lied 
recipeManager.createRecipeLookup(recipeTypeMan).get()
.filter(recipe => {
return recipeIds.reduce(recipeId => {recipeId == recipe.id}) > 0
// return recipeIds.includes(recipe.getId().toString())
}).toList()
if(hide) {
return recipeManager.hideRecipes(recipeTypeMan, recipeList)
this part should be the issue
.createRecipeLookup(recipeTypeMan).includeHidden().get()
the includeHidden() is important for showing
setHideRecipes(recipeManager, hide, recipeType, recipeIds){
console.log('toggled hiding recipes for category '+recipeType+': ' +hide)
// console.log(recipeIds)
const recipeTypeMan = recipeManager.getRecipeType(recipeType).get()
const recipeList = recipeManager.createRecipeLookup(recipeTypeMan).includeHidden().get()
.filter(recipe => {
return recipeIds.length > 0 ?
recipeIds.reduce(recipeId => {
return recipe.id.toString().match(recipeId) > 0
}) > 0 :
false
}).toList()
if(hide) {
return recipeManager.hideRecipes(recipeTypeMan, recipeList)
}
recipeManager.unhideRecipes(recipeTypeMan, recipeList)
},
I've tried with and without .includeHidden(). I don't think I see a difference? What exactly is considered hidden by JEI here?
const runtime = global.jeiRuntime;
const recipeManager = runtime.recipeManager;
function hideRecipes() {
let recipeCategories = recipeManager.createRecipeCategoryLookup().includeHidden().get().toList();
recipeCategories.forEach(category =>{
let recipeType = category.getRecipeType()
let recipiesToHide = recipeManager.createRecipeLookup(recipeType).get().toArray();
console.log('now logging recipes')
console.log(recipiesToHide)
recipeManager.hideRecipes(recipeType, recipiesToHide)
})
}
function showRecipes() {
let recipeCategories = recipeManager.createRecipeCategoryLookup().includeHidden().get().toList();
recipeCategories.forEach(category =>{
let recipeType = category.getRecipeType()
let recipiesToShow = recipeManager.createRecipeLookup(recipeType).includeHidden().get().toArray();
console.log('now logging recipes')
console.log(recipiesToShow)
recipeManager.unhideRecipes(recipeType, recipiesToShow)
})
}
NetworkEvents.dataReceived('channel_show', event =>{
showRecipes();
event.player.tell('data received')
})
NetworkEvents.dataReceived('channel_hide', event =>{
hideRecipes();
event.player.tell('data received')
})
this works, only downside are the const, they are only defined after reload or second join of the game since jei isnt initialized before the scripts run but it is more readable like this
let me know if you can work with that or get stuck somewhere else
i also checked astages github, recipestaging does hide there: https://github.com/Alessandro-Casale/AStages/blob/1.20.X/src/main/java/com/alessandro/astages/integration/jei/ARecipeStagesJEIPlugin.java
the code I initially put here was pretty close to what you have here, just with .includeHidden() added in and that I just send the client every stage the player has instead. I added in .includeHidden() and I don't think I noticed any difference, but that might be because I use mod based restrictions instead. Maybe an oversight?
Either way, what I have already works, though JEI doesn't update when you're in-world unless I force the client to run the /reload command. If there's a way to just force only JEI to update I'd love to know how but what I have works. I've more or less ignored ASfages' recipe hiding since it doesn't work for non-vanilla recipe categories anyway. I guess the most unfortunate thing about all this is that EMI can't do recipe hiding unless I get into loading asset data, and unless there's a way to avoid the huge memory leaks that happen form reloading resources, I'd rather not go that route for now
const JEIStageHelper = {
/**
* @param {Internal.IRecipeManager} recipeManager
* @param {boolean} hide
* @param {string} recipeTypeId
* @param {string[]} recipeIds
*/
setHideRecipes(recipeManager, hide, recipeTypeId, recipeIds){
console.log('toggled hiding recipes for category '+recipeTypeId+': ' +hide)
// console.log(recipeIds)
const recipeType = recipeManager.getRecipeType(recipeTypeId).get()
const recipeList = recipeManager.createRecipeLookup(recipeType).includeHidden().get()
.filter(recipe => {
return recipeIds.length > 0 ?
recipeIds.reduce(recipeId => {
return recipe.id.toString().match(recipeId) > 0
}) > 0 :
false
}).toList()
if(hide) {
return recipeManager.hideRecipes(recipeType, recipeList)
}
recipeManager.unhideRecipes(recipeType, recipeList)
},
/**
* @param {Internal.IRecipeManager} recipeManager
* @param {boolean} hide
* @param {string} recipeTypeId
*/
setHideRecipeCategory(recipeManager, hide, recipeTypeId){
console.log('toggled hiding for recipe category '+recipeTypeId+': ' +hide)
const recipeType = recipeManager.getRecipeType(recipeTypeId).get()
if(hide) return recipeManager.hideRecipeCategory(recipeType)
recipeManager.unhideRecipeCategory(recipeType)
},
/**
*
* @param {Internal.ListTag} stageUpdateData
* @param {string} stage
*/
hasStage(stageUpdateData, stage){
// console.log(stageUpdateData)
const stageArr = stageUpdateData
for (let i = 0; i < stageArr.length; i++) {
if(stage == stageArr[i]) return true
}
return false
}
}
const STAGES = global.constants.gamestages
const stages = Object.keys(STAGES)
/**
* manage received stages from data received
*/
NetworkEvents.dataReceived('stage_update', event => {
const {data} = event
const playerStagesListTag = data.player_stages
const JEIRecipeManager = global.jeiRuntime.recipeManager
for(let stage in STAGES){
let recipeTypes = STAGES[stage]
let playerHasStage = JEIStageHelper.hasStage(playerStagesListTag, stage)
for(let recipeType in recipeTypes){
let recipes = recipeTypes[recipeType]
recipes === 'hide_category' ?
JEIStageHelper.setHideRecipeCategory(JEIRecipeManager, !playerHasStage, recipeType) :
JEIStageHelper.setHideRecipes(JEIRecipeManager, !playerHasStage, recipeType, recipes)
}
}
event.player.runCommandSilent('reload')
})
the only thing I changed was that I included hidden recipes and use regex for recipe filtering
almost forgot to add, although AStages' recipe hiding doesn't seem work for whatever reason, I still use it's recipe restrictions. I just don't use it's recipe hiding feature
When im home this weekend i can copy your code and fiddle around until it works, im certain we can make it work
You could create an issue on github for that
I've got a bit of a clearer head now and I realised that there all of AStages' stuff works just fine, but I was using EMI when I was first asking for help (which doesn't update the same way JEI does). But now that I've solved that I've noticed my filter skips everything even though I've tested that the regex I've defined does in fact work
/**
* @param {Internal.IRecipeManager} recipeManager
* @param {boolean} hide
* @param {string} recipeTypeId
* @param {string[]} recipeIds
*/
setHideRecipes(recipeManager, hide, recipeTypeId, recipeIds){
console.log('toggled hiding recipes for category '+recipeTypeId+': ' +hide)
console.log('recipes to hide: ' + recipeIds)
const recipeType = recipeManager.getRecipeType(recipeTypeId).get()
const recipeList = recipeManager.createRecipeLookup(recipeType).includeHidden().get()
.filter(recipe => {
console.log('test recipe.id: ' +recipe.id.toString())
for(let recipeId in recipeIds){
if (recipe.id.toString().match(recipeId)) {
console.log('match: ' +recipe.id.toString().match(recipeId))
return true
}
}
return false
}).toList()
console.log('found recipes: ' +recipeList)
if(hide) {
return recipeManager.hideRecipes(recipeType, recipeList)
}
recipeManager.unhideRecipes(recipeType, recipeList)
},
I've tried passing exact regex matches and literal strings and neither seem to filter correctly
Do you have a list of recipes of certain items you want to hide or is it entire mods and stuff?
for(let recipeId in recipeIds){
swap the in to of and the filter will work
that's a little frustrating. It seems to work exactly as I need it to now. As I understand it, string objects and strings are different? Is that why using for ... in doesn't work but for ... of does?
also, would you happen to know why the stream-based method I used here doesn't filter correctly?
Im sorry you are on your own until the weekend again
that's fine, I'll close this thread for now since I've have what I need, though I'll put an update if I found out why the stream-based method doesn't filter like I'd expect it to
I think I got it, this seems to work it doesn't filter properly but it's not erroring at least
/**
* @param {Internal.IRecipeManager} recipeManager
* @param {boolean} hide
* @param {string} recipeTypeId
* @param {string[]} recipeIds
*/
setHideRecipes(recipeManager, hide, recipeTypeId, recipeIds){
console.log('set hide recipe cat.: ' +recipeTypeId+ ' | ' +hide)
let recipeType = recipeManager.getRecipeType(recipeTypeId).get()
let recipeList = recipeManager.createRecipeLookup(recipeType).includeHidden().get()
.filter(recipe => {
return recipeIds.length > 0 ?
recipeIds.some(recipeId => { recipe.id.toString().match(recipeId)}) :
false
}).toList()
console.log('found recipes: ' +recipeList)
if(hide) {
return recipeManager.hideRecipes(recipeType, recipeList)
}
recipeManager.unhideRecipes(recipeType, recipeList)
},
this was really only just for a minor performance improvement, but I think it makes it a little easier to read
hmm, I think I'm running into a similar issue from when I used for ...in instead of for ... of, just with streams instead? I'm not getting any matches but it's not causing any errors at least
ok, after some more testing I have somehting that does actually work
/**
* @param {Internal.IRecipeManager} recipeManager
* @param {boolean} hide
* @param {string} recipeTypeId
* @param {string[]} recipeIds
*/
setHideRecipes(recipeManager, hide, recipeTypeId, recipeIds){
console.log('set hide recipe cat.: ' +recipeTypeId+ ' | ' +hide)
let recipeType = recipeManager.getRecipeType(recipeTypeId).get()
let recipeList = recipeManager.createRecipeLookup(recipeType).includeHidden().get()
.filter(recipe => {
return recipeIds.length > 0 ?
recipeIds.some(recipeId => {
if(typeof recipeId != 'string') // assume it's regex
return recipeId.test(recipe.id.toString())
return recipeId === recipe.id.toString()
}) :
false
}).toList()
console.log('found recipes: ' +recipeList)
if(hide) {
return recipeManager.hideRecipes(recipeType, recipeList)
}
recipeManager.unhideRecipes(recipeType, recipeList)
}
You should just be able to feed in an array of regex or strings, a JEI recipe category and the global recipe manager and the hide paramater will toggle recipe hiding for the recipes, you still need to reload the client with the /reload command after running this function but it works and I think it's pretty neat
I think I can close this issue now