From 934241bf19f0ee95abb7c78cf728fb021b907b9b Mon Sep 17 00:00:00 2001 From: Geeves Date: Sat, 14 Sep 2024 11:11:22 +0200 Subject: [PATCH 1/2] Autowiki --- .github/workflows/autowiki.yml | 52 ++++ .gitignore | 3 + aurorastation.dme | 5 + code/__DEFINES/autowiki.dm | 7 + code/__HELPERS/sorting/cmp.dm | 6 + code/_compile_options.dm | 1 + code/datums/datum.dm | 5 + code/game/world.dm | 4 + code/modules/autowiki/autowiki.dm | 56 ++++ code/modules/autowiki/pages/_page.dm | 64 ++++ code/modules/autowiki/pages/guns.dm | 64 ++++ code/unit_tests/autowiki.dm | 35 +++ tools/autowiki/autowiki.js | 89 ++++++ tools/autowiki/package-lock.json | 436 +++++++++++++++++++++++++++ tools/autowiki/package.json | 10 + 15 files changed, 837 insertions(+) create mode 100644 .github/workflows/autowiki.yml create mode 100644 code/__DEFINES/autowiki.dm create mode 100644 code/modules/autowiki/autowiki.dm create mode 100644 code/modules/autowiki/pages/_page.dm create mode 100644 code/modules/autowiki/pages/guns.dm create mode 100644 code/unit_tests/autowiki.dm create mode 100644 tools/autowiki/autowiki.js create mode 100644 tools/autowiki/package-lock.json create mode 100644 tools/autowiki/package.json diff --git a/.github/workflows/autowiki.yml b/.github/workflows/autowiki.yml new file mode 100644 index 00000000000..82d0ac76f32 --- /dev/null +++ b/.github/workflows/autowiki.yml @@ -0,0 +1,52 @@ +name: Autowiki +on: + schedule: + - cron: "5 4 * * *" + workflow_dispatch: +permissions: + contents: read + +jobs: + autowiki: + runs-on: ubuntu-20.04 + steps: + - name: "Check for AUTOWIKI_USERNAME" + id: secrets_set + env: + ENABLER_SECRET: ${{ secrets.AUTOWIKI_USERNAME }} + run: | + unset SECRET_EXISTS + if [ -n "$ENABLER_SECRET" ]; then SECRET_EXISTS=true ; fi + echo "SECRETS_ENABLED=$SECRET_EXISTS" >> $GITHUB_OUTPUT + - name: Checkout + if: steps.secrets_set.outputs.SECRETS_ENABLED + uses: actions/checkout@v3 + - name: Restore BYOND cache + if: steps.secrets_set.outputs.SECRETS_ENABLED + uses: actions/cache@v3 + with: + path: ~/BYOND + key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }} + - name: Install rust-g + if: steps.secrets_set.outputs.SECRETS_ENABLED + run: | + sudo dpkg --add-architecture i386 + sudo apt update || true + sudo apt install -o APT::Immediate-Configure=false libssl1.1:i386 + bash tools/ci/install_rust_g.sh + - name: Compile and generate Autowiki files + if: steps.secrets_set.outputs.SECRETS_ENABLED + run: | + bash tools/ci/install_byond.sh + source $HOME/BYOND/byond/bin/byondsetup + tools/build/build --ci autowiki + - name: Run Autowiki + if: steps.secrets_set.outputs.SECRETS_ENABLED + env: + USERNAME: ${{ secrets.AUTOWIKI_USERNAME }} + PASSWORD: ${{ secrets.AUTOWIKI_PASSWORD }} + run: | + cd tools/autowiki + npm install + cd ../.. + node tools/autowiki/autowiki.js data/autowiki_edits.txt data/autowiki_files/ diff --git a/.gitignore b/.gitignore index f8e2e95003d..859a7f395ac 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,6 @@ define_sanity_output.txt # Unit test / coverage reports .cache + +# Misc. Node Modules +**/node_modules/* diff --git a/aurorastation.dme b/aurorastation.dme index a24d2ace0ce..3cc8aa62d28 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -37,6 +37,7 @@ #include "code\__DEFINES\assemblies.dm" #include "code\__DEFINES\assert.dm" #include "code\__DEFINES\atmos.dm" +#include "code\__DEFINES\autowiki.dm" #include "code\__DEFINES\auxtools.dm" #include "code\__DEFINES\background.dm" #include "code\__DEFINES\battle_monsters.dm" @@ -1665,6 +1666,9 @@ #include "code\modules\atmospherics\components\unary\unary_base.dm" #include "code\modules\atmospherics\components\unary\vent_pump.dm" #include "code\modules\atmospherics\components\unary\vent_scrubber.dm" +#include "code\modules\autowiki\autowiki.dm" +#include "code\modules\autowiki\pages\_page.dm" +#include "code\modules\autowiki\pages\guns.dm" #include "code\modules\awaymissions\bluespaceartillery.dm" #include "code\modules\awaymissions\corpse.dm" #include "code\modules\awaymissions\exile.dm" @@ -3731,6 +3735,7 @@ #include "code\modules\world_api\commands\tickets.dm" #include "code\modules\xgm\xgm_gas_data.dm" #include "code\modules\xgm\xgm_gas_mixture.dm" +#include "code\unit_tests\autowiki.dm" #include "code\unit_tests\chemistry_tests.dm" #include "code\unit_tests\cooking_tests.dm" #include "code\unit_tests\create_and_destroy.dm" diff --git a/code/__DEFINES/autowiki.dm b/code/__DEFINES/autowiki.dm new file mode 100644 index 00000000000..4edf385bcc8 --- /dev/null +++ b/code/__DEFINES/autowiki.dm @@ -0,0 +1,7 @@ +#ifdef AUTOWIKI + #define AUTOWIKI_SKIP(skip) autowiki_skip = skip + #define IS_AUTOWIKI_SKIP(datum) datum.autowiki_skip +#else + #define AUTOWIKI_SKIP(skip) + #define IS_AUTOWIKI_SKIP(datum) UNLINT(FALSE) +#endif diff --git a/code/__HELPERS/sorting/cmp.dm b/code/__HELPERS/sorting/cmp.dm index 4b3b935c6e7..2397dd263f8 100644 --- a/code/__HELPERS/sorting/cmp.dm +++ b/code/__HELPERS/sorting/cmp.dm @@ -10,6 +10,12 @@ /proc/cmp_text_dsc(a,b) return sorttext(a,b) +/proc/cmp_typepaths_asc(A, B) + return sorttext("[B]","[A]") + +/proc/cmp_typepaths_name_asc(atom/A, atom/B) + return sorttext(initial(A.name), initial(B.name)) + /proc/cmp_embed_text_asc(a,b) if(isdatum(a)) a = REF(a) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 25fefdf36f9..3877971099a 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -34,6 +34,7 @@ // #define TESTING // Creates debug feedback messages and enables many optional testing procs/checks // #define UNIT_TEST // #define MANUAL_UNIT_TEST +// #define AUTOWIKI #if defined(MANUAL_UNIT_TEST) && !defined(SPACEMAN_DMM) && !defined(OPENDREAM) #warn Manual unit test is defined, remember to recomment it before PRing! diff --git a/code/datums/datum.dm b/code/datums/datum.dm index f94d98d80bb..b61d62e5ca5 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -66,6 +66,11 @@ // Create and destroy is weird and I wanna cover my bases var/harddel_deets_dumped = FALSE +#ifdef AUTOWIKI + /// When set to TRUE, will not be added to the autowiki + var/autowiki_skip = FALSE +#endif + /** * Default implementation of clean-up code. * diff --git a/code/game/world.dm b/code/game/world.dm index 2fcc0eecc7c..9d282c45869 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -129,6 +129,10 @@ GLOBAL_PROTECT(config) Master.Initialize(10, FALSE, TRUE) +#ifdef AUTOWIKI + setup_autowiki() +#endif + #undef RECOMMENDED_VERSION return diff --git a/code/modules/autowiki/autowiki.dm b/code/modules/autowiki/autowiki.dm new file mode 100644 index 00000000000..e221fbc2750 --- /dev/null +++ b/code/modules/autowiki/autowiki.dm @@ -0,0 +1,56 @@ +/// When the `AUTOWIKI` define is enabled, will generate an output file for tools/autowiki/autowiki.js to consume. +/// Autowiki code intentionally still *exists* even without the define, to ensure developers notice +/// when they break it immediately, rather than until CI or worse, call time. +#if defined(AUTOWIKI) || defined(UNIT_TESTS) +/proc/setup_autowiki() + Master.sleep_offline_after_initializations = FALSE + UNTIL(SSticker.current_state == GAME_STATE_PREGAME) + + //trigger things to run the whole process + SSticker.restart_timeout = 0 + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(generate_autowiki))) + SSticker.current_state = GAME_STATE_SETTING_UP + +/proc/generate_autowiki() + var/output = generate_autowiki_output() + rustg_file_write(output, "data/autowiki_edits.txt") + qdel(world) +#endif + +/// Returns a string of the autowiki output file +/proc/generate_autowiki_output() + var/total_output = "" + + for (var/datum/autowiki/autowiki_type as anything in subtypesof(/datum/autowiki)) + var/datum/autowiki/autowiki = new autowiki_type + + if(autowiki.generate_multiple) + var/output = autowiki.generate_multiple() + + if (!islist(output)) + CRASH("[autowiki_type] does not generate a proper output when generate_multiple is set!") + + for(var/list in output) + total_output += json_encode(list) + "\n" + + if(!autowiki.page) + continue + + var/list/all_page_names = list() + for(var/list in output) + all_page_names += autowiki.include_template(list["title"]) + + total_output += json_encode(list("title" = autowiki.page, "text" = all_page_names.Join(" "))) + "\n" + continue + + var/output = autowiki.generate() + + if (!istext(output)) + CRASH("[autowiki_type] does not generate a proper output!") + + total_output += json_encode(list( + "title" = autowiki.page, + "text" = output, + )) + "\n" + + return total_output diff --git a/code/modules/autowiki/pages/_page.dm b/code/modules/autowiki/pages/_page.dm new file mode 100644 index 00000000000..1078d66cd6b --- /dev/null +++ b/code/modules/autowiki/pages/_page.dm @@ -0,0 +1,64 @@ +/// A representation of an automated wiki page. +/datum/autowiki + /// The page on the wiki to be replaced. + /// This should never be a user-facing page, like "Guide to circuits". + /// It should always be a template that only Autowiki should touch. + /// For example: "Template:Autowiki/CircuitInfo". + var/page + + /// If the generation of this autowiki should call /generate_multiple(), + /// which should return a list of list(title = "Page Title", contents) + /// allowing for the generation of multiple pages in the same autowiki + var/generate_multiple = FALSE + +/// Override and return the new text of the page. +/// This proc can be impure, usually to call `upload_file`. +/datum/autowiki/proc/generate() + SHOULD_CALL_PARENT(FALSE) + CRASH("[type] does not implement generate()!") + +/datum/autowiki/proc/generate_multiple() + SHOULD_CALL_PARENT(FALSE) + +/// Generates an auto formatted template user. +/// Your autowiki should ideally be a *lot* of these. +/// It lets wiki editors edit it much easier later, without having to enter repo. +/// Parameters will be passed in by name. That means your template should expect +/// something that looks like `{{ Autowiki_Circuit|name=Combiner|description=This combines }}` +/// Lists, which must be array-like (no keys), will be turned into a flat list with their key and a number, +/// such that list("food" = list("fruit", "candy")) -> food1=fruit|food2=candy +/// Your page should respect AUTOWIKI_SKIP, and check for this using IS_AUTOWIKI_SKIP +/datum/autowiki/proc/include_template(name, parameters) + var/template_text = "{{[name]" + + var/list/prepared_parameters = list() + for (var/key in parameters) + var/value = parameters[key] + if (islist(value)) + for (var/index in 1 to length(value)) + prepared_parameters["[key][index]"] = "[value[index]]" + else + prepared_parameters[key] = value + + for (var/parameter_name in prepared_parameters) + template_text += "|[parameter_name]=" + template_text += "[prepared_parameters[parameter_name]]" + + template_text += "}}" + + return template_text + +/// Takes an icon and uploads it to Autowiki-name.png. +/// Do your best to make sure this is unique, so it doesn't clash with other autowiki icons. +/datum/autowiki/proc/upload_icon(icon/icon, name) + // Fuck you + // We don't have this so fingers crossed LOL + // if (IsAdminAdvancedProcCall()) + // return + + fcopy(icon, "data/autowiki_files/[name].png") + +/// Escape a parameter such that it can be correctly put inside a wiki output +/datum/autowiki/proc/escape_value(parameter) + // | is a special character in MediaWiki, and must be escaped by...using another template. + return replacetextEx("[parameter]", "|", "{{!}}") diff --git a/code/modules/autowiki/pages/guns.dm b/code/modules/autowiki/pages/guns.dm new file mode 100644 index 00000000000..5045a552c0a --- /dev/null +++ b/code/modules/autowiki/pages/guns.dm @@ -0,0 +1,64 @@ +/datum/autowiki/guns + page = "Template:Autowiki/Content/GunData" + + +/datum/autowiki/guns/generate() + var/output = "" + + for(var/typepath in sort_list(subtypesof(/obj/item/gun), GLOBAL_PROC_REF(cmp_typepaths_asc))) + var/obj/item/gun/generating_gun = new typepath() + if(IS_AUTOWIKI_SKIP(generating_gun)) + continue + + var/filename = SANITIZE_FILENAME(escape_value(format_text(generating_gun.name))) + + var/list/gun_data = list( + "name" = generating_gun.name, + "desc" = generating_gun.desc + ) + + var/list/valid_mag_types = list() + if(istype(generating_gun, /obj/item/gun/projectile)) + var/obj/item/gun/projectile/projectile_gun = generating_gun + valid_mag_types = projectile_gun.allowed_magazines + + var/ammo = "" + for(var/ammo_typepath in valid_mag_types) + var/obj/item/ammo_magazine/generating_mag = new ammo_typepath() + if(IS_AUTOWIKI_SKIP(generating_mag)) + continue + + var/ammo_filename = SANITIZE_FILENAME(escape_value(format_text(generating_mag.name))) + + if(!fexists("data/autowiki_files/[ammo_filename].png")) + upload_icon(getFlatIcon(generating_mag, no_anim = TRUE), ammo_filename) + + ammo += include_template("Autowiki/AmmoMagazine", list( + "icon" = escape_value(ammo_filename), + "name" = escape_value(generating_mag.name), + "capacity" = escape_value(generating_mag.max_ammo), + )) + + qdel(generating_mag) + + gun_data["ammo_types"] = ammo + + + upload_icon(getFlatIcon(generating_gun, no_anim = TRUE), filename) + gun_data["icon"] = filename + + output += include_template("Autowiki/Gun", gun_data) + + qdel(generating_gun) + + return output + +/datum/autowiki/guns/proc/wiki_sanitize_assoc(list/sanitizing_list) + var/list/sanitized = list() + + for(var/key in sanitizing_list) + var/value = sanitizing_list[key] + + sanitized[escape_value(key)] = escape_value(value) + + return sanitized diff --git a/code/unit_tests/autowiki.dm b/code/unit_tests/autowiki.dm new file mode 100644 index 00000000000..7f07b07e389 --- /dev/null +++ b/code/unit_tests/autowiki.dm @@ -0,0 +1,35 @@ +/// Tests that all autowikis generate something without runtiming +/datum/unit_test/autowiki + +/datum/unit_test/autowiki/start_test() + TEST_ASSERT(istext(generate_autowiki_output()), "generate_autowiki_output() did not finish successfully!") + +/// Test that `include_template` produces reasonable results +/datum/unit_test/autowiki_include_template + +/datum/unit_test/autowiki_include_template/start_test() + var/datum/autowiki/autowiki_api = new + + TEST_ASSERT_EQUAL( \ + autowiki_api.include_template("Template"), \ + "{{Template}}", \ + "Basic template did not format correctly" \ + ) + + TEST_ASSERT_EQUAL( \ + autowiki_api.include_template("Template", list("name" = "Mothblocks")), \ + "{{Template|name=Mothblocks}}", \ + "Template with basic arguments did not format correctly" \ + ) + + TEST_ASSERT_EQUAL( \ + autowiki_api.include_template("Template", list("name" = autowiki_api.escape_value("P|peline"))), \ + "{{Template|name=P{{!}}peline}}", \ + "Template with escaped arguments did not format correctly" \ + ) + + TEST_ASSERT_EQUAL( \ + autowiki_api.include_template("Template", list("food" = list("fruit", "candy"))), \ + "{{Template|food1=fruit|food2=candy}}", \ + "Template with array arguments did not format correctly" \ + ) diff --git a/tools/autowiki/autowiki.js b/tools/autowiki/autowiki.js new file mode 100644 index 00000000000..22648531bca --- /dev/null +++ b/tools/autowiki/autowiki.js @@ -0,0 +1,89 @@ +const fs = require("fs").promises; +const MWBot = require("mwbot"); + +const { USERNAME, PASSWORD } = process.env; + +if (!USERNAME) { + console.error("USERNAME was not set."); + process.exit(1); +} + +if (!PASSWORD) { + console.error("PASSWORD was not set."); + process.exit(1); +} + +const PAGE_EDIT_FILENAME = process.argv[2]; + +if (!PAGE_EDIT_FILENAME) { + console.error("No filename specified to edit pages"); + process.exit(1); +} + +const FILE_EDIT_FILENAME = process.argv[3]; + +if (!FILE_EDIT_FILENAME) { + console.error("No filename specified to edit files"); + process.exit(1); +} + +async function main() { + console.log(`Reading from ${PAGE_EDIT_FILENAME}`); + const editFile = await ( + await fs.readFile(PAGE_EDIT_FILENAME, "utf8") + ).split("\n"); + + console.log(`Logging in as ${USERNAME}`); + + const bot = new MWBot(); + + await bot.loginGetEditToken({ + apiUrl: "https://wiki.aurorastation.org/api.php", + username: USERNAME, + password: PASSWORD, + }); + + console.log("Logged in"); + + // This is not Promise.all as to not flood with a bunch of traffic at once + for (const editLine of editFile) { + if (editLine.length === 0) { + continue; + } + + let { title, text } = JSON.parse(editLine); + text = + "This page is automated by Autowiki. Do NOT edit it manually." + + text; + + console.log(`Editing ${title}...`); + await bot.edit( + title, + text, + `Autowiki edit @ ${new Date().toISOString()}` + ); + } + + // Same here + for (const asset of await fs.readdir(FILE_EDIT_FILENAME)) { + const assetPath = `${FILE_EDIT_FILENAME}/${asset}`; + const assetName = `Autowiki-${asset}`; + + console.log(`Replacing ${assetName}...`); + await bot + .upload( + assetName, + assetPath, + `Autowiki upload @ ${new Date().toISOString()}` + ) + .catch((error) => { + if (error.code === "fileexists-no-change") { + console.log(`${assetName} is an exact duplicate`); + } else { + return Promise.reject(error); + } + }); + } +} + +main().catch(console.error); diff --git a/tools/autowiki/package-lock.json b/tools/autowiki/package-lock.json new file mode 100644 index 00000000000..942c763e82c --- /dev/null +++ b/tools/autowiki/package-lock.json @@ -0,0 +1,436 @@ +{ + "name": "autowiki", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==" + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" + }, + "aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mwbot": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mwbot/-/mwbot-2.1.3.tgz", + "integrity": "sha512-ULQWIfRWfh0WJKmqcapshk/5VNU1KEfrI7+hLeO3WoyuTGBVlhr3eE9TEyc4Q46vmztR6YHlaA6TmDGQr7/akg==", + "requires": { + "bluebird": "^3.7.2", + "request": "^2.88.2", + "semlog": "^0.6.10", + "semver": "^7.3.8" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "prettyjson": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.2.5.tgz", + "integrity": "sha512-rksPWtoZb2ZpT5OVgtmy0KHVM+Dca3iVwWY9ifwhcexfjebtgjg3wmrUt9PvJ59XIYBcknQeYHD8IAnVlh9lAw==", + "requires": { + "colors": "1.4.0", + "minimist": "^1.2.0" + } + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semlog": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/semlog/-/semlog-0.6.10.tgz", + "integrity": "sha512-FFUGeVy3vJnOV+3jOL4deYb1iVJpVLCJQHpXlDqMYVykl9t7G/Uuu71sEWjwJtTbaj5AmDXAQtTQN/t1sUhE2Q==", + "requires": { + "chalk": "^1.1.3", + "prettyjson": "^1.1.3" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" + }, + "sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } +} diff --git a/tools/autowiki/package.json b/tools/autowiki/package.json new file mode 100644 index 00000000000..39ac2bc7d5d --- /dev/null +++ b/tools/autowiki/package.json @@ -0,0 +1,10 @@ +{ + "name": "autowiki", + "version": "1.0.0", + "description": "Automatically publish generated pages to the tg wiki", + "main": "autowiki.js", + "author": "Mothblocks", + "dependencies": { + "mwbot": "^2.0.0" + } +} From 8236b85ff7bdb0a2ea0653534609089ebf0ad882 Mon Sep 17 00:00:00 2001 From: Geeves Date: Sat, 14 Sep 2024 14:01:50 +0200 Subject: [PATCH 2/2] generate food recipes --- aurorastation.dme | 1 + code/__HELPERS/lists.dm | 9 ++ code/controllers/subsystems/plants.dm | 36 +++++-- code/modules/autowiki/pages/food_recipes.dm | 114 ++++++++++++++++++++ code/modules/cooking/recipes/recipe.dm | 11 -- code/modules/hydroponics/seed.dm | 2 + 6 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 code/modules/autowiki/pages/food_recipes.dm diff --git a/aurorastation.dme b/aurorastation.dme index 3cc8aa62d28..a0f6dfd1a87 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -1668,6 +1668,7 @@ #include "code\modules\atmospherics\components\unary\vent_scrubber.dm" #include "code\modules\autowiki\autowiki.dm" #include "code\modules\autowiki\pages\_page.dm" +#include "code\modules\autowiki\pages\food_recipes.dm" #include "code\modules\autowiki\pages\guns.dm" #include "code\modules\awaymissions\bluespaceartillery.dm" #include "code\modules\awaymissions\corpse.dm" diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 767f0d5189e..aca0bff806e 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -42,6 +42,15 @@ return "[output][and_text][input[index]]" +/// Takes an empty list OR associated list and adds the item to it if it doesn't exist, or increments the count if it does exist +/proc/simple_counting_list(var/list/input, var/item) + var/name = "[item]" // index items by name; usually works fairly well for loose equality + if(name in input) + input[name]++ + else + input[name] = 1 + return input + //Returns a newline-separated list that counts equal-ish items, outputting count and item names, optionally with icons and specific determiners /proc/counting_english_list(var/list/input, output_icons = TRUE, determiners = DET_NONE, nothing_text = "nothing", line_prefix = "\t", first_item_prefix = "\n", last_item_suffix = "\n", and_text = "\n", comma_text = "\n", final_comma_text = "") var/list/counts = list() // counted input items diff --git a/code/controllers/subsystems/plants.dm b/code/controllers/subsystems/plants.dm index 5642d2b221a..4a626473f08 100644 --- a/code/controllers/subsystems/plants.dm +++ b/code/controllers/subsystems/plants.dm @@ -6,14 +6,32 @@ SUBSYSTEM_DEF(plants) priority = SS_PRIORITY_PLANTS runlevels = RUNLEVELS_PLAYING - var/list/product_descs = list() // Stores generated fruit descs. - var/list/seeds = list() // All seed data stored here. - var/list/gene_tag_masks = list() // Gene obfuscation for delicious trial and error goodness. - var/list/plant_icon_cache = list() // Stores images of growth, fruits and seeds. - var/list/plant_sprites = list() // List of all harvested product sprites. - var/list/plant_product_sprites = list() // List of all growth sprites plus number of growth stages. - var/list/gene_masked_list = list() // Stores list of masked genes rather than recreating it later - var/list/plant_gene_datums = list() // Stores gene masked list as datums + /// Stores generated fruit descs. + var/list/product_descs = list() + + /// All seed data stored here. + var/list/seeds = list() + + /// Stores seed data based on the kitchen tag + var/list/seeds_by_kitchen_tag = list() + + /// Gene obfuscation for delicious trial and error goodness. + var/list/gene_tag_masks = list() + + /// Stores images of growth, fruits and seeds. + var/list/plant_icon_cache = list() + + /// List of all harvested product sprites. + var/list/plant_sprites = list() + + /// List of all growth sprites plus number of growth stages. + var/list/plant_product_sprites = list() + + /// Stores list of masked genes rather than recreating it later + var/list/gene_masked_list = list() + + /// Stores gene masked list as datums + var/list/plant_gene_datums = list() var/list/processing = list() @@ -50,6 +68,8 @@ SUBSYSTEM_DEF(plants) for(var/type in typesof(/datum/seed)-/datum/seed) var/datum/seed/S = new type seeds[S.name] = S + if(S.kitchen_tag) + seeds_by_kitchen_tag[S.kitchen_tag] = S S.uid = "[seeds.len]" S.roundstart = 1 diff --git a/code/modules/autowiki/pages/food_recipes.dm b/code/modules/autowiki/pages/food_recipes.dm new file mode 100644 index 00000000000..26e9b34553d --- /dev/null +++ b/code/modules/autowiki/pages/food_recipes.dm @@ -0,0 +1,114 @@ +/datum/autowiki/food_recipes + page = "Template:Autowiki/Content/FoodRecipeData" + + +/datum/autowiki/food_recipes/generate() + var/output = "" + + var/list/available_recipes = GET_SINGLETON_SUBTYPE_MAP(/singleton/recipe) + for(var/recipe_path in available_recipes) + var/singleton/recipe/recipe = GET_SINGLETON(recipe_path) + var/list/recipe_data = list() + + // result + var/obj/item/recipe_result = new recipe.result() + recipe_data["name"] = initial(recipe_result.name) + + var/filename = SANITIZE_FILENAME(escape_value(format_text(initial(recipe_result.name)))) + var/flat_icon = getFlatIcon(recipe_result, no_anim = TRUE) + if(flat_icon) + upload_icon(flat_icon, filename) + recipe_data["icon"] = filename + + var/obj/item/reagent_containers/cooking_container/board/bowl/food_container = new() + + // ingredients + recipe_data["ingredients"] = list() + + // ingredients, items + if(recipe.items) + var/list/ingredients = list() + for(var/ingredient_path in recipe.items) + var/obj/item/ingredient = new ingredient_path(food_container) + ingredients = simple_counting_list(ingredients, initial(ingredient.name)) + for(var/ingredient in ingredients) + var/count = ingredients[ingredient] + recipe_data["ingredients"] += "[count]x [lowertext(ingredient)]" + + // ingredients, fruits + if(recipe.fruit) + for(var/fruit_name in recipe.fruit) + var/needs_slice = findtext(fruit_name, " slice") + var/needs_dried = findtext(fruit_name, "dried ") + var/parsed_fruit_name = fruit_name + if(needs_slice) + parsed_fruit_name = replacetext(parsed_fruit_name, " slice", "") + if(needs_dried) + parsed_fruit_name = replacetext(parsed_fruit_name, "dried ", "") + var/datum/seed/seed_datum = SSplants.seeds_by_kitchen_tag[parsed_fruit_name] + var/obj/item/reagent_containers/food/snacks/grown/product = seed_datum.spawn_seed(food_container) + if(needs_slice) + var/reagents_to_transfer = product.reagents.total_volume / 4 + var/obj/item/reagent_containers/food/snacks/fruit_slice/product_slice = new(food_container, product.seed) + product.reagents.trans_to_obj(product_slice, reagents_to_transfer) + qdel(product) + product = product_slice + if(needs_dried) + product.dry = TRUE + + var/count = recipe.fruit[fruit_name] + recipe_data["ingredients"] += "[count]x [lowertext(fruit_name)]" + + // ingredients, reagents + if(recipe.reagents) + for(var/reagent_path in recipe.reagents) + var/count = recipe.reagents[reagent_path] + var/singleton/reagent/reagent = GET_SINGLETON(reagent_path) + food_container.reagents.add_reagent(reagent_path, count) + + recipe_data["ingredients"] += "[count]u [lowertext(reagent.name)]" + + // coating + if(recipe.coating) + for(var/obj/item/reagent_containers/food/snacks/thing in food_container) + thing.coating = recipe.coating + + var/singleton/reagent/reagent = GET_SINGLETON(recipe.coating) + recipe_data["ingredients"] += "[reagent.name] coating" + + // kitchen appliance + if(recipe.appliance) + recipe_data["appliances"] = recipe.get_appliance_names() + + // fin + recipe_data["ingredients"] = english_list(recipe_data["ingredients"], and_text = ", ") + + var/list/cooking_results = recipe.make_food(food_container) + var/obj/cooking_obj = cooking_results[1] + + var/list/result_reagents = list() + for(var/_reagent in cooking_obj.reagents.reagent_volumes) + var/singleton/reagent/reagent = GET_SINGLETON(_reagent) + result_reagents += "[REAGENT_VOLUME(cooking_obj.reagents, _reagent)]u [lowertext(reagent.name)]" + if(length(result_reagents)) + recipe_data["result_reagents"] = english_list(result_reagents, and_text = ", ") + + output += include_template("Autowiki/FoodRecipe", recipe_data) + + qdel(recipe_result) + + for(var/atom/thing in food_container) + qdel(thing) + qdel(food_container) + + return output + +/datum/autowiki/food_recipes/proc/wiki_sanitize_assoc(list/sanitizing_list) + var/list/sanitized = list() + + for(var/key in sanitizing_list) + var/value = sanitizing_list[key] + + sanitized[escape_value(key)] = escape_value(value) + + return sanitized diff --git a/code/modules/cooking/recipes/recipe.dm b/code/modules/cooking/recipes/recipe.dm index b8e6aa04dbe..47b84150275 100644 --- a/code/modules/cooking/recipes/recipe.dm +++ b/code/modules/cooking/recipes/recipe.dm @@ -33,7 +33,6 @@ /singleton/recipe - var/display_name var/list/reagents // example: = list(/singleton/reagent/drink/berryjuice = 5) // do not list same reagent twice var/list/recipe_taste_override // example: = list("uncooked dough" = "crispy dough") var/list/items // example: = list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo @@ -175,16 +174,6 @@ return !coating || (S.coating == coating) -//general version -/singleton/recipe/proc/make(var/obj/container as obj) - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - O.reagents.trans_to_obj(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - - return result_obj - // food-related //This proc is called under the assumption that the container has already been checked and found to contain the necessary ingredients /singleton/recipe/proc/make_food(var/obj/container as obj) diff --git a/code/modules/hydroponics/seed.dm b/code/modules/hydroponics/seed.dm index f180ede6a6a..744c5fc5b60 100644 --- a/code/modules/hydroponics/seed.dm +++ b/code/modules/hydroponics/seed.dm @@ -807,6 +807,8 @@ var/mob/living/simple_animal/mushroom/mush = product mush.seed = src + return product + // When the seed in this machine mutates/is modified, the tray seed value // is set to a new datum copied from the original. This datum won't actually // be put into the global datum list until the product is harvested, though.