Jump to content

[Beta] Modded Character Save Portraits Fix


Recommended Posts

Hello! I got a little burnt out making my own character mod, so I decided to solve a little annoying problem with DST, that is the lack of save slot portrait support on modded characters!

Now, here's the thing though, I need your guy's help, and it's not help with coding, as I already did all that. At the moment, my mod only supports my own character mod (Hat Kid), I need suggestions on what character mods are used the most, and which creators are fine with me adding support for their character.

Here are a couple examples of what my mod does

icon.png.9d8e1a59e298c8ae265832a5ffb8152e.png

Before

179695705_withoutmod.thumb.png.0c93a90c7f93429375fe7d8224884bd4.png

After

809424361_withmod.thumb.png.92ade7a8f1094b5d064e54b5bb0490a5.png

Edit 1: I'm going to go ahead and implement support for Hamlet and Shipwrecked characters, since they're Klei property I shouldn't need to ask anyone.

Edited by TheSkylarr
Link to comment
Share on other sites

Honestly I think it should be possible to pull data from the modicon at minimum. To show that the character is from that mod. As modicon loads no matter what when viewing all mods.

 

It would be a nice fallback in the event that pulling data from the actual mods is not easy?

  • Like 1
Link to comment
Share on other sites

31 minutes ago, IronHunter said:

Honestly I think it should be possible to pull data from the modicon at minimum. To show that the character is from that mod. As modicon loads no matter what when viewing all mods.

 

It would be a nice fallback in the event that pulling data from the actual mods is not easy?

At this point I'm not sure how to actually edit the table that deals with the portraits directly, but I'm using a workaround by adding the character asset manually from my own bank of images, the drawback to this is it takes unnecessary ram when actually in game, and also, it seems on single player worlds the "invisible characters" I create to make this work are select able, which shouldn't happen.

If anyone could help me find a way to directly manipulate the images there that would be helpful lol

Link to comment
Share on other sites

> At this point I'm not sure how to actually edit the table that deals with the portraits directly

For the image you sent, are you editing the default image, or the server listing screens and server creation (read: creation or resume) screens?

Try

scripts/screens/serverlistingscreen.lua:1045 and/or scripts/screens/redux/serverlistingscreen.lua:1109

Spoiler

                if not table.contains(DST_CHARACTERLIST, character) then
                    if table.contains(MODCHARACTERLIST, character) then
                        atlas = atlas.."/"..character
                    else
                        character = #character > 0 and "mod_small" or "unknown"
                    end
                end

 

I could be being dumb, but try making a folder inside data/images/ called "saveslot_portraits", then put the <characterName>.xml and corresponding .tex file there

If you already are, or if this doesn't work:

You could maybe try to hook in to the screen creation. Possibly

AddClassPostConstruct("screens/servercreationscreen", function (theScreenInstance)
    -- do stuff to the created screen
end)

Though I'm not 100% on this, as Carl said the PostConstruct only works the first time?

, then loop over the widgets, and replace their

widget.CHAR_ICON:SetTexture( .xml, .tex)

And

widget.CHAR.img:SetTexture(atlas, character..".tex")

You could probably even screw with the SetTexture function itself. But that sounds like a horrible hack

If you want to do it correctly for when you click on the server in the top, you might also need to do something to the

servercreationscreen.lua and redux/servercreationscreen.lua too

 

Also, not that useful, but:

> takes unnecessary ram when actually in game

I did some screwing around with TheNet commands

Spoiler

print("EASILY SEARCHABLE STRING")

print("isDedicated")
print(GLOBAL.TheNet:IsDedicated()) -- is server (whether it's fully loaded yet, or not)

print("isServer")
print(GLOBAL.TheNet:GetIsServer()) -- is server (fully loaded only / ready to accept clients)

print("isMasterSimulation")
print(GLOBAL.TheNet:GetIsMasterSimulation()) -- same result as "is server"??

print("isClient")
print(GLOBAL.TheNet:GetIsClient()) -- is a connected client

print("isHosting")
print(GLOBAL.TheNet:GetIsHosting()) -- is client host, or is server (has access to log files)

print("isServerClientHosted")
print(GLOBAL.TheNet:GetServerIsClientHosted()) -- actually does what it says

print("isServerDedicated")
print(GLOBAL.TheNet:GetServerIsDedicated()) -- seems broken??

print("isAdmin")
print(GLOBAL.TheNet:GetIsServerAdmin()) -- assuming this works?

print("isOwner")
print(GLOBAL.TheNet:GetIsServerOwner()) -- not fully tested
print(TheNet:GetIsServerOwner())

 

If this is true:

not GLOBAL.TheNet:GetIsClient() and not GLOBAL.TheNet:IsDedicated()

Then you're in the main menu

Stuff will get unloaded as soon as you try to load game as well, so you just need to only load your bank when that's true. I attached an openoffice spreadsheet of the states (I can save in some other formats too if you need). Or you can just copy paste the hidden commands and screw around with them

 

Finally (and probably least useful), I did some screwing around with lua.io

Spoiler

	require "io"
	local file = io.open(path, "r")
	local file = io.open(filename, "w")
	file:close()

and then I found my package.path was something like


	default path: "C:\Program Files (x86)\Steam\steamapps\common\Don't Starve Together\data"
		../mods/test_mod\scripts\?.lua;
		../mods/workshop-1290774114\scripts\?.lua;
		../mods/workshop-1295277999\scripts\?.lua;
		../mods/workshop-2302837868\scripts\?.lua;
		../mods/workshop-351325790\scripts\?.lua;
		../mods/workshop-352373173\scripts\?.lua;
		../mods/workshop-1918863324\scripts\?.lua;
		../mods/workshop-376333686\scripts\?.lua;
		../mods/workshop-944320099\scripts\?.lua;
		../mods/workshop-2375395675\scripts\?.lua;
		../mods/workshop-1529942793\scripts\?.lua;
		../mods/workshop-1213299911\scripts\?.lua;
		../mods/workshop-496048926\scripts\?.lua;
		../mods/workshop-1603516353\scripts\?.lua;
		../mods/workshop-343753877\scripts\?.lua;
		../mods/workshop-1684100178\scripts\?.lua;
		../mods/workshop-1584066691\scripts\?.lua;
		../mods/workshop-666155465\scripts\?.lua;
		../mods/workshop-727774324\scripts\?.lua;
		scripts\?.lua;
		scriptlibs\?.lua;
		scripts/?.lua	

 

 

 

dstNetTest.ods

Edited by Bigfootmech
Link to comment
Share on other sites

2 hours ago, Bigfootmech said:

For the image you sent, are you editing the default image, or the server listing screens and server creation (read: creation or resume) screens?

Actually I'm not editing any part of the vanilla game, I'm simply loading "skeleton" versions of the characters by adding characters with almost no data. The game actually tries to look for modded character saveslot portraits when it sees it in the mod list, so I just decided that I add characters, but with a client mod so it runs on the server list screen. The characters are unselectable, but completely unloading them would be ideal, however, if I can directly add to the save portrait table, that would be way more efficient than this hacky way.

Quote

not GLOBAL.TheNet:GetIsClient() and not GLOBAL.TheNet:IsDedicated()

Do I use it in a statement like this?

if not GLOBAL.TheNet:GetIsClient() and not GLOBAL.TheNet:IsDedicated() then
  --Fix character save slot code
  --Don't have it on hand since im at work lol
 end

 

Finally, I'm not familiar at all with Lua IO, I have much to learn, though theoretically I know how I could get workshop id's for mods, I don't know how I would detect characters within these mods and add them to the table.

Link to comment
Share on other sites

I think the game tries to look for the saveslot portrait without adding the character? Though I could be wrong?

What are you adding when you add these "barebones" characters? :o (I haven't actually done any character mods)

 

I'd labelled it first, so I knew what was going on :p. But yeah

Spoiler

local function IsInMainMenu()
    return not GLOBAL.TheNet:GetIsClient() and not GLOBAL.TheNet:IsDedicated() -- isn't client + isn't server = main menu; "is dedicated" = even locally hosted server on self.
end

if IsInMainMenu() then
  --Fix character save slot code
  --Don't have it on hand since im at work lol
 end

 

 

Not sure if the io is useful tbh. That's why I added it last as a "maybe". You might have more luck digging around modindex or modutil

Edited by Bigfootmech
Link to comment
Share on other sites


local function IsInMainMenu()
    return not GLOBAL.TheNet:GetIsClient() and not GLOBAL.TheNet:IsDedicated() -- isn't client + isn't server = main menu; "is dedicated" = even locally hosted server on self.
end

if IsInMainMenu() then
  --Fix character save slot code
  --Don't have it on hand since im at work lol
 end

Ah okay, this makes a lot of sense. To be honest, even though I've been modding since July last year, I haven't learned a lot of core stuff, like using return haha.

9 hours ago, Bigfootmech said:

What are you adding when you add these "barebones" characters? :o (I haven't actually done any character mods)

In character mods, there is a simple line of code to add mod characters to the list of characters, like if I wanted to add Wilbur from Shipwrecked, I would do

Assets = {
	Asset( "IMAGE", "images/saveslot_portraits/wilbur.tex" ),
    Asset( "ATLAS", "images/saveslot_portraits/wilbur.xml" )
}

AddModCharacter("wilbur", "ROBOT")

And now the game knows a character called wilbur exists, and since I loaded the his saveslot portrait, it sees a saveslot portrait for Wilbur, and loads it.

 

 

Link to comment
Share on other sites

I don't think I'm the right person to be teaching you coding principles. Especially since I'm bastardizing them myself. '^_^

But either making "self-documenting" functions, or using comments is a good start on knowing what your code does. Whether it's you who's gonna come back to it months later, or someone new.

 

return is just the return value of a function. It's what it sends back after it's done executing.

https://www.tutorialspoint.com/lua/lua_functions.htm

This is pretty common in coding/programming

Lua is interesting (for me) in that, you can have multiple return values from one function :)

local function MyFunctionName(inputVarOne, inputVarTwo, ...)
    -- I do something
    -- I do something else
    return outputVarOne, outputVarTwo, outputVarThree
end

local returnedVarOne, returnedVarTwo, returnedVarThree = MyFunctionName()

 

Interesting :o. I don't know how all that is currently handled in DST. Would have to look in to the code

Currently, I know: "env" is set up in like modutil I think? one of the mod script files.

So you should be able to find what "AddModCharacter" does by text searching through all the files in the /scripts/ folder. Which can be done by your preferred IDE, or Notepad++/Sublime, or even Windows Explorer, if you set to to index the text in that folder.

*searches*

ok,

Spoiler

 modutil.lua Line 494:


	env.AddModCharacter = function(name, gender, modes)
		initprint("AddModCharacter", name, gender, modes)
		AddModCharacter(name, gender, modes)
	end

and line 66:


local function AddModCharacter(name, gender, modes)

 

This tells us that the original definition of AddModCharacter is in modutil.

We can then look at what it does

Spoiler

local function AddModCharacter(name, gender, modes)
    table.insert(MODCHARACTERLIST, name)
    if not DoesCharacterExistInGendersTable(name) then
		if gender == nil then
			print( "Warning: Mod Character " .. name .. " does not currently specify a gender. Please update the call to AddModCharacter to include a gender. \"FEMALE\", \"MALE\", \"ROBOT\", or \"NEUTRAL\", or \"PLURAL\" " )
			gender = "NEUTRAL"
		end
		gender = gender:upper()
		if not CHARACTER_GENDERS[gender] then
			CHARACTER_GENDERS[gender] = {}
		end
		table.insert( CHARACTER_GENDERS[gender], name )
	else
		print( "Warning: Mod Character " .. name .. " already exists in the CHARACTER_GENDERS table. It was either added previously, or added twice. You only need to call AddModCharacter now." )
	end

	MODCHARACTERMODES[name] = modes
end

 

This means, it:

  • Adds the "mod character" name to the MODCHARACTERLIST
  • Adds the character's gender in the right place in CHARACTER_GENDERS
  • Sets the character's "modes" whatever that is, in MODCHARACTERMODES

We can look back to our scripts/screens/serverlistingscreen.lua:1045 to see if it's relevant

                    if table.contains(MODCHARACTERLIST, character) then

Yup, we need it.

Possibly not so much the the gender, and "modes"

Is it worth cutting the "AddModCharacter" command down any further? possibly not.

*searches again*

MODCHARACTERLIST seems to be defined in "constants" for whatever reason

I'm also not 100% sure where it's used to display characters in the join screen in game, but if you told me that it does, I'd believe you. If you told me it was the one in dlcsupport.lua I'd believe you even more.

Either way, we can at least tell it's used in live game logic from the networking.lua

So yeah, the easiest thing to do would be to continue using

"AddModCharacter"

But only in the context of the main menu.

ie: what you're doing already :p

 

One other thing you could maybe do, is try scour other mods for their use of "AddModCharacter". And somehow add this to your client-only main-menu-only loaded mod. But I'm not sure how complicated this would end up being.

Link to comment
Share on other sites

Wow, thank you for the analysis of everything here, glad to see I'm doing this at least semi-correctly! I already do some commenting, but not all that much. The example I sent you of the Wilbur code was with the comments removed, as they were mostly personal notes to myself and a rant about how AddModCharacter should NOT be the simplest way to add portraits, though it seems like it is...

As for looking through my mods to see when characters are added, I actually only have a few character mods installed outside of Shipwrecked and Hamlet characters, which I already added support for. I wonder if there's a way to hook into AddModCharacter, and get the workshop id of the mod the character is from. I think I might be able to put together some hacky form of character detection, but I'll have to look more into how LuaIO works.

 

Link to comment
Share on other sites

Weird thing, upon testing the suggested improvements to not load the mod, it seems to still load itself when I load a world without caves.

I found something to detect this though, I just added

 and not GLOBAL.TheNet:GetServerIsClientHosted()

and that seems to detect it correctly.

Link to comment
Share on other sites

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
 Share

×
  • Create New...