Creating a Sleeves Content Pack - Floogen/FashionSense GitHub Wiki

Requires FS v3.1+

A content pack for Fashion Sense (FS) allows mod authors to create sleeves with the following benefits:

  • Can be larger than 16x32 pixels
  • Can be animated
  • Can override the player's sleeves color

Fashion Sense content packs are compatible with other FS packs, meaning you can have as many Fashion Sense sleeves as you'd like!

TL;DR Edition

  1. Create a parent folder with the content pack's name.

  2. Create and fill in manifest.json as a content pack.

  3. Create a Sleeves folder, add it under the content pack's main folder.

  4. Create a sub-folder under Sleeves with the name of the sleeves

    a. The name of the folder(s) under Sleeves do not matter.

  5. Create a sleeves.json under the created sub-folder, using the required fields found here.

  6. Create a sleeves.png under the same sub-folder.

    a. The image size doesn't matter, so long as it is under 16384x16384 pixels (though smaller files are preferred to reduce memory usage).


Structure

A Fashion Sense content pack consists of the following structure:

[FS] Example Pack
├── manifest.json
│
└── Sleeves
    ├── Tentacles
    │   ├── sleeves.json
    │   └── sleeves.png
    │
    └── Striped Sleeves
        ├── sleeves.json
        └── sleeves.png

If this is your first time creating a content pack, it may be useful to read the Stardew Valley Wiki for creating a content pack.

 

First Steps

The first step to making your content pack is to create a top level folder with the name of your content pack. For example: [FS] Example Pack.

After that, you'll want to create the manifest.json file under that folder. Detailed instructions can be found on the Stardew Valley Wiki. Additionally, you can check out the example manifest.json as a reference.

For example:

[FS] Example Pack
└──  manifest.json



It is important to note that the manifest.json file must contain the following for it to be a content pack by Fashion Sense:

"ContentPackFor": {
    "UniqueID": "PeacefulEnd.FashionSense",
    "MinimumVersion": "USE.LATEST.VERSION"
}

Note: Replace "MinimumVersion": "USE.LATEST.VERSION" with the latest version number of Fashion Sense found here.

 

Creating Sleeves

The folder structure

You will first need to create a Sleeves folder underneath your main content pack folder.

After creating the Sleeves folder you'll want to create a sub-folder for every sleeves you want to add, like so:

.
└── Sleeves
    ├── Tentacles
    │
    └── Striped Sleeves

You can also have sub-folders under the Sleeves folder for organizing, like so:

.
└── Sleeves
    ├── Animated or Colorable
    │   └── Tentacles
    │
    └── Static
        └── Striped Sleeves

 

Adding the sleeves

To add a sleeves to your content pack, the framework requires a sleeves.json under each sub-folder of Sleeves.

For example:

.
└── Sleeves
    ├── Tentacles
    │   ├── sleeves.json
    │   └── sleeves.png
    │
    └── Striped Sleeves
        ├── sleeves.json
        └── sleeves.png

The file sleeves.json determines how the sleeves is to be applied and allows the usage of certain conditions for animation.

An overview of the required properties for sleeves.json:

Property Description Default
Name Required Name of the sleeves, which can contain spaces. N/A
Format Recommended The format version to use, which should be set to Fashion Sense's current version. "1.0.0"
BackSleeves Optional 1 Specifies how the sleeves will look while the player is facing backwards.
See this page for more details.
null
RightSleeves Optional 1 Specifies how the sleeves will look while the player is facing right.
See this page for more details.
null
FrontSleeves Optional 1 Specifies how the sleeves will look while the player is facing forward.
See this page for more details.
null
LeftSleeves Optional 1 Specifies how the sleeves will look while the player is facing left.
See this page for more details.
null
  1. At least one SleevesModel(FrontSleeves, BackSleeves, etc.) must be given for the sleeves to be valid.

    a. Note that SleevesModel have their own required properties, as can be seen here.

Basic JSON example

This is a simple sleeves.json, which adds a static (non-animated) sleeves that applies for all directions while the player is walking or running.

{
  "Name": "Striped Sleeves",
  "FrontSleeves": {
    "ColorMasks": [
      [ 212, 208, 197 ]
    ],
    "UseShirtColors": true,
    "DisableSkinGrayscale": true,
    "SkinToneMasks": {
      "LightTone": [ 249, 174, 137 ],
      "MediumTone": [ 224, 107, 101 ],
      "DarkTone": [ 107, 0, 58 ]
    },
    "StartingPosition": {
      "X": 0,
      "Y": 0
    },
    "BodyPosition": {
      "X": 0,
      "Y": 0
    },
    "SleevesSize": {
      "Width": 16,
      "Length": 32
    },
    "IdleAnimation": [
      {
        "Frame": 0,
        "Duration": 125
      }
    ],
    "MovementAnimation": [
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 1,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 1,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 2,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 2,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      }
    ]
  },
  "BackSleeves": {
    "ColorMasks": [
      [ 212, 208, 197 ]
    ],
    "UseShirtColors": true,
    "DisableSkinGrayscale": true,
    "SkinToneMasks": {
      "LightTone": [ 249, 174, 137 ],
      "MediumTone": [ 224, 107, 101 ],
      "DarkTone": [ 107, 0, 58 ]
    },
    "StartingPosition": {
      "X": 0,
      "Y": 64
    },
    "BodyPosition": {
      "X": 0,
      "Y": 0
    },
    "SleevesSize": {
      "Width": 16,
      "Length": 32
    },
    "IdleAnimation": [
      {
        "Frame": 0,
        "Duration": 125
      }
    ],
    "MovementAnimation": [
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 1,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 1,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 2,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 2,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      }
    ]
  },
  "RightSleeves": {
    "ColorMasks": [
      [ 212, 208, 197 ]
    ],
    "UseShirtColors": true,
    "DisableSkinGrayscale": true,
    "SkinToneMasks": {
      "LightTone": [ 249, 174, 137 ],
      "MediumTone": [ 224, 107, 101 ],
      "DarkTone": [ 107, 0, 58 ]
    },
    "StartingPosition": {
      "X": 0,
      "Y": 32
    },
    "BodyPosition": {
      "X": 0,
      "Y": 2
    },
    "SleevesSize": {
      "Width": 16,
      "Length": 32
    },
    "IdleAnimation": [
      {
        "Frame": 0,
        "Duration": 125
      }
    ],
    "MovementAnimation": [
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 1,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 1,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 2,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 2,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      }
    ]
  },
  "LeftSleeves": {
    "Flipped": true,
    "ColorMasks": [
      [ 212, 208, 197 ]
    ],
    "UseShirtColors": true,
    "DisableSkinGrayscale": true,
    "SkinToneMasks": {
      "LightTone": [ 249, 174, 137 ],
      "MediumTone": [ 224, 107, 101 ],
      "DarkTone": [ 107, 0, 58 ]
    },
    "StartingPosition": {
      "X": 0,
      "Y": 32
    },
    "BodyPosition": {
      "X": 0,
      "Y": 2
    },
    "SleevesSize": {
      "Width": 16,
      "Length": 32
    },
    "IdleAnimation": [
      {
        "Frame": 0,
        "Duration": 125
      }
    ],
    "MovementAnimation": [
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 1,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 1,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 0,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 2,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      },
      {
        "Frame": 2,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsRunning",
            "Value": true
          }
        ]
      }
    ]
  }
}

The StartingPosition property is used to determine where the starting point is (top-left most pixel for the SleevesModel).

The BodyPosition property is used to tell the framework where the player's body would be in relation to the sleeves.

Note: It is important to know that each SleevesModel added (FrontSleeves, BackSleeves, etc.) must have a StartingPosition, BodyPosition and SleevesSize, as the framework uses those properties to display the sleeves.

Basic sleeves example

The corresponding sleeves.png

The layout requirements for sleeves.png is fairly relaxed, as the framework utilizes StartingPosition, BodyPosition and SleevesSize to determine where the sleeves sprites are located.

See the example pack for references on how to create your sleeves.


Animated JSON example

This is an animated sleeves.json, which adds a sleeves with an uniform animation and only applies while the player is facing forward.

{
  "Name": "Tentacles",
  "FrontSleeves": {
    "ColorMasks": [
      [56, 56, 56],
      [114, 110, 100],
      [212, 208, 197]
    ],
    "UseShirtColors": true,
    "DisableSkinGrayscale": true,
    "SkinToneMasks": {
      "LightTone": [144, 144, 144],
      "MediumTone": [121, 121, 121],
      "DarkTone": [73, 73, 73],
    },
    "StartingPosition": {
      "X": 0,
      "Y": 0
    },
    "BodyPosition": {
      "X": 11,
      "Y": -5
    },
    "SleevesSize": {
      "Width": 48,
      "Length": 32
    },
    "UniformAnimation": [
      {
        "Frame": 0,
        "Duration": 125,
        "Conditions": [
          {
            "Name": "IsCarrying",
            "Value": false
          }
        ]
      },
      {
        "Frame": 1,
        "Duration": 125,
        "Conditions": [
          {
            "Name": "DidPreviousFrameDisplay",
            "Value": true
          },
          {
            "Name": "IsCarrying",
            "Value": false
          }
        ]
      },
      {
        "Frame": 2,
        "Duration": 125,
        "Conditions": [
          {
            "Name": "DidPreviousFrameDisplay",
            "Value": true
          },
          {
            "Name": "IsCarrying",
            "Value": false
          }
        ]
      },
      {
        "Frame": 1,
        "Duration": 125,
        "Conditions": [
          {
            "Name": "DidPreviousFrameDisplay",
            "Value": true
          },
          {
            "Name": "IsCarrying",
            "Value": false
          }
        ]
      },
      {
        "Frame": 2,
        "Duration": 125,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsCarrying",
            "Value": true
          }
        ]
      }
    ]
  },
  "BackSleeves": {
    "ColorMasks": [
      [56, 56, 56],
      [114, 110, 100],
      [212, 208, 197]
    ],
    "UseShirtColors": true,
    "DisableGrayscale": false,
    "DisableSkinGrayscale": true,
    "SkinToneMasks": {
      "LightTone": [144, 144, 144],
      "MediumTone": [121, 121, 121],
      "DarkTone": [73, 73, 73],
    },
    "DrawBeforeHair": true,
    "StartingPosition": {
      "X": 0,
      "Y": 0
    },
    "BodyPosition": {
      "X": 11,
      "Y": -5
    },
    "SleevesSize": {
      "Width": 48,
      "Length": 32
    },
    "UniformAnimation": [
      {
        "Frame": 0,
        "Duration": 125,
        "Conditions": [
          {
            "Name": "IsCarrying",
            "Value": false
          }
        ]
      },
      {
        "Frame": 1,
        "Duration": 125,
        "Conditions": [
          {
            "Name": "DidPreviousFrameDisplay",
            "Value": true
          },
          {
            "Name": "IsCarrying",
            "Value": false
          }
        ]
      },
      {
        "Frame": 2,
        "Duration": 125,
        "Conditions": [
          {
            "Name": "DidPreviousFrameDisplay",
            "Value": true
          },
          {
            "Name": "IsCarrying",
            "Value": false
          }
        ]
      },
      {
        "Frame": 1,
        "Duration": 125,
        "Conditions": [
          {
            "Name": "DidPreviousFrameDisplay",
            "Value": true
          },
          {
            "Name": "IsCarrying",
            "Value": false
          }
        ]
      },
      {
        "Frame": 2,
        "Duration": 125,
        "EndWhenFarmerFrameUpdates": true,
        "Conditions": [
          {
            "Name": "IsCarrying",
            "Value": true
          }
        ]
      }
    ]
  }
}

You can see UniformAnimation was specified with 3 frames. UniformAnimation always plays, so long as IdleAnimation and MovementAnimation aren't given.

To play an animation only while the player is moving, you would instead utilize MovementAnimation. For more details see the animations page.

Conditions can also be used to determine which frames are played. See the conditions page for more details.

Animated sleeves example

The corresponding sleeves.png

The layout requirements for animated sleeves.png is fairly relaxed, as it only requires the following condition(s):

  • For SleevesModel with animation, each frame of the sleeves should be placed next to each other horizontally (as seen in the example above).

See the example pack for references on how to create your sleeves.

Next Step

If you're looking for examples, see the examples page.

⚠️ **GitHub.com Fallback** ⚠️