SMAPI - Stardew Modding API

SMAPI - Stardew Modding API

971k Downloads

Integrate Content Merging

ClxS opened this issue ยท 9 comments

commented

A member of the community sent me their code for content merging, sounds like a great addition that'll get us most of the way to content only mods.

commented

Can you share the code that was sent with me?

I also have some ideas, important to implement crop rendering without overriding the crop class.

The problem is that if we want to keep the savegame system working without mucking it up too much, we will need to figure a way to keep sprite sheet indexes withotu changing when people mess with the mods they have (or when mods change their content).

I say that because Crops (and some other stuff, like player shirts) are rendered based on their sprite numbers on sprite sheet, and that rendering code is in some messy areas, we would have to use heavy MSIL editing to render it different, and might be prone to break when the game updates, the "correct" way to add new crops, is put new data in the existing spritesheet, I found out how to edit the sprite sheet already (I used the existing event that happens when stuff is loaded), but we need a way to generate the numbers for the added crops, and to keep them unchanged after first loaded.

commented

You are letting the json branch get quite far from the others, I am having problems syncing them.

I say this, because I think for some stuff both Cecil and JSON will be needed.

As for the Crop thing:

I reverse engineered how the Crop rendering work.

First, we have "objects", that are generic items you can pickup and put on the ground (sometimes), like the Iron Ore node (that had the bug where you could put it in your inventory).

Then we have the "Crop", that is always rendered on top of a "HoeDirt"

Forage-able stuff, aren't crops, with the exception of one particular spring thing, thati n the game code when deciding where to forage, DO spawn a HoeDirt in the forest, then later delete it... there are some code paths exclusively to that "forableAble" crop (I think the name is "Spring Onion" or something like that)

Crops need two numbers relevant to us: The "seed number", that is the coordinate of the seed package item in the objects spritesheet (file Maps\springobjects.xnb )

the formula is just the basic formula to convert a number to 2D coordinates, assuming 16 to 16 tiles, so the "seed number" (parsnip) is 472, translating to X 256 and Y 304.

When you plant a crop, the game check by seed number (thus all crops have number) to see what crop it is.

To render a crop, then it takes a number (in Data\Crops.xnb) named "rowInSpriteSheet" to what to render from that sprite sheet, the sprite sheet has two columns, so the actual coordinates need to be fixed as appropriate.

One particular row has special meaning, that is row number 23, that is for forageable crops.
And dead crops have the coordinate in the sprite sheet literally hardcoded (ie: the precise coordinates are baked in the function that renders it, to render it differently would require injection).

All dead crops share the same 4 sprites though... so no problem to add new crops.

So, to add a new crop, we need to add an item to the sprinbobjects, and generate a seed number for it, that we can't change between game sessions (since this is the number that is saved on the inventory for seed packages), we then use that seed number to generate new crop data, in that data we must put there a generated row number... this row number is always read at runtime, meaning we can change it all we want when loading data, we just have to be careful to never set it to "23"

commented

Thanks for the information!
Mmm I can see this becoming a bit of an issue when merging multiple mods which add additional crops.
So for this I suggest we have people provide their standalone crop sprite images in their manifest.json as under the 'crops' array. Then we modify the xnb when loading the mods, and edit the sprite sheet to have the crops in the correct place. I think that should work well. I'll have to give it a try soon.

Ah I'm aware about the branches being a little out of date. I'll merge the master into the JSON branch on Sunday since I'm away tomorrow.
Once both the JSON and IL Injection features are finished then they'll be put into the master branch and I hope it'd be fairly easy to get this working then. (IL Injection is mostly complete. I need to add some more to make it backwards compatible since but it's nearly there.)

commented

I took a proper read of it earlier and it turns out I misunderstood them. It was just code to read their own config files. I've replaced it with the json format I was going to put in anyway.

I'm reluctant to do that level of injection for the reason you said. Don't want to end up breaking when things update.
I'll take a closer look at how assets are manually added soon and get back to you with my thoughts on it. I don't know enough about how it works currently to say much.

commented

I found out that things will be harder than I thought.

The problem is not the crop image (like I said, we can edit the Texture2D easily upon the game load).

The problem is that the crop constructor loads the xnb from the disc, creates a dictionary from the yaml, then uses it.

First problem is... boo CA for making such inefficient thing! (every time you plant a crop for example the constructor is called, thus the game reads the HDD every time you plant something!)

Second, is that we will need injection, since we will have to override the crop constructor :( I don't think we should edit the xnb files on disc...

Tehre is an exception though: all xnb is loaded by XNA ContentManager, I don't checked if it creates a cache or not, if it does we probably can force it to create the cache, then edit the contents.

commented

This isn't a problem. You could just the extend XNA ContentManager and replace gamePtr.content with you own. Then you can easily override the Load method

The real problem is, that CA implemented "temporaryContent" (ContentManager) in some files (Event, TitleMenu, GrandpaStory, ...)

These ones could only altered by Injection...

commented

I'd just like to say for the record that I've created a "content only" mod that doesn't modify any game files (adds a custom location and edits tiles in an existing location after the game loads it to provide a path). I haven't looked into crop code yet, but one issue I'm facing is with the game engine's xml serialization code. It currently can't serialize any instances of custom made derived classes as all types to include are specified in their base classes.

I created my location as a new GameLocation so it can be serialized, but I'd love to be able to make child classes of other locations to inherit some methods while overriding others and also have the benefit of the game recognizing it as a parent type when it does "is" checks.

Btw, source for anyone interested (sorry for pastebin and not git link): http://pastebin.com/uqk74i0Q

commented

@seberoth I just want to say thanks for the suggestion. It works perfectly. We now have content-only XNB replacement mods by overriding the ContentManager and redirecting to our own XNB files when they've been added!

commented

Implemented into Farmhand, Will likely not be done in SMAPI