Difference between revisions of "Module:SMAPI compatibility"
Jump to navigation
Jump to search
Pathoschild (talk | contribs) (update CurseForge URL to no longer need the key) |
Pathoschild (talk | contribs) (if a mod has no mod page URLs, link to the source URL if any) |
||
Line 86: | Line 86: | ||
elseif chucklefishId then | elseif chucklefishId then | ||
url = "https://community.playstarbound.com/resources/" .. mw.uri.encode(chucklefishId, "PATH") | url = "https://community.playstarbound.com/resources/" .. mw.uri.encode(chucklefishId, "PATH") | ||
− | + | elseif customUrl then | |
url = customUrl | url = customUrl | ||
+ | elseif hasSource then | ||
+ | url = sourceUrl | ||
end | end | ||
Revision as of 02:17, 29 September 2023
This module provides the table structure and data for the Modding:Mod compatibility list.
Examples
Compatible mod
{{#invoke:SMAPI compatibility|entry |name = Lookup Anything |author = Pathoschild |id = Pathoschild.LookupAnything |nexus = 541 |github = Pathoschild/StardewMods }}
mod name | author | compatibility | broke in | source | |
---|---|---|---|---|---|
Lookup Anything | Pathoschild | ✓ use latest version. | source | # |
Broken mod
{{#invoke:SMAPI compatibility|entry |name = Lookup Anything |author = Pathoschild |id = Pathoschild.LookupAnything |nexus = 541 |github = Pathoschild/StardewMods |summary = |broke in = Stardew Valley 1.2 }}
mod name | author | compatibility | broke in | source | |
---|---|---|---|---|---|
Lookup Anything | Pathoschild | ↻ broken, not updated yet. | Stardew Valley 1.2 | source | # |
Unofficial update
For an unofficial update, use the broken-mod template and add these under the other fields:
|unofficial url = https://community.playstarbound.com/attachments/201345000 |unofficial version = 1.18.2-unofficial.1-example
mod name | author | compatibility | broke in | source | |
---|---|---|---|---|---|
Lookup Anything | Pathoschild | ⚠ broken, use unofficial version (1.18.2-unofficial.1-example). | Stardew Valley 1.2 | source | # |
Usage
Limitations
The name, author, and id arguments are comma-separated. If the actual value contains a comma, use ,
instead.
Main fields (shown above)
field | purpose |
---|---|
name
|
The normalised display name for the mod. Delimit alternate names with commas. |
author
|
The name of the author, as shown on Nexus or in its manifest.json file. Delimit alternate names with commas. |
id
|
The latest unique mod ID, as listed in its manifest.json file. Delimit alternate/older IDs with commas (ideally in latest to oldest order). For very old mods with no ID, use none to disable validation checks. |
nexus
|
The mod's unique ID on Nexus (if any). This is the number in the mod page's URL. |
github
|
The mod's GitHub repository in the form owner/repo. |
summary
|
Specify custom notes or instructions about the mod's compatibility. Should usually be blank. |
broke in
|
The SMAPI or Stardew Valley update which broke this mod (if applicable). |
Other fields
field | purpose |
---|---|
status
|
Whether the mod is compatible with the latest versions of Stardew Valley and SMAPI (see #Valid statuses). If not specified, it defaults to unofficial if an unofficial URL is given, else broken if broke in is specified, else ok .
|
unofficial url
|
A page URL where the player can download an unofficial update, if any. |
unofficial version
|
The unofficial update's version number, if any. |
chucklefish
|
The mod's ID in the Chucklefish mod repository. |
curse
|
The mod's project ID and key in the CurseForge mod repository. The ID is shown on the mod page next to "Project ID", and the key is shown in the mod page's URL. This must be in the form id,key .
|
moddrop
|
The mod's ID in the ModDrop mod repository. |
url
|
The arbitrary mod URL, if not on a known mod site. Avoid if possible, since this makes crossreferencing more difficult. |
source
|
An arbitrary source code URL, if not on GitHub. Avoid if possible, since this makes crossreferencing more difficult. |
warnings
|
Text explaining additional compatibility warnings about the mod (e.g., not compatible with Linux/Mac). |
content pack for
|
The name of the mod which loads this content pack. |
dev note
|
Special notes intended for developers who maintain unofficial updates or submit pull requests. |
Valid statuses
status | description |
---|---|
ok
|
The mod is compatible. This is the default and doesn't need to be specified. Default summary: use latest version. |
optional
|
The mod is compatible, if you use an optional download on the mod page. Default summary: use optional download.[1] |
unofficial
|
The mod is compatible using an unofficial update. There's no need to specify this; if you also set unofficial url and unofficial version, you can remove the status field. |
workaround
|
The mod isn't compatible, but the player can fix it or there's a good alternative. A summary should be provided manually. If you also set unofficial url and unofficial version, you can remove the status field. |
broken
|
The mod isn't compatible. The message depends on whether the source link is set.Default summary: broken, not updated yet or broken, not open-source. |
abandoned
|
The mod is no longer maintained by the author, and an unofficial update or continuation is unlikely. This should only be used when the author has definitively abandoned the mod (either explicitly, or by removing the mod page or downloads). Default summary: no longer maintained. |
obsolete
|
The mod is no longer needed and should be removed. |
unknown
|
The mod's compatibility status hasn't been tested. This should only be used as a placeholder (e.g., when adding a new beta), it should never be used long since that defeats the purpose of the compatibility list. |
local p = {}
local private = {}
-- whether to handle Stardew Valley beta fields (don't forget to comment or uncomment the beta fields in /doc)
local enableBeta = false
--##########
--## Public functions
--##########
-- Start a SMAPI compatibility table.
-- @test mw.log(p.header({}))
function p.header(frame)
return
private.style(frame)
.. '<table class="wikitable sortable plainlinks" id="mod-list">'
.. "<tr><th>mod name</th><th>author</th><th><abbr title=\"This only shows whether a mod is *compatible*; it may have bugs unrelated to SMAPI compatibility.\">compatibility</abbr></th><th>broke in</th><th>source</th><th> </th></tr>";
end
-- End a SMAPI compatibility table.
-- @test mw.log(p.footer())
function p.footer()
return '</table>'
end
--- Render a mod row in the SMAPI compatibility table.
-- @param frame The arguments passed to the script.
-- @test mw.log(p.entry({ args = { name="Content Patcher, ContentPatcher", author="Pathoschild, Pathos", id="Pathoschild.ContentPatcher, ContentPatcher", nexus="1915", chucklefish="4250", curse="309243,content-patcher", github="Pathoschild/StardewMods", warnings="warning A, warning B" }}))
function p.entry(frame)
-- read input args
local names = private.parseCommaDelimited(frame.args["name"] or '')
local authors = private.parseCommaDelimited(frame.args["author"] or '')
local ids = private.parseCommaDelimited(frame.args["id"] or '')
local nexusId = private.emptyToNil(frame.args["nexus"])
local github = private.emptyToNil(frame.args["github"])
local summary = private.emptyToNil(frame.args["summary"])
local brokeIn = private.emptyToNil(frame.args["broke in"])
local status = private.emptyToNil(frame.args["status"])
local unofficialVersion = private.emptyToNil(frame.args["unofficial version"])
local unofficialUrl = private.emptyToNil(frame.args["unofficial url"])
local chucklefishId = private.emptyToNil(frame.args["chucklefish"])
local curseforgeId = private.emptyToNil(frame.args["curse"])
local moddropId = private.emptyToNil(frame.args["moddrop"])
local customUrl = private.emptyToNil(frame.args["url"])
local customSource = private.emptyToNil(frame.args["source"])
local warnings = private.parseCommaDelimited(frame.args["warnings"])
local devNote = private.emptyToNil(frame.args["dev note"])
local contentPackFor = private.emptyToNil(frame.args["content pack for"])
local betaSummary = nil
local betaBrokeIn = nil
local betaStatus = nil
local betaUnofficialVersion = nil
local betaUnofficialUrl = nil
if enableBeta then
local betaSummary = private.emptyToNil(frame.args["beta summary"])
local betaBrokeIn = private.emptyToNil(frame.args["beta broke in"])
local betaStatus = private.emptyToNil(frame.args["beta status"])
local betaUnofficialVersion = private.emptyToNil(frame.args["beta unofficial version"])
local betaUnofficialUrl = private.emptyToNil(frame.args["beta unofficial url"])
end
-- get source url
local sourceUrl = customSource
if github then
sourceUrl = "https://github.com/" .. string.gsub(mw.uri.encode(github, "PATH"), "%%2F", "/")
end
local hasSource = sourceUrl ~= nil
-- parse compatibility
local compat = private.getCompatInfo(status, summary, brokeIn, unofficialVersion, unofficialUrl, hasSource)
local betaCompat = nil
if enableBeta and (betaStatus or betaBrokeIn or betaUnofficialUrl or betaUnofficialVersion) then
betaCompat = private.getCompatInfo(betaStatus, betaSummary, betaBrokeIn, betaUnofficialVersion, betaUnofficialUrl, hasSource)
end
-- get main URL
local url = nil
if nexusId then
url = "https://www.nexusmods.com/stardewvalley/mods/" .. mw.uri.encode(nexusId, "PATH")
elseif moddropId then
url = "https://www.moddrop.com/stardew-valley/mods/" .. mw.uri.encode(moddropId, "PATH")
elseif curseforgeId then
url = "https://www.curseforge.com/projects/" .. mw.uri.encode(curseforgeId, "PATH")
elseif chucklefishId then
url = "https://community.playstarbound.com/resources/" .. mw.uri.encode(chucklefishId, "PATH")
elseif customUrl then
url = customUrl
elseif hasSource then
url = sourceUrl
end
-- build HTML row
local row = mw.html.create("tr")
row:addClass("mod")
row:attr("id", names[1] and mw.uri.anchorEncode(names[1]));
row:attr("data-id", table.concat(ids, ","))
row:attr("data-name", table.concat(names, ","))
row:attr("data-author", table.concat(authors, ","))
row:attr("data-cf-id", chucklefishId)
row:attr("data-curseforge-id", curseforgeId)
row:attr("data-moddrop-id", moddropId)
row:attr("data-nexus-id", nexusId)
row:attr("data-github", github)
row:attr("data-custom-source", customSource)
row:attr("data-url", url)
row:attr("data-status", compat.status)
row:attr("data-summary", compat.summary)
row:attr("data-broke-in", compat.brokeIn)
row:attr("data-unofficial-version", compat.unofficialVersion)
row:attr("data-unofficial-url", compat.unofficialUrl)
if enableBeta then
row:attr("data-beta-status", betaCompat and betaCompat.status)
row:attr("data-beta-summary", betaCompat and betaCompat.summary)
row:attr("data-beta-broke-in", betaCompat and betaCompat.brokeIn)
row:attr("data-beta-unofficial-version", betaCompat and betaCompat.unofficialVersion)
row:attr("data-beta-unofficial-url", betaCompat and betaCompat.unofficialUrl)
end
row:attr("data-warnings", private.emptyToNil(table.concat(warnings, ",")))
row:attr("data-content-pack-for", contentPackFor)
row:attr("data-dev-note", devNote)
row:newline()
-- add name field
do
local field = mw.html.create("td")
field:wikitext("[" .. (url or '') .. " " .. (names[1] or '') .. "]")
local nameCount = #names
if nameCount > 1 then
field:wikitext("<br /><small>(aka ")
for i = 1, nameCount do
if i > 1 then
field:wikitext(names[i])
if i < nameCount then
field:wikitext(", ")
end
end
end
field:wikitext(")</small>")
end
row:node(field)
row:newline()
end
-- add author field
do
local field = mw.html.create("td")
field:wikitext(authors[1])
local authorCount = #authors
if authorCount > 1 then
field:wikitext("<br /><small>(aka ")
for i = 1, authorCount do
if i > 1 then
field:wikitext(authors[i])
if i < authorCount then
field:wikitext(", ")
end
end
end
field:wikitext(")</small>")
end
row:node(field)
row:newline()
end
-- add summary field
do
local field = mw.html.create("td")
-- stable status
field:wikitext("<span class=\"mod-summary\">" .. compat.summaryIcon .. " " .. compat.summary .. "</span>")
if compat.status == "optional" then
field:wikitext("<ref name=\"optional-update\" />")
end
-- beta status
if betaCompat ~= nil then
field:wikitext("<br />")
field:wikitext("'''SDV 1.5.5 beta only:''' <span class=\"mod-beta-summary\">" .. betaCompat.summaryIcon .. " " .. betaCompat.summary .. "</span>")
if betaCompat.status == "optional" then
field:wikitext("<ref name=\"optional-update\" />")
end
end
-- warnings
do
local warningCount = #warnings
if warningCount > 0 then
for i = 1, warningCount do
field:wikitext("<br />⚠ " .. warnings[i])
end
end
end
row:node(field)
row:newline()
end
-- add 'broke in' field
do
local field = mw.html.create("td")
if betaCompat ~= nil and betaCompat.brokeIn ~= nil then
field:wikitext(betaCompat.brokeIn)
elseif compat.brokeIn ~= nil then
field:wikitext(compat.brokeIn)
end
row:node(field)
row:newline()
end
-- add 'source' field
do
if sourceUrl then
row:wikitext("<td class=\"mod-source\">[" .. sourceUrl .. " source]</td>")
else
row:wikitext("<td class=\"mod-source\"><span>closed source</span></td>")
end
row:newline()
end
-- add metadata field
do
local field = mw.html.create("td")
field:attr("class", "mod-metadata")
-- anchor
field:wikitext("[[#" .. (names[1] or '') .. "|#]] ")
-- dev note
if devNote then
local devNoteField = mw.html.create("abbr")
devNoteField:attr("title", devNote)
devNoteField:wikitext("[dev note]")
field:node(devNoteField)
end
-- validation
if #ids == 0 then
field:wikitext("[⚠ no id] ")
end
row:node(field)
end
return tostring(row)
end
--##########
--## Private functions
--##########
-- Get the <templatestyles> tag for the module's stylesheet.
-- @param frame The arguments passed to the script.
function private.style(frame)
if frame.extensionTag ~= nil then
return frame:extensionTag('templatestyles', '', {src = 'Module:SMAPI compatibility/styles.css'})
else
return "" -- called from the debug console
end
end
-- Get the normalised compatibility info for a mod.
-- @param status The specified status code. If nil or blank, it'll be derived from the other fields.
-- @param summary A human-readable summary of the compatibility info. If nil or blank, it'll be derived from the other fields.
-- @param brokeIn The SMAPI or Stardew Valley version which broke the mod, if applicable.
-- @param unofficialVersion The unofficial version which fixes compatibility, if applicable.
-- @param unofficialUrl The URL for the unofficial version which fixes compatibility, if applicable.
-- @param hasSource Whether the mod has public source code available.
function private.getCompatInfo(status, summary, brokeIn, unofficialVersion, unofficialUrl, hasSource)
-- derive status
if status == nil then
if unofficialVersion ~= nil then
status = "unofficial"
elseif brokeIn ~= nil then
status = "broken"
else
status = "ok"
end
end
-- derive summary icon
local summaryIcon = "✓"
if status == "unofficial" or status == "workaround" then
summaryIcon = "⚠"
elseif status == "broken" and hasSource then
summaryIcon = "↻"
elseif status == "broken" or status == "obsolete" or status == "abandoned" then
summaryIcon = "✖"
end
-- derive summary
if not summary then
if status == "ok" then
summary = "use latest version."
elseif status == "optional" then
summary = "use optional download."
elseif status == "unofficial" then
summary = "broken, use [" .. (unofficialUrl or "") .. " " .. "unofficial version]"
if unofficialVersion ~= nil then
summary = summary .. " (<small>" .. unofficialVersion .. "</small>)"
end
summary = summary .. "."
elseif status == "workaround" then
summary = "broken. '''error:''' should specify summary."
elseif status == "broken" then
if hasSource then
summary = "broken, not updated yet."
else
summary = "broken, not open-source."
end
elseif status == "obsolete" then
summary = "remove this mod (obsolete)."
elseif status == "abandoned" then
summary = "remove this mod (no longer maintained)."
else
summary = "'''error:''' unknown status '" .. status .. "'."
end
end
return {
status = status,
summaryIcon = summaryIcon,
summary = summary,
brokeIn = brokeIn,
unofficialVersion = unofficialVersion,
unofficialUrl = unofficialUrl
}
end
-- Get a nil value if the specified value is an empty string, else return the value unchanged.
-- @param value The string to check.
function private.emptyToNil(value)
if value ~= "" then
return value
else
return nil
end
end
-- Parse a comma-delimited string into an array.
-- @param value The string to parse.
function private.parseCommaDelimited(value)
local result = {}
if value ~= nil then
local values = mw.text.split(value, ",", true)
for i = 1, #values do
v = mw.text.trim(values[i])
if v ~= "" then
table.insert(result, v)
end
end
end
return result
end
return p