diff --git a/.github/workflows/tgs_test.yml b/.github/workflows/tgs_test.yml index f8e48d2763..eaaf9b307b 100644 --- a/.github/workflows/tgs_test.yml +++ b/.github/workflows/tgs_test.yml @@ -2,33 +2,39 @@ name: TGS Test Suite on: push: branches: - - master + - master + - "project/**" + - "gh-readonly-queue/master/**" + - "gh-readonly-queue/project/**" paths: - - '.tgs.yml' - - '.github/workflows/tgs_test.yml' - - 'dependencies.sh' - - 'code/__DEFINES/tgs.config.dm' - - 'code/__DEFINES/tgs.dm' - - 'code/game/world.dm' - - 'code/modules/tgs/**' - - 'tools/tgs_scripts/**' - - 'tools/tgs_test/**' + - ".tgs.yml" + - ".github/workflows/tgs_test.yml" + - "dependencies.sh" + - "code/__DEFINES/tgs.config.dm" + - "code/__DEFINES/tgs.dm" + - "code/game/world.dm" + - "code/modules/tgs/**" + - "tools/bootstrap/**" + - "tools/tgs_scripts/**" + - "tools/tgs_test/**" pull_request: branches: - - master + - master + - "project/**" paths: - - '.tgs.yml' - - '.github/workflows/tgs_test.yml' - - 'dependencies.sh' - - 'code/__DEFINES/tgs.config.dm' - - 'code/__DEFINES/tgs.dm' - - 'code/game/world.dm' - - 'code/modules/tgs/**' - - 'tools/tgs_scripts/**' - - 'tools/tgs_test/**' + - ".tgs.yml" + - ".github/workflows/tgs_test.yml" + - "dependencies.sh" + - "code/__DEFINES/tgs.config.dm" + - "code/__DEFINES/tgs.dm" + - "code/game/world.dm" + - "code/modules/tgs/**" + - "tools/bootstrap/**" + - "tools/tgs_scripts/**" + - "tools/tgs_test/**" merge_group: branches: - - master + - master env: TGS_API_PORT: 5000 PR_NUMBER: ${{ github.event.number }} @@ -61,4 +67,4 @@ jobs: uses: actions/checkout@v4 - name: Test TGS Integration - run: dotnet run -c Release --project tools/tgs_test ${{ github.repository }} /tgs_instances/chompstation ${{ env.TGS_API_PORT }} ${{ github.event.pull_request.head.sha || github.sha }} ${{ secrets.GITHUB_TOKEN }} ${{ env.PR_NUMBER }} + run: dotnet run -c Release --project tools/tgs_test ${{ github.repository }} /tgs_instances/tgstation ${{ env.TGS_API_PORT }} ${{ github.event.pull_request.head.sha || github.sha }} ${{ secrets.GITHUB_TOKEN }} ${{ env.PR_NUMBER }} diff --git a/.github/workflows/update_tgs_dmapi.yml b/.github/workflows/update_tgs_dmapi.yml index 81ae79d4a3..ec59af559d 100644 --- a/.github/workflows/update_tgs_dmapi.yml +++ b/.github/workflows/update_tgs_dmapi.yml @@ -2,47 +2,59 @@ name: Update TGS DMAPI on: schedule: - - cron: "0 0 1 */2 *" + - cron: "0 0 * * *" workflow_dispatch: - jobs: update-dmapi: runs-on: ubuntu-24.04 - name: Updates the TGS DMAPI + name: Update the TGS DMAPI + permissions: + contents: write + pull-requests: write steps: - - name: Clone - uses: actions/checkout@v4 + - name: Clone + uses: actions/checkout@v4 - - name: Branch - run: | - git branch -f tgs-dmapi-update - git checkout tgs-dmapi-update - git reset --hard master + - name: Branch + run: | + git branch -f tgs-dmapi-update + git checkout tgs-dmapi-update + git reset --hard master + - name: Apply DMAPI update + uses: tgstation/tgs-dmapi-updater@v2 + id: dmapi-update + with: + header-path: "code/__defines/tgs.dm" + library-path: "code/modules/tgs" - - name: Apply DMAPI update - uses: tgstation/tgs-dmapi-updater@v2 - id: dmapi-update - with: - header-path: 'code/__defines/tgs.dm' - library-path: 'code/modules/tgs' + - name: Commit and Push + continue-on-error: true + run: | + git config user.name "chompstation-ci[bot]" + git config user.email "12787406+chompstation-ci[bot]@users.noreply.github.com" + git add . + git commit -m 'Update TGS DMAPI' + git push -f -u origin tgs-dmapi-update + - name: Generate App Token + id: app-token-generation + uses: actions/create-github-app-token@v2 + if: env.APP_PRIVATE_KEY != '' && env.APP_ID != '' + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + env: + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} + APP_ID: ${{ secrets.APP_ID }} - - name: Commit and Push - continue-on-error: true - run: | - git config --local user.email "action@github.com" - git config --local user.name "DMAPI Update" - git add . - git commit -m 'Update TGS DMAPI' - git push -f -u origin tgs-dmapi-update - - - name: Create Pull Request - uses: repo-sync/pull-request@v2 - with: - source_branch: "tgs-dmapi-update" - destination_branch: "master" - pr_title: "Automatic DMAPI Update" - pr_body: "This pull request updates the DMAPI to the latest version." - pr_label: "Infrastructure" - pr_allow_empty: false - github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Create Pull Request + uses: repo-sync/pull-request@v2 + if: ${{ success() }} + with: + source_branch: "tgs-dmapi-update" + destination_branch: "master" + pr_title: "Automatic TGS DMAPI Update" + pr_body: "This pull request updates the TGS DMAPI to the latest version. Please note any changes that may be breaking or unimplemented in your codebase by checking what changes are in the definitions file: code/__DEFINES/tgs.dm before merging.\n\n${{ steps.dmapi-update.outputs.release-notes }}" + pr_label: "Tools" + pr_allow_empty: false + github_token: ${{ steps.app-token-generation.outputs.token || secrets.GITHUB_TOKEN }} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 050a6eab6b..5efaf21e8f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,7 +3,7 @@ "tasks": [ { "type": "process", - "command": "tools/build/build", + "command": "tools/build/build.sh", "windows": { "command": ".\\tools\\build\\build.bat" }, @@ -26,11 +26,11 @@ }, { "type": "process", - "command": "tools/build/build", + "command": "tools/build/build.sh", "args": ["-DLOWMEMORYMODE"], "windows": { "command": ".\\tools\\build\\build.bat", - "args": ["-DLOWMEMORYMODE", "-DENABLE_BYOND_TRACY"] + "args": ["-DLOWMEMORYMODE"] }, "options": { "env": { @@ -50,7 +50,7 @@ }, { "type": "process", - "command": "tools/build/build", + "command": "tools/build/build.sh", "args": ["-DLOWMEMORYMODE", "-DCITESTING", "-DUSE_MAP_TETHER"], "windows": { "command": ".\\tools\\build\\build.bat", @@ -74,7 +74,7 @@ }, { "type": "process", - "command": "tools/build/build", + "command": "tools/build/build.sh", "args": ["-DLOWMEMORYMODE", "-DCITESTING", "-DUSE_MAP_STELLARDELIGHT"], "windows": { "command": ".\\tools\\build\\build.bat", @@ -98,7 +98,7 @@ }, { "type": "process", - "command": "tools/build/build", + "command": "tools/build/build.sh", "args": ["-DLOWMEMORYMODE", "-DCITESTING", "-DUSE_MAP_GROUNDBASE"], "windows": { "command": ".\\tools\\build\\build.bat", @@ -122,7 +122,7 @@ }, { "type": "process", - "command": "tools/build/build", + "command": "tools/build/build.sh", "args": ["-DLOWMEMORYMODE", "-DCITESTING", "-DUSE_MAP_MINITEST"], "windows": { "command": ".\\tools\\build\\build.bat", diff --git a/code/__defines/chat.dm b/code/__defines/chat.dm index e427ef4c7d..1656b93ea5 100644 --- a/code/__defines/chat.dm +++ b/code/__defines/chat.dm @@ -35,3 +35,12 @@ #define MESSAGE_TYPE_ADMINLOG "adminlog" #define MESSAGE_TYPE_ATTACKLOG "attacklog" #define MESSAGE_TYPE_DEBUG "debug" + +/// Adds a generic box around whatever message you're sending in chat. Really makes things stand out. +#define boxed_message(str) ("
" + str + "
") +/// Adds a box around whatever message you're sending in chat. Can apply color and/or additional classes. Available colors: red, green, blue, purple. Use it like red_box +#define custom_boxed_message(classes, str) ("
" + str + "
") +/// Makes a fieldset with a neaty styled name. Can apply additional classes. +#define fieldset_block(title, content, classes) ("
" + title + "" + content + "
") +/// Makes a horizontal line with text in the middle +#define separator_hr(str) ("
" + str + "
") diff --git a/code/__defines/tgs.dm b/code/__defines/tgs.dm index 30b8c6e748..b9b539c32e 100644 --- a/code/__defines/tgs.dm +++ b/code/__defines/tgs.dm @@ -164,7 +164,7 @@ * * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED]. * * http_handler - Optional user defined [/datum/tgs_http_handler]. */ -/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_TRUSTED, datum/tgs_http_handler/http_handler) //CHOMPEdit we need trusted +/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler) CAN_BE_REDEFINED(TRUE) return @@ -181,7 +181,6 @@ /// Consumers MUST run this macro at the start of [/world/proc/Topic]. #define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return -#define VGS_TOPIC var/vgs_topic_return = VgsTopic(args[1]); if(vgs_topic_return) return vgs_topic_return // VOREStation Edit - VGS /// Consumers MUST call this as late as possible in [world/proc/Reboot] (BEFORE ..()). /world/proc/TgsReboot() diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index d484c28e3d..b4f97684ba 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -747,3 +747,7 @@ /// Turn this on if you want all admin-PMs to go to be sent to discord, and not only the first message of a ticket. /datum/config_entry/flag/discord_ahelps_all default = FALSE + +/datum/config_entry/number/rounds_until_hard_restart + default = -1 + min_val = 0 diff --git a/code/controllers/configuration/entries/vorestation.dm b/code/controllers/configuration/entries/vorestation.dm index ae9730dbc4..3aafac99e6 100644 --- a/code/controllers/configuration/entries/vorestation.dm +++ b/code/controllers/configuration/entries/vorestation.dm @@ -2,11 +2,7 @@ /datum/config_entry/flag/items_survive_digestion default = TRUE -/datum/config_entry/string/vgs_access_identifier // VGS - default = null - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/number/vgs_server_port // VGS +/datum/config_entry/number/register_server_port default = null min_val = 0 max_val = 65535 diff --git a/code/datums/elements/footstep.dm b/code/datums/elements/footstep.dm index 59f8906ce7..4c570350d7 100644 --- a/code/datums/elements/footstep.dm +++ b/code/datums/elements/footstep.dm @@ -152,7 +152,7 @@ // we are wearing shoes var/obj/item/clothing/shoes/feet = source.shoes - if(feet.blocks_footsteps) + if(istype(feet) && feet.blocks_footsteps) var/shoestep_type = prepared_steps[FOOTSTEP_MOB_SHOE] if(!isnull(shoestep_type) && footstep_sounds[shoestep_type]) // shoestep type can be null playsound(source.loc, pick(footstep_sounds[shoestep_type][1]), diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm index 6da919b139..1ecd7da806 100644 --- a/code/datums/helper_datums/getrev.dm +++ b/code/datums/helper_datums/getrev.dm @@ -1,82 +1,91 @@ /datum/getrev - var/branch - var/revision + var/commit // git rev-parse HEAD var/date - var/showinfo + var/originmastercommit // git rev-parse origin/master var/list/testmerge = list() /datum/getrev/New() - if(world.TgsAvailable()) // Try TGS maybe - testmerge = world.TgsTestMerges() - var/datum/tgs_revision_information/REV = world.TgsRevision() - if(REV) - revision = REV.origin_commit || REV.commit - branch = "-Using TGS-" // TGS doesn't provide branch info yet - date = "-Using TGS-" // Or date + commit = rustg_git_revparse("HEAD") + if(commit) + date = rustg_git_commit_date(commit) + originmastercommit = rustg_git_revparse("origin/master") - if(!revision) // File parse method - var/list/head_branch = file2list(".git/HEAD", "\n") - if(head_branch.len) - branch = copytext(head_branch[1], 17) +/datum/getrev/proc/load_tgs_info() + testmerge = world.TgsTestMerges() + var/datum/tgs_revision_information/revinfo = world.TgsRevision() + if(revinfo) + commit = revinfo.commit + originmastercommit = revinfo.origin_commit + date = revinfo.timestamp || rustg_git_commit_date(commit) - var/list/head_log = file2list(".git/logs/HEAD", "\n") - for(var/line=head_log.len, line>=1, line--) - if(head_log[line]) - var/list/last_entry = splittext(head_log[line], " ") - if(last_entry.len < 2) continue - revision = last_entry[2] - // Get date/time - if(last_entry.len >= 5) - var/unix_time = text2num(last_entry[5]) - if(unix_time) - date = unix2date(unix_time) - break + // goes to DD log and config_error.txt + log_world(get_log_message()) - to_world_log("-Revision Info-") - to_world_log("Branch: [branch]") - to_world_log("Date: [date]") - to_world_log("Revision: [revision]") +/datum/getrev/proc/get_log_message() + var/list/msg = list() + msg += "Running /tg/ revision: [date]" + if(originmastercommit) + msg += "origin/master: [originmastercommit]" + + for(var/line in testmerge) + var/datum/tgs_revision_information/test_merge/tm = line + msg += "Test merge active of PR #[tm.number] commit [tm.head_commit]" + //SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.head_commit]", "title" = "[tm.title]", "author" = "[tm.author]")) + + if(commit && commit != originmastercommit) + msg += "HEAD: [commit]" + else if(!originmastercommit) + msg += "No commit information" + + msg += "Running rust-g version [rustg_get_version()]" + + return msg.Join("\n") /datum/getrev/proc/GetTestMergeInfo(header = TRUE) - . = list() if(!testmerge.len) - return - if(header) - . += "The following pull requests are currently test merged:" - for(var/datum/tgs_revision_information/test_merge/tm as anything in testmerge) - var/cm = tm.head_commit //CHOMPStation Edit TGS4 + return "" + . = header ? "The following pull requests are currently test merged:
" : "" + for(var/line in testmerge) + var/datum/tgs_revision_information/test_merge/tm = line + var/cm = tm.head_commit var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext_char(cm, 1, 11)) - if(details && findtext(details, "\[s\]") && (!usr || !usr.client.holder)) - continue - . += "#[tm.number][details]" + . += "#[tm.number][details]
" /client/verb/showrevinfo() set category = "OOC.Game" set name = "Show Server Revision" set desc = "Check the current server code revision" - if(!GLOB.revdata) - to_chat(src, span_warning("Please wait until server initializations are complete.")) - return - var/list/msg = list() + // Round ID + if(GLOB.round_id) + msg += span_bold("Round ID") + ": [GLOB.round_id]" - if(GLOB.revdata.revision) - msg += span_bold("Server revision:") + " B:[GLOB.revdata.branch] D:[GLOB.revdata.date]" - if(CONFIG_GET(string/githuburl)) - msg += span_bold("Commit:") + " [GLOB.revdata.revision]" - else - msg += span_bold("Commit:") + " [GLOB.revdata.revision]" // CHOMPEdit - Actually SHOW the revision - else - msg += span_bold("Server revision:") + " Unknown" + msg += span_bold("BYOND Version") + ": [world.byond_version].[world.byond_build]" + if(DM_VERSION != world.byond_version || DM_BUILD != world.byond_build) + msg += span_bold("Compiled with BYOND Version") + ": [DM_VERSION].[DM_BUILD]" + // Revision information + var/datum/getrev/revdata = GLOB.revdata + msg += span_bold("Server revision compiled on") + ": [revdata.date]" + var/pc = revdata.originmastercommit + if(pc) + msg += span_bold("Master commit") + ": [pc]" + if(length(revdata.testmerge)) + msg += revdata.GetTestMergeInfo() + if(revdata.commit && revdata.commit != revdata.originmastercommit) + msg += span_bold("Local commit") + ": [revdata.commit]" + else if(!pc) + msg += "No commit information" if(world.TgsAvailable()) var/datum/tgs_version/version = world.TgsVersion() - msg += span_bold("TGS version:") + " [version.raw_parameter]" - var/datum/tgs_version/api_version = world.TgsApiVersion() - msg += span_bold("DMAPI version:") + " [api_version.raw_parameter]" + msg += span_bold("TGS version") + ": [version.raw_parameter]" + msg += span_bold("DMAPI version") + ": [TGS_DMAPI_VERSION]" - if(GLOB.revdata.testmerge.len) - msg += GLOB.revdata.GetTestMergeInfo() - - to_chat(src, msg.Join("
")) + // Game mode odds + //msg += "
Current Informational Settings:" + //msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist) ? "Yes" : "No"]" + //msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist) ? "Yes" : "No"]" + //msg += "Enforce Human Authority: [CONFIG_GET(string/human_authority) ? "Yes" : "No"]" + //msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists) ? "Yes" : "No"]" + to_chat(src, fieldset_block("Server Revision Info", span_infoplain(jointext(msg, "
")), "boxed_message"), type = MESSAGE_TYPE_INFO) diff --git a/code/game/world.dm b/code/game/world.dm index 21644e608e..fc0f79f6fc 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,3 +1,7 @@ +#define RESTART_COUNTER_PATH "data/round_counter.txt" + +GLOBAL_VAR(restart_counter) + #define RECOMMENDED_VERSION 513 /world/New() world_startup_time = world.timeofday @@ -32,7 +36,7 @@ if(byond_version < RECOMMENDED_VERSION) to_world_log("Your server's byond version does not meet the recommended requirements for this server. Please update BYOND") - TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED) // CHOMPEdit - tgs event handler + InitTgs() config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) @@ -40,7 +44,6 @@ ConfigLoaded() makeDatumRefLists() - VgsNew() var servername = CONFIG_GET(string/servername) if(config && servername != null && CONFIG_GET(flag/server_suffix) && world.port > 0) @@ -103,6 +106,11 @@ return +/// Initializes TGS and loads the returned revising info into GLOB.revdata +/world/proc/InitTgs() + TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED) + GLOB.revdata.load_tgs_info() + /// Runs after config is loaded but before Master is initialized /world/proc/ConfigLoaded() // Everything in here is prioritized in a very specific way. @@ -119,12 +127,15 @@ else //probably windows, if not this should work anyway CONFIG_SET(string/python_path, "python") + if(fexists(RESTART_COUNTER_PATH)) + GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) + fdel(RESTART_COUNTER_PATH) + var/world_topic_spam_protect_ip = "0.0.0.0" var/world_topic_spam_protect_time = world.timeofday /world/Topic(T, addr, master, key) - VGS_TOPIC // VOREStation Edit - VGS //CHOMP Edit swapped lines around - TGS_TOPIC //CHOMP Edit swapped lines around + TGS_TOPIC log_topic("\"[T]\", from:[addr], master:[master], key:[key]") if (T == "ping") @@ -275,8 +286,8 @@ var/world_topic_spam_protect_time = world.timeofday return list2params(positions) else if(T == "revision") - if(GLOB.revdata.revision) - return list2params(list(branch = GLOB.revdata.branch, date = GLOB.revdata.date, revision = GLOB.revdata.revision)) + if(GLOB.revdata.commit) + return list2params(list(testmerge = GLOB.revdata.testmerge, date = GLOB.revdata.date, commit = GLOB.revdata.commit, originmastercommit = GLOB.revdata.originmastercommit)) else return "unknown" @@ -457,6 +468,25 @@ var/world_topic_spam_protect_time = world.timeofday else return "Database connection failed or not set up" +/// Returns TRUE if the world should do a TGS hard reboot. +/world/proc/check_hard_reboot() + if(!TgsAvailable()) + return FALSE + // byond-tracy can't clean up itself, and thus we should always hard reboot if its enabled, to avoid an infinitely growing trace. + //if(Tracy?.enabled) + // return TRUE + var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) + switch(ruhr) + if(-1) + return FALSE + if(0) + return TRUE + else + if(GLOB.restart_counter >= ruhr) + return TRUE + else + text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) + return FALSE /world/Reboot(reason = 0, fast_track = FALSE) /*spawn(0) @@ -477,6 +507,14 @@ var/world_topic_spam_protect_time = world.timeofday if(CONFIG_GET(string/server)) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite C << link("byond://[CONFIG_GET(string/server)]") + if(check_hard_reboot()) + log_world("World hard rebooted at [time_stamp()]") + //shutdown_logging() // See comment below. + //QDEL_NULL(Tracy) + //QDEL_NULL(Debugger) + TgsEndProcess() + return ..() + TgsReboot() log_world("World rebooted at [time_stamp()]") ..() @@ -774,3 +812,5 @@ var/global/game_id = null if (debug_server) call_ext(debug_server, "auxtools_shutdown")() . = ..() + +#undef RESTART_COUNTER_PATH diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm index ef61823dc7..7b0bad2904 100644 --- a/code/modules/admin/permissionedit.dm +++ b/code/modules/admin/permissionedit.dm @@ -455,7 +455,13 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm #undef RANK_DONE +/// Changes, for this round only, the flags a particular admin gets to use /datum/admins/proc/change_admin_flags(admin_ckey, admin_key, datum/admins/admin_holder) + if(!check_rights(R_PERMISSIONS)) + return + if(IsAdminAdvancedProcCall()) + to_chat(usr, span_adminprefix("Rank Modification blocked: Advanced ProcCall detected."), confidential = TRUE) + return var/new_flags = input_bitfield( usr, "Admin rights
This will affect only the current admin [admin_key]", @@ -465,6 +471,8 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm 590, allowed_edit_field = usr.client.holder.can_edit_rights_flags(), ) + if(isnull(new_flags)) + return admin_holder.disassociate() diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 7278c29aff..e1b0998fae 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -151,7 +151,10 @@ log_and_message_admins("[ckey] has registered their Discord ID. Their Discord snowflake ID is: [their_id]", src) //YW EDIT admin_chat_message(message = "[ckey] has registered their Discord ID. Their Discord is: <@[their_id]>", color = "#4eff22") //YW EDIT notes_add(ckey, "Discord ID: [their_id]") - world.VgsAddMemberRole(their_id) + var/port = CONFIG_GET(number/register_server_port) + if(port) + // Designed to be used with `tools/registration` + world.Export("http://127.0.0.1:[port]?member=[url_encode(json_encode(their_id))]") else to_chat(src, span_warning("There was an error registering your Discord ID in the database. Contact an administrator.")) log_and_message_admins("[ckey] failed to register their Discord ID. Their Discord snowflake ID is: [their_id]. Is the database connected?", src) @@ -352,6 +355,8 @@ alert = TRUE if(alert) for(var/client/X in GLOB.admins) + if(!check_rights_for(X, R_HOLDER)) + continue if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) X << 'sound/voice/bcriminal.ogg' //ChompEDIT - back to beepsky window_flash(X) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 1a407ef0c0..7037762bc1 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -402,8 +402,10 @@ var/list/preferences_datums = list() if(tgui_alert(user, "Are you sure you want to override slot [slotnum], [choice]'s savedata?", "Confirm Override", list("No", "Yes")) == "Yes") overwrite_character(slotnum) sanitize_preferences() - save_character() save_preferences() + save_character() + load_preferences(TRUE) + load_character() attempt_vr(user.client?.prefs_vr,"load_vore","") ShowChoices(user) diff --git a/code/modules/client/preferences_tgui.dm b/code/modules/client/preferences_tgui.dm index 839a1107a7..d6234392de 100644 --- a/code/modules/client/preferences_tgui.dm +++ b/code/modules/client/preferences_tgui.dm @@ -136,8 +136,8 @@ switch(action) // Basic actions if("load") - if(!IsGuestKey(usr.key)) - open_load_dialog(usr) + if(!IsGuestKey(ui.user.key)) + open_load_dialog(ui.user) . = TRUE if("save") save_character() @@ -146,22 +146,22 @@ VARSET_IN(src, saved_notification, FALSE, 1 SECONDS) . = TRUE if("reload") - load_preferences() + load_preferences(TRUE) load_character() attempt_vr(client.prefs_vr,"load_vore","") //VOREStation Edit sanitize_preferences() . = TRUE if("resetslot") - if("Yes" != tgui_alert(usr, "This will reset the current slot. Continue?", "Reset current slot?", list("No", "Yes"))) + if("Yes" != tgui_alert(ui.user, "This will reset the current slot. Continue?", "Reset current slot?", list("No", "Yes"))) return - if("Yes" != tgui_alert(usr, "Are you completely sure that you want to reset this character slot?", "Reset current slot?", list("No", "Yes"))) + if("Yes" != tgui_alert(ui.user, "Are you completely sure that you want to reset this character slot?", "Reset current slot?", list("No", "Yes"))) return reset_slot() sanitize_preferences() . = TRUE if("copy") - if(!IsGuestKey(usr.key)) - open_copy_dialog(usr) + if(!IsGuestKey(ui.user.key)) + open_copy_dialog(ui.user) . = TRUE // More specific stuff if("switch_category") @@ -169,11 +169,11 @@ for(var/datum/category_group/player_setup_category/PS in player_setup.categories) if(PS.name == new_category) player_setup.selected_category = PS - update_tgui_static_data(usr, ui) + update_tgui_static_data(ui.user, ui) break . = TRUE if("game_prefs") - usr.client.game_options() + ui.user.client.game_options() . = TRUE if("refresh_character_preview") if(!COOLDOWN_FINISHED(src, ui_refresh_cooldown)) @@ -192,7 +192,7 @@ PMH.screen_loc = LAZYACCESS(preview_screen_locs, "PMH") /datum/preferences/tgui_close(mob/user) - save_character() + // save_character() save_preferences() /datum/preferences/proc/create_character_profiles() diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index b35b726c0b..33545d6cdd 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -163,7 +163,7 @@ // Admins with RLOOC displayed who weren't already in for(var/client/admin in GLOB.admins) if(!(admin in receivers) && admin.prefs?.read_preference(/datum/preference/toggle/holder/show_rlooc)) - if(check_rights_for(admin, R_ADMIN|R_SERVER)) //Stop rLOOC showing for retired staff //CHOMPEdit, admins should see LOOC + if(check_rights_for(admin, (R_SERVER|R_ADMIN))) //Stop rLOOC showing for retired staff r_receivers |= admin msg = GLOB.is_valid_url.Replace(msg,span_linkify("$1")) diff --git a/code/modules/client/verbs/who.dm b/code/modules/client/verbs/who.dm index 91c6253f8d..c46dd22d4f 100644 --- a/code/modules/client/verbs/who.dm +++ b/code/modules/client/verbs/who.dm @@ -1,3 +1,5 @@ +#define NO_ADMINS_ONLINE_MESSAGE "Adminhelps are also sent through TGS to services like Discord. If no admins are available in game, sending an adminhelp might still be noticed and responded to." + /client/verb/who() set name = "Who" set category = "OOC.Resources" @@ -58,6 +60,8 @@ set category = "Admin" set name = "Staffwho" + var/header = GLOB.admins.len == 0 ? "No Admins Currently Online" : "Current Admins" + var/msg = "" var/modmsg = "" var/devmsg = "" @@ -132,6 +136,8 @@ if(CONFIG_GET(flag/show_mentors)) msg += "\n" + span_bold(" Current Mentors ([num_mentors_online]):") + "\n" + mentormsg - msg += "\n" + span_info("Adminhelps are also sent to Discord. If no admins are available in game try anyway and an admin on Discord may see it and respond.") + msg += "\n" + span_info(NO_ADMINS_ONLINE_MESSAGE) - to_chat(src,span_filter_notice("[jointext(msg, "
")]")) + to_chat(src, fieldset_block(span_bold(header), span_filter_notice("[jointext(msg, "
")]"), "boxed_message"), type = MESSAGE_TYPE_INFO) + +#undef NO_ADMINS_ONLINE_MESSAGE diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index f4e7589ec1..4d59f9a03f 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -656,7 +656,7 @@ var/global/list/light_type_cache = list() playsound(src, W.usesound, 75, 1) user.visible_message("[user.name] opens [src]'s casing.", \ "You open [src]'s casing.", "You hear a noise.") - new construct_type(src.loc, fixture = src) + new construct_type(src.loc, src) qdel(src) return diff --git a/code/modules/tgs/includes.dm b/code/modules/tgs/includes.dm index bd77c07ecd..f5118ed55a 100644 --- a/code/modules/tgs/includes.dm +++ b/code/modules/tgs/includes.dm @@ -14,12 +14,9 @@ #include "v5\_defines.dm" #include "v5\api.dm" -#include "v5\api_vgs.dm" //VOREStation Edit - Vgs #include "v5\bridge.dm" #include "v5\chunking.dm" #include "v5\commands.dm" -#include "v5\chat_commands.dm" //CHOMPEdit -#include "v5\chat_commands_zz_ch.dm" //CHOMPEdit #include "v5\serializers.dm" #include "v5\topic.dm" #include "v5\undefs.dm" diff --git a/code/modules/tgs/v5/_defines.dm b/code/modules/tgs/v5/_defines.dm index 86355dd812..a47bfd7800 100644 --- a/code/modules/tgs/v5/_defines.dm +++ b/code/modules/tgs/v5/_defines.dm @@ -15,7 +15,6 @@ #define DMAPI5_BRIDGE_COMMAND_CHAT_SEND 5 #define DMAPI5_BRIDGE_COMMAND_CHUNK 6 #define DMAPI5_BRIDGE_COMMAND_EVENT 7 -#define DMAPI5_BRIDGE_COMMAND_ADD_MEMBER_ROLE 51 // VOREStation Edit #define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier" #define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands" @@ -37,7 +36,6 @@ #define DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE "chatMessage" #define DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL "minimumSecurityLevel" #define DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION "eventInvocation" -#define DMAPI5_BRIDGE_PARAMETER_CHAT_USER_ID "chatUserId" // VOREStation Edit #define DMAPI5_BRIDGE_RESPONSE_NEW_PORT "newPort" #define DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION "runtimeInformation" @@ -86,7 +84,6 @@ #define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10 #define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11 #define DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT 12 -#define DMAPI5_TOPIC_COMMAND_GET_CHAT_COMMANDS 51 // VOREStation Edit - GetChatCommands command #define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType" #define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand" diff --git a/code/modules/tgs/v5/api_vgs.dm b/code/modules/tgs/v5/api_vgs.dm deleted file mode 100644 index 1f05bd1d08..0000000000 --- a/code/modules/tgs/v5/api_vgs.dm +++ /dev/null @@ -1,90 +0,0 @@ -// We currently isolate ourselves in a different variable so that only the specific APIs we choose will be active. -// Eventually it would be good to handle all the TGS Apis properly and we can use the same. -GLOBAL_DATUM(vgs, /datum/tgs_api) - -// Supply our own New functionality so we can read from config instead of world params -/world/proc/VgsNew(datum/tgs_event_handler/event_handler) - var/current_api = GLOB.vgs - if(current_api) - TGS_ERROR_LOG("API datum already set (\ref[current_api] ([current_api]))! Was TgsNew() called more than once?") - return - - // If we don't have a configured access identifier we aren't meant to use VGS - if(!CONFIG_GET(string/vgs_access_identifier)) - TGS_INFO_LOG("Skipping VGS: No access identifier configured") - return - - var/datum/tgs_api/api_datum = /datum/tgs_api/v5/vgs1 - TGS_INFO_LOG("Activating API for version [api_datum]") - - if(event_handler && !istype(event_handler)) - TGS_ERROR_LOG("Invalid parameter for event_handler: [event_handler]") - event_handler = null - - var/datum/tgs_api/new_api = new api_datum(event_handler) - GLOB.vgs = new_api - - var/result = new_api.OnWorldNew() - if(!result || result == TGS_UNIMPLEMENTED) - GLOB.vgs = null - TGS_ERROR_LOG("Failed to activate API!") - -/world/proc/VgsTopic(T) - var/datum/tgs_api/api = GLOB.vgs - if(api) - var/result = api.OnTopic(T) - if(result != TGS_UNIMPLEMENTED) - return result - -/world/TgsReboot() - var/datum/tgs_api/api = GLOB.vgs - if(api) - api.OnReboot() - else - return ..() - -/world/TgsInitializationComplete() - var/datum/tgs_api/api = GLOB.vgs - if(api) - api.OnInitializationComplete() - else - return ..() - -/world/proc/VgsAddMemberRole(chat_user_id) - var/datum/tgs_api/v5/vgs1/api = GLOB.vgs - if(api) - api.AddMemberRole(chat_user_id) - -/datum/tgs_api/v5/vgs1 - server_port = 8080 // Default port - -// Override to prevent error messages from the lack of revision/test_merge information, and to use config isntead of params. -/datum/tgs_api/v5/vgs1/OnWorldNew() - if(CONFIG_GET(number/vgs_server_port)) - server_port = CONFIG_GET(number/vgs_server_port) - access_identifier = CONFIG_GET(string/vgs_access_identifier) - - var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands())) //CHOMPEdit TGS update - if(!istype(bridge_response)) - TGS_ERROR_LOG("Failed initial bridge request!") - return FALSE - - var/list/runtime_information = bridge_response[DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION] - if(!istype(runtime_information)) - TGS_ERROR_LOG("Failed to decode runtime information from bridge response: [json_encode(bridge_response)]!") - return FALSE - - version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) - instance_name = runtime_information[DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME] - - chat_channels = list() - DecodeChannels(runtime_information) - - return TRUE - -/datum/tgs_api/v5/vgs1/proc/AddMemberRole(chat_user_id) - Bridge(DMAPI5_BRIDGE_COMMAND_ADD_MEMBER_ROLE, list(DMAPI5_BRIDGE_PARAMETER_CHAT_USER_ID = chat_user_id)) - -// /datum/tgs_api/v5/vgs1/RequireInitialBridgeResponse() -// while(!instance_name) -// sleep(1) diff --git a/code/modules/tgs/v5/topic.dm b/code/modules/tgs/v5/topic.dm index d0e25b3c01..59e5e63e5c 100644 --- a/code/modules/tgs/v5/topic.dm +++ b/code/modules/tgs/v5/topic.dm @@ -60,12 +60,6 @@ intercepted_message_queue = null return result - // VOREStation Edit Start - GetChatCommands command - if(DMAPI5_TOPIC_COMMAND_GET_CHAT_COMMANDS) - var/topic_response = list(DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()) - return json_encode(topic_response) - // VOREStation Edit - End - if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION) var/list/event_notification = topic_parameters[DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION] if(!istype(event_notification)) diff --git a/code/modules/tgs/v5/undefs.dm b/code/modules/tgs/v5/undefs.dm index 66c662e768..ca49e46cdf 100644 --- a/code/modules/tgs/v5/undefs.dm +++ b/code/modules/tgs/v5/undefs.dm @@ -15,7 +15,6 @@ #undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND #undef DMAPI5_BRIDGE_COMMAND_CHUNK #undef DMAPI5_BRIDGE_COMMAND_EVENT -#undef DMAPI5_BRIDGE_COMMAND_ADD_MEMBER_ROLE // VOREStation Edit #undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER #undef DMAPI5_PARAMETER_CUSTOM_COMMANDS @@ -37,7 +36,6 @@ #undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE #undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL #undef DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION -#undef DMAPI5_BRIDGE_PARAMETER_CHAT_USER_ID // VOREStation Edit #undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT #undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION @@ -86,7 +84,6 @@ #undef DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK #undef DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST #undef DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT -#undef DMAPI5_TOPIC_COMMAND_GET_CHAT_COMMANDS // VOREStation Edit - Custom Commands #undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE #undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND diff --git a/code/modules/tgs/v5/chat_commands_zz_ch.dm b/code/modules/tgs_commands/chompstation.dm similarity index 91% rename from code/modules/tgs/v5/chat_commands_zz_ch.dm rename to code/modules/tgs_commands/chompstation.dm index 276cbfa786..2b3ec24f34 100644 --- a/code/modules/tgs/v5/chat_commands_zz_ch.dm +++ b/code/modules/tgs_commands/chompstation.dm @@ -116,3 +116,15 @@ /datum/tgs_chat_command/vore/Run(datum/tgs_chat_user/sender, params) return "vore" + +// - FAX +/datum/tgs_chat_command/readfax + name = "readfax" + help_text = "Reads a fax with specified faxid" + admin_only = TRUE + +/datum/tgs_chat_command/readfax/Run(sender, params) + var/list/all_params = splittext(params, " ") + var/faxid = all_params[1] + var/faxmsg = return_file_text("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html") + return "FAX: ```[strip_html_properly(faxmsg)]```" diff --git a/code/modules/tgs/v5/chat_commands.dm b/code/modules/tgs_commands/vorestation.dm similarity index 81% rename from code/modules/tgs/v5/chat_commands.dm rename to code/modules/tgs_commands/vorestation.dm index b0769257a4..fedde105a8 100644 --- a/code/modules/tgs/v5/chat_commands.dm +++ b/code/modules/tgs_commands/vorestation.dm @@ -8,23 +8,21 @@ var/afks = 0 var/active = 0 var/bellied = 0 -// var/map_name = "n/a" //CHOMP Remove we don't use this and it is causing problems with the dmb compiler. -// if(using_map && using_map.full_name) -// map_name = using_map.full_name + var/map_name = "n/a" + if(using_map && using_map.full_name) + map_name = using_map.full_name - for(var/X in GLOB.clients) - var/client/C = X - if(C) - counts++ - if(!(isnewplayer(C.mob) || istype(C.mob, /mob/observer))) - if(C.mob && isbelly(C.mob.loc)) - bellied++ - if(C.is_afk()) - afks++ - else - active++ + for(var/client/C in GLOB.clients) + counts++ + if(!(isnewplayer(C.mob) || istype(C.mob, /mob/observer))) + if(C.mob && isbelly(C.mob.loc)) + bellied++ + if(C.is_afk()) + afks++ + else + active++ - return "Current server status:\n**Web Manifest:** \n**Players:** [counts]\n**Active:** [active]\n**Bar Statues:** [afks]\n**Bellied:** [bellied]\n\n**Round Duration:** [roundduration2text()]" //CHOMPEdit + return "Current server status:\n**Web Manifest:** \n**Players:** [counts]\n**Active:** [active]\n**Bar Statues:** [afks]\n**Bellied:** [bellied]\n\n**Round Duration:** [roundduration2text()]\n**Current Map:** [map_name]" // CHOMPEdit /datum/tgs_chat_command/parsetest name = "parsetest" @@ -140,21 +138,3 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations) GLOB.pending_discord_registrations[GLOB.pending_discord_registrations.len] = list("ckey" = key_to_find, "id" = sender.id, "time" = world.realtime) return "[sender.friendly_name], I've sent you a message in-game. Please verify your username there to complete your registration within 10 minutes." - -/*//YW Commands //CHOMP Commenting this out for now. Should now be using Virgo's version. -//Status -/datum/tgs_chat_command/status/Run(datum/tgs_chat_user/sender, params) - return "Current server status:**Players:** [TGS_CLIENT_COUNT]\n**Round Duration:** [roundduration2text()]" -*/ -// - FAX -/datum/tgs_chat_command/readfax - name = "readfax" - help_text = "Reads a fax with specified faxid" - //required_parameters = 1 Is not a thing - admin_only = TRUE - -/datum/tgs_chat_command/readfax/Run(sender, params) - var/list/all_params = splittext(params, " ") - var/faxid = all_params[1] - var/faxmsg = return_file_text("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html") // CHOMPEdit - return "FAX: ```[strip_html_properly(faxmsg)]```" diff --git a/code/modules/tickets/procs.dm b/code/modules/tickets/procs.dm index 2cd19a236d..11255afb26 100644 --- a/code/modules/tickets/procs.dm +++ b/code/modules/tickets/procs.dm @@ -55,7 +55,7 @@ ADMIN_VERB(cmd_mentor_ticket_panel, (R_ADMIN|R_SERVER|R_MOD|R_MENTOR), "Mentor T GLOB.tickets.BrowseTickets(browse_to) /proc/message_mentors(var/msg) - msg = span_mentor_channel(span_prefix("Mentor:") + span_message("[msg]")) + msg = span_mentor_channel(span_prefix("Mentor: ") + span_message("[msg]")) for(var/client/C in GLOB.admins) to_chat(C, msg) diff --git a/code/modules/tickets/tickets.dm b/code/modules/tickets/tickets.dm index bc19e56111..760aa86450 100644 --- a/code/modules/tickets/tickets.dm +++ b/code/modules/tickets/tickets.dm @@ -20,6 +20,7 @@ else world.TgsTargetedChatBroadcast(message,TRUE) +// //TICKET MANAGER // @@ -318,9 +319,9 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list) var/list/activemins = adm["present"] var activeMins = activemins.len if(is_bwoink) - ahelp_discord_message("ADMINHELP: FROM: [key_name_admin(usr)] TO [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.") + ahelp_discord_message("[level == 0 ? "MENTORHELP" : "ADMINHELP"]: FROM: [key_name_admin(usr)] TO [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.") else - ahelp_discord_message("ADMINHELP: FROM: [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.") + ahelp_discord_message("[level == 0 ? "MENTORHELP" : "ADMINHELP"]: FROM: [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.") // Also send it to discord since that's the hip cool thing now. SSwebhooks.send( @@ -357,11 +358,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list) tgui_interact(usr.client.mob) //private -/datum/ticket/proc/FullMonty(ref_src, admin = FALSE) +/datum/ticket/proc/FullMonty(ref_src, admin_commands = FALSE) if(!ref_src) ref_src = "\ref[src]" if(initiator && initiator.mob) - if(admin) + if(admin_commands) . = ADMIN_FULLMONTY_NONAME(initiator.mob) else . = "Initiator disconnected." @@ -403,22 +404,26 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list) //won't bug irc /datum/ticket/proc/MessageNoRecipient(msg) var/ref_src = "\ref[src]" - var/chat_msg = span_admin_pm_notice(span_adminhelp("Ticket [TicketHref("#[id]", ref_src)]") + span_bold(": [LinkedReplyName(ref_src)] [FullMonty(ref_src)]:") + msg) AddInteraction(span_red("[LinkedReplyName(ref_src)]: [msg]")) //send this msg to all admins - if(level == 0) - for (var/client/C in GLOB.admins) - if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping)) - C << 'sound/effects/mentorhelp.mp3' - message_mentors(chat_msg) - else if(level == 1) - for(var/client/X in GLOB.admins) - if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) - X << 'sound/effects/adminhelp.ogg' - window_flash(X) - to_chat(X, chat_msg) + switch(level) + if(0) + for (var/client/C in GLOB.admins) + var/chat_msg = span_mentor_channel(span_admin_pm_notice(span_adminhelp("Ticket [TicketHref("#[id]", ref_src)]") + span_bold(" (Mentor): [LinkedReplyName(ref_src)] [FullMonty(ref_src, check_rights_for(C, (R_ADMIN|R_SERVER|R_MOD)))]:") + msg)) + if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping)) + C << 'sound/effects/mentorhelp.mp3' + to_chat(C, chat_msg) + if(1) + for(var/client/X in GLOB.admins) + var/chat_msg = span_admin_pm_notice(span_adminhelp("Ticket [TicketHref("#[id]", ref_src)] (Admin)") + span_bold(": [LinkedReplyName(ref_src)] [FullMonty(ref_src, check_rights_for(X, (R_ADMIN|R_SERVER|R_MOD)))]:") + msg) + if(!check_rights_for(X, R_HOLDER)) + continue + if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) + X << 'sound/effects/adminhelp.ogg' + window_flash(X) + to_chat(X, chat_msg) /* //Reopen a closed ticket @@ -649,6 +654,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list) level = level + 1 + AddInteraction("[key_name_admin(usr)] escalated Ticket.") message_mentors("[usr.ckey] escalated Ticket [TicketHref("#[id]")]") log_admin("[key_name(usr)] escalated ticket [src.name]") to_chat(src.initiator, span_mentor("[usr.ckey] escalated your ticket to admins.")) diff --git a/code/modules/tickets/tickets_ui.dm b/code/modules/tickets/tickets_ui.dm index a41dd7398a..3bb42e44ef 100644 --- a/code/modules/tickets/tickets_ui.dm +++ b/code/modules/tickets/tickets_ui.dm @@ -45,7 +45,7 @@ "closed_at" = (world.time - T.closed_at), "opened_at_date" = gameTimestamp(wtime = T.opened_at), "closed_at_date" = gameTimestamp(wtime = T.closed_at), - "actions" = check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) ? T.FullMonty(,TRUE) : T.FullMonty(), + "actions" = T.FullMonty(null, check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD))), "log" = T._interactions, ) @@ -235,7 +235,7 @@ data["opened_at_date"] = gameTimestamp(wtime = opened_at) data["closed_at_date"] = gameTimestamp(wtime = closed_at) - data["actions"] = check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) ? FullMonty(ref_src, TRUE) : FullMonty(ref_src) + data["actions"] = FullMonty(ref_src, check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD))) data["log"] = _interactions @@ -296,7 +296,7 @@ dat += "
Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)" dat += "

" if(initiator) - dat += span_bold("Actions:") + " [check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) ? FullMonty(ref_src, TRUE) : FullMonty(ref_src)]
" + dat += span_bold("Actions:") + " [FullMonty(ref_src, check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)))]
" else dat += span_bold("DISCONNECTED") + "[GLOB.TAB][ClosureLinks(ref_src)]
" dat += "
Log:

" diff --git a/config/example/config.txt b/config/example/config.txt index acb2c07cdc..3cfd01c016 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -473,10 +473,8 @@ ITEMS_SURVIVE_DIGESTION # Path to the folder that BYOND should export faxes into so they are readable on the web. #FAX_EXPORT_DIR data/faxes -# TCP port on which to connect to the VGS instance. -#VGS_SERVER_PORT 8888 -# Secret pre-shared-key to authenticate to VGS. -#VGS_ACCESS_IDENTIFIER some_password_here +# Port for helper script to add member roles in discord (see tools/registration) +#REGISTER_SERVER_PORT 6969 MULTI_Z_EXPLOSION_SCALAR 0.35 @@ -623,3 +621,6 @@ JUKEBOX_TRACK_FILES config/jukebox.json #Turn this on if you want all admin-PMs to go to be sent to discord, and not only the first message of a ticket. #DISCORD_AHELPS_ALL + +## Uncomment to set the number of /world/Reboot()s before the DreamDaemon restarts itself. 0 means restart every round. Requires tgstation server tools. +#ROUNDS_UNTIL_HARD_RESTART 10 diff --git a/dependencies.sh b/dependencies.sh index 20b97c7d2f..d2b937981f 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -8,7 +8,7 @@ export BYOND_MAJOR=516 export BYOND_MINOR=1664 # Macro Count -export MACRO_COUNT=8 +export MACRO_COUNT=9 #rust_g git tag export RUST_G_VERSION=3.11.0 diff --git a/html/admin/panels.css b/html/admin/panels.css index 22373ee0ca..c1699b7843 100644 --- a/html/admin/panels.css +++ b/html/admin/panels.css @@ -1,10 +1,54 @@ -body {padding:0px;margin:0px;} -#top {position:fixed;top:5px;left:10%;width:80%;text-align:center;background-color:#fff;border:2px solid #ccc;} -#main {position:relative;top:10px;left:3%;width:96%;text-align:center;z-index:0;} -#searchable {table-layout:fixed;width:100%;text-align:center;"#f4f4f4";} -tr.norm {background-color:#f4f4f4;} -tr.title {background-color:#ccc;} -tr.alt {background-color:#e7e7e7;} -.small {font-size:80%;} -a {text-decoration:none;} -a:hover {color:#d3d;} +body { + padding: 0px; + margin: 0px; +} + +#top { + position: fixed; + top: 5px; + left: 10%; + width: 80%; + text-align: center; + background-color: #fff; + border: 2px solid #ccc; +} + +#main { + position: relative; + top: 10px; + left: 3%; + width: 96%; + text-align: center; + z-index: 0; +} + +#searchable { + table-layout: fixed; + width: 100%; + text-align: center; + background-color: "#f4f4f4"; +} + +tr.norm { + background-color: #f4f4f4; +} + +tr.title { + background-color: #ccc; +} + +tr.alt { + background-color: #e7e7e7; +} + +.small { + font-size: 80%; +} + +a { + text-decoration: none; +} + +a:hover { + color: #d3d; +} diff --git a/html/browser/common.css b/html/browser/common.css index 671df60ed3..06f29333fe 100644 --- a/html/browser/common.css +++ b/html/browser/common.css @@ -1,422 +1,518 @@ -body -{ - padding: 0; - margin: 0; - background-color: #272727; - font-size: 12px; - color: #ffffff; - line-height: 170%; +body { + padding: 0; + margin: 0; + background-color: #272727; + font-size: 12px; + color: #ffffff; + line-height: 170%; } -hr -{ - background-color: #40628a; - height: 1px; +hr { + background-color: #40628a; + height: 1px; } -a, a:link, a:visited, a:active, .linkOn, .linkOff -{ - color: #ffffff; - text-decoration: none; - background: #40628a; - border: 1px solid #161616; - padding: 1px 4px 1px 4px; - margin: 0 2px 0 0; - cursor:default; - white-space:nowrap; +a, +button, +a:link, +a:visited, +a:active, +.linkOn, +.linkOff { + color: #ffffff; + text-decoration: none; + background: #40628a; + border: 1px solid #161616; + padding: 1px 4px 1px 4px; + margin: 0 2px 0 0; + cursor: default; } -a:hover -{ - color: #40628a; - background: #ffffff; +a:hover { + color: #40628a; + background: #ffffff; } -a.white, a.white:link, a.white:visited, a.white:active -{ - color: #40628a; - text-decoration: none; - background: #ffffff; - border: 1px solid #161616; - padding: 1px 4px 1px 4px; - margin: 0 2px 0 0; - cursor:default; +a.white, +a.white:link, +a.white:visited, +a.white:active { + color: #40628a; + text-decoration: none; + background: #ffffff; + border: 1px solid #161616; + padding: 1px 4px 1px 4px; + margin: 0 2px 0 0; + cursor: default; } -a.white:hover -{ - color: #ffffff; - background: #40628a; +a.white:hover { + color: #ffffff; + background: #40628a; } -.linkOn, a.linkOn:link, a.linkOn:visited, a.linkOn:active, a.linkOn:hover -{ - color: #ffffff; - background: #2f943c; - border-color: #24722e; +.linkOn, +a.linkOn:link, +a.linkOn:visited, +a.linkOn:active, +a.linkOn:hover { + color: #ffffff; + background: #2f943c; + border-color: #24722e; } -.linkOff, a.linkOff:link, a.linkOff:visited, a.linkOff:active, a.linkOff:hover -{ - color: #ffffff; - background: #999999; - border-color: #666666; +.linkOff, +a.linkOff:link, +a.linkOff:visited, +a.linkOff:active, +a.linkOff:hover { + color: #ffffff; + background: #999999; + border-color: #666666; } -a.icon, .linkOn.icon, .linkOff.icon -{ - position: relative; - padding: 1px 4px 2px 20px; +a.icon, +.linkOn.icon, +.linkOff.icon { + position: relative; + padding: 1px 4px 2px 20px; } -a.icon img, .linkOn.icon img -{ - position: absolute; - top: 0; - left: 0; - width: 18px; - height: 18px; +a.icon img, +.linkOn.icon img { + position: absolute; + top: 0; + left: 0; + width: 18px; + height: 18px; } -a.icon64, .linkOn.icon64, .linkOff.icon64 -{ - position: relative; - padding: 1px 4px 2px 68px; - font-size: 55px; +a.icon64, +.linkOn.icon64, +.linkOff.icon64 { + position: relative; + padding: 1px 4px 2px 68px; + font-size: 55px; } -a.icon64 img, .linkOn.icon64 img -{ - position: absolute; - top: 0; - left: 0; - width: 66px; - height: 66px; +a.icon64 img, +.linkOn.icon64 img { + position: absolute; + top: 0; + left: 0; + width: 66px; + height: 66px; } -ul -{ - padding: 4px 0 0 10px; - margin: 0; - list-style-type: none; +ul { + padding: 4px 0 0 10px; + margin: 0; + list-style-type: none; } -li -{ - padding: 0 0 2px 0; +li { + padding: 0 0 2px 0; } -img, a img -{ - border-style:none; +img, +a img { + border-style: none; } -h1, h2, h3, h4, h5, h6 -{ - margin: 0; - padding: 16px 0 8px 0; - color: #517087; +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + padding: 16px 0 8px 0; + color: #517087; } -h1 -{ - font-size: 15px; +h1 { + font-size: 15px; } -h2 -{ - font-size: 14px; +h2 { + font-size: 14px; } -h3 -{ - font-size: 13px; +h3 { + font-size: 13px; } -h4 -{ - font-size: 12px; +h4 { + font-size: 12px; } -.uiWrapper -{ - - width: 100%; - height: 100%; - padding-top:32px; +.uiWrapper { + width: 100%; + height: 100%; } -.uiTitle -{ - clear: both; - padding: 6px 8px 6px 8px; - border-bottom: 2px solid #161616; - background: #383838; - color: #98B0C3; - font-size: 16px; +.uiTitle { + clear: both; + padding: 6px 8px 6px 8px; + border-bottom: 2px solid #161616; + background: #383838; + color: #98b0c3; + font-size: 16px; } -.uiTitleWrapper -{ - position:fixed; - top:0px; - left:0px; - right:0px; - z-index: 10 +.uiTitle.icon { + padding: 6px 8px 6px 42px; + background-position: 2px 50%; + background-repeat: no-repeat; } -.uiTitleButtons -{ - position:fixed; - top:0px; - right:0px; - height:32px; - z-index:11; +.uiContent { + clear: both; + padding: 8px; + font-family: Verdana, Geneva, sans-serif; } -.uiTitle.icon -{ - padding: 6px 8px 6px 42px; - background-position: 2px 50%; - background-repeat: no-repeat; +.good, +.green { + color: #00ff00; } -.uiContent -{ - clear: both; - padding: 8px; - font-family: Verdana, Geneva, sans-serif; +.average, +.orange { + color: #d09000; } -.good, .green -{ - color: #00ff00; +.bad, +.red { + color: #ff0000; } -.average, .orange -{ - color: #d09000; +.highlight { + color: #8BA5C4; } -.bad, .red -{ - color: #ff0000; -} - -.highlight -{ - color: #8BA5C4; -} - -.dark -{ - color: #272727; +.dark { + color: #272727; } .darkgreen { - color: #008000; + color: #008000; } .grey { - color: #838383; + color: #838383; } - .crimson { - color: #dc143c; + color: #dc143c; } .maroon { - color: #800000; + color: #800000; } .brown { - color: #8d4925; + color: #8d4925; } .blue { - color: #0000ff; + color: #0000ff; } .black { - color: #000000; + color: #000000; } .white { - color: #000000; + color: #000000; } .darkgray { - color: #808080; + color: #808080; } .gray { - color: #a9a9a9; + color: #a9a9a9; } .yellow { - color: #ffcc00; + color: #ffcc00; } .pink { - color: #ffc0cb; + color: #ffc0cb; } .cyan { - color: #00ffff; + color: #00ffff; } .nicegreen { - color: #14a833; + color: #14a833; } -.notice -{ - position: relative; - background: #E9C183; - color: #15345A; - font-size: 10px; - font-style: italic; - padding: 2px 4px 0 4px; - margin: 4px; +.notice { + position: relative; + background: #e9c183; + color: #15345a; + font-size: 10px; + font-style: italic; + padding: 2px 4px 0 4px; + margin: 4px; } -.boldnotice -{ - position: relative; - background: #E9C183; - color: #15345A; - font-weight: bold; - font-size: 10px; - font-style: italic; - padding: 2px 4px 0 4px; - margin: 4px; +.boldnotice { + position: relative; + background: #E9C183; + color: #15345A; + font-weight: bold; + font-size: 10px; + font-style: italic; + padding: 2px 4px 0 4px; + margin: 4px; } -.notice.icon -{ - padding: 2px 4px 0 20px; +.notice.icon { + padding: 2px 4px 0 20px; } -.notice img -{ - position: absolute; - top: 0; - left: 0; - width: 16px; - height: 16px; +.notice img { + position: absolute; + top: 0; + left: 0; + width: 16px; + height: 16px; } -div.notice -{ - clear: both; +div.notice { + clear: both; } -.statusDisplay -{ - background: #000000; - color: #ffffff; - border: 1px solid #40628a; - padding: 4px; - margin: 3px 0; +.statusDisplay { + background: #000000; + color: #ffffff; + border: 1px solid #40628a; + padding: 4px; + margin: 3px 0; } -.block -{ - padding: 8px; - margin: 10px 4px 4px 4px; - border: 1px solid #40628a; - background-color: #202020; +.statusLabel { + width: 138px; + float: left; + overflow: hidden; + color: #98b0c3; } -.block h3 -{ - padding: 0; +.statusValue { + float: left; } -.progressBar -{ - width: 240px; - height: 14px; - border: 1px solid #666666; - float: left; - margin: 0 5px; - overflow: hidden; +.block { + padding: 8px; + margin: 10px 4px 4px 4px; + border: 1px solid #40628a; + background-color: #202020; } -.progressFill -{ - width: 100%; - height: 100%; - background: #40628a; - overflow: hidden; +.block h3 { + padding: 0; } -.progressFill.good -{ - color: #ffffff; - background: #00ff00; +.progressBar { + width: 240px; + height: 14px; + border: 1px solid #666666; + float: left; + margin: 0 5px; + overflow: hidden; } -.progressFill.average -{ - color: #ffffff; - background: #d09000; +.progressFill { + width: 100%; + height: 100%; + background: #40628a; + overflow: hidden; } -.progressFill.bad -{ - color: #ffffff; - background: #ff0000; +.progressFill.good { + color: #ffffff; + background: #00ff00; } -.progressFill.highlight -{ - color: #ffffff; - background: #8BA5C4; +.progressFill.average { + color: #ffffff; + background: #d09000; } -.clearBoth -{ - clear: both; +.progressFill.bad { + color: #ffffff; + background: #ff0000; } -.clearLeft -{ - clear: left; +.progressFill.highlight { + color: #ffffff; + background: #8ba5c4; } -.clearRight -{ - clear: right; +.clearBoth { + clear: both; } -.line -{ - width: 100%; - clear: both; +.clearLeft { + clear: left; +} + +.clearRight { + clear: right; +} + +.line { + width: 100%; + clear: both; } .italic, .italics { - font-style: italic; + font-style: italic; } .bold { - font-weight: bold; + font-weight: bold; } .underline { - text-decoration: underline; + text-decoration: underline; } -.spoiler{ - background-color: gray; - color: transparent; - user-select: none; +.spoiler { + background-color: gray; + color: transparent; + user-select: none; } -.spoiler:hover{ - background-color: inherit; - color: inherit; +.spoiler:hover { + background-color: inherit; + color: inherit; +} + +.switch { + position: relative; + display: inline-block; + width: 50px; + height: 26px; +} + +.switch input { + display: none; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #383838; + transition: 0.4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 4px; + bottom: 4px; + background-color: #98b0c3; + transition: 0.4s; +} + +.slider.red:before { + background-color: #d6858b; +} + +.slider.locked:before { + content: url("padlock.png"); + background-color: #b4b4b4; +} + +input:checked+.slider { + background-color: #40628a; +} + +input:checked+.slider.red { + background-color: #a92621; +} + +input:checked+.slider.locked { + background-color: #707070; +} + +input:focus+.slider { + box-shadow: 0 0 1px #2196f3; +} + +input:focus+.slider.red { + box-shadow: 0 0 1px #f3212d; +} + +input:focus+.slider.locked { + box-shadow: 0 0 1px #979797; +} + +input:checked+.slider:before { + transform: translateX(24px); +} + +.switch span { + display: inline-block; + position: relative; + width: 60px; + margin-left: 60px; +} + +ul.sparse { + padding-bottom: 20px; +} + +.sparse li { + margin-top: 2px; +} + +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} + +.severity { + margin: 0px; + padding: 1px 8px 1px 8px; + border-radius: 25px; + border: 1px solid #161616; + background: #40628a; + color: #ffffff; +} + +.severity img { + display: inline-block; + vertical-align: middle; +} + +.code { + padding: 6px 8px; + border: 1px solid #161616; + background: #383838; + color: #ffffff; + font-size: 12px; + display: block; + margin: 4px 0; + font-family: "Courier New", Courier, monospace; +} + +.user-select { + user-select: all; } diff --git a/tgui/README.md b/tgui/README.md index 15c3ca2aed..a1f7584b2c 100644 --- a/tgui/README.md +++ b/tgui/README.md @@ -68,20 +68,20 @@ will need these: **Via Juke Build (cross-platform)**: -- `tools/build/build tgui` - Build tgui in production mode. -- `tools/build/build tgui-dev` - Build tgui in production mode. - - `tools/build/build tgui-dev --reload` - Reload byond cache once. - - `tools/build/build tgui-dev --debug` - Run server with debug logging +- `tools/build/build.sh tgui` - Build tgui in production mode. +- `tools/build/build.sh tgui-dev` - Build tgui in production mode. + - `tools/build/build.sh tgui-dev --reload` - Reload byond cache once. + - `tools/build/build.sh tgui-dev --debug` - Run server with debug logging enabled. -- `tools/build/build tgui-lint` - Show (and auto-fix) problems with the code. -- `tools/build/build tgui-test` - Run unit and integration tests. -- `tools/build/build tgui-analyze` - Run a bundle analyzer. -- `tools/build/build tgui-clean` - Clean up tgui folder. +- `tools/build/build.sh tgui-lint` - Show (and auto-fix) problems with the code. +- `tools/build/build.sh tgui-test` - Run unit and integration tests. +- `tools/build/build.sh tgui-analyze` - Run a bundle analyzer. +- `tools/build/build.sh tgui-clean` - Clean up tgui folder. > With Juke Build, you can run multiple targets together, e.g.: > > ``` -> tools/build/build tgui tgui-lint tgui-tsc tgui-test +> tools/build/build.sh tgui tgui-lint tgui-tsc tgui-test > ``` **Via Bun (cross-platform)**: diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index d29fa79546..b2c920b8d8 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -1126,6 +1126,70 @@ img.icon.bigicon { border-bottom: 1px dashed hsl(0, 0%, 100%); } +.fieldset_legend { + position: relative; + max-width: 95%; + font-size: 120%; + background-color: var(--color-section); + padding: var(--space-s) var(--space-m); + border-color: inherit; + z-index: 0; + + &:before { + content: ''; + position: absolute; + inset: 0; + border: var(--border-thickness-tiny) solid; + border-color: inherit; + border-radius: var(--border-radius-medium); + mask-image: linear-gradient(to top, black 50%, transparent 50%); + z-index: -1; + } +} + +.boxed_message { + background: hsl(220, 10%, 10%); + border: var(--border-thickness-tiny) solid; + border-left: var(--border-thickness-large) solid; + border-color: hsla(220, 40%, 75%, 0.25); + margin: var(--space-m) 0; + padding: var(--space-m) var(--space-l); + border-radius: var(--border-radius-medium); + + &.red_box { + background: hsl(0, 20%, 10%); + border-color: hsla(0, 100%, 50%, 0.5); + } + + &.green_box { + background: hsl(140, 20%, 10%); + border-color: hsla(120, 100%, 50%, 0.5); + } + + &.blue_box { + background: hsl(220, 20%, 10%); + border-color: hsla(225, 90%, 65%, 0.5); + } + + &.purple_box { + background: hsl(260, 25%, 12.5%); + border-color: hsla(260, 100%, 75%, 0.5); + } + + hr { + margin: var(--space-m) -0.75em; + border-color: inherit; + } +} + +// Provides a horizontal bar with text in the middle +// I got this off stackoverflow +.separator { + display: flex; + align-items: center; + text-align: center; +} + $alert-stripe-colors: ( 'default': hsl(199, 100%, 11%), 'green': hsl(120, 100%, 12%), diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index 43d3676148..962d411804 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -1148,6 +1148,47 @@ h2.alert { border-bottom: 1px dashed hsl(0, 0%, 0%); } +.fieldset_legend { + background: hsl(0, 0%, 100%); // Chat background color + + &:before { + background: hsl(0, 0%, 100%); // Chat background color + } +} + +.boxed_message { + background: hsl(220, 100%, 97.5%); + border-color: hsla(220, 75%, 25%, 0.5); + + &.red_box { + background: hsl(0, 100%, 97.5%); + border-color: hsla(0, 100%, 50%, 0.5); + } + + &.green_box { + background: hsl(140, 100%, 97.5%); + border-color: hsl(120, 100%, 33%, 0.5); + } + + &.blue_box { + background: hsl(220, 100%, 97.5%); + border-color: hsla(225, 100%, 50%, 0.5); + } + + &.purple_box { + background: hsl(260, 100%, 97.5%); + border-color: hsla(260, 100%, 50%, 0.5); + } +} + +// Provides a horizontal bar with text in the middle +// I got this off stackoverflow +.separator { + display: flex; + align-items: center; + text-align: center; +} + $alert-stripe-colors: ( 'default': hsl(231, 100%, 85%), 'green': hsl(120, 100%, 84%), diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss index d131f209f1..7504926401 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss @@ -1127,6 +1127,70 @@ img.icon.bigicon { border-bottom: 1px dashed hsl(0, 0%, 100%); } +.fieldset_legend { + position: relative; + max-width: 95%; + font-size: 120%; + background-color: var(--color-section); + padding: var(--space-s) var(--space-m); + border-color: inherit; + z-index: 0; + + &:before { + content: ''; + position: absolute; + inset: 0; + border: var(--border-thickness-tiny) solid; + border-color: inherit; + border-radius: var(--border-radius-medium); + mask-image: linear-gradient(to top, black 50%, transparent 50%); + z-index: -1; + } +} + +.boxed_message { + background: hsl(220, 10%, 10%); + border: var(--border-thickness-tiny) solid; + border-left: var(--border-thickness-large) solid; + border-color: hsla(220, 40%, 75%, 0.25); + margin: var(--space-m) 0; + padding: var(--space-m) var(--space-l); + border-radius: var(--border-radius-medium); + + &.red_box { + background: hsl(0, 20%, 10%); + border-color: hsla(0, 100%, 50%, 0.5); + } + + &.green_box { + background: hsl(140, 20%, 10%); + border-color: hsla(120, 100%, 50%, 0.5); + } + + &.blue_box { + background: hsl(220, 20%, 10%); + border-color: hsla(225, 90%, 65%, 0.5); + } + + &.purple_box { + background: hsl(260, 25%, 12.5%); + border-color: hsla(260, 100%, 75%, 0.5); + } + + hr { + margin: var(--space-m) -0.75em; + border-color: inherit; + } +} + +// Provides a horizontal bar with text in the middle +// I got this off stackoverflow +.separator { + display: flex; + align-items: center; + text-align: center; +} + $alert-stripe-colors: ( 'default': hsl(199, 100%, 11%), 'green': hsl(120, 100%, 12%), diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss index 0f901de4bb..111cad899f 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss @@ -1146,6 +1146,47 @@ h2.alert { border-bottom: 1px dashed hsl(0, 0%, 0%); } +.fieldset_legend { + background: hsl(0, 0%, 100%); // Chat background color + + &:before { + background: hsl(0, 0%, 100%); // Chat background color + } +} + +.boxed_message { + background: hsl(220, 100%, 97.5%); + border-color: hsla(220, 75%, 25%, 0.5); + + &.red_box { + background: hsl(0, 100%, 97.5%); + border-color: hsla(0, 100%, 50%, 0.5); + } + + &.green_box { + background: hsl(140, 100%, 97.5%); + border-color: hsl(120, 100%, 33%, 0.5); + } + + &.blue_box { + background: hsl(220, 100%, 97.5%); + border-color: hsla(225, 100%, 50%, 0.5); + } + + &.purple_box { + background: hsl(260, 100%, 97.5%); + border-color: hsla(260, 100%, 50%, 0.5); + } +} + +// Provides a horizontal bar with text in the middle +// I got this off stackoverflow +.separator { + display: flex; + align-items: center; + text-align: center; +} + $alert-stripe-colors: ( 'default': hsl(231, 100%, 85%), 'green': hsl(120, 100%, 84%), diff --git a/tools/bootstrap/javascript_.ps1 b/tools/bootstrap/javascript_.ps1 index 5f286dd4d4..dbf9a7a9b8 100644 --- a/tools/bootstrap/javascript_.ps1 +++ b/tools/bootstrap/javascript_.ps1 @@ -37,6 +37,11 @@ function Get-Bun { $BunTag = " (baseline)" } + if (Test-Path $BunTargetDir -PathType Container) { + Write-Output "Bun target directory exists but bun.exe is missing. Re-downloading." + Remove-Item $BunTargetDir -Recurse -Force + } + $BunSource = "https://github.com/oven-sh/bun/releases/download/bun-v$BunVersion/$BunRelease.zip" Write-Output "Downloading Bun v$BunVersion$BunTag" diff --git a/tools/build/README.md b/tools/build/README.md index 195c9eff83..ce63f21487 100644 --- a/tools/build/README.md +++ b/tools/build/README.md @@ -9,7 +9,7 @@ This build script is the recommended way to compile the game, including not only a) Double-click `BUILD.bat` in the repository root to build (will wait for a key press before it closes). b) Double-click `tools/build/build.bat` to build (will exit as soon as it finishes building). - Linux: - a) Run `tools/build/build` from the repository root. + a) Run `tools/build/build.sh` from the repository root. The script will skip build steps whose inputs have not changed since the last run. @@ -18,7 +18,7 @@ The script will skip build steps whose inputs have not changed since the last ru You can get a list of all targets that you can build by running the following command: ``` -tools/build/build --help +tools/build/build.sh --help ``` ## Dependencies diff --git a/tools/registration/.gitignore b/tools/registration/.gitignore new file mode 100644 index 0000000000..1687d79e66 --- /dev/null +++ b/tools/registration/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +config.json diff --git a/tools/registration/config.json.example b/tools/registration/config.json.example new file mode 100644 index 0000000000..a95663c923 --- /dev/null +++ b/tools/registration/config.json.example @@ -0,0 +1,6 @@ +{ + "port": 6969, + "token": "your-token-goes-here", + "guildId": "your-guild-snowflake-goes-here", + "roleId": "your-role-snowflake-goes-here" +} diff --git a/tools/registration/index.js b/tools/registration/index.js new file mode 100644 index 0000000000..af35488282 --- /dev/null +++ b/tools/registration/index.js @@ -0,0 +1,72 @@ +const http = require("node:http"); +const { Client, GatewayIntentBits } = require("discord.js"); +const { port, token, guildId, roleId } = require("./config.json"); + +async function main() { + const client = new Client({ intents: [] }); + await client.login(token); + + const guild = await client.guilds.fetch(guildId); + if (!guild) { + console.error("ERROR in config, unable to resolve guild " + guildId); + return; + } + + const role = await guild.roles.fetch(roleId); + if (!role) { + console.error("ERROR in config, invalid role ID ", roleId); + return; + } + + const server = http.createServer({}, (req, res) => { + const url = req.url; + if (!url) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ data: "error parsing URL" })) + return; + } + + let urlObj; + try { + urlObj = new URL(`http://localhost${url}`); + } catch (err) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ data: "error parsing URL: " + err })) + return; + } + if (!urlObj.search) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ data: "error parsing URL" })) + return; + } + + const params = urlObj.searchParams; + const member_id = params.get("member"); + + if (!member_id) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ data: "error parsing URL" })) + return; + } + + let memberIdRaw; + try { + memberIdRaw = JSON.parse(member_id); + } catch(err) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ data: "error parsing URL: " + err })) + return; + } + + guild.members.addRole({ user: memberIdRaw, role, reason: "SS13 Registration" }) + console.log("Successfully registered ", memberIdRaw); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ data: "success" })) + }); + + server.listen(port); + console.log("Server running on port", port); +} + +main(); diff --git a/tools/registration/package.json b/tools/registration/package.json new file mode 100644 index 0000000000..e2b1bd7fe1 --- /dev/null +++ b/tools/registration/package.json @@ -0,0 +1,14 @@ +{ + "name": "registration", + "version": "1.0.0", + "description": "Small discord.js bot to handle adding member roles on discord registration", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "tigercat2000", + "license": "MIT", + "dependencies": { + "discord.js": "^14.21.0" + } +} diff --git a/tools/tgs_scripts/InstallDeps.sh b/tools/tgs_scripts/InstallDeps.sh index 839f90f53e..b4212890e7 100644 --- a/tools/tgs_scripts/InstallDeps.sh +++ b/tools/tgs_scripts/InstallDeps.sh @@ -6,22 +6,31 @@ has_git="$(command -v git)" has_curl="$(command -v curl)" has_cargo="$(command -v ~/.cargo/bin/cargo)" has_sudo="$(command -v sudo)" -# FIXME: yt-dlp +has_ytdlp="$(command -v yt-dlp)" has_pip3="$(command -v pip3)" +has_unzip="$(command -v unzip)" set -e set -x # apt packages, libssl needed by rust-g but not included in TGS barebones install -if ! ( [ -x "$has_git" ] && [ -x "$has_curl" ] && [ -f "/usr/lib/i386-linux-gnu/libssl.so" ] ); then - echo "Installing apt dependencies..." +if ! ( [ -x "$has_git" ] && [ -x "$has_curl" ] && [ -x "$has_pip3" ] && [ -x "$has_unzip" ] && [ -f "/usr/lib/i386-linux-gnu/libssl.so" ] ); then + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "!!! HEY YOU THERE, READING THE TGS LOGS READ THIS!!!" + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "We are about to try installing native dependencies, we will use 'sudo' if possible for this, but it may fail because the tgstation-server user doesn't have passwordless sudo." + echo "WE DO NOT RECOMMEND GRANTING PASSWORDLESS SUDO!!! Instead, install all the dependencies yourself with the following command:" + echo ".................................................................................................................................................." + echo "sudo apt-get install -y lib32z1 git pkg-config libssl-dev:i386 libssl-dev zlib1g-dev:i386 curl libclang-dev g++-multilib python3 python3-pip unzip" + echo ".................................................................................................................................................." + echo "Attempting to install apt dependencies..." if ! [ -x "$has_sudo" ]; then dpkg --add-architecture i386 apt-get update - apt-get install -y lib32z1 git pkg-config libssl-dev:i386 libssl-dev zlib1g-dev:i386 curl + apt-get install -y lib32z1 git pkg-config libssl-dev:i386 libssl-dev zlib1g-dev:i386 curl libclang-dev g++-multilib python3 python3-pip unzip else sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install -y lib32z1 git pkg-config libssl-dev:i386 libssl-dev zlib1g-dev:i386 curl + sudo apt-get install -y lib32z1 git pkg-config libssl-dev:i386 libssl-dev zlib1g-dev:i386 curl libclang-dev g++-multilib python3 python3-pip unzip fi fi @@ -31,3 +40,13 @@ if ! [ -x "$has_cargo" ]; then curl https://sh.rustup.rs -sSf | sh -s -- -y . ~/.profile fi + +# install or update yt-dlp when not present, or if it is present with pip3, +# which we assume was used to install it +if ! [ -x "$has_ytdlp" ]; then + echo "Installing yt-dlp with pip3..." + pip3 install yt-dlp --break-system-packages +else + echo "Ensuring yt-dlp is up-to-date with pip3..." + pip3 install yt-dlp -U --break-system-packages +fi diff --git a/tools/tgs_scripts/PreCompile.bat b/tools/tgs_scripts/PreCompile.bat index ce7572c9ef..43d3066ae8 100644 --- a/tools/tgs_scripts/PreCompile.bat +++ b/tools/tgs_scripts/PreCompile.bat @@ -1,15 +1,6 @@ @echo off cd /D "%~dp0" set TG_BOOTSTRAP_CACHE=%cd% -IF NOT %1 == "" ( - rem TGS4+: we are passed the game directory on the command line - cd %1 -) ELSE IF EXIST "..\Game\B\tgstation.dmb" ( - rem TGS3: Game/B/tgstation.dmb exists, so build in Game/A - cd ..\Game\A -) ELSE ( - rem TGS3: Otherwise build in Game/B - cd ..\Game\B -) +cd %1 set CBT_BUILD_MODE=TGS tools\build\build diff --git a/tools/tgs_scripts/PreCompile.sh b/tools/tgs_scripts/PreCompile.sh index ae8cba7a45..eea5f0958c 100755 --- a/tools/tgs_scripts/PreCompile.sh +++ b/tools/tgs_scripts/PreCompile.sh @@ -32,7 +32,28 @@ env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --ignore-rust-version --re mv target/i686-unknown-linux-gnu/release/librust_g.so "$1/librust_g.so" cd .. +# +cd "$original_dir" +# update dreamluau +if [ ! -d "dreamluau" ]; then + echo "Cloning dreamluau..." + git clone https://github.com/tgstation/dreamluau + cd dreamluau + ~/.cargo/bin/rustup target add i686-unknown-linux-gnu +else + echo "Fetching dreamlaua..." + cd dreamluau + git fetch + ~/.cargo/bin/rustup target add i686-unknown-linux-gnu +fi + +echo "Deploying Dreamlaua..." +git checkout "$DREAMLUAU_VERSION" +env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --ignore-rust-version --release --target=i686-unknown-linux-gnu +mv target/i686-unknown-linux-gnu/release/libdreamluau.so "$1/libdreamluau.so" +cd .. + # compile tgui echo "Compiling tgui..." cd "$1" -env TG_BOOTSTRAP_CACHE="$original_dir" TG_BOOTSTRAP_NODE_LINUX=1 CBT_BUILD_MODE="TGS" tools/bootstrap/node tools/build/build.js +env TG_BOOTSTRAP_CACHE="$original_dir" CBT_BUILD_MODE="TGS" tools/bootstrap/javascript.sh tools/build/build.js diff --git a/tools/tgs_test/Program.cs b/tools/tgs_test/Program.cs index 943da0c098..8bc77e9e8e 100644 --- a/tools/tgs_test/Program.cs +++ b/tools/tgs_test/Program.cs @@ -1,4 +1,4 @@ -// Simple app meant to test chompstation's TGS integration given a fresh TGS install with the default account +// Simple app meant to test tgstation's TGS integration given a fresh TGS install with the default account // // Args: Repository Owner/Name, TGS instance path, TGS API port, Pushed commit hash (For .tgs.yml access), GitHub Token, (OPTIONAL) PR Number @@ -176,7 +176,7 @@ try new InstanceCreateRequest { ConfigurationType = ConfigurationType.HostWrite, - Name = "chompstation", + Name = "tgstation", Path = instancePath }, default); diff --git a/tools/tgs_test/README.md b/tools/tgs_test/README.md index 0f2b54458f..436f21d894 100644 --- a/tools/tgs_test/README.md +++ b/tools/tgs_test/README.md @@ -7,5 +7,5 @@ This is a simple app that does a few things - Connects to a TGS instance via command line parameters. - Uses the .tgs.yml information to automatically set up a TGS instance. - Runs a TGS deploy/launch and validates that they succeeded. - - Look for its invocation in the GitHub workflows + +Look for its invocation in the GitHub workflows diff --git a/vorestation.dme b/vorestation.dme index 13c5db7181..7f1863b4e5 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -4528,6 +4528,8 @@ #include "code\modules\tgchat\message.dm" #include "code\modules\tgchat\to_chat.dm" #include "code\modules\tgs\includes.dm" +#include "code\modules\tgs_commands\chompstation.dm" +#include "code\modules\tgs_commands\vorestation.dm" #include "code\modules\tgui\external.dm" #include "code\modules\tgui\modal.dm" #include "code\modules\tgui\states.dm"