So, you want add your own cards + play shipfic with them online?
Excellent, let's get down into how to do it.
Step 0: Have art for your shipfic cards
It goes without saying that if you want to add your own cards, you first need to have cards!
Check out the TSSSF card generator as well as the released card assets from Horrible People. TSSSF.net uses 788x1088 .png images for all card art
Once you have your .png files ready, you'll need to upload them online. If you're only making a small number of shipfic cards, uploading them to a 3rd party website like the secret shipfic booru or derpibooru is perfect. If you plan on making dozens of cards and releasing multiple expansion packs, you chould check out Appendix A: Pack Format for a better way to organizing your packs
Step 1: Create a pack.json file
All the metadata for each shipfic card is stored in a JSON file. JSON is a very common file format which you can edit easily in a free text editor like notepad++.
The pack.json file doesn't need to be named pack.json, but it does need to have the following structure:
{
"name": "My Shipfic Cards",
"format": "link:1",
"namespace": "authorName.packName",
"cards": {
"pony": {
// add any pony cards here
},
"ship": {
// add any ship cards here
},
"goal": {
// add any goal cards here
},
"start": {
// add any start cards here
}
}
}
Here, the name attribute is what you want to display as the name of your pack, and the namespace attribute is a unique name associated with your pack (it's standard to do authorName.packName, but you can use anything as long as it only uses A-z characters and periods)
Replace the // add
comments with additional JSON to add your cards to the pack, like so. Make sure to
replace the names with ones that fit and the URLS with URLs that point to your uploaded cards
{
"name": "My Shipfic Cards",
"format": "link:1",
"namespace": "authorName.packName",
"cards": {
"pony": {
"BrokenWingRainbowDash": {
"url": "https://tsssf.net/packs/Core/Pony/BrokenWingRainbowDash.png"
},
"UnicornChangeling": {
"url": "https://tsssf.net/packs/Core/Pony/UnicornChangeling.png"
}
},
"ship": {
"LovePoisonIsNoJoke": {
"url": "https://tsssf.net/packs/Core/Ship/LovePoisonIsNoJoke.png"
}
},
"goal": {
"ItsNotEvil": {
"url": "https://tsssf.net/packs/Core/Goal/ItsNotEvil.png"
}
},
"start": {
"FanficAuthorTwilight": {
"url": "https://tsssf.net/packs/Core/Start/FanficAuthorTwilight.png"
}
}
}
}
Step 2: Add Additional Attributes to Each Card
Now that every card has an image associated with it, it's time to add additional attributes specific to whether it's a pony card, a ship card, or a goal card.
There's many, many more attributes used by TSSSF.net across all the cards, but the above example should be enough to get you started. See Appendix B: Pony, Ship, + Goal attributes for a complete list, or check out the core deck's pack.jsonfile for even more examples.
{
"name": "My Shipfic Cards",
"format": "link:1",
"namespace": "authorName.packName",
"cards": {
"pony": {
"BrokenWingRainbowDash": {
"url": "https://tsssf.net/packs/Core/Pony/BrokenWingRainbowDash.png",
"name": "Rainbow Dash",
"race": "pegasus",
"gender": "female",
"action": "replace",
"keywords": [
"Mane 6"
]
},
"UnicornChangeling": {
"url": "https://tsssf.net/packs/Core/Pony/UnicornChangeling.png",
"name": "Changeling",
"race": "unicorn",
"action": "Changeling(unicorn)",
"keywords": [
"Changeling",
"Villain"
]
}
},
"ship": {
"LovePoisonIsNoJoke": {
"url": "https://tsssf.net/packs/Core/Ship/LovePoisonIsNoJoke.png",
"action": "lovePoison"
}
},
"goal": {
"ItsNotEvil": {
"url": "https://tsssf.net/packs/Core/Goal/ItsNotEvil.png",
"points": "1"
}
},
"start": {
"FanficAuthorTwilight": {
"url": "https://tsssf.net/packs/Core/Start/FanficAuthorTwilight.png",
"name": "Twilight Sparkle",
"race": "unicorn",
"gender": "female",
"keywords": [
"Mane 6"
]
}
}
}
}
Step 3: Upload to TSSSF.net
Now that you have your json file ready to go, it's time to upload it! Host a new game, open the 'Choose Cards' tab, and upload your JSON file.
If you've done everything correctly, you should now see your cards on the page and you can now play shipfic with them!
Appendix A: Pack Format
The link:1 format, described above, is a good way to quickly put together a pack of cards to play online. However, for larger collections of cards it is convenient to store all the cards in the same folder structure, rather than linking individual cards together. Hence, the pack:1 format.
The Pack:1 File Structure
The difference between the pack:1 format and the link:1 format is that the pack:1 format omits the url attributes and instead, stores the images files in a consistent location based on the card's key name, card type, namespace, and a root attribute, e.g.
For example, the following pack.json file
{
"name": "My Shipfic Cards",
"format": "pack:1",
"namespace": "authorName.packName",
"root": "https://tsssf.net/packs",
"cards": {
"pony": {
"BrokenWingRainbowDash": {
"name": "Rainbow Dash",
"race": "pegasus",
"gender": "female",
"action": "replace",
"keywords": [
"Mane 6"
]
}
},
"ship": {
"LovePoisonIsNoJoke": {
"action": "lovePoison"
}
},
"goal": {
"ItsNotEvil": {
"points": "1"
}
},
"start": {
"FanficAuthorTwilight": {
"name": "Twilight Sparkle",
"race": "unicorn",
"gender": "female",
"keywords": [
"Mane 6"
]
}
}
}
}
corresponds to image files uploaded to the following paths:
https://tsssf.net/packs/authorName/packName/Ship/LovePoisonIsNoJoke.png
https://tsssf.net/packs/authorName/packName/Goal/ItsNotEvil.png
https://tsssf.net/packs/authorName/packName/Start/FanficAuthorTwilight.png
Note that periods in the namespace attribute correspond to nested folders in the path name.
Thumbnail Images
In addition to the above file structure, every .png image has a corresponding thumbnail image, suffixed with .thumb.jpg.
https://tsssf.net/packs/authorName/packName/Pony/BrokenWingRainbowDash.thumb.jpg
The .thumb.jpg images are only 197 x 272 pixels and are much smaller than their .png counterparts, making them load much more quickly.
Appendix B: Pony, Ship, + Goal Attributes
For examples of cards which use the attributes explained in this section, see the various pack.json files used by tsssf.net.
Pony Attributes
Attribute | Valid Values | Notes/Description |
---|---|---|
name | Any string, required "name1/name2/..." | Should be the simplest name of the pony in question, e.g. Twilight Sparkle for cards like Fanfic Author Twilight |
race | "earth" | |
"pegasus" | ||
"unicorn" | ||
"alicorn" | ||
"race1/race2/..." | Any combination of the above races separated by a slash. Will count as each of those races. | |
gender | "male" | |
"female" | ||
"male/female" | Counts as both male and female for all goals | |
keywords | Any string array | Contains all non-name keywords for the pony |
altTimeline | true | Gives a card the altTimeline/apocalypse/hour glass symbol |
changeGoalPointValues | true | When this card is on the grid, prevents goal cards from being worth their normal amount of points |
count | any number | Changes how many ponies this card counts as, e.g. 2 for Aloe & Lotus |
action | "swap" | |
"3swap" | ||
"replace" | ||
"search" | ||
"copy" | ||
"fullCopy" | Copies a card's power as well as all its symbols, names, keywords, etc. | |
"draw" | ||
"newGoal" | ||
"playFromDiscard" | ||
"interrupt" | Allows the card to a pony card which was just played, even if it's not that player's turn. | |
"ship" | Allows the pony card to be played as a ship as well | |
"shipWithEverypony" | Special ability for HorriblePeople.GraciousGivers.Pony.PrincessCelestAI | |
"Changeling(<type>)" | Allows the card to set a disguise when played and when moved. <type> can be any of the following:
earth
pegasus unicorn alicorn nonAlicornFemale plushling replace | |
"ChangelingNoRedisguise(<type>)" | Same as Changeling(<type>), but the card cannot redisguise when moved | |
"exchangeCardsBetweenHands" | Causes players to randomly trade one card with the player next to them | |
"Reminder(<message>)" | Adds a checkbox to remind the player to do something before the end of their turn. <message> is the text that's displayed next to the checkbox |
Ship Attributes
Attribute | Valid Values | Notes/Description |
---|---|---|
action | "genderChange" | |
"raceChange" | ||
"timelineChange" | ||
"lovePoison" | ||
"makePrincess" | ||
"keywordChange" | ||
"clone" | ||
"addKeywords(<list>)" | <list> is a list of keywords (no quotation marks) separated by commas, e.g. Object Nightmare, Villain |
|
"raceGenderChange" | ||
"keywordChange" |
Goal Attributes
Attribute | Valid Values | Notes/Description |
---|---|---|
points | any number in quotes, e.g. "3", "-1", "0.5" | |
multiple numbers (which follow the above rules) separated by a slash in quotes e.g."3/4" | Used for cards which can be worth different point values, depending on certain criteria | |
goalLogic | See Appendix C: Goal Logic |
Appendix C: Goal Logic
Goal logic is an attribute on goal cards which specifies the criteria needed to achieve a goal. Because there are many different kinds of goals and criteria, goal logic is expressed in its own language and has its own specific syntax. To give you an idea to what the language looks like, here are a few examples of the text written on various goal cards and the goal logic associated with it.
Card Text | Goal Logic |
---|---|
Win this goal when 6 earth pony/earth pony ships are on the grid | ExistsShip(race=earth,race=earth,6) |
Win this goal when at least 5 time travelers are on the grid | ExistsPony(altTimeline=true, 5) |
Win this Goal when Rainbow Dash is shipped with any 3 females | ExistsPonyShippedTo(name=Rainbow Dash, Select(gender=female,3)) |
Win this goal when 6 ponies with the Mane 6 keyword are shipped in a chain | ExistsChain(Mane 6 in keywords, 6) |
Win this goal when you break up Shining Armor with any female except Twilight Sparkle | BreakShip(name=Shining Armor, gender=female && name != Twilight Sparkle) |
Goal functions
Valid goal logic consists of a single goal function with all its criteria/count parameters passed in to it. Each goal function checks something different, so make sure you use the right one for your goal
ExistsPony(<criterion>)
ExistsPony(<criterion>, <count>)
ExistsShip(<criterion1>, <criterion2>)
ExistsShip(<criterion1>, <criterion2>, <count>)
ExistsPonyShippedTo(<criterion1>, Select(<criterion2>, <count>))
ExistsPonyShippedTo(<criterion1>, AllOf(<criterion2>, <criterion3>, ...))
ExistsPonyShippedTo(<criterion>, ShippedWith2Versions)
ExistsChain(<criterion>, <count>)
PlayPonies(<criterion>, <count>)
PlayShips(<criterion1>, <criterion2>, <count>)
PlayShipCards(<criterion>)
PlayShipCards(<criterion>, <count>)
BreakShip(<criterion1>, <criterion2>)
BreakShip(<criterion1>, <criterion2>, <count>)
SwapCount(<count>)
There's a few other niche goal functions, but they exist primarily for special cards
ExistsShipGeneric(ShippedWithOppositeGenderedSelf)
Criteria
Criteria are what actually determine if a particular pony card matches or not based on the pony card's attributes. They are passed in as parameters to goal functions
<attribute> = <value> | Matches when the card has attribute set to value |
<attribute> != <value> | Matches when the card does NOT have attribute set to value |
<value> in keywords | Matches when the card has value in its keywords |
<value> !in keywords | Matches when the card does NOT have value in its keywords |
genderSwapped | Matches cards which have had their gender swapped |
* | Matches every card |
Criteria do not use quotation marks anywhere for simplicity's sake. Whitespace between operators like = and != is not necessary, though may help readability. For names which contain two or more words, the whitespace in the name will not be removed, but the whitespace before and after the name will. Names and attributes are cAsE sEnSiTiVe
You can combine multiple criteria into a single check with the following criteria
<criterion1> || <criterion2> | Matches when the card matches criterion1, criterion2, or both |
<criterion1> && <criterion2> | Matches when the card matches both criterion1 and criterion2 |
During the game, cards can sometimes change genders, change races, or gain new keywords. Thus, it is sometimes helpful for criteria to test what the card's original attributes were, rather than what they are now. This can be accommodated by adding "_b" (b for base) to an attribute's name. Here's an example for a criterion which uses this to test if a card has been changed into an alicorn
race=alicorn && race_b != alicorn