Jump to content

Recommended Posts

First off, I'm hoping I'm posting this correctly
Second, I have two resolutions I'm willing to accept from this: I want to be able to modify the recipe tab in the same way Craft Tab Enabler did, but for a specific character (My functional modded character) This is the most important thing I want to solve here. My other resolution, that is not required, is having this constantly update to better fit the currently visible tabs (basically remove the giant gap that is visible when most tabs are hidden)

And before you ask "Why do you need to do this?" Well, for the same reason Craft Tab Enabler was created (Mostly). I'm wanting to use this concept to solve the issue of tabs being hidden behind other tabs. My character uses a type of "level system" allowing it to unlock different levels of the "Bonus Tech" which the ancient tab ends up covering tabs such as the Celestial tab when you approach the Celestial Orb.

I've spent hours searching DST's scripts trying to find something that might help me solve this on my own, but nothing I've done has gotten me the results I'm looking for. Searching online I couldn't find anything that would help me in this situation either.

So to sum up my question: How do I remake Craft Tab Enabler but have it only apply to a single character?

For a reference with Craft Tab Enabler's code:

local RECIPETABS = GLOBAL.RECIPETABS

local sorttabs = {}


for _,v in pairs(RECIPETABS)
do
    table.insert(sorttabs, v)
end

table.sort(
    sorttabs,
    function(a, b)
        return (a.sort == b.sort) and (a.str < b.str) or (a.sort < b.sort)
    end
)

for i,v in ipairs(sorttabs)
do
    v.sort = i
    v.crafting_station = nil
end

 

The Craft Tab Enabler Mod just orders the recipetabs and deletes the crafting_station requirement. Did you add as your sixth argument for AddRecipeTab a true? If yes, try change it to a nil and see if it works.

	local numtabslots = 1 --reserver 1 slot for crafting station tabs
		for k, v in pairs(RECIPETABS) do
			table.insert(tabnames, v)
			if not v.crafting_station then
				numtabslots = numtabslots + 1
			end
		end

		for k, v in pairs(CUSTOM_RECIPETABS) do
			if v.owner_tag == nil or owner:HasTag(v.owner_tag) then
				table.insert(tabnames, v)
				if not v.crafting_station then
					numtabslots = numtabslots + 1
				end
			end
		end

This is from the widget crafttabs, as you can see, it checks if crafting_station is true or not and if it's not true, the numtabslot is increased, which I think means an own slot for the crafting tab.

For your second problem. I'm not sure how you can do this, perhaps you could try to change crafting_station to true or nil depending on if you want the tab to be put in the last space with all the other tabs with the crafting station requirement or not.

  • Like 1

Is my custom recipe tab relevant here? It's DST's vanilla tabs that are generating problems. The Ancient tab is hiding other vanilla tabs behind it. And either way, my recipe tab's sixth argument is already nil, you're meant to start with the tab available.
Ultroman has said in a different topic "There seems to be a maximum number of tabs being visible at a time." Which is why I'm wanting to replicate Craft Tab Enablers recipe tab layout. I'm wanting it to only happen for my specific character as it would be annoying for other players who aren't playing as my character and don't need it.

Ah sorry, I misunderstood your question, I thought you had multiple custom tabs that needed to be shown.

Try if this will work:

local function crafttabs(inst)
	if not GLOBAL.TheNet:GetIsClient() then return end
	local sorttabs = {}
	local RECIPETABS = GLOBAL.RECIPETABS
	for _,v in pairs(RECIPETABS)
	do
	    table.insert(sorttabs, v)
	end

	table.sort(
	    sorttabs,
	    function(a, b)
	        return (a.sort == b.sort) and (a.str < b.str) or (a.sort < b.sort)
	    end
	)

	for i,v in ipairs(sorttabs)
	do
	    v.sort = i
	    v.crafting_station = nil
	end
end

AddPrefabPostInit("yourcharacter", crafttabs)

This should only change the crafting_station values on the clients of those that have your mod character.

If it doesn't, you will perhaps need to make a check at the beginning that the instance is your character, something like:

if inst.prefab == "yourcharacter" then
 	.........code....
end

 

Edited by Monti18
  • Like 1
  • Thanks 1

Second method worked, thanks a lot! Only problem is using something to change characters in-game results in the effects staying, but I think I can solve this on my own. If anyone happens to come up with a solution for my second problem, (Removing gaps) I'd be glad if you shared!

Oh hi Carl! I'm not too sure how to use those. I also realized that it doesn't only apply to the character, when anyone picks my modded character, the change applies to all players, not being visible until they change characters or rejoin. I'm guessing this is because of "GLOBAL.RECIPETABS," is there a way for me to make this client-side?

As your mod is all_clients_require_mod = true, the only way is with the checks to see if you are on the client, which we did.

I had some time so I tried to get it working when changing the character and hopefully also for all players.

This is the code that I have now:

local RECIPETABS = GLOBAL.RECIPETABS

local sorttabs = {}
local RECIPETABS = GLOBAL.RECIPETABS
for _,v in pairs(RECIPETABS) do
	table.insert(sorttabs, v)
end

table.sort(sorttabs, function(a, b)
	return (a.sort == b.sort) and (a.str < b.str) or (a.sort < b.sort)
end)

local sort_number = {}
local crafting_station_bool = {}

for i,v in pairs(sorttabs) do
	sort_number[i] = v.sort 
	crafting_station_bool[i] = v.crafting_station 
end

local function crafttabs(inst)
	if not GLOBAL.TheNet:GetIsClient() then return end
	if inst.prefab == "yourcharacter" then
		for i,v in ipairs(sorttabs) do
	   		v.sort = i
	    	v.crafting_station = nil
		end
	else
		for i,v in ipairs(sorttabs) do
			v.sort = sort_number[i]
			v.crafting_station = crafting_station_bool[i]
		end
	end
end

AddPlayerPostInit(crafttabs)

When playtesting, I could change character and the change was reverted. I saved the sort values and the crafting station values in other tables and overrode the values that were changed with the original ones, when the character is not yourcharacter. I think like this it should also work for all players, but I have at the moment no means to test this, so try if it works.

As for removing the gaps, I think that you will need to write something to change the widget crafttabs, especially the self.tabs.spacing, as this is the spacing of the tabs and done once at the beginning and then never again, so you would need to add an update function to this.

  • Like 1

So I invested some more time for your second problem and I think I have the answer for it!

I'm not sure if it will work with more players as I can't test that.

First, add this to the common_postinit of your character:

Spoiler

--we need to make two net_strings to pass the information to the client
inst.net_tab_craftingstation_false = net_string(inst.GUID,"tab_craftingstation_false", "tab_craftingstation_false_dirty")
inst:ListenForEvent("tab_craftingstation_false_dirty", function(inst)
        if not TheNet:GetIsClient() then return end
        if inst.HUD and inst.HUD.controls.crafttabs then
            local str = inst.net_tab_craftingstation_false:value()
            inst.HUD.controls.crafttabs:UpdateTabSpacing(str, false) -- we call the updatetabspacing function with the string that we got from the net_string and the bool that is needed
        end
    end)

inst.net_tab_craftingstation_true = net_string(inst.GUID,"tab_craftingstation_true", "tab_craftingstation_true_dirty")
inst:ListenForEvent("tab_craftingstation_true_dirty", function(inst)
        if not TheNet:GetIsClient() then return end
        if inst.HUD and inst.HUD.controls.crafttabs then
            local str = inst.net_tab_craftingstation_true:value()
            inst.HUD.controls.crafttabs:UpdateTabSpacing(str, true)
        end
    end)

 

These are net_strings so that the client knows which tabs are changed and to which boolean value.

The rest is placed into the modmain:

Spoiler

AddPrefabPostInit("yourcharacter", function (inst)
	local function changetabs(inst,tab,bool)
	    if tab ~= nil then
	    	if type(tab) == "string" then
		       if bool == true then
		    		inst.net_tab_craftingstation_true:set(tab) --give the tab name to the client with wanted boolean value
		    	else
		    		inst.net_tab_craftingstation_false:set(tab)
		    	end
		    elseif type(tab) == "table" then
	        	for k,v in ipairs(tab) do
		        	if bool == true then
		    			inst.net_tab_craftingstation_true:set(v) --give the tab name to the client with wanted boolean value
		    		else
		    			inst.net_tab_craftingstation_false:set(v)
		    		end
		    	end
			end
	    end
	end
	inst.changetabs = changetabs --save the function under inst.changetabs so that the function can be called from anywhere
end)

 

This adds a function to your character that you can call with inst.changetabs. It has 3 argument, inst(like for most of them), tab and bool.

Tab is the tabs that you want to change, you can either just give one tab as a string or a table of strings.The names of the tabs can be found in constants.lua in RECIPETABS. The bool is to determine if the tab should have an own place in the layout or not. True means it has no place in the layout, it will be placed in the last place and can be hidden behind other tabs. False means it has always a place in the layout.

Now we only need to update the crafttab widget. The problem is that the tabs determined at the start of the game and they are not changed in between. So we are gonna delete all tabs, then make new ones with only the ones we want.

Spoiler

local TabGroup = require "widgets/tabgroup"

local tab_bg =
{
    atlas = "images/hud.xml",
    normal = "tab_normal.tex",
    selected = "tab_selected.tex",
    highlight = "tab_highlight.tex",
    bufferedhighlight = "tab_place.tex",
    overlay = "tab_researchable.tex",
}

AddClassPostConstruct("widgets/crafttabs", function(self,str,bool) -- we get a string and a bool with the function, the string is the tab and the bool is for the crafting_station
	self.UpdateTabSpacing = function(self)   -- we add a new function to crafttabs that we can call when changing the amount of tabs
		self.tabs:Kill()   --we delete all the tabs that were there
		
      --we change crafting_station on the client of the tab we want to change to the value we want to have
     	for k,v in pairs(sorttabs) do
			if v.str == str then
				v.crafting_station = bool
			end
		end
      
		self.tabs = self:AddChild(TabGroup())   --we add our new tabs
		self.tabs:SetPosition(-16,0,0)

		local tabnames = {}
		local numtabslots = 1 --reserver 1 slot for crafting station tabs
		for k, v in pairs(sorttabs) do --we use our table with the modified values to get the tabs that we want
			table.insert(tabnames, v)
			if not v.crafting_station then
				numtabslots = numtabslots + 1
			end
		end
		
      		for k, v in pairs(GLOBAL.CUSTOM_RECIPETABS) do
			if v.owner_tag == nil or self.owner:HasTag(v.owner_tag) then
				table.insert(tabnames, v)
				if not v.crafting_station then
					numtabslots = numtabslots + 1
				end
			end
		end
      
	    self.tabs.spacing = 790 / numtabslots

--this is only copy/paste of the crafttabs widget that make the tabs, so if Klei changes something here, you will also need to change it.

	    self.tabbyfilter = {} 
	    local was_crafting_station = nil
    	for k, v in ipairs(tabnames) do
	        local tab = self.tabs:AddTab(
	            STRINGS.TABS[v.str],
	            GLOBAL.softresolvefilepath(tab_bg.atlas),
	            v.icon_atlas or GLOBAL.softresolvefilepath("images/hud.xml"),
	            v.icon,
	            tab_bg.normal,
	            tab_bg.selected,
	            tab_bg.highlight,
	            tab_bg.bufferedhighlight,
	            tab_bg.overlay,

	            function(widget) --select fn
	                if not self.controllercraftingopen then

	                    if self.craft_idx_by_tab[k] then
	                        self.crafting.idx = self.craft_idx_by_tab[k]
	                    end

	                    local default_filter = function(recname)
	                        local recipe = GLOBAL.GetValidRecipe(recname)
	                        return recipe ~= nil
	                        and recipe.tab == v
	                        and (self.owner.replica.builder == nil or
	                        self.owner.replica.builder:CanLearn(recname))
	                    end

	                    local advanced_filter = function(recname)
	                        local recipe = GLOBAL.GetValidRecipe(recname)
	                        return recipe ~= nil
	                        and recipe.tab == v
	                        and (self.owner.replica.builder == nil or
	                        self.owner.replica.builder:CanLearn(recname))
	                    end

	                    self.crafting:SetFilter(advanced_filter)
	                    self.crafting:Open()
	                    self.preventautoclose = nil
	                end
	            end,

	            function(widget) --deselect fn
	                self.craft_idx_by_tab[k] = self.crafting.idx
	                self.crafting:Close()
	                self.preventautoclose = nil
	                if self.isquagmire then
	                    widget.inst:DoTaskInTime(0, function()
	                        if widget.focus then
	                            widget.ongainfocusfn()
	                        end
	                    end)
	                end
	            end,

	            was_crafting_station and v.crafting_station --collapsed
	        )
	        was_crafting_station = v.crafting_station
	        tab.filter = v
	        tab.icon = v.icon
	        tab.icon_atlas = v.icon_atlas or GLOBAL.softresolvefilepath("images/hud.xml")
	        tab.tabname = STRINGS.TABS[v.str]

	        if self.isquagmire then
	            tab.disable_scaling = true
	            tab.overlay_scaling = true
	        end

	        self.tabbyfilter[v] = tab
	    end
    self:UpdateRecipes()  --we update the recipes, otherwise all tabs can be seen, even if you can't produce anything there
	end
end)

 

Now if you want to change the amount of tabs shown, you need to call the function with the parameters you want and it should change.

This methods changes some things for crafttabs, so I'm not sure how well compatible it's with mods that change the crafttabs, but I also don't know how else you couold change it.

I commented the most important parts of the code, if you have questions, let me know.

Edited by Monti18
forgot that changes are not propagated to client
  • Like 1

First script works, only my character has their tabs changed even after changing characters, but I'm not sure what you mean by 

Quote

Now if you want to change the amount of tabs shown, you need to call the function with the parameters you want and it should change.

Which function specifically and how would I get this to update when new tabs become available or old tabs become unavailable?

I mean the function changetabs, specifically I saved the function in the variable inst.changetabs in your character.

I don't know how your character gets new tabs or loses tabs, but lets say you have a level system, and you reach lvl 10 and want to add one new tab.

The tab is "ANCIENT" for the ancient tab.

You should have a function that runs when you level up and if your level is equal 10 then you run

inst.changetabs(inst,"ANCIENT", false)

This means that the ancient tabs crafting_station value is set to false, which means that the tab will have its own place.

If you explain a bit when you wanted the tabs to update, I could try to help you.

 

Edit: I just saw that my method was flawed, as the changes to sorttable are not propagated to the client, so there was no change there. I updated the functions in the post before to reflect the changes.

If you want to test how it works, insert the spoiler into your modmain and press Left Alt to substract recipe tabs.

Spoiler

local recipetablist = {
    "CELESTIAL",
    "MOON_ALTAR",
    "CARTOGRAPHY",
    "SCULPTING",
    "ORPHANAGE",
    "PERDOFFERING",
    "MADSCIENCE",
    "FOODPROCESSING",
    "FISHING",
    "WINTERSFEASTCOOKING",
    "HERMITCRABSHOP",
    "TURFCRAFTING",
}


local function testfn(inst)
	if inst.changetabs ~= nil then
		local tab = recipetablist[math.random(1,12)]
		inst.changetabs(inst,tab,true)
	end
end

AddModRPCHandler("test","changetabs",testfn)

GLOBAL.TheInput:AddControlHandler(38, function()
    SendModRPCToServer(MOD_RPC["test"]["changetabs"])
end)

 

 

Edited by Monti18
changed method to work for client
  • Like 1

Ah, I see, that makes a lot more sense.

Been trying for a while now, but can't seem to be getting it working, am I doing something wrong?
In modmain.lua:

Spoiler

-- Reorder crafting tabs
local RECIPETABS = GLOBAL.RECIPETABS

local sorttabs = {}
local RECIPETABS = GLOBAL.RECIPETABS
for _,v in pairs(RECIPETABS) do
	table.insert(sorttabs, v)
end

table.sort(sorttabs, function(a, b)
	return (a.sort == b.sort) and (a.str < b.str) or (a.sort < b.sort)
end)

local sort_number = {}
local crafting_station_bool = {}

for i,v in pairs(sorttabs) do
	sort_number[i] = v.sort 
	crafting_station_bool[i] = v.crafting_station 
end

local function crafttabs(inst)
	if GLOBAL.TheNet:GetIsClient() then return end
	if inst.prefab == "rimuru" then
		for i,v in ipairs(sorttabs) do
	   		v.sort = i
	    	v.crafting_station = nil
		end
	else
		for i,v in ipairs(sorttabs) do
			v.sort = sort_number[i]
			v.crafting_station = crafting_station_bool[i]
		end
	end
end

AddPlayerPostInit(crafttabs)

-- Extra data to keep crafting clean
AddPrefabPostInit("rimuru", function (inst)
	local function changetabs(inst,tab,bool)
	    if tab ~= nil then
	    	if type(tab) == "string" then
		       if bool == true then
		    		inst.net_tab_craftingstation_true:set(tab) --give the tab name to the client with wanted boolean value
		    	else
		    		inst.net_tab_craftingstation_false:set(tab)
		    	end
		    elseif type(tab) == "table" then
	        	for k,v in ipairs(tab) do
		        	if bool == true then
		    			inst.net_tab_craftingstation_true:set(v) --give the tab name to the client with wanted boolean value
		    		else
		    			inst.net_tab_craftingstation_false:set(v)
		    		end
		    	end
			end
	    end
	end
	inst.changetabs = changetabs --save the function under inst.changetabs so that the function can be called from anywhere
end)

-- Even more data (This modifies recipe tabs so they can be changed in-game)
local TabGroup = require "widgets/tabgroup"

local tab_bg =
{
    atlas = "images/hud.xml",
    normal = "tab_normal.tex",
    selected = "tab_selected.tex",
    highlight = "tab_highlight.tex",
    bufferedhighlight = "tab_place.tex",
    overlay = "tab_researchable.tex",
}

AddClassPostConstruct("widgets/crafttabs", function(self,str,bool) -- we get a string and a bool with the function, the string is the tab and the bool is for the crafting_station
	self.UpdateTabSpacing = function(self)   -- we add a new function to crafttabs that we can call when changing the amount of tabs
		self.tabs:Kill()   --we delete all the tabs that were there
		
      --we change crafting_station on the client of the tab we want to change to the value we want to have
     	for k,v in pairs(sorttabs) do
			if v.str == str then
				v.crafting_station = bool
			end
		end
      
		self.tabs = self:AddChild(TabGroup())   --we add our new tabs
		self.tabs:SetPosition(-16,0,0)

		local tabnames = {}
		local numtabslots = 1 --reserver 1 slot for crafting station tabs
		for k, v in pairs(sorttabs) do --we use our table with the modified values to get the tabs that we want
			table.insert(tabnames, v)
			if not v.crafting_station then
				numtabslots = numtabslots + 1
			end
		end
		
      		for k, v in pairs(GLOBAL.CUSTOM_RECIPETABS) do
			if v.owner_tag == nil or self.owner:HasTag(v.owner_tag) then
				table.insert(tabnames, v)
				if not v.crafting_station then
					numtabslots = numtabslots + 1
				end
			end
		end
      
	    self.tabs.spacing = 790 / numtabslots

--this is only copy/paste of the crafttabs widget that make the tabs, so if Klei changes something here, you will also need to change it.

	    self.tabbyfilter = {} 
	    local was_crafting_station = nil
    	for k, v in ipairs(tabnames) do
	        local tab = self.tabs:AddTab(
	            STRINGS.TABS[v.str],
	            GLOBAL.softresolvefilepath(tab_bg.atlas),
	            v.icon_atlas or GLOBAL.softresolvefilepath("images/hud.xml"),
	            v.icon,
	            tab_bg.normal,
	            tab_bg.selected,
	            tab_bg.highlight,
	            tab_bg.bufferedhighlight,
	            tab_bg.overlay,

	            function(widget) --select fn
	                if not self.controllercraftingopen then

	                    if self.craft_idx_by_tab[k] then
	                        self.crafting.idx = self.craft_idx_by_tab[k]
	                    end

	                    local default_filter = function(recname)
	                        local recipe = GLOBAL.GetValidRecipe(recname)
	                        return recipe ~= nil
	                        and recipe.tab == v
	                        and (self.owner.replica.builder == nil or
	                        self.owner.replica.builder:CanLearn(recname))
	                    end

	                    local advanced_filter = function(recname)
	                        local recipe = GLOBAL.GetValidRecipe(recname)
	                        return recipe ~= nil
	                        and recipe.tab == v
	                        and (self.owner.replica.builder == nil or
	                        self.owner.replica.builder:CanLearn(recname))
	                    end

	                    self.crafting:SetFilter(advanced_filter)
	                    self.crafting:Open()
	                    self.preventautoclose = nil
	                end
	            end,

	            function(widget) --deselect fn
	                self.craft_idx_by_tab[k] = self.crafting.idx
	                self.crafting:Close()
	                self.preventautoclose = nil
	                if self.isquagmire then
	                    widget.inst:DoTaskInTime(0, function()
	                        if widget.focus then
	                            widget.ongainfocusfn()
	                        end
	                    end)
	                end
	            end,

	            was_crafting_station and v.crafting_station --collapsed
	        )
	        was_crafting_station = v.crafting_station
	        tab.filter = v
	        tab.icon = v.icon
	        tab.icon_atlas = v.icon_atlas or GLOBAL.softresolvefilepath("images/hud.xml")
	        tab.tabname = STRINGS.TABS[v.str]

	        if self.isquagmire then
	            tab.disable_scaling = true
	            tab.overlay_scaling = true
	        end

	        self.tabbyfilter[v] = tab
	    end
    self:UpdateRecipes()  --we update the recipes, otherwise all tabs can be seen, even if you can't produce anything there
	end
end)

 

Inside character.lua (Cropped):

Spoiler

local common_postinit = function(inst) 
    --we need to make two net_strings to pass the information to the client
    inst.net_tab_craftingstation_false = net_string(inst.GUID,"tab_craftingstation_false", "tab_craftingstation_false_dirty")
    inst:ListenForEvent("tab_craftingstation_false_dirty", function(inst)
        if not TheNet:GetIsClient() then return end
        if inst.HUD and inst.HUD.controls.crafttabs then
            local str = inst.net_tab_craftingstation_false:value()
            inst.HUD.controls.crafttabs:UpdateTabSpacing(str, false) -- we call the updatetabspacing function with the string that we got from the net_string and the bool that is needed
        end
    end)

    inst.net_tab_craftingstation_true = net_string(inst.GUID,"tab_craftingstation_true", "tab_craftingstation_true_dirty")
    inst:ListenForEvent("tab_craftingstation_true_dirty", function(inst)
        if not TheNet:GetIsClient() then return end
        if inst.HUD and inst.HUD.controls.crafttabs then
            local str = inst.net_tab_craftingstation_true:value()
            inst.HUD.controls.crafttabs:UpdateTabSpacing(str, true)
        end
    end)
end

-- Bunch of code

local function great_sage(inst) -- Basically the level system
    if inst.components.sanity.current >= (TUNING.RIMURU_SANITY*8) then 
        inst.components.builder.science_bonus = (3)
        inst.components.builder.magic_bonus = (3)
        inst.components.builder.ancient_bonus = (3)
        if inst.changetabs then
            inst.changetabs(inst,"SCIENCE", false)
            inst.changetabs(inst,"MAGIC", false)
            inst.changetabs(inst,"ANCIENT", false)
        end
    elseif inst.components.sanity.current >= (TUNING.RIMURU_SANITY*4) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*8) then 
        inst.components.builder.science_bonus = (2)
        inst.components.builder.magic_bonus = (2)
        inst.components.builder.ancient_bonus = (2)
        if inst.changetabs then
            inst.changetabs(inst,"SCIENCE", false)
            inst.changetabs(inst,"MAGIC", false)
            inst.changetabs(inst,"ANCIENT", false)
        end
    elseif inst.components.sanity.current >= (TUNING.RIMURU_SANITY*2) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*4)then 
        inst.components.builder.science_bonus = (1)
        inst.components.builder.magic_bonus = (1)
        inst.components.builder.ancient_bonus = (1)
        if inst.changetabs then
            inst.changetabs(inst,"SCIENCE", false)
            inst.changetabs(inst,"MAGIC", false)
            inst.changetabs(inst,"ANCIENT", false)
        end
    elseif inst.components.sanity.current > (0) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*2) then 
        inst.components.builder.science_bonus = (0)
        inst.components.builder.magic_bonus = (0)
        inst.components.builder.ancient_bonus = (0)
        if inst.changetabs then
            inst.changetabs(inst,"SCIENCE", true)
            inst.changetabs(inst,"MAGIC", true)
            inst.changetabs(inst,"ANCIENT", true)
        end
    end
end

local master_postinit = function(inst)
    inst.components.builder.science_bonus = (3)
    inst.components.builder.magic_bonus = (3)
    inst.components.builder.ancient_bonus = (3)
    if inst.changetabs then
        inst.changetabs(inst,"SCIENCE", false)
        inst.changetabs(inst,"MAGIC", false)
        inst.changetabs(inst,"ANCIENT", false)
    end
    
    inst:ListenForEvent("sanitydelta", function(inst) great_sage(inst) end) -- Actual system
    
    great_sage(inst) -- Make sure correct tabs are visible when starting

 

FYI: I'm checking "if inst.changetabs then" because of the first run of "great_sage(inst)" which ends up crashing on the first run because inst.changetabs does not exist yet (afterwards it does) though it's completely possible that this is the problem and I need to change it.

And just incase you want/need to see the oiriginal uncut files I'll attach a zip of both files.

OG_Files.zip

Yes the crash is happening as the function is added in PrefabPostInit, but since I changed the code you can now add the function to your character prefab, as it now doesn't depend on sorttable.

When you have something like this where you don't know why it's not working, try adding print statements at multiple points in the function to see where the error is happening.

What exactly is happening? Is it just that nothing changes?

When I tried it with your mod, it worked, but the way you call the function just makes it unuseable, as it resets the tabs all the time. You need to make a function that only calls changetabs when there really is change of the recipetabs.

Try to make such a function, if you don't have anything that works this evening I will write you one.

  • Like 1
Quote

Yes the crash is happening as the function is added in PrefabPostInit, but since I changed the code you can now add the function to your character prefab, as it now doesn't depend on sorttable.

Wouldn't that mean it would have worked when I did it? Or am I completely missing what you were trying to say?

 

I definitely didn't take into consideration that I was constantly calling the function. Definitely should have tried using prints but that slipped my mind at the time. It also just rendered in my mind that you made it so I can use tables for multiple tabs instead of making a new line for every tab I want to enable/disable.

Using your advice I made a new function that I can call whenever I want tabs enabled/disabled. I'm trying to make it check if the tab is already disabled/disabled and if so, not to call the changetabs function, is there a way to check if tabs are enabled or not?
My current code: (tabenabled and tabdisabled are placeholders)

local function tabsignal(inst,tab,toggle)
	if inst.changetabs then
		if type(tab) == "string" then
			if (not toggle and tabdisabled) or (toggle and tabenabled) then
				inst.changetabs(inst,tab,toggle)
			end
		elseif type(tab) == "table" then
			local enabletable = {}
			local disabletable = {}
	        for k,v in ipairs(tab) do
				if toggle and tabdisabled then
					table.insert(enabletable,v)
				elseif toggle and tabenabled then
					table.insert(disabletable,v)
				end
			end
			if enabletable ~= nil then
				inst.changetabs(inst,enabletable,false)
			end
			if disabletable ~= nil then
				inst.changetabs(inst,disabletable,true)
			end
		end
	end
end

 

I meant that it would have worked if you added the function changetabs to your character prefab instead of adding it in a prefabpostinit, as great_sage is called before AddPrefabPostInit is run. If changetabs is in your character prefab, great_sage can call it when it is run, as the function can be referenced now.

To check if tabs are enabled or not, you would need to make a list and save if a tab is enabled or not, or you would need to send an RPC and or netvar, as these values are saved on the client.

I wouldn't make it so complicated, as then you have this function that is called a few times per second, which consumes quite a lot of computing power.

I would change the way great_sage works, below is my idea:

Spoiler

--I used the tabs that you had in your example, you can change it however you want
local rimurutabs = {
  "SCIENCE",
  "MAGIC",
  "ANCIENT",
}

--As you change the values of the bonus for each step of great sage to the same number, I made it into a function
local function set_bonus(num)
  inst.components.builder.science_bonus = (num)
  inst.components.builder.magic_bonus = (num)
  inst.components.builder.ancient_bonus = (num)
end

local function great_sage(inst)
  local sanity_num -- we add a local number and give it a value depending on the value of rimurus sanity
  if inst.components.sanity.current >= (TUNING.RIMURU_SANITY*8) then 
    sanity_num = 3
  elseif inst.components.sanity.current >= (TUNING.RIMURU_SANITY*4) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*8) then 
    sanity_num = 2
  elseif inst.components.sanity.current >= (TUNING.RIMURU_SANITY*2) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*4) then
    sanity_num = 1
  elseif inst.components.sanity.current > (0) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*2) then
    sanity_num = 0
  end
  --we check if great sage is still in the same "level" as the last time it was checked, 
  --if yes, we stop the function here, so that there is no functions running that don't need to
  if sanity_num == inst.great_sage_num then 
    return
  end
  if sanity_num ~= nil and inst.changetabs ~= nil then
    set_bonus(sanity_num) --we set the bonus depending on the sanity
    if sanity_num == 0 then -- we change the tabs, but this is only called if there was a change in "level" of great sage
   	inst.changetabs(inst, rimurutabs, true)
    else
     	inst.changetabs(inst, rimurutabs, false)
    end
    inst.great_sage_num = sanity_num -- we save the "level" value into a variable that will be called the next time great sage runs
  end
end
    

 

I just realised that at the moment, when you are changing multiple tabs at once, there is only one that as changed, as the dirty event is only fired once as the changes are too quick.

I have an idea on how to do this, I will try to do it tomorrow.

Edited by Monti18
  • Like 1

I changed the functions a bit, that's why I will put all you need to use in your mod in this post.

First, we start with the modmain:

Spoiler

local RECIPETABS = GLOBAL.RECIPETABS

local sorttabs = {}
local RECIPETABS = GLOBAL.RECIPETABS
for _,v in pairs(RECIPETABS) do
	table.insert(sorttabs, v)
end

table.sort(sorttabs, function(a, b)
	return (a.sort == b.sort) and (a.str < b.str) or (a.sort < b.sort)
end)

local sort_number = {}
local crafting_station_bool = {}

for i,v in pairs(sorttabs) do
	sort_number[i] = v.sort 
	crafting_station_bool[i] = v.crafting_station 
end

local function crafttabs(inst)
	if GLOBAL.TheNet:GetIsClient() then return end
	if inst.prefab == "rimuru" then
		print("rimuru crafttabs")
		for i,v in ipairs(sorttabs) do
			print(v.str,i)
	   		v.sort = i
		end
	else
		for i,v in ipairs(sorttabs) do
			v.sort = sort_number[i]
			v.crafting_station = crafting_station_bool[i]
		end
	end
end

AddPlayerPostInit(crafttabs)

local function OnChangeTabsTrue(inst,tab)
	if inst and inst.HUD and inst.HUD.controls.crafttabs then
		inst.HUD.controls.crafttabs:UpdateTabSpacing(tab,true)
	end
end

AddClientModRPCHandler("rimuru", "changetabstrue", OnChangeTabsTrue)

local function OnChangeTabsFalse(inst,tab)
	if inst and inst.HUD and inst.HUD.controls.crafttabs then
		inst.HUD.controls.crafttabs:UpdateTabSpacing(tab,false)
	end
end

AddClientModRPCHandler("rimuru", "changetabsfalse", OnChangeTabsFalse)

local TabGroup = require "widgets/tabgroup"

local tab_bg =
{
    atlas = "images/hud.xml",
    normal = "tab_normal.tex",
    selected = "tab_selected.tex",
    highlight = "tab_highlight.tex",
    bufferedhighlight = "tab_place.tex",
    overlay = "tab_researchable.tex",
}

AddClassPostConstruct("widgets/crafttabs", function(self) -- we get a string and a bool with the function, the string is the tab and the bool is for the crafting_station
	self.UpdateTabSpacing = function(self,str,bool)   -- we add a new function to crafttabs that we can call when changing the amount of tabs
		self.tabs:Kill()   --we delete all the tabs that were there
		print("UpdateTabSpacing", str,bool)

      --we change crafting_station on the client of the tab we want to change to the value we want to have
      	if str ~= nil then
	     	for k,v in pairs(GLOBAL.RECIPETABS) do
				if v.str == str then
					v.crafting_station = bool
				end
			end
		end

		self.tabs = self:AddChild(TabGroup())   --we add our new tabs
		self.tabs:SetPosition(-16,0,0)

		local tabnames = {}
		local numtabslots = 1 --reserver 1 slot for crafting station tabs
		for k, v in pairs(sorttabs) do --we use our table with the modified values to get the tabs that we want
			table.insert(tabnames, v)
			if not v.crafting_station then
				numtabslots = numtabslots + 1
			end
		end
		
      	for k, v in pairs(GLOBAL.CUSTOM_RECIPETABS) do
			if v.owner_tag == nil or self.owner:HasTag(v.owner_tag) then
				table.insert(tabnames, v)
				if not v.crafting_station then
					numtabslots = numtabslots + 1
				end
			end
		end
      
	    self.tabs.spacing = 790 / numtabslots

--this is only copy/paste of the crafttabs widget that make the tabs, so if Klei changes something here, you will also need to change it.

	    self.tabbyfilter = {} 
	    local was_crafting_station = nil
    	for k, v in ipairs(tabnames) do
	        local tab = self.tabs:AddTab(
	            STRINGS.TABS[v.str],
	            GLOBAL.softresolvefilepath(tab_bg.atlas),
	            v.icon_atlas or GLOBAL.softresolvefilepath("images/hud.xml"),
	            v.icon,
	            tab_bg.normal,
	            tab_bg.selected,
	            tab_bg.highlight,
	            tab_bg.bufferedhighlight,
	            tab_bg.overlay,

	            function(widget) --select fn
	                if not self.controllercraftingopen then

	                    if self.craft_idx_by_tab[k] then
	                        self.crafting.idx = self.craft_idx_by_tab[k]
	                    end

	                    local default_filter = function(recname)
	                        local recipe = GLOBAL.GetValidRecipe(recname)
	                        return recipe ~= nil
	                        and recipe.tab == v
	                        and (self.owner.replica.builder == nil or
	                        self.owner.replica.builder:CanLearn(recname))
	                    end

	                    local advanced_filter = function(recname)
	                        local recipe = GLOBAL.GetValidRecipe(recname)
	                        return recipe ~= nil
	                        and recipe.tab == v
	                        and (self.owner.replica.builder == nil or
	                        self.owner.replica.builder:CanLearn(recname))
	                    end

	                    self.crafting:SetFilter(advanced_filter)
	                    self.crafting:Open()
	                    self.preventautoclose = nil
	                end
	            end,

	            function(widget) --deselect fn
	                self.craft_idx_by_tab[k] = self.crafting.idx
	                self.crafting:Close()
	                self.preventautoclose = nil
	                if self.isquagmire then
	                    widget.inst:DoTaskInTime(0, function()
	                        if widget.focus then
	                            widget.ongainfocusfn()
	                        end
	                    end)
	                end
	            end,

	            was_crafting_station and v.crafting_station --collapsed
	        )
	        was_crafting_station = v.crafting_station
	        tab.filter = v
	        tab.icon = v.icon
	        tab.icon_atlas = v.icon_atlas or GLOBAL.softresolvefilepath("images/hud.xml")
	        tab.tabname = STRINGS.TABS[v.str]

	        if self.isquagmire then
	            tab.disable_scaling = true
	            tab.overlay_scaling = true
	        end

	        self.tabbyfilter[v] = tab
	    end
    self:UpdateRecipes()  --we update the recipes, otherwise all tabs can be seen, even if you can't produce anything there
	end
end)

 

If I understand your mod correctly, you will only need to change the tabs depending on the sanity, so you won't need to incorporate the function that works like craftabsenabler.

This goes into your character prefab:

Spoiler

local function changetabs(inst,tab,bool)
	    if tab ~= nil and bool ~= nil then
	    	if type(tab) == "string" then
	    		if bool == true then
	    			SendModRPCToClient(GetClientModRPC("rimuru","changetabstrue"),inst.userid,inst,tab)
	    		else
	    			SendModRPCToClient(GetClientModRPC("rimuru","changetabsfalse"),inst.userid,inst,tab)
	    		end
		    elseif type(tab) == "table" then
	        	for k,v in ipairs(tab) do
	        		if bool == true then
	    				SendModRPCToClient(GetClientModRPC("rimuru","changetabstrue"),inst.userid,inst,v)
	    			else
	    				SendModRPCToClient(GetClientModRPC("rimuru","changetabsfalse"),inst.userid,inst,v)
	    			end
	        	end
			end
	    end
	end

--I used the tabs that you had in your example, you can change it however you want
local rimurutabs = {
  --"SCIENCE",  --If you try to make the base tabs disappear, the hud will look wonky.
  --"MAGIC",
  "ANCIENT",
  "CELESTIAL",
}

--As you change the values of the bonus for each step of great sage to the same number, I made it into a function
local function set_bonus(inst,num)
  inst.components.builder.science_bonus = (num)
  inst.components.builder.magic_bonus = (num)
  inst.components.builder.ancient_bonus = (num)
end

local function great_sage(inst)
  local sanity_num -- we add a local number and give it a value depending on the value of rimurus sanity
  if inst.components.sanity.current >= (TUNING.RIMURU_SANITY*8) then 
    sanity_num = 3
  elseif inst.components.sanity.current >= (TUNING.RIMURU_SANITY*4) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*8) then 
    sanity_num = 2
  elseif inst.components.sanity.current >= (TUNING.RIMURU_SANITY*2) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*4) then
    sanity_num = 1
  elseif inst.components.sanity.current > (0) and inst.components.sanity.current < (TUNING.RIMURU_SANITY*2) then
    sanity_num = 0
  end
  --we check if great sage is still in the same "level" as the last time it was checked, if yes, we stop the function here, so that there is no functions running that don't need to
  if sanity_num == inst.great_sage_num then 
    return
  end
  if sanity_num ~= nil then
    set_bonus(inst,sanity_num) --we set the bonus depending on the sanity
    if sanity_num == 0 then -- we change the tabs, but this is only called if there was a change in "level" of great sage
   		changetabs(inst, rimurutabs, true)
    else
     	changetabs(inst, rimurutabs, false)
    end
    inst.great_sage_num = sanity_num -- we save the "level" value into a variable that will be called the next time great sage runs
  end
end



--in master postinit (not necessary)

inst.changetabs = changetabs

 

We send a ClientModRPC to send the data to the client, as with netvars, these fast changes are not possible.

You can change the great sage function as you want, this was just to show how I would to it.

Edited by Monti18
some more changes...
  • Like 1

Oh sorry, stupid error from my side, I copied the wrong line in the function changetabs.

It should be 

 		elseif type(tab) == "table" then
	        	for k,v in ipairs(tab) do
	        		if bool == true then
	    				SendModRPCToClient(GetClientModRPC("rimuru","changetabs"),inst.userid,inst,v,true)
	    			else
	    				SendModRPCToClient(GetClientModRPC("rimuru","changetabs"),inst.userid,inst,v,false)
	    			end
	        	end
		end

as RPC are not able to send tables, and I forgot to change tab to v.

  • Like 1

Ah, the most relatable mistake in coding history. We got past that part but now I'm realizing that in the modmain "sorttabs" doesn't exist so "for k, v in pairs(sorttabs) do" can't do anything, obviously causing a crash. I don't have the slightest idea what table is meant to go here, clearly not an empty one, I know that much.

You're right :D

Yeah I forgot that this won't there anymore, you can just use GLOBAL.RECIPETABS, as we use these to see what recipetabs there are, but I changed the function a bit (again, I know) because I think there could be problems with the sort order, that's why I reimplemented the first function with sorttabs, but I only change the sort number. You can then leave sorttabs there.

I also changed the ClientRPCs because they sometimes don't give the boolean argument to the client, which makes this unusuable.

If you want to change the basic tabs, your hud will look wonky, when I tried it with science and magic, it looked strange, so perhaps leave them out.

Take a look at the previous previous post, I updated it to reflect the changes (modmain and changetabs function).

I hope it's working now, during my testing it worked, but perhaps I missed to add something.

  • Like 1

Well, it worked! However now it's also trying to add in the custom recipe tabs from other characters! (Not visible most of the time, just flashes on screen for a millisecond when the layout tries changing) But this results in icons going off the gui tab! (gif for visual explanation, I'm using the console to set my sanity)

ezgif.com-gif-maker.gif

Do you mean other players that are playing at the same time or if you are changing character? I hate these interactions with the clients... 

You could try checking if it's your character with self.owner.prefab == "rimuru" at the start of the UpdateTabSpacing function and see if that works. 

  • Like 1

No no, I mean when you have multiple character mods enabled, if that mod adds custom recipe tabs those tabs will flash on screen. (The purple blob with a halo is a recipe tab for a different modded character, Rimuru shouldn't ever be able to see it.)

Ah now I understand!

I'm not sure which recipe tab you mean, do you mean the celestial tab? Just the one below the carneval tab. Because this one is from the base game, I forgot to remove it from the rimurutabs table, this was purely for testing to see how it works with more tabs. Just remove it from rimirutasb and you shouldn't be able to see it.

  • Like 1

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...