Merge remote-tracking branch 'upstream/master'

This commit is contained in:
keronshb
2021-09-11 14:16:14 -04:00
111 changed files with 7566 additions and 4644 deletions
+42 -20
View File
@@ -13,11 +13,20 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Setup cache
- name: Restore SpacemanDMM cache
uses: actions/cache@v2
with:
path: $HOME/SpacemanDMM
key: ${{ runner.os }}-spacemandmm
path: ~/SpacemanDMM
key: ${{ runner.os }}-spacemandmm-${{ secrets.CACHE_PURGE_KEY }}
- name: Restore Yarn cache
uses: actions/cache@v2
with:
path: tgui/.yarn/cache
key: ${{ runner.os }}-yarn-${{ secrets.CACHE_PURGE_KEY }}-${{ hashFiles('tgui/yarn.lock') }}
restore-keys: |
${{ runner.os }}-build-${{ secrets.CACHE_PURGE_KEY }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install Tools
run: |
pip3 install setuptools
@@ -28,11 +37,10 @@ jobs:
run: |
bash tools/ci/check_filedirs.sh tgstation.dme
bash tools/ci/check_changelogs.sh
bash tools/ci/check_grep.sh
find . -name "*.php" -print0 | xargs -0 -n1 php -l
find . -name "*.json" -not -path "*/node_modules/*" -print0 | xargs -0 python3 ./tools/json_verifier.py
tgui/bin/tgui --lint
tgui/bin/tgui --test
bash tools/ci/check_grep.sh
tools/build/build --ci lint
tools/bootstrap/python -m dmi.test
tools/bootstrap/python -m mapmerge2.dmm_test
~/dreamchecker > ${GITHUB_WORKSPACE}/output-annotations.txt 2>&1
@@ -48,19 +56,16 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Setup cache
- name: Restore BYOND cache
uses: actions/cache@v2
with:
path: $HOME/BYOND
key: ${{ runner.os }}-byond
path: ~/BYOND
key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }}
- name: Compile All Maps
run: |
bash tools/ci/install_byond.sh
source $HOME/BYOND/byond/bin/byondsetup
python3 tools/ci/template_dm_generator.py
tools/build/build
env:
CBT_BUILD_MODE : ALL_MAPS
tools/build/build --ci dm -DCIBUILDING -DCITESTING -DALL_MAPS
run_all_tests:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
@@ -76,11 +81,20 @@ jobs:
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v2
- name: Setup cache
- name: Restore BYOND cache
uses: actions/cache@v2
with:
path: $HOME/BYOND
key: ${{ runner.os }}-byond
path: ~/BYOND
key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }}
- name: Restore Yarn cache
uses: actions/cache@v2
with:
path: tgui/.yarn/cache
key: ${{ runner.os }}-yarn-${{ secrets.CACHE_PURGE_KEY }}-${{ hashFiles('tgui/yarn.lock') }}
restore-keys: |
${{ runner.os }}-build-${{ secrets.CACHE_PURGE_KEY }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Setup database
run: |
sudo systemctl start mysql
@@ -88,7 +102,7 @@ jobs:
mysql -u root -proot tg_ci < SQL/tgstation_schema.sql
mysql -u root -proot -e 'CREATE DATABASE tg_ci_prefixed;'
mysql -u root -proot tg_ci_prefixed < SQL/tgstation_schema_prefixed.sql
- name: Install rust dependencies
- name: Install rust-g
run: |
sudo dpkg --add-architecture i386
sudo apt update || true
@@ -96,15 +110,14 @@ jobs:
bash tools/ci/install_rust_g.sh
- name: Install auxmos
run: |
sudo apt update || true
bash tools/ci/install_auxmos.sh
- name: Compile and run tests
run: |
bash tools/ci/install_byond.sh
source $HOME/BYOND/byond/bin/byondsetup
tools/build/build -DCIBUILDING
tools/build/build --ci -DCIBUILDING
bash tools/ci/run_server.sh
env:
CBT_BUILD_MODE: TEST_RUN
test_windows:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
@@ -112,6 +125,15 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Restore Yarn cache
uses: actions/cache@v2
with:
path: tgui/.yarn/cache
key: ${{ runner.os }}-yarn-${{ secrets.CACHE_PURGE_KEY }}-${{ hashFiles('tgui/yarn.lock') }}
restore-keys: |
${{ runner.os }}-build-${{ secrets.CACHE_PURGE_KEY }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Compile
run: pwsh tools/ci/build.ps1
env:
+2 -2
View File
@@ -12,8 +12,8 @@ jobs:
- name: Setup cache
uses: actions/cache@v2
with:
path: $HOME/SpacemanDMM
key: ${{ runner.os }}-spacemandmm
path: ~/SpacemanDMM
key: ${{ runner.os }}-spacemandmm-${{ secrets.CACHE_PURGE_KEY }}
- name: Install SpacemanDMM
run: bash tools/ci/install_spaceman_dmm.sh dmdoc
- name: Generate documentation
+17 -59
View File
@@ -7,14 +7,16 @@
#Ignore byond config folder.
/cfg/**/*
#Ignore rust-g and auxmos libraries which are compiled with scripts
*.so
/tools/build/binaries/**/*
# Ignore compiled linux libs in the root folder, e.g. librust_g.so
/*.so
#Ignore compiled files and other files generated during compilation.
*.mdme
*.mdme.*
*.dmb
*.rsc
*.m.dme
*.test.dme
*.lk
*.int
*.backup
@@ -53,27 +55,6 @@ __pycache__/
*.py[cod]
*$py.class
# C extensions
#*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
@@ -81,8 +62,7 @@ var/
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-*.txt
# Unit test / coverage reports
htmlcov/
@@ -92,7 +72,6 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
@@ -101,10 +80,6 @@ coverage.xml
# Django stuff:
*.log
local_settings.py
# Flask instance folder
instance/
# Scrapy stuff:
.scrapy
@@ -112,9 +87,6 @@ instance/
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
@@ -127,10 +99,6 @@ celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# IntelliJ IDEA / PyCharm (with plugin)
.idea
@@ -153,12 +121,6 @@ Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
#*.cab
#*.msi
#*.msm
#*.msp
# Windows shortcuts
*.lnk
@@ -199,11 +161,10 @@ Temporary Items
#Visual studio stuff
*.vscode/*
!/.vscode/extensions.json
tools/MapAtmosFixer/MapAtmosFixer/obj/*
tools/MapAtmosFixer/MapAtmosFixer/bin/*
tools/CreditsTool/bin/*
tools/CreditsTool/obj/*
/tools/MapAtmosFixer/MapAtmosFixer/obj/*
/tools/MapAtmosFixer/MapAtmosFixer/bin/*
/tools/CreditsTool/bin/*
/tools/CreditsTool/obj/*
#GitHub Atom
.atom-build.json
@@ -228,13 +189,10 @@ tools/CreditsTool/obj/*
!/config/title_screens/images/exclude
#Linux docker
tools/LinuxOneShot/SetupProgram/obj/*
tools/LinuxOneShot/SetupProgram/bin/*
tools/LinuxOneShot/SetupProgram/.vs
tools/LinuxOneShot/Database
tools/LinuxOneShot/TGS_Config
tools/LinuxOneShot/TGS_Instances
tools/LinuxOneShot/TGS_Logs
# Common build tooling
!/tools/build
/tools/LinuxOneShot/SetupProgram/obj/*
/tools/LinuxOneShot/SetupProgram/bin/*
/tools/LinuxOneShot/SetupProgram/.vs
/tools/LinuxOneShot/Database
/tools/LinuxOneShot/TGS_Config
/tools/LinuxOneShot/TGS_Instances
/tools/LinuxOneShot/TGS_Logs
+3 -2
View File
@@ -1,2 +1,3 @@
@call tools\build\build
@pause
@echo off
call "%~dp0\tools\build\build.bat" %*
pause
+3
View File
@@ -0,0 +1,3 @@
@echo off
call "%~dp0\tools\build\build.bat" dist-clean
pause
+3
View File
@@ -0,0 +1,3 @@
@echo off
call "%~dp0\tools\build\build.bat" server %*
pause
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -437,8 +437,9 @@
#define COMPONENT_BLOCK_SHARPEN_BLOCKED 2
#define COMPONENT_BLOCK_SHARPEN_ALREADY 4
#define COMPONENT_BLOCK_SHARPEN_MAXED 8
#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act" //called on item when microwaved (): (obj/machinery/microwave/M)
#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act" //called on item when microwaved (): (obj/machinery/microwave/M)
#define COMSIG_ITEM_WORN_OVERLAYS "item_worn_overlays" //from base of obj/item/worn_overlays(): (isinhands, icon_file, used_state, style_flags, list/overlays)
#define COMSIG_ARMOR_PLATED "armor_plated" //called when an armor plate is successfully applied to an object
// THE FOLLOWING TWO BLOCKS SHOULD RETURN BLOCK FLAGS AS DEFINED IN __DEFINES/combat.dm!
#define COMSIG_ITEM_CHECK_BLOCK "check_block" //from base of obj/item/check_block(): (mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
#define COMSIG_ITEM_RUN_BLOCK "run_block" //from base of obj/item/run_block(): (mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
+28 -14
View File
@@ -1,30 +1,44 @@
//defines that give qdel hints. these can be given as a return in destory() or by calling
//! Defines that give qdel hints.
//!
//! These can be given as a return in [/atom/proc/Destroy] or by calling [/proc/qdel].
/// `qdel` should queue the object for deletion.
#define QDEL_HINT_QUEUE 0
/// `qdel` should let the object live after calling [/atom/proc/Destroy].
#define QDEL_HINT_LETMELIVE 1
/// Functionally the same as the above. `qdel` should assume the object will gc on its own, and not check it.
#define QDEL_HINT_IWILLGC 2
/// Qdel should assume this object won't GC, and queue a hard delete using a hard reference.
#define QDEL_HINT_HARDDEL 3
// Qdel should assume this object won't gc, and hard delete it posthaste.
#define QDEL_HINT_HARDDEL_NOW 4
#define QDEL_HINT_QUEUE 0 //qdel should queue the object for deletion.
#define QDEL_HINT_LETMELIVE 1 //qdel should let the object live after calling destory.
#define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
#define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference.
#define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste.
#ifdef LEGACY_REFERENCE_TRACKING
/** If LEGACY_REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb.
*
* Functionally identical to QDEL_HINT_QUEUE if GC_FAILURE_HARD_LOOKUP is not enabled in _compiler_options.dm.
#ifdef REFERENCE_TRACKING
/** If REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb.
*
* Functionally identical to [QDEL_HINT_QUEUE] if [GC_FAILURE_HARD_LOOKUP] is not enabled in _compiler_options.dm.
*/
#define QDEL_HINT_FINDREFERENCE 5
/// Behavior as QDEL_HINT_FINDREFERENCE, but only if the GC fails and a hard delete is forced.
#define QDEL_HINT_FINDREFERENCE 5
/// Behavior as [QDEL_HINT_FINDREFERENCE], but only if the GC fails and a hard delete is forced.
#define QDEL_HINT_IFFAIL_FINDREFERENCE 6
#endif
#define GC_QUEUE_CHECK 1
#define GC_QUEUE_HARDDELETE 2
#define GC_QUEUE_COUNT 2 //increase this when adding more steps.
#define QDEL_ITEM_ADMINS_WARNED (1<<0) //! Set when admins are told about lag causing qdels in this type.
#define QDEL_ITEM_SUSPENDED_FOR_LAG (1<<1) //! Set when a type can no longer be hard deleted on failure because of lag it causes while this happens.
// Defines for the [gc_destroyed][/datum/var/gc_destroyed] var.
#define GC_QUEUED_FOR_QUEUING -1
#define GC_CURRENTLY_BEING_QDELETED -2
// Defines for the time left for an item to get its reference cleaned
#define GC_FILTER_QUEUE 5 MINUTES
#define GC_DEL_QUEUE 10 SECONDS
#define QDELING(X) (X.gc_destroyed)
#define QDELETED(X) (!X || QDELING(X))
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+13 -2
View File
@@ -38,6 +38,9 @@
#define RUST_G (__rust_g || __detect_rust_g())
#endif
/// Gets the version of rust_g
/proc/rustg_get_version() return call(RUST_G, "get_version")()
/**
* This proc generates a cellular automata noise grid which can be used in procedural generation methods.
*
@@ -77,8 +80,8 @@
#define RUSTG_HTTP_METHOD_PATCH "patch"
#define RUSTG_HTTP_METHOD_HEAD "head"
#define RUSTG_HTTP_METHOD_POST "post"
#define rustg_http_request_blocking(method, url, body, headers) call(RUST_G, "http_request_blocking")(method, url, body, headers)
#define rustg_http_request_async(method, url, body, headers) call(RUST_G, "http_request_async")(method, url, body, headers)
#define rustg_http_request_blocking(method, url, body, headers, options) call(RUST_G, "http_request_blocking")(method, url, body, headers, options)
#define rustg_http_request_async(method, url, body, headers, options) call(RUST_G, "http_request_async")(method, url, body, headers, options)
#define rustg_http_check_request(req_id) call(RUST_G, "http_check_request")(req_id)
#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET"
@@ -99,3 +102,11 @@
#define rustg_sql_disconnect_pool(handle) call(RUST_G, "sql_disconnect_pool")(handle)
#define rustg_sql_check_query(job_id) call(RUST_G, "sql_check_query")("[job_id]")
#define rustg_url_encode(text) call(RUST_G, "url_encode")(text)
#define rustg_url_decode(text) call(RUST_G, "url_decode")(text)
#ifdef RUSTG_OVERRIDE_BUILTINS
#define url_encode(text) rustg_url_encode(text)
#define url_decode(text) rustg_url_decode(text)
#endif
+3
View File
@@ -0,0 +1,3 @@
// RUSTG_OVERRIDE_BUILTINS is not used since the file APIs don't work well over Linux.
#define url_encode(text) rustg_url_encode("[text]")
#define url_decode(text) rustg_url_decode("[text]")
+6
View File
@@ -49,10 +49,16 @@ When using time2text(), please use "DDD" to find the weekday. Refrain from using
#define TICKS *world.tick_lag
#define MILLISECONDS * 0.01
#define DS2TICKS(DS) ((DS)/world.tick_lag)
#define TICKS2DS(T) ((T) TICKS)
#define MS2DS(T) ((T) MILLISECONDS)
#define DS2MS(T) ((T) * 100)
#define GAMETIMESTAMP(format, wtime) time2text(wtime, format)
#define WORLDTIME2TEXT(format) GAMETIMESTAMP(format, world.time)
#define WORLDTIMEOFDAY2TEXT(format) GAMETIMESTAMP(format, world.timeofday)
+4
View File
@@ -343,3 +343,7 @@
#define STICKY_NODROP "sticky-nodrop" //sticky nodrop sounds like a bad soundcloud rapper's name
#define TRAIT_SACRIFICED "sacrificed" //Makes sure that people cant be cult sacrificed twice.
#define TRAIT_SPACEWALK "spacewalk"
/// obtained from mapping helper
#define MAPPING_HELPER_TRAIT "mapping-helper"
+10
View File
@@ -1,9 +1,12 @@
//wrapper macros for easier grepping
#define DIRECT_OUTPUT(A, B) A << B
#define DIRECT_INPUT(A, B) A >> B
#define SEND_IMAGE(target, image) DIRECT_OUTPUT(target, image)
#define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound)
#define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text)
#define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text)
#define READ_FILE(file, text) DIRECT_INPUT(file, text)
#ifdef EXTOOLS_LOGGING
// proc hooked, so we can just put in standard TRUE and FALSE
#define WRITE_LOG(log, text) extools_log_write(log,text,TRUE)
@@ -13,6 +16,7 @@
#define WRITE_LOG(log, text) rustg_log_write(log, text, "true")
#define WRITE_LOG_NO_FORMAT(log, text) rustg_log_write(log, text, "false")
#endif
//print a warning message to world.log
#define WARNING(MSG) warning("[MSG] in [__FILE__] at line [__LINE__] src: [UNLINT(src)] usr: [usr].")
/proc/warning(msg)
@@ -38,6 +42,12 @@
SEND_TEXT(world.log, text)
#endif
#ifdef REFERENCE_TRACKING_LOG
#define log_reftracker(msg) log_world("## REF SEARCH [msg]")
#else
#define log_reftracker(msg)
#endif
/* Items with ADMINPRIVATE prefixed are stripped from public logs. */
/proc/log_admin(text)
+13 -13
View File
@@ -218,20 +218,14 @@
to_chat(world, "<BR><BR><BR><span class='big bold'>The round has ended.</span>")
log_game("The round has ended.")
if(LAZYLEN(GLOB.round_end_notifiees))
world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE)
CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed
for(var/I in round_end_events)
var/datum/callback/cb = I
cb.InvokeAsync()
LAZYCLEARLIST(round_end_events)
for(var/client/C in GLOB.clients)
if(!C.credits)
C.RollCredits()
C.playtitlemusic(40)
CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed
var/speed_round = FALSE
if(world.time - SSticker.round_start_time <= 300 SECONDS)
speed_round = TRUE
@@ -264,12 +258,15 @@
send2adminchat("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]")
if(LAZYLEN(GLOB.round_end_notifiees))
world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE)
if(length(CONFIG_GET(keyed_list/cross_server)))
send_news_report()
//tell the nice people on discord what went on before the salt cannon happens.
world.TgsTargetedChatBroadcast("The current round has ended. Please standby for your shift interlude Nanotrasen News Network's report!", FALSE)
world.TgsTargetedChatBroadcast(send_news_report(),FALSE)
world.TgsTargetedChatBroadcast(send_news_report(), FALSE)
CHECK_TICK
@@ -543,21 +540,24 @@
///Generate a report for how much money is on station, as well as the richest crewmember on the station.
/datum/controller/subsystem/ticker/proc/market_report()
var/list/parts = list()
parts += "<span class='header'>Station Economic Summary:</span>"
///This is the richest account on station at roundend.
var/datum/bank_account/mr_moneybags
///This is the station's total wealth at the end of the round.
var/station_vault = 0
///How many players joined the round.
var/total_players = GLOB.joined_player_list.len
var/list/typecache_bank = typecacheof(list(/datum/bank_account/department, /datum/bank_account/remote))
for(var/datum/bank_account/current_acc in SSeconomy.generated_accounts)
var/static/list/typecache_bank = typecacheof(list(/datum/bank_account/department, /datum/bank_account/remote))
for(var/i in SSeconomy.generated_accounts)
var/datum/bank_account/current_acc = SSeconomy.generated_accounts[i]
if(typecache_bank[current_acc.type])
continue
station_vault += current_acc.account_balance
if(!mr_moneybags || mr_moneybags.account_balance < current_acc.account_balance)
mr_moneybags = current_acc
parts += "<div class='panel stationborder'>There were [station_vault] credits collected by crew this shift.<br>"
parts += "<div class='panel stationborder'><span class='header'>Station Economic Summary:</span><br>"
parts += "<b>General Statistics:</b><br>"
parts += "There were [station_vault] credits collected by crew this shift.<br>"
if(total_players > 0)
parts += "An average of [station_vault/total_players] credits were collected.<br>"
// log_econ("Roundend credit total: [station_vault] credits. Average Credits: [station_vault/total_players]")
+47 -23
View File
@@ -1,7 +1,7 @@
//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this
//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this
//uncommented, but not visible in the release version)
//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed.
//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed.
// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error
#ifdef DEBUG
@@ -11,34 +11,44 @@
#ifdef TESTING
#define DATUMVAR_DEBUGGING_MODE
/*
* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete.
*
* * Requires TESTING to be defined to work.
*/
///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while
//#define REFERENCE_TRACKING
#ifdef REFERENCE_TRACKING
///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools.
//#define LEGACY_REFERENCE_TRACKING
#ifdef LEGACY_REFERENCE_TRACKING
///alternate to reftracking, extool variant
//#define EXTOOLS_REFERENCE_TRACKING
///Use the legacy reference on things hard deleting by default.
///Should we be logging our findings or not
#define REFERENCE_TRACKING_LOG
///Used for doing dry runs of the reference finder, to test for feature completeness
//#define REFERENCE_TRACKING_DEBUG
///Run a lookup on things hard deleting by default.
//#define GC_FAILURE_HARD_LOOKUP
#ifdef GC_FAILURE_HARD_LOOKUP
#define FIND_REF_NO_CHECK_TICK
#endif //ifdef GC_FAILURE_HARD_LOOKUP
#endif //ifdef LEGACY_REFERENCE_TRACKING
#endif //ifdef REFERENCE_TRACKING
//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
/*
* Enables debug messages for every single reaction step. This is 1 message per 0.5s for a SINGLE reaction. Useful for tracking down bugs/asking me for help in the main reaction handiler (equilibrium.dm).
*
* * Requires TESTING to be defined to work.
*/
//#define REAGENTS_TESTING
// #define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
// #define TRACK_MAX_SHARE //Allows max share tracking, for use in the atmos debugging ui
#endif //ifdef TESTING
//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER
#ifndef PRELOAD_RSC //set to:
#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour;
#endif // 1 to use the default behaviour;
// 2 for preloading absolutely everything;
//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between
#ifndef PRELOAD_RSC //set to:
#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour;
#endif // 1 to use the default behaviour;
// 2 for preloading absolutely everything;
#ifdef LOWMEMORYMODE
#define FORCE_MAP "_maps/runtimestation.json"
@@ -47,7 +57,7 @@
//Update this whenever you need to take advantage of more recent byond features
#define MIN_COMPILER_VERSION 513
#define MIN_COMPILER_BUILD 1514
#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD
#if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM)
//Don't forget to update this part
#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update.
#error You need version 513.1514 or higher
@@ -58,10 +68,6 @@
#warn compiling in TESTING mode. testing() debug messages will be visible.
#endif
#ifdef GC_FAILURE_HARD_LOOKUP
#define FIND_REF_NO_CHECK_TICK
#endif
#ifdef CIBUILDING
#define UNIT_TESTS
#endif
@@ -70,6 +76,24 @@
#define TESTING
#endif
#if defined(UNIT_TESTS)
//Hard del testing defines
#define REFERENCE_TRACKING
#define REFERENCE_TRACKING_DEBUG
#define FIND_REF_NO_CHECK_TICK
#endif
#ifdef TGS
// TGS performs its own build of dm.exe, but includes a prepended TGS define.
#define CBT
#endif
// A reasonable number of maximum overlays an object needs
// If you think you need more, rethink it
#define MAX_ATOM_OVERLAYS 100
#if !defined(CBT) && !defined(SPACEMAN_DMM)
#warn Building with Dream Maker is no longer supported and will result in errors.
#warn In order to build, run BUILD.bat in the root directory.
#warn Consider switching to VSCode editor instead, where you can press Ctrl+Shift+B to build.
#endif
+1 -1
View File
@@ -43,7 +43,7 @@
INVOKE_ASYNC(aicamera, /obj/item/camera.proc/captureimage, A, usr)
return
var/obj/item/W = get_active_held_item()
var/obj/item/W = get_active_held_item(TRUE)
if(!W && A.Adjacent(src) && (isobj(A) || ismob(A)))
var/atom/movable/C = A
+39 -1
View File
@@ -1,14 +1,25 @@
// Clickable stat() button.
/obj/effect/statclick
name = "Initializing..."
// blocks_emissive = NONE
var/target
INITIALIZE_IMMEDIATE(/obj/effect/statclick)
/obj/effect/statclick/Initialize(mapload, text, target) //Don't port this to Initialize it's too critical
/obj/effect/statclick/Initialize(mapload, text, target)
. = ..()
name = text
src.target = target
if(istype(target, /datum)) //Harddel man bad
RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/cleanup)
/obj/effect/statclick/Destroy()
target = null
return ..()
/obj/effect/statclick/proc/cleanup()
SIGNAL_HANDLER
qdel(src)
/obj/effect/statclick/proc/update(text)
name = text
@@ -51,3 +62,30 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller")
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
/client/proc/debug_controller()
set category = "Debug"
set name = "Debug Controller"
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
if(!holder)
return
var/list/controllers = list()
var/list/controller_choices = list()
for (var/datum/controller/controller in world)
if (istype(controller, /datum/controller/subsystem))
continue
controllers["[controller] (controller.type)"] = controller //we use an associated list to ensure clients can't hold references to controllers
controller_choices += "[controller] (controller.type)"
var/datum/controller/controller_string = input("Select controller to debug", "Debug Controller") as null|anything in controller_choices
var/datum/controller/controller = controllers[controller_string]
if (!istype(controller))
return
debug_variables(controller)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller")
message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
@@ -39,7 +39,9 @@
/datum/config_entry/flag/announce_admin_login
/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
/datum/config_entry/string/centcom_source_whitelist
/datum/config_entry/flag/autoadmin // if autoadmin is enabled
protection = CONFIG_ENTRY_LOCKED
@@ -333,3 +333,12 @@
config_entry_value = 0.333
min_val = 0
integer = FALSE
/datum/config_entry/number/hard_deletes_overrun_threshold
integer = FALSE
min_val = 0
default = 0.5
/datum/config_entry/number/hard_deletes_overrun_limit
default = 0
min_val = 0
+95 -14
View File
@@ -15,7 +15,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
// The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC.
var/defcon = 5
//the world.time of the last check, so the mc can restart US if we hang.
// (Real friends look out for *eachother*)
// (Real friends look out for *eachother*)
var/lasttick = 0
// Track the MC iteration to make sure its still on track.
@@ -33,6 +33,22 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/datum/controller/failsafe/Initialize()
set waitfor = FALSE
Failsafe.Loop()
if (!Master || defcon == 0) //Master is gone/not responding and Failsafe just exited its loop
defcon = 3 //Reset defcon level as its used inside the emergency loop
while (defcon > 0)
var/recovery_result = emergency_loop()
if (recovery_result == 1) //Exit emergency loop and delete self if it was able to recover MC
break
else if (defcon == 1) //Exit Failsafe if we weren't able to recover the MC in the last stage
log_game("FailSafe: Failed to recover MC while in emergency state. Failsafe exiting.")
message_admins(span_boldannounce("Failsafe failed criticaly while trying to recreate broken MC. Please manually fix the MC or reboot the server. Failsafe exiting now."))
message_admins(span_boldannounce("You can try manually calling these two procs:."))
message_admins(span_boldannounce("/proc/recover_all_SS_and_recreate_master: Most stuff should still function but expect instability/runtimes/broken stuff."))
message_admins(span_boldannounce("/proc/delete_all_SS_and_recreate_master: Most stuff will be broken but basic stuff like movement and chat should still work."))
else if (recovery_result == -1) //Failed to recreate MC
defcon--
sleep(initial(processing_interval)) //Wait a bit until the next try
if(!QDELETED(src))
qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us
@@ -45,42 +61,56 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
while(running)
lasttick = world.time
if(!Master)
// Replace the missing Master! This should never, ever happen.
new /datum/controller/master()
// Break out of the main loop so we go into emergency state
break
// Only poke it if overrides are not in effect.
if(processing_interval > 0)
if(Master.processing && Master.iteration)
if (defcon > 1 && (!Master.stack_end_detector || !Master.stack_end_detector.check()))
to_chat(GLOB.admins, span_boldannounce("ERROR: The Master Controller code stack has exited unexpectedly, Restarting..."))
defcon = 0
var/rtn = Recreate_MC()
if(rtn > 0)
master_iteration = 0
to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying."))
// Check if processing is done yet.
if(Master.iteration == master_iteration)
switch(defcon)
if(4,5)
--defcon
if(3)
message_admins("<span class='adminnotice'>Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.</span>")
--defcon
if(2)
to_chat(GLOB.admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.</span>")
--defcon
if(1)
to_chat(GLOB.admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...</span>")
if(3)
message_admins(span_adminnotice("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks."))
--defcon
if(2)
to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks."))
--defcon
if(1)
to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting..."))
--defcon
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, "<span class='adminnotice'>MC restarted successfully</span>")
to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
to_chat(GLOB.admins, "<span class='boldannounce'>ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.</span>")
to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying."))
//if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again
//no need to handle that specially when defcon 0 can handle it
if(0) //DEFCON 0! (mc failed to restart)
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, "<span class='adminnotice'>MC restarted successfully</span>")
to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
else
defcon = min(defcon + 1,5)
master_iteration = Master.iteration
@@ -92,6 +122,57 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
defcon = 5
sleep(initial(processing_interval))
//Emergency loop used when Master got deleted or the main loop exited while Defcon == 0
//Loop is driven externally so runtimes only cancel the current recovery attempt
/datum/controller/failsafe/proc/emergency_loop()
//The code in this proc should be kept as simple as possible, anything complicated like to_chat might rely on master existing and runtime
//The goal should always be to get a new Master up and running before anything else
. = -1
switch (defcon) //The lower defcon goes the harder we try to fix the MC
if (2 to 3) //Try to normally recreate the MC two times
. = Recreate_MC()
if (1) //Delete the old MC first so we don't transfer any info, in case that caused any issues
del(Master)
. = Recreate_MC()
if (. == 1) //We were able to create a new master
master_iteration = 0
SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
to_chat(GLOB.admins, span_adminnotice("Failsafe recovered MC while in emergency state [defcon_pretty()]"))
else
log_game("FailSafe: Failsafe in emergency state and was unable to recreate MC while in defcon state [defcon_pretty()].")
message_admins(span_boldannounce("Failsafe in emergency state and master down, trying to recreate MC while in defcon level [defcon_pretty()] failed."))
///Recreate all SSs which will still cause data survive due to Recover(), the new Master will then find and take them from global.vars
/proc/recover_all_SS_and_recreate_master()
del(Master)
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
sortTim(subsytem_types, /proc/cmp_subsystem_init)
for(var/I in subsytem_types)
new I
. = Recreate_MC()
if (. == 1) //We were able to create a new master
SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after recovering all subsystems!"))
else
message_admins(span_boldannounce("Failed to create new MC!"))
///Delete all existing SS to basically start over
/proc/delete_all_SS_and_recreate_master()
del(Master)
for(var/global_var in global.vars)
if (istype(global.vars[global_var], /datum/controller/subsystem))
del(global.vars[global_var])
. = Recreate_MC()
if (. == 1) //We were able to create a new master
SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after deleting and recreating all subsystems!"))
else
message_admins(span_boldannounce("Failed to create new MC!"))
/datum/controller/failsafe/proc/defcon_pretty()
return defcon
+1 -1
View File
@@ -14,7 +14,7 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
var/datum/controller/exclude_these = new
gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
QDEL_IN(exclude_these, 0) //signal logging isn't ready
QDEL_IN(exclude_these, 0) //signal logging isn't ready
log_world("[vars.len - gvars_datum_in_built_vars.len] global variables")
+59 -57
View File
@@ -18,15 +18,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master
name = "Master"
// Are we processing (higher values increase the processing delay by n ticks)
/// Are we processing (higher values increase the processing delay by n ticks)
var/processing = TRUE
// How many times have we ran
/// How many times have we ran
var/iteration = 0
/// Stack end detector to detect stack overflows that kill the mc's main loop
var/datum/stack_end_detector/stack_end_detector
// world.time of last fire, for tracking lag outside of the mc
/// world.time of last fire, for tracking lag outside of the mc
var/last_run
// List of subsystems to process().
/// List of subsystems to process().
var/list/subsystems
// Vars for keeping track of tick drift.
@@ -34,25 +36,27 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/init_time
var/tickdrift = 0
/// How long is the MC sleeping between runs, read only (set by Loop() based off of anti-tick-contention heuristics)
var/sleep_delta = 1
///Only run ticker subsystems for the next n ticks.
/// Only run ticker subsystems for the next n ticks.
var/skip_ticks = 0
/// makes the mc main loop runtime
var/make_runtime = FALSE
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
// The type of the last subsystem to be process()'d.
/// The type of the last subsystem to be fire()'d.
var/last_type_processed
var/datum/controller/subsystem/queue_head //Start of queue linked list
var/datum/controller/subsystem/queue_tail //End of queue linked list (used for appending to the list)
var/datum/controller/subsystem/queue_head //!Start of queue linked list
var/datum/controller/subsystem/queue_tail //!End of queue linked list (used for appending to the list)
var/queue_priority_count = 0 //Running total so that we don't have to loop thru the queue each run to split up the tick
var/queue_priority_count_bg = 0 //Same, but for background subsystems
var/map_loading = FALSE //Are we loading in a new map?
var/map_loading = FALSE //!Are we loading in a new map?
var/current_runlevel //for scheduling different subsystems for different stages of the round
var/current_runlevel //!for scheduling different subsystems for different stages of the round
var/sleep_offline_after_initializations = TRUE
var/static/restart_clear = 0
@@ -61,8 +65,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/static/random_seed
//current tick limit, assigned before running a subsystem.
//used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits
///current tick limit, assigned before running a subsystem.
///used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits
var/static/current_ticklimit = TICK_LIMIT_RUNNING
/datum/controller/master/New()
@@ -81,15 +85,27 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/list/_subsystems = list()
subsystems = _subsystems
if (Master != src)
if (istype(Master))
if (istype(Master)) //If there is an existing MC take over his stuff and delete it
Recover()
qdel(Master)
Master = src
else
//Code used for first master on game boot or if existing master got deleted
Master = src
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
sortTim(subsytem_types, /proc/cmp_subsystem_init)
//Find any abandoned subsystem from the previous master (if there was any)
var/list/existing_subsystems = list()
for(var/global_var in global.vars)
if (istype(global.vars[global_var], /datum/controller/subsystem))
existing_subsystems += global.vars[global_var]
//Either init a new SS or if an existing one was found use that
for(var/I in subsytem_types)
_subsystems += new I
Master = src
var/datum/controller/subsystem/existing_subsystem = locate(I) in existing_subsystems
if (istype(existing_subsystem))
_subsystems += existing_subsystem
else
_subsystems += new I
if(!GLOB)
new /datum/controller/global_vars
@@ -109,7 +125,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
log_world("Shutdown complete")
// Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart,
// -1 if we encountered a runtime trying to recreate it
// -1 if we encountered a runtime trying to recreate it
/proc/Recreate_MC()
. = -1 //so if we runtime, things know we failed
if (world.time < Master.restart_timeout)
@@ -120,7 +136,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/delay = 50 * ++Master.restart_count
Master.restart_timeout = world.time + delay
Master.restart_clear = world.time + (delay * 2)
Master.processing = FALSE //stop ticking this one
if (Master) //Can only do this if master hasn't been deleted
Master.processing = FALSE //stop ticking this one
try
new/datum/controller/master()
catch
@@ -156,22 +173,22 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined."
BadBoy.flags |= SS_NO_FIRE
if(msg)
to_chat(GLOB.admins, "<span class='boldannounce'>[msg]</span>")
to_chat(GLOB.admins, span_boldannounce("[msg]"))
log_world(msg)
if (istype(Master.subsystems))
if(FireHim)
Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one
Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one
subsystems = Master.subsystems
current_runlevel = Master.current_runlevel
StartProcessing(10)
else
to_chat(world, "<span class='boldannounce'>The Master Controller is having some issues, we will need to re-initialize EVERYTHING</span>")
to_chat(world, span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING"))
Initialize(20, TRUE)
// Please don't stuff random bullshit here,
// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
/datum/controller/master/Initialize(delay, init_sss, tgs_prime)
set waitfor = 0
@@ -181,7 +198,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems)
to_chat(world, "<span class='boldannounce'>Initializing subsystems...</span>")
to_chat(world, span_boldannounce("Initializing subsystems..."))
// Sort subsystems by init_order, so they initialize in the correct order.
sortTim(subsystems, /proc/cmp_subsystem_init)
@@ -190,7 +207,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Initialize subsystems.
current_ticklimit = CONFIG_GET(number/tick_limit_mc_init)
for (var/datum/controller/subsystem/SS in subsystems)
if (SS.flags & SS_NO_INIT)
if (SS.flags & SS_NO_INIT || SS.initialized) //Don't init SSs with the correspondig flag or if they already are initialzized
continue
SS.Initialize(REALTIMEOFDAY)
CHECK_TICK
@@ -198,7 +215,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!"
to_chat(world, "<span class='boldannounce'>[msg]</span>")
to_chat(world, span_boldannounce("[msg]"))
log_world(msg)
if (!current_runlevel)
@@ -255,11 +272,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master/proc/Loop()
. = -1
//Prep the loop (most of this is because we want MC restarts to reset as much state as we can, and because
// local vars rock
// local vars rock
//all this shit is here so that flag edits can be refreshed by restarting the MC. (and for speed)
var/list/tickersubsystems = list()
var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
var/timer = world.time
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
@@ -305,8 +322,12 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/error_level = 0
var/sleep_delta = 1
var/list/subsystems_to_check
//the actual loop.
//setup the stack overflow detector
stack_end_detector = new()
var/datum/stack_canary/canary = stack_end_detector.prime_canary()
canary.use_variable()
//the actual loop.
while (1)
tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag)))
var/starting_tick_usage = TICK_USAGE
@@ -317,7 +338,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
//Anti-tick-contention heuristics:
//if there are mutiple sleeping procs running before us hogging the cpu, we have to run later.
// (because sleeps are processed in the order received, longer sleeps are more likely to run first)
// (because sleeps are processed in the order received, longer sleeps are more likely to run first)
if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit.
sleep_delta *= 2
current_ticklimit = TICK_LIMIT_RUNNING * 0.5
@@ -423,6 +444,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
continue
if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time)
continue
if (SS.postponed_fires >= 1)
SS.postponed_fires--
SS.update_nextfire()
continue
SS.enqueue()
. = 1
@@ -439,12 +464,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/tick_precentage
var/tick_remaining
var/ran = TRUE //this is right
var/ran_non_ticker = FALSE
var/bg_calc //have we swtiched current_tick_budget to background mode yet?
var/tick_usage
//keep running while we have stuff to run and we haven't gone over a tick
// this is so subsystems paused eariler can use tick time that later subsystems never used
// this is so subsystems paused eariler can use tick time that later subsystems never used
while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC)
ran = FALSE
bg_calc = FALSE
@@ -459,20 +483,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if (!(queue_node_flags & SS_TICKER) && skip_ticks)
queue_node = queue_node.queue_next
continue
//super special case, subsystems where we can't make them pause mid way through
//if we can't run them this tick (without going over a tick)
//we bump up their priority and attempt to run them next tick
//(unless we haven't even ran anything this tick, since its unlikely they will ever be able run
// in those cases, so we just let them run)
if (queue_node_flags & SS_NO_TICK_CHECK)
if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker)
if (!(queue_node_flags & SS_BACKGROUND))
queue_node.queued_priority += queue_priority_count * 0.1
queue_priority_count -= queue_node_priority
queue_priority_count += queue_node.queued_priority
current_tick_budget -= queue_node_priority
queue_node = queue_node.queue_next
continue
if (!bg_calc && (queue_node_flags & SS_BACKGROUND))
current_tick_budget = queue_priority_count_bg
@@ -481,7 +491,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
tick_remaining = TICK_LIMIT_RUNNING - TICK_USAGE
if (current_tick_budget > 0 && queue_node_priority > 0)
tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority)
//Give the subsystem a precentage of the remaining tick based on the remaning priority
tick_precentage = tick_remaining * (queue_node_priority / current_tick_budget)
else
tick_precentage = tick_remaining
@@ -489,8 +500,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
current_ticklimit = round(TICK_USAGE + tick_precentage)
if (!(queue_node_flags & SS_TICKER))
ran_non_ticker = TRUE
ran = TRUE
queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING)
@@ -535,14 +544,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node.last_fire = world.time
queue_node.times_fired++
if (queue_node_flags & SS_TICKER)
queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait)
else if (queue_node_flags & SS_POST_FIRE_TIMING)
queue_node.next_fire = world.time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100))
else if (queue_node_flags & SS_KEEP_TIMING)
queue_node.next_fire += queue_node.wait
else
queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100))
queue_node.update_nextfire()
queue_node.queued_time = 0
@@ -554,7 +556,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
. = 1
//resets the queue, and all subsystems, while filtering out the subsystem lists
// called if any mc's queue procs runtime or exit improperly.
// called if any mc's queue procs runtime or exit improperly.
/datum/controller/master/proc/SoftReset(list/ticker_SS, list/runlevel_SS)
. = 0
log_world("MC: SoftReset called, resetting MC queue state.")
+43 -14
View File
@@ -29,11 +29,11 @@
var/initialized = FALSE
/// Set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
/// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
/// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
var/can_fire = TRUE
///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
/*
* The following variables are managed by the MC and should not be modified directly.
@@ -69,6 +69,9 @@
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
/// How many fires have we been requested to postpone
var/postponed_fires = 0
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
@@ -122,12 +125,38 @@
dequeue()
can_fire = 0
flags |= SS_NO_FIRE
Master.subsystems -= src
if (Master)
Master.subsystems -= src
return ..()
/** Update next_fire for the next run.
* reset_time (bool) - Ignore things that would normally alter the next fire, like tick_overrun, and last_fire. (also resets postpone)
*/
/datum/controller/subsystem/proc/update_nextfire(reset_time = FALSE)
var/queue_node_flags = flags
if (reset_time)
postponed_fires = 0
if (queue_node_flags & SS_TICKER)
next_fire = world.time + (world.tick_lag * wait)
else
next_fire = world.time + wait
return
if (queue_node_flags & SS_TICKER)
next_fire = world.time + (world.tick_lag * wait)
else if (queue_node_flags & SS_POST_FIRE_TIMING)
next_fire = world.time + wait + (world.tick_lag * (tick_overrun/100))
else if (queue_node_flags & SS_KEEP_TIMING)
next_fire += wait
else
next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100))
//Queue it to run.
// (we loop thru a linked list until we get to the end or find the right point)
// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
// (we loop thru a linked list until we get to the end or find the right point)
// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
/datum/controller/subsystem/proc/enqueue()
var/SS_priority = priority
var/SS_flags = flags
@@ -191,9 +220,9 @@
queue_next.queue_prev = queue_prev
if (queue_prev)
queue_prev.queue_next = queue_next
if (src == Master.queue_tail)
if (Master && (src == Master.queue_tail))
Master.queue_tail = queue_prev
if (src == Master.queue_head)
if (Master && (src == Master.queue_head))
Master.queue_head = queue_next
queued_time = 0
if (state == SS_QUEUED)
@@ -217,10 +246,11 @@
//used to initialize the subsystem AFTER the map has loaded
/datum/controller/subsystem/Initialize(start_timeofday)
initialized = TRUE
// SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday)
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!"
to_chat(world, "<span class='boldannounce'>[msg]</span>")
log_subsystem("INIT", msg)
to_chat(world, span_boldannounce("[msg]"))
log_subsystem(msg)
return time
/datum/controller/subsystem/stat_entry(msg)
@@ -243,11 +273,10 @@
if (SS_IDLE)
. = " "
//could be used to postpone a costly subsystem for (default one) var/cycles, cycles
//for instance, during cpu intensive operations like explosions
/// Causes the next "cycle" fires to be missed. Effect is accumulative but can reset by calling update_nextfire(reset_time = TRUE)
/datum/controller/subsystem/proc/postpone(cycles = 1)
if(next_fire - world.time < wait)
next_fire += (wait*cycles)
if (can_fire && cycles >= 1)
postponed_fires += cycles
//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
@@ -258,7 +287,7 @@
if (NAMEOF(src, can_fire))
//this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
if (var_value)
next_fire = world.time + wait
update_nextfire(reset_time = TRUE)
if (NAMEOF(src, queued_priority)) //editing this breaks things.
return FALSE
. = ..()
+97 -100
View File
@@ -29,37 +29,35 @@ SUBSYSTEM_DEF(garbage)
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
init_order = INIT_ORDER_GARBAGE
var/list/collection_timeout = list(2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level
//Stat tracking
var/delslasttick = 0 // number of del()'s we've done this tick
var/gcedlasttick = 0 // number of things that gc'ed last tick
var/delslasttick = 0 // number of del()'s we've done this tick
var/gcedlasttick = 0 // number of things that gc'ed last tick
var/totaldels = 0
var/totalgcs = 0
var/highest_del_time = 0
var/highest_del_tickusage = 0
var/highest_del_ms = 0
var/highest_del_type_string = ""
var/list/pass_counts
var/list/fail_counts
var/list/items = list() // Holds our qdel_item statistics datums
var/list/items = list() // Holds our qdel_item statistics datums
//Queue
var/list/queues
#ifdef LEGACY_REFERENCE_TRACKING
#ifdef REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
#ifdef REFERENCE_TRACKING_DEBUG
//Should we save found refs. Used for unit testing
var/should_save_refs = FALSE
#endif
#endif
/datum/controller/subsystem/garbage/PreInit()
queues = new(GC_QUEUE_COUNT)
pass_counts = new(GC_QUEUE_COUNT)
fail_counts = new(GC_QUEUE_COUNT)
for(var/i in 1 to GC_QUEUE_COUNT)
queues[i] = list()
pass_counts[i] = 0
fail_counts[i] = 0
InitQueues()
/datum/controller/subsystem/garbage/stat_entry(msg)
var/list/counts = list()
@@ -90,13 +88,18 @@ SUBSYSTEM_DEF(garbage)
for(var/path in items)
var/datum/qdel_item/I = items[path]
dellog += "Path: [path]"
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
dellog += "\tSUSPENDED FOR LAG"
if (I.failures)
dellog += "\tFailures: [I.failures]"
dellog += "\tqdel() Count: [I.qdels]"
dellog += "\tDestroy() Cost: [I.destroy_time]ms"
if (I.hard_deletes)
dellog += "\tTotal Hard Deletes [I.hard_deletes]"
dellog += "\tTotal Hard Deletes: [I.hard_deletes]"
dellog += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms"
dellog += "\tHighest Time Spent Hard Deleting: [I.hard_delete_max]ms"
if (I.hard_deletes_over_threshold)
dellog += "\tHard Deletes Over Threshold: [I.hard_deletes_over_threshold]"
if (I.slept_destroy)
dellog += "\tSleeps: [I.slept_destroy]"
if (I.no_respect_force)
@@ -122,6 +125,15 @@ SUBSYSTEM_DEF(garbage)
/datum/controller/subsystem/garbage/proc/InitQueues()
if (isnull(queues)) // Only init the queues if they don't already exist, prevents overriding of recovered lists
queues = new(GC_QUEUE_COUNT)
pass_counts = new(GC_QUEUE_COUNT)
fail_counts = new(GC_QUEUE_COUNT)
for(var/i in 1 to GC_QUEUE_COUNT)
queues[i] = list()
pass_counts[i] = 0
fail_counts[i] = 0
/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
if (level == GC_QUEUE_CHECK)
@@ -153,7 +165,6 @@ SUBSYSTEM_DEF(garbage)
if(GCd_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
var/refID = L[2]
var/datum/D
D = locate(refID)
@@ -162,8 +173,8 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
#ifdef LEGACY_REFERENCE_TRACKING
reference_find_on_fail -= refID //It's deleted we don't care anymore.
#ifdef REFERENCE_TRACKING
reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
@@ -171,35 +182,43 @@ SUBSYSTEM_DEF(garbage)
// Something's still referring to the qdel'd object.
fail_counts[level]++
#ifdef REFERENCE_TRACKING
var/ref_searching = FALSE
#endif
switch (level)
if (GC_QUEUE_CHECK)
#ifdef REFERENCE_TRACKING
D.find_references()
#elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
D.find_references_legacy()
INVOKE_ASYNC(D, /datum/proc/find_references)
ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else
D.find_references_legacy()
INVOKE_ASYNC(D, /datum/proc/find_references)
ref_searching = TRUE
#endif
reference_find_on_fail -= refID
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
#ifdef TESTING
log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
#ifdef TESTING
for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
var/client/admin = c
if(!check_rights_for(admin, R_ADMIN))
continue
to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
#endif
#ifdef REFERENCE_TRACKING
GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only.
continue
#endif
I.failures++
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
#ifdef REFERENCE_TRACKING
if(ref_searching)
return //ref searching intentionally cancels all further fires while running so things that hold references don't end up getting deleted, so we want to return here instead of continue
#endif
continue
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
if (MC_TICK_CHECK)
@@ -208,27 +227,17 @@ SUBSYSTEM_DEF(garbage)
Queue(D, level+1)
#ifdef REFERENCE_TRACKING
if(ref_searching)
return
#endif
if (MC_TICK_CHECK)
return
if (count)
queue.Cut(1,count+1)
count = 0
#ifdef LEGACY_REFERENCE_TRACKING
/datum/controller/subsystem/garbage/proc/add_type_to_findref(type)
if(!ispath(type))
return "NOT A VAILD PATH"
reference_find_on_fail_types |= typecacheof(type)
/datum/controller/subsystem/garbage/proc/remove_type_from_findref(type)
if(!ispath(type))
return "NOT A VALID PATH"
reference_find_on_fail_types -= typesof(type)
/datum/controller/subsystem/garbage/proc/clear_findref_types()
reference_find_on_fail_types = list()
#endif
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
if (isnull(D))
return
@@ -238,63 +247,66 @@ SUBSYSTEM_DEF(garbage)
var/gctime = world.time
var/refid = "\ref[D]"
#ifdef LEGACY_REFERENCE_TRACKING
if(reference_find_on_fail_types[D.type])
SSgarbage.reference_find_on_fail[REF(D)] = TRUE
#endif
D.gc_destroyed = gctime
var/list/queue = queues[level]
queue[++queue.len] = list(gctime, refid) // not += for byond reasons
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
var/time = world.timeofday
var/tick = TICK_USAGE
var/ticktime = world.time
++delslasttick
++totaldels
var/type = D.type
var/refID = "\ref[D]"
var/tick_usage = TICK_USAGE
del(D)
tick = (TICK_USAGE-tick+((world.time-ticktime)/world.tick_lag*100))
tick_usage = TICK_USAGE_TO_MS(tick_usage)
var/datum/qdel_item/I = items[type]
I.hard_deletes++
I.hard_delete_time += TICK_DELTA_TO_MS(tick)
I.hard_delete_time += tick_usage
if (tick_usage > I.hard_delete_max)
I.hard_delete_max = tick_usage
if (tick_usage > highest_del_ms)
highest_del_ms = tick_usage
highest_del_type_string = "[type]"
var/time = MS2DS(tick_usage)
if (tick > highest_del_tickusage)
highest_del_tickusage = tick
time = world.timeofday - time
if (!time && TICK_DELTA_TO_MS(tick) > 1)
time = TICK_DELTA_TO_MS(tick)/100
if (time > highest_del_time)
highest_del_time = time
if (time > 10)
log_game("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete)")
message_admins("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete).")
if (time > 0.1 SECONDS)
postpone(time)
var/threshold = CONFIG_GET(number/hard_deletes_overrun_threshold)
if (threshold && (time > threshold SECONDS))
if (!(I.qdel_flags & QDEL_ITEM_ADMINS_WARNED))
log_game("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete)")
message_admins("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete).")
I.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
I.hard_deletes_over_threshold++
var/overrun_limit = CONFIG_GET(number/hard_deletes_overrun_limit)
if (overrun_limit && I.hard_deletes_over_threshold >= overrun_limit)
I.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG
/datum/controller/subsystem/garbage/Recover()
InitQueues() //We first need to create the queues before recovering data
if (istype(SSgarbage.queues))
for (var/i in 1 to SSgarbage.queues.len)
queues[i] |= SSgarbage.queues[i]
/// Qdel Item: Holds statistics on each type that passes thru qdel
/datum/qdel_item
var/name = ""
var/qdels = 0 //Total number of times it's passed thru qdel.
var/destroy_time = 0 //Total amount of milliseconds spent processing this type's Destroy()
var/failures = 0 //Times it was queued for soft deletion but failed to soft delete.
var/hard_deletes = 0 //Different from failures because it also includes QDEL_HINT_HARDDEL deletions
var/hard_delete_time = 0//Total amount of milliseconds spent hard deleting this type.
var/no_respect_force = 0//Number of times it's not respected force=TRUE
var/no_hint = 0 //Number of times it's not even bother to give a qdel hint
var/slept_destroy = 0 //Number of times it's slept in its destroy
var/name = "" //!Holds the type as a string for this type
var/qdels = 0 //!Total number of times it's passed thru qdel.
var/destroy_time = 0 //!Total amount of milliseconds spent processing this type's Destroy()
var/failures = 0 //!Times it was queued for soft deletion but failed to soft delete.
var/hard_deletes = 0 //!Different from failures because it also includes QDEL_HINT_HARDDEL deletions
var/hard_delete_time = 0 //!Total amount of milliseconds spent hard deleting this type.
var/hard_delete_max = 0 //!Highest time spent hard_deleting this in ms.
var/hard_deletes_over_threshold = 0 //!Number of times hard deletes took longer than the configured threshold
var/no_respect_force = 0 //!Number of times it's not respected force=TRUE
var/no_hint = 0 //!Number of times it's not even bother to give a qdel hint
var/slept_destroy = 0 //!Number of times it's slept in its destroy
var/qdel_flags = 0 //!Flags related to this type's trip thru qdel.
/datum/qdel_item/New(mytype)
name = "[mytype]"
@@ -307,12 +319,12 @@ SUBSYSTEM_DEF(garbage)
if(!istype(D))
del(D)
return
var/datum/qdel_item/I = SSgarbage.items[D.type]
if (!I)
I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
I.qdels++
if(isnull(D.gc_destroyed))
if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
return
@@ -328,12 +340,12 @@ SUBSYSTEM_DEF(garbage)
if(!D)
return
switch(hint)
if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
SSgarbage.Queue(D)
if (QDEL_HINT_IWILLGC)
D.gc_destroyed = world.time
return
if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
if(!force)
D.gc_destroyed = null //clear the gc variable (important!)
return
@@ -350,17 +362,17 @@ SUBSYSTEM_DEF(garbage)
I.no_respect_force++
SSgarbage.Queue(D)
if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
#ifdef LEGACY_REFERENCE_TRACKING
if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
#ifdef REFERENCE_TRACKING
if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(D)
D.find_references_legacy()
if (QDEL_HINT_IFFAIL_FINDREFERENCE)
D.find_references()
if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
SSgarbage.Queue(D)
SSgarbage.reference_find_on_fail[REF(D)] = TRUE
SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
#endif
else
#ifdef TESTING
@@ -371,18 +383,3 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
#ifdef TESTING
/proc/writeDatumCount()
var/list/datums = list()
for(var/datum/D in world)
datums[D.type] += 1
for(var/datum/D)
datums[D.type] += 1
datums = sortTim(datums, /proc/cmp_numeric_dsc, associative = TRUE)
if(fexists("data/DATUMCOUNT.txt"))
fdel("data/DATUMCOUNT.txt")
var/outfile = file("data/DATUMCOUNT.txt")
for(var/path in datums)
outfile << "[datums[path]]\t\t\t\t\t[path]"
#endif
+8 -2
View File
@@ -366,10 +366,16 @@ SUBSYSTEM_DEF(research)
techweb_categories[I.category] = list(I.id = TRUE)
/datum/controller/subsystem/research/proc/techweb_node_by_id(id)
return techweb_nodes[id] || error_node
if(techweb_nodes[id])
return techweb_nodes[id]
stack_trace("Attempted to access node ID [id] which didn't exist")
return error_node
/datum/controller/subsystem/research/proc/techweb_design_by_id(id)
return techweb_designs[id] || error_design
if(techweb_designs[id])
return techweb_designs[id]
stack_trace("Attempted to access design ID [id] which didn't exist")
return error_design
/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D)
for(var/i in techweb_nodes)
+1
View File
@@ -71,6 +71,7 @@
R.update_icon()
to_chat(user, "<span class='info'>You strengthen [R], improving its resistance against melee, bullet and laser damage.</span>")
else
SEND_SIGNAL(O, COMSIG_ARMOR_PLATED, amount, maxamount)
to_chat(user, "<span class='info'>You strengthen [O], improving its resistance against melee attacks.</span>")
+5 -1
View File
@@ -57,9 +57,13 @@
*/
var/list/cooldowns
#ifdef TESTING
#ifdef REFERENCE_TRACKING
var/running_find_references
var/last_find_references = 0
#ifdef REFERENCE_TRACKING_DEBUG
///Stores info about where refs are found, used for sanity checks and testing
var/list/found_refs
#endif
#endif
#ifdef DATUMVAR_DEBUGGING_MODE
@@ -0,0 +1,32 @@
/**
Stack End Detector.
Can detect if a given code stack has exited, used by the mc for stack overflow detection.
**/
/datum/stack_end_detector
var/datum/weakref/_WF
var/datum/stack_canary/_canary
/datum/stack_end_detector/New()
_canary = new()
_WF = WEAKREF(_canary)
/** Prime the stack overflow detector.
Store the return value of this proc call in a proc level var.
Can only be called once.
**/
/datum/stack_end_detector/proc/prime_canary()
if (!_canary)
CRASH("Prime_canary called twice")
. = _canary
_canary = null
/// Returns true if the stack is still going. Calling before the canary has been primed also returns true
/datum/stack_end_detector/proc/check()
return !!_WF.resolve()
/// Stack canary. Will go away if the stack it was primed by is ended by byond for return or stack overflow reasons.
/datum/stack_canary
/// empty proc to avoid warnings about unused variables. Call this proc on your canary in the stack it's watching.
/datum/stack_canary/proc/use_variable()
+11 -3
View File
@@ -6,10 +6,12 @@
var/body
var/headers
var/url
/// If present response body will be saved to this file.
var/output_file
var/_raw_response
/datum/http_request/proc/prepare(method, url, body = "", list/headers)
/datum/http_request/proc/prepare(method, url, body = "", list/headers, output_file)
if (!length(headers))
headers = ""
else
@@ -19,15 +21,16 @@
src.url = url
src.body = body
src.headers = headers
src.output_file = output_file
/datum/http_request/proc/execute_blocking()
_raw_response = rustg_http_request_blocking(method, url, body, headers)
_raw_response = rustg_http_request_blocking(method, url, body, headers, build_options())
/datum/http_request/proc/begin_async()
if (in_progress)
CRASH("Attempted to re-use a request object.")
id = rustg_http_request_async(method, url, body, headers)
id = rustg_http_request_async(method, url, body, headers, build_options())
if (isnull(text2num(id)))
stack_trace("Proc error: [id]")
@@ -35,6 +38,11 @@
else
in_progress = TRUE
/datum/http_request/proc/build_options()
if(output_file)
return json_encode(list("output_filename"=output_file,"body_filename"=null))
return null
/datum/http_request/proc/is_complete()
if (isnull(id))
return TRUE
+6
View File
@@ -568,6 +568,12 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
if(usr.incapacitated() || !Adjacent(usr) || usr.lying)
return
if(iscyborg(usr))
var/obj/item/gripper/gripper = usr.get_active_held_item(TRUE)
if(istype(gripper))
gripper.pre_attack(src, usr, get_dist(src, usr))
return
if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota
usr.UnarmedAttack(src)
+109 -25
View File
@@ -745,9 +745,9 @@
Grippers oh god oh fuck
***********************************************************************/
/obj/item/weapon/gripper
/obj/item/gripper
name = "engineering gripper"
desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more. Alt click to drop instead of use."
desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more."
icon = 'icons/obj/device.dmi'
icon_state = "gripper"
@@ -776,18 +776,29 @@
var/obj/item/wrapped = null // Item currently being held.
//Used to interact with UI's of held items, such as gas tanks and airlock electronics.
/obj/item/weapon/gripper/AltClick(mob/user)
// Used to drop whatever's in the gripper.
/obj/item/gripper/proc/drop_held(silent = FALSE)
if(wrapped)
wrapped.forceMove(get_turf(wrapped))
to_chat(user, "<span class='notice'>You drop the [wrapped].</span>")
if(!silent)
to_chat(usr, "<span class='notice'>You drop the [wrapped].</span>")
modify_appearance(wrapped, FALSE)
wrapped = null
return ..()
update_appearance()
return TRUE
return FALSE
/obj/item/weapon/gripper/pre_attack(var/atom/target, var/mob/living/silicon/robot/user, proximity, params)
/obj/item/gripper/proc/takeitem(obj/item/item, silent = FALSE)
if(!silent)
to_chat(usr, "<span class='notice'>You collect \the [item].</span>")
item.loc = src
wrapped = item
update_appearance()
if(!proximity)
return
/obj/item/gripper/pre_attack(atom/target, mob/living/silicon/robot/user, params)
var/proximity = get_dist(user, target)
if(proximity > 1)
return STOP_ATTACK_PROC_CHAIN
if(!wrapped)
for(var/obj/item/thing in src.contents)
@@ -795,21 +806,24 @@
break
if(wrapped) //Already have an item.
var/obj/item/item = wrapped
drop_held(TRUE)
//Temporary put wrapped into user so target's attackby() checks pass.
wrapped.loc = user
item.loc = user
//Pass the attack on to the target. This might delete/relocate wrapped.
var/resolved = target.attackby(wrapped,user)
if(!resolved && wrapped && target)
wrapped.afterattack(target,user,1)
var/resolved = target.attackby(item, user, params)
if(!resolved && item && target)
item.afterattack(target, user, proximity, params)
//If wrapped was neither deleted nor put into target, put it back into the gripper.
if(wrapped && user && (wrapped.loc == user))
wrapped.loc = src
else
wrapped = null
if(item && user && (item.loc == user))
takeitem(item, TRUE)
return
else
item = null
return STOP_ATTACK_PROC_CHAIN
else if(istype(target,/obj/item))
else if(isitem(target))
var/obj/item/I = target
var/grab = 0
@@ -824,24 +838,94 @@
//We can grab the item, finally.
if(grab)
to_chat(user, "<span class='notice'>You collect \the [I].</span>")
I.loc = src
wrapped = I
takeitem(I)
return
else
to_chat(user, "<span class='danger'>Your gripper cannot hold \the [target].</span>")
/obj/item/weapon/gripper/mining
// Rare cases - meant to be handled by code\modules\mob\living\silicon\robot\robot.dm:584 and the weirdness of get_active_held_item() of borgs.
/obj/item/gripper/attack_self(mob/user)
if(wrapped)
wrapped.attack_self(user)
return
. = ..()
// Splitable items
/obj/item/gripper/AltClick(mob/user)
if(wrapped)
wrapped.AltClick(user)
return
. = ..()
// Even rarer cases
/obj/item/gripper/CtrlClick(mob/user)
if(wrapped)
wrapped.CtrlClick(user)
return
. = ..()
// At this point you're just kidding me, but have this one as well.
/obj/item/gripper/CtrlShiftClick(mob/user)
if(wrapped)
wrapped.CtrlShiftClick(user)
return
. = ..()
// Make it clear what we can do with it.
/obj/item/gripper/examine(mob/user)
. = ..()
if(wrapped)
. += "<span class='notice'>It is holding [icon2html(wrapped, user)] [wrapped].</span>"
. += "<span class='notice'>Examine the little preview to examine it.</span>"
. += "<span class='notice'>Attempting to drop the gripper will only drop [wrapped].</span>"
// Resets vis_contents and if holding something, add it to vis_contents.
/obj/item/gripper/update_appearance(updates)
. = ..()
vis_contents = list()
if(wrapped)
modify_appearance(wrapped, TRUE)
vis_contents += wrapped
// Generates the "minified" version of the item being held and adjust it's position.
/obj/item/gripper/proc/modify_appearance(obj/item, minify = FALSE)
if(minify)
var/matrix/new_transform = new
new_transform.Scale(0.5, 0.5)
item.transform = new_transform
item.pixel_x = 8
item.pixel_y = -8
else
item.pixel_x = initial(pixel_x)
item.pixel_y = initial(pixel_y)
item.transform = new
// I kind of wanted the item to be held in the gripper when stored as well, but i realized "store" is just drop as well, so i'll do this for now.
// This will handle cases where the borg runs out of power or is damaged enough so the module is forcefully stored.
/obj/item/gripper/cyborg_unequip(mob/user)
. = ..()
if(wrapped)
drop_held()
// Clear references on being destroyed
/obj/item/Destroy()
for(var/obj/item/gripper/gripper in vis_locs)
if(gripper.wrapped == src)
gripper.wrapped = null
gripper.update_appearance()
. = ..()
/obj/item/gripper/mining
name = "shelter capsule deployer"
desc = "A simple grasping tool for carrying and deploying shelter capsules. Alt click to drop instead of use."
desc = "A simple grasping tool for carrying and deploying shelter capsules."
icon_state = "gripper_mining"
can_hold = list(
/obj/item/survivalcapsule
)
/obj/item/weapon/gripper/medical
/obj/item/gripper/medical
name = "medical gripper"
desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more. Alt click to drop instead of use."
desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more."
icon_state = "gripper_medical"
can_hold = list(
/obj/item/storage/bag/bio,
+1 -1
View File
@@ -16,7 +16,7 @@ GLOBAL_LIST(topic_status_cache)
call(debug_server, "auxtools_init")()
enable_debugging()
AUXTOOLS_CHECK(AUXMOS)
#ifdef REFERENCE_TRACKING
#ifdef EXTOOLS_REFERENCE_TRACKING
enable_reference_tracking()
#endif
world.Profile(PROFILE_START)
+1 -1
View File
@@ -195,7 +195,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
// #endif
/datum/admins/proc/create_or_modify_area,
/datum/admins/proc/fixcorruption,
#ifdef REFERENCE_TRACKING
#ifdef EXTOOLS_REFERENCE_TRACKING
/datum/admins/proc/view_refs,
/datum/admins/proc/view_del_failures,
#endif
+13 -2
View File
@@ -2862,7 +2862,7 @@
return
if(!CONFIG_GET(string/centcom_ban_db))
to_chat(usr, "<span class='warning'>Centcom Galactic Ban DB is disabled!</span>")
to_chat(usr, span_warning("Centcom Galactic Ban DB is disabled!"))
return
var/ckey = href_list["centcomlookup"]
@@ -2889,8 +2889,19 @@
dat += "<center><b>0 bans detected for [ckey]</b></center>"
else
bans = json_decode(response["body"])
dat += "<center><b>[bans.len] ban\s detected for [ckey]</b></center>"
//Ignore bans from non-whitelisted sources, if a whitelist exists
var/list/valid_sources
if(CONFIG_GET(string/centcom_source_whitelist))
valid_sources = splittext(CONFIG_GET(string/centcom_source_whitelist), ",")
dat += "<center><b>Bans detected for [ckey]</b></center>"
else
//Ban count is potentially inaccurate if they're using a whitelist
dat += "<center><b>[bans.len] ban\s detected for [ckey]</b></center>"
for(var/list/ban in bans)
if(valid_sources && !(ban["sourceName"] in valid_sources))
continue
dat += "<b>Server: </b> [sanitize(ban["sourceName"])]<br>"
dat += "<b>RP Level: </b> [sanitize(ban["sourceRoleplayLevel"])]<br>"
dat += "<b>Type: </b> [sanitize(ban["type"])]<br>"
@@ -1,4 +1,4 @@
#ifdef REFERENCE_TRACKING
#ifdef EXTOOLS_REFERENCE_TRACKING
GLOBAL_LIST_EMPTY(deletion_failures)
@@ -102,29 +102,21 @@ GLOBAL_LIST_EMPTY(deletion_failures)
#endif
#ifdef LEGACY_REFERENCE_TRACKING
#ifdef REFERENCE_TRACKING
/datum/verb/legacy_find_refs()
set category = "Debug"
set name = "Find References"
set src in world
find_references_legacy(FALSE)
/datum/proc/find_references_legacy(skip_alert)
/datum/proc/find_references(skip_alert)
running_find_references = type
if(usr?.client)
if(usr.client.running_find_references)
testing("CANCELLED search for references to a [usr.client.running_find_references].")
log_reftracker("CANCELLED search for references to a [usr.client.running_find_references].")
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = TRUE
SSgarbage.next_fire = world.time + world.tick_lag
SSgarbage.update_nextfire(reset_time = TRUE)
return
if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes")
if(!skip_alert && tgui_alert(usr,"Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", list("Yes", "No")) != "Yes")
running_find_references = null
return
@@ -134,92 +126,122 @@ GLOBAL_LIST_EMPTY(deletion_failures)
if(usr?.client)
usr.client.running_find_references = type
testing("Beginning search for references to a [type].")
last_find_references = world.time
log_reftracker("Beginning search for references to a [type].")
var/starting_time = world.time
//Time to search the whole game for our ref
DoSearchVar(GLOB, "GLOB") //globals
log_reftracker("Finished searching globals")
DoSearchVar(GLOB) //globals
for(var/datum/thing in world) //atoms (don't beleive its lies)
DoSearchVar(thing, "World -> [thing]")
DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time)
log_reftracker("Finished searching atoms")
for(var/datum/thing) //datums
DoSearchVar(thing, "World -> [thing]")
DoSearchVar(thing, "Datums -> [thing.type]", search_time = starting_time)
log_reftracker("Finished searching datums")
//Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself
for(var/client/thing) //clients
DoSearchVar(thing, "World -> [thing]")
DoSearchVar(thing, "Clients -> [thing.type]", search_time = starting_time)
log_reftracker("Finished searching clients")
log_reftracker("Completed search for references to a [type].")
testing("Completed search for references to a [type].")
if(usr?.client)
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = TRUE
SSgarbage.next_fire = world.time + world.tick_lag
SSgarbage.update_nextfire(reset_time = TRUE)
/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64, search_time = world.time)
#ifdef REFERENCE_TRACKING_DEBUG
if(!found_refs && SSgarbage.should_save_refs)
found_refs = list()
#endif
/datum/verb/qdel_then_find_references()
set category = "Debug"
set name = "qdel() then Find References"
set src in world
qdel(src, TRUE) //force a qdel
if(!running_find_references)
find_references_legacy(TRUE)
/datum/verb/qdel_then_if_fail_find_references()
set category = "Debug"
set name = "qdel() then Find References if GC failure"
set src in world
qdel_and_find_ref_if_fail(src, TRUE)
/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64)
if(usr?.client && !usr.client.running_find_references)
return
if(!recursive_limit)
log_reftracker("Recursion limit reached. [container_name]")
return
if(istype(potential_container, /datum))
var/datum/datum_container = potential_container
if(datum_container.last_find_references == last_find_references)
return
datum_container.last_find_references = last_find_references
var/list/vars_list = datum_container.vars
for(var/varname in vars_list)
if (varname == "vars")
continue
var/variable = vars_list[varname]
if(variable == src)
testing("Found [type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]")
else if(islist(variable))
DoSearchVar(variable, "[container_name] -> list", recursive_limit - 1)
else if(islist(potential_container))
var/normal = IS_NORMAL_LIST(potential_container)
for(var/element_in_list in potential_container)
if(element_in_list == src)
testing("Found [type] \ref[src] in list [container_name].")
else if(element_in_list && !isnum(element_in_list) && normal && potential_container[element_in_list] == src)
testing("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
else if(islist(element_in_list))
DoSearchVar(element_in_list, "[container_name] -> list", recursive_limit - 1)
//Check each time you go down a layer. This makes it a bit slow, but it won't effect the rest of the game at all
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
if(istype(potential_container, /datum))
var/datum/datum_container = potential_container
if(datum_container.last_find_references == search_time)
return
datum_container.last_find_references = search_time
var/list/vars_list = datum_container.vars
for(var/varname in vars_list)
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references
continue
var/variable = vars_list[varname]
if(variable == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[varname] = TRUE
#endif
log_reftracker("Found [type] \ref[src] in [datum_container.type]'s \ref[datum_container] [varname] var. [container_name]")
continue
if(islist(variable))
DoSearchVar(variable, "[container_name] \ref[datum_container] -> [varname] (list)", recursive_limit - 1, search_time)
else if(islist(potential_container))
var/normal = IS_NORMAL_LIST(potential_container)
var/list/potential_cache = potential_container
for(var/element_in_list in potential_cache)
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
//Check normal entrys
if(element_in_list == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[potential_cache] = TRUE
#endif
log_reftracker("Found [type] \ref[src] in list [container_name].")
continue
var/assoc_val = null
if(!isnum(element_in_list) && normal)
assoc_val = potential_cache[element_in_list]
//Check assoc entrys
if(assoc_val == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[potential_cache] = TRUE
#endif
log_reftracker("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
continue
//We need to run both of these checks, since our object could be hiding in either of them
//Check normal sublists
if(islist(element_in_list))
DoSearchVar(element_in_list, "[container_name] -> [element_in_list] (list)", recursive_limit - 1, search_time)
//Check assoc sublists
if(islist(assoc_val))
DoSearchVar(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", recursive_limit - 1, search_time)
/proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE)
SSgarbage.reference_find_on_fail[REF(thing_to_del)] = TRUE
qdel(thing_to_del, force)
thing_to_del.qdel_and_find_ref_if_fail(force)
/datum/proc/qdel_and_find_ref_if_fail(force = FALSE)
SSgarbage.reference_find_on_fail["\ref[src]"] = TRUE
qdel(src, force)
#endif
@@ -47,7 +47,7 @@
usr.client.debug_variables(src)
return
#ifdef REFERENCE_TRACKING
#ifdef EXTOOLS_REFERENCE_TRACKING
if(href_list[VV_HK_VIEW_REFERENCES])
var/datum/D = locate(href_list[VV_HK_TARGET])
if(!D)
@@ -253,7 +253,7 @@
purpose_fulfilled = TRUE
make_glow()
animate(glow, transform = matrix() * 1.5, alpha = 255, time = 125)
sound_to_playing_players(volume = 100, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/ratvar_rises.ogg')) //End the sounds
sound_to_playing_players('sound/effects/ratvar_rises.ogg', 90, FALSE, channel = CHANNEL_JUSTICAR_ARK)
sleep(125)
make_glow()
animate(glow, transform = matrix() * 3, alpha = 0, time = 5)
@@ -318,19 +318,19 @@
if(-INFINITY to GATEWAY_REEBE_FOUND)
if(!second_sound_played)
sound_to_playing_players('sound/magic/clockwork/invoke_general.ogg', 30, FALSE)
sound_to_playing_players(volume = 10, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
sound_to_playing_players('sound/effects/clockcult_gateway_charging.ogg', 10, FALSE, channel = CHANNEL_JUSTICAR_ARK)
second_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_charging"
if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING)
if(!third_sound_played)
sound_to_playing_players(volume = 30, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_active.ogg', TRUE))
sound_to_playing_players('sound/effects/clockcult_gateway_active.ogg', 30, FALSE, channel = CHANNEL_JUSTICAR_ARK)
third_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_active"
if(GATEWAY_RATVAR_COMING to GATEWAY_RATVAR_ARRIVAL)
if(!fourth_sound_played)
sound_to_playing_players(volume = 70, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_closing.ogg', TRUE))
sound_to_playing_players('sound/effects/clockcult_gateway_closing.ogg', 70, FALSE, channel = CHANNEL_JUSTICAR_ARK)
fourth_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_closing"
+10 -8
View File
@@ -3,21 +3,21 @@
/datum/asset/simple/tgui_common
keep_local_name = TRUE
assets = list(
"tgui-common.bundle.js" = 'tgui/public/tgui-common.bundle.js',
"tgui-common.bundle.js" = file("tgui/public/tgui-common.bundle.js"),
)
/datum/asset/simple/tgui
keep_local_name = TRUE
assets = list(
"tgui.bundle.js" = 'tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui/public/tgui.bundle.css',
"tgui.bundle.js" = file("tgui/public/tgui.bundle.js"),
"tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
)
/datum/asset/simple/tgui_panel
keep_local_name = TRUE
assets = list(
"tgui-panel.bundle.js" = 'tgui/public/tgui-panel.bundle.js',
"tgui-panel.bundle.css" = 'tgui/public/tgui-panel.bundle.css',
"tgui-panel.bundle.js" = file("tgui/public/tgui-panel.bundle.js"),
"tgui-panel.bundle.css" = file("tgui/public/tgui-panel.bundle.css"),
)
/datum/asset/simple/headers
@@ -168,10 +168,12 @@
/datum/asset/simple/namespaced/tgfont
assets = list(
"tgfont.eot" = 'tgui/packages/tgfont/dist/tgfont.eot',
"tgfont.woff2" = 'tgui/packages/tgfont/dist/tgfont.woff2',
"tgfont.eot" = file("tgui/packages/tgfont/dist/tgfont.eot"),
"tgfont.woff2" = file("tgui/packages/tgfont/dist/tgfont.woff2"),
)
parents = list(
"tgfont.css" = file("tgui/packages/tgfont/dist/tgfont.css"),
)
parents = list("tgfont.css" = 'tgui/packages/tgfont/dist/tgfont.css')
/datum/asset/spritesheet/chat
name = "chat"
@@ -237,12 +237,28 @@
/obj/item/clothing/head/helmet/space/hardsuit/mining/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
/obj/item/clothing/head/helmet/space/hardsuit/mining/proc/upgrade_icon(datum/source, amount, maxamount)
SIGNAL_HANDLER
if(amount)
name = "reinforced [initial(name)]"
hardsuit_type = "mining_goliath"
if(amount == maxamount)
hardsuit_type = "mining_goliath_full"
icon_state = "hardsuit[on]-[hardsuit_type]"
if(ishuman(loc))
var/mob/living/carbon/human/wearer = loc
if(wearer.head == src)
wearer.update_inv_head()
/obj/item/clothing/suit/space/hardsuit/mining
icon_state = "hardsuit-mining"
name = "mining hardsuit"
desc = "A special suit that protects against hazardous, low pressure environments. Has reinforced plating for wildlife encounters."
item_state = "mining_hardsuit"
hardsuit_type = "mining"
max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF
armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 75, "wound" = 15)
@@ -254,6 +270,21 @@
/obj/item/clothing/suit/space/hardsuit/mining/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
/obj/item/clothing/suit/space/hardsuit/mining/proc/upgrade_icon(datum/source, amount, maxamount)
SIGNAL_HANDLER
if(amount)
name = "reinforced [initial(name)]"
hardsuit_type = "mining_goliath"
if(amount == maxamount)
hardsuit_type = "mining_goliath_full"
icon_state = "hardsuit-[hardsuit_type]"
if(ishuman(loc))
var/mob/living/carbon/human/wearer = loc
if(wearer.wear_suit == src)
wearer.update_inv_wear_suit()
//Syndicate hardsuit
/obj/item/clothing/head/helmet/space/hardsuit/syndi
+2 -2
View File
@@ -152,8 +152,8 @@
/obj/item/clothing/suit/armor/riot
name = "riot suit"
desc = "A suit of semi-flexible polycarbonate body armor with heavy padding to protect against melee attacks. Helps the wearer resist shoving in close quarters."
icon_state = "swat"
item_state = "swat_suit"
icon_state = "riot"
item_state = "riot"
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
+3
View File
@@ -6,6 +6,7 @@
var/hoodtype = /obj/item/clothing/head/hooded/winterhood //so the chaplain hoodie or other hoodies can override this
///Alternative mode for hiding the hood, instead of storing the hood in the suit it qdels it, useful for when you deal with hooded suit with storage.
var/alternative_mode = FALSE
var/no_t //do not update sprites when pulling up hood so we can avoid oddities with certain mechanics
/obj/item/clothing/suit/hooded/Initialize()
. = ..()
@@ -51,6 +52,8 @@
update_icon()
/obj/item/clothing/suit/hooded/update_icon_state()
if(no_t)
return
icon_state = "[initial(icon_state)]"
if(ishuman(hood?.loc))
var/mob/living/carbon/human/H = hood.loc
@@ -8,8 +8,6 @@
#define CONE_WAFFLE 8
#define CONE_CHOC 9
/obj/machinery/icecream_vat
name = "ice cream vat"
desc = "Ding-aling ding dong. Get your Nanotrasen-approved ice cream!"
@@ -35,6 +33,8 @@
/datum/reagent/consumable/ethanol/singulo = 6,
/datum/reagent/consumable/peachjuice = 6,
/datum/reagent/consumable/grapejuice = 6)
var/custom_taste
var/custom_color
/obj/machinery/icecream_vat/proc/get_ingredient_list(type)
switch(type)
@@ -99,7 +99,10 @@
dat += "<b>Peach ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_PEACH]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_PEACH];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_PEACH];amount=5'><b>x5</b></a> [product_types[ICECREAM_PEACH]] dollops left. (Ingredients: milk, ice, peach juice)<br>"
dat += "<b>Grape ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_GRAPE]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_GRAPE];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_GRAPE];amount=5'><b>x5</b></a> [product_types[ICECREAM_GRAPE]] dollops left. (Ingredients: milk, ice, grape juice)<br>"
dat += "<b>Blue ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_BLUE]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_BLUE];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_BLUE];amount=5'><b>x5</b></a> [product_types[ICECREAM_BLUE]] dollops left. (Ingredients: milk, ice, singulo)<br>"
dat += "<b>Custom ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_CUSTOM]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_CUSTOM];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_CUSTOM];amount=5'><b>x5</b></a> [product_types[ICECREAM_CUSTOM]] dollops left. (Ingredients: milk, ice, optional flavoring)<br></div>"
dat += "<b>Custom ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_CUSTOM]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_CUSTOM];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_CUSTOM];amount=5'><b>x5</b></a> [product_types[ICECREAM_CUSTOM]] dollops left. (Ingredients: milk, ice, optional flavoring)<br>"
dat += "<a href='?src=[REF(src)];custom_taste=1;'><b>Change custom taste: [custom_taste ? custom_taste : "Default"]</b></a>"
dat += "<br><a href='?src=[REF(src)];custom_color=1;'><b>Change custom color: [custom_color ? custom_color : "Default"]</b></a>"
dat += "<br><a href='?src=[REF(src)];reset_custom=1;'><b>Reset custom ice cream taste and color to defaults</b></a></div>"
dat += "<br><b>CONES</b><br><div class='statusDisplay'>"
dat += "<b>Waffle cones:</b> <a href='?src=[REF(src)];cone=[CONE_WAFFLE]'><b>Dispense</b></a> <a href='?src=[REF(src)];make=[CONE_WAFFLE];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[CONE_WAFFLE];amount=5'><b>x5</b></a> [product_types[CONE_WAFFLE]] cones left. (Ingredients: flour, sugar)<br>"
dat += "<b>Chocolate cones:</b> <a href='?src=[REF(src)];cone=[CONE_CHOC]'><b>Dispense</b></a> <a href='?src=[REF(src)];make=[CONE_CHOC];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[CONE_CHOC];amount=5'><b>x5</b></a> [product_types[CONE_CHOC]] cones left. (Ingredients: flour, sugar, coco powder)<br></div>"
@@ -128,7 +131,7 @@
visible_message("[icon2html(src, viewers(src))] <span class='info'>[user] scoops delicious [flavour_name] ice cream into [I].</span>")
product_types[dispense_flavour] -= 1
if(beaker && beaker.reagents.total_volume)
I.add_ice_cream(flavour_name, beaker.reagents)
I.add_ice_cream(flavour_name, beaker.reagents, custom_color, custom_taste)
else
I.add_ice_cream(flavour_name)
if(I.reagents.total_volume < 10)
@@ -213,14 +216,25 @@
if(href_list["refill"])
RefillFromBeaker()
updateDialog()
if(href_list["refresh"])
updateDialog()
if(href_list["close"])
usr.unset_machine()
usr << browse(null,"window=icecreamvat")
if(href_list["custom_taste"])
custom_taste = stripped_input(usr, "Set a custom taste for the custom icecream. 50 characters max, leave blank to go back to the default option.", max_length = 50)
if(href_list["custom_color"])
custom_color = input(usr, "Choose a color for the custom icecream. Cancel to go back to the default option.") as color|null
if(href_list["reset_custom"])
custom_taste = null
custom_color = null
updateDialog() // i have no clue why we even have refresh when this is a thing but sure
return
/obj/item/reagent_containers/food/snacks/icecream
@@ -251,7 +265,7 @@
desc = "Delicious [cone_name] cone, but no ice cream."
/obj/item/reagent_containers/food/snacks/icecream/proc/add_ice_cream(flavour_name, datum/reagents/R)
/obj/item/reagent_containers/food/snacks/icecream/proc/add_ice_cream(flavour_name, datum/reagents/R, custom_color, custom_taste)
name = "[flavour_name] icecream"
switch (flavour_name) // adding the actual reagents advertised in the ingredient list
if ("vanilla")
@@ -286,8 +300,11 @@
if(R && R.total_volume >= 4) //consumable reagents have stronger taste so higher volume will allow non-food flavourings to break through better.
var/mutable_appearance/flavoring = mutable_appearance(icon,"icecream_custom")
var/datum/reagent/master = R.get_master_reagent()
flavoring.color = master.color
filling_color = master.color
flavoring.color = custom_color ? custom_color : master.color
filling_color = custom_color ? custom_color : master.color
if(custom_taste)
tastes = list("[custom_taste]" = 1)
reagents.force_alt_taste = TRUE
name = "[master.name] icecream"
desc = "A delicious [cone_type] cone filled with artisanal icecream. Made with real [master.name]. Ain't that something."
R.trans_to(src, 4)
+12
View File
@@ -641,6 +641,18 @@ Since Ramadan is an entire month that lasts 29.5 days on average, the start and
/datum/holiday/easter/getStationPrefix()
return pick("Fluffy","Bunny","Easter","Egg")
/datum/holiday/ianbirthday
name = "Ian's Birthday" //github.com/tgstation/tgstation/commit/de7e4f0de0d568cd6e1f0d7bcc3fd34700598acb
begin_month = SEPTEMBER
begin_day = 9
end_day = 10
/datum/holiday/ianbirthday/greet()
return "Happy birthday, Ian!"
/datum/holiday/ianbirthday/getStationPrefix()
return pick("Ian", "Corgi", "Erro")
//Random citadel thing for halloween species
/proc/force_enable_halloween_species()
var/list/oldlist = SSevents.holidays
@@ -8,61 +8,73 @@
..()
return late ? INITIALIZE_HINT_LATELOAD : INITIALIZE_HINT_QDEL
//airlock helpers
/obj/effect/mapping_helpers/airlock
layer = DOOR_HELPER_LAYER
/obj/effect/mapping_helpers/airlock/Initialize(mapload)
. = ..()
if(!mapload)
log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(!airlock)
log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
else
payload(airlock)
/obj/effect/mapping_helpers/airlock/proc/payload(obj/machinery/door/airlock/payload)
return
/obj/effect/mapping_helpers/airlock/cyclelink_helper
name = "airlock cyclelink helper"
icon_state = "airlock_cyclelink_helper"
/obj/effect/mapping_helpers/airlock/cyclelink_helper/Initialize(mapload)
. = ..()
if(!mapload)
log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
if(airlock.cyclelinkeddir)
log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!")
else
airlock.cyclelinkeddir = dir
/obj/effect/mapping_helpers/airlock/cyclelink_helper/payload(obj/machinery/door/airlock/airlock)
if(airlock.cyclelinkeddir)
log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!")
else
log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
airlock.cyclelinkeddir = dir
/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi
name = "airlock multi-cyclelink helper"
icon_state = "airlock_multicyclelink_helper"
var/cycle_id
/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi/payload(obj/machinery/door/airlock/airlock)
if(airlock.closeOtherId)
log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] closeOtherId, but it's already set!")
else
airlock.closeOtherId = cycle_id
/obj/effect/mapping_helpers/airlock/locked
name = "airlock lock helper"
icon_state = "airlock_locked_helper"
/obj/effect/mapping_helpers/airlock/locked/Initialize(mapload)
. = ..()
if(!mapload)
log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
if(airlock.locked)
log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!")
else
airlock.locked = TRUE
/obj/effect/mapping_helpers/airlock/locked/payload(obj/machinery/door/airlock/airlock)
if(airlock.locked)
log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!")
else
log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
airlock.locked = TRUE
/obj/effect/mapping_helpers/airlock/unres
name = "airlock unresctricted side helper"
icon_state = "airlock_unres_helper"
/obj/effect/mapping_helpers/airlock/unres/Initialize(mapload)
. = ..()
if(!mapload)
log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
airlock.unres_sides ^= dir
/obj/effect/mapping_helpers/airlock/unres/payload(obj/machinery/door/airlock/airlock)
airlock.unres_sides ^= dir
/obj/effect/mapping_helpers/airlock/abandoned
name = "airlock abandoned helper"
icon_state = "airlock_abandoned"
/obj/effect/mapping_helpers/airlock/abandoned/payload(obj/machinery/door/airlock/airlock)
if(airlock.abandoned)
log_mapping("[src] at [AREACOORD(src)] tried to make [airlock] abandoned but it's already abandoned!")
else
log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
airlock.abandoned = TRUE
//needs to do its thing before spawn_rivers() is called
@@ -79,9 +91,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
//This helper applies components to things on the map directly.
/obj/effect/mapping_helpers/component_injector
name = "Component Injector"
icon_state = "component"
late = TRUE
var/target_type
var/target_name
var/all = FALSE //Will inject into all fitting the criteria if true, otherwise first found
var/target_type //Will inject into atoms of this type
var/target_name //Will inject into atoms with this name
var/component_type
//Late init so everything is likely ready and loaded (no warranty)
@@ -98,8 +112,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
continue
var/cargs = build_args()
A._AddComponent(cargs)
if(!all)
qdel(src)
return
if(all)
qdel(src)
return
/obj/effect/mapping_helpers/component_injector/proc/build_args()
return list(component_type)
@@ -115,3 +132,276 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
CRASH("Wrong disease type passed in.")
var/datum/disease/D = new disease_type()
return list(component_type,D)
// /obj/effect/mapping_helpers/component_injector/areabound
// name = "Areabound Injector"
// icon_state = "component_areabound"
// component_type = /datum/component/areabound
// target_type = /atom/movable
/obj/effect/mapping_helpers/dead_body_placer
name = "Dead Body placer"
late = TRUE
icon_state = "deadbodyplacer"
var/bodycount = 2 //number of bodies to spawn
/obj/effect/mapping_helpers/dead_body_placer/LateInitialize()
var/area/a = get_area(src)
var/list/trays = list()
for (var/i in a.contents)
if (istype(i, /obj/structure/bodycontainer/morgue))
trays += i
if(!trays.len)
log_mapping("[src] at [x],[y] could not find any morgues.")
return
for (var/i = 1 to bodycount)
var/obj/structure/bodycontainer/morgue/j = pick(trays)
var/mob/living/carbon/human/h = new /mob/living/carbon/human(j, 1)
h.death()
for (var/part in h.internal_organs) //randomly remove organs from each body, set those we keep to be in stasis
if (prob(40))
qdel(part)
else
var/obj/item/organ/O = part
O.organ_flags |= ORGAN_FROZEN
j.update_appearance()
qdel(src)
//On Ian's birthday, the hop's office is decorated.
/obj/effect/mapping_helpers/ianbirthday
name = "Ian's Bday Helper"
late = TRUE
icon_state = "iansbdayhelper"
var/balloon_clusters = 2
/obj/effect/mapping_helpers/ianbirthday/LateInitialize()
if(locate(/datum/holiday/ianbirthday) in SSevents.holidays)
birthday()
qdel(src)
/obj/effect/mapping_helpers/ianbirthday/proc/birthday()
var/area/a = get_area(src)
var/list/table = list()//should only be one aka the front desk, but just in case...
var/list/openturfs = list()
//confetti and a corgi balloon! (and some list stuff for more decorations)
for(var/thing in a.contents)
if(istype(thing, /obj/structure/table/reinforced))
table += thing
if(isopenturf(thing))
// new /obj/effect/decal/cleanable/confetti(thing)
// if(locate(/obj/structure/bed/dogbed/ian) in thing)
// new /obj/item/toy/balloon/corgi(thing)
// else
openturfs += thing
//cake + knife to cut it!
if(length(table))
var/turf/food_turf = get_turf(pick(table))
new /obj/item/kitchen/knife(food_turf)
var/obj/item/reagent_containers/food/snacks/store/cake/birthday/iancake = new(food_turf)
iancake.desc = "Happy birthday, Ian!"
// remind me to give ian proper baloons!
//some balloons! this picks an open turf and pops a few balloons in and around that turf, yay.
// for(var/i in 1 to balloon_clusters)
// var/turf/clusterspot = pick_n_take(openturfs)
// new /obj/item/toy/balloon(clusterspot)
// var/balloons_left_to_give = 3 //the amount of balloons around the cluster
// var/list/dirs_to_balloon = GLOB.cardinals.Copy()
// while(balloons_left_to_give > 0)
// balloons_left_to_give--
// var/chosen_dir = pick_n_take(dirs_to_balloon)
// var/turf/balloonstep = get_step(clusterspot, chosen_dir)
// var/placed = FALSE
// if(isopenturf(balloonstep))
// var/obj/item/toy/balloon/B = new(balloonstep)//this clumps the cluster together
// placed = TRUE
// if(chosen_dir == NORTH)
// B.pixel_y -= 10
// if(chosen_dir == SOUTH)
// B.pixel_y += 10
// if(chosen_dir == EAST)
// B.pixel_x -= 10
// if(chosen_dir == WEST)
// B.pixel_x += 10
// if(!placed)
// new /obj/item/toy/balloon(clusterspot)
//remind me to add wall decor!
/obj/effect/mapping_helpers/ianbirthday/admin//so admins may birthday any room
name = "generic birthday setup"
icon_state = "bdayhelper"
/obj/effect/mapping_helpers/ianbirthday/admin/LateInitialize()
birthday()
qdel(src)
//Ian, like most dogs, loves a good new years eve party.
/obj/effect/mapping_helpers/iannewyear
name = "Ian's New Years Helper"
late = TRUE
icon_state = "iansnewyrshelper"
/obj/effect/mapping_helpers/iannewyear/LateInitialize()
if(SSevents.holidays && SSevents.holidays[NEW_YEAR])
fireworks()
qdel(src)
/obj/effect/mapping_helpers/iannewyear/proc/fireworks()
var/area/a = get_area(src)
var/list/table = list()//should only be one aka the front desk, but just in case...
var/list/openturfs = list()
for(var/thing in a.contents)
if(istype(thing, /obj/structure/table/reinforced))
table += thing
else if(isopenturf(thing))
if(locate(/obj/structure/bed/dogbed/ian) in thing)
new /obj/item/clothing/head/festive(thing)
var/obj/item/reagent_containers/food/drinks/bottle/champagne/iandrink = new(thing)
iandrink.name = "dog champagne"
iandrink.pixel_y += 8
iandrink.pixel_x += 8
else
openturfs += thing
var/turf/fireworks_turf = get_turf(pick(table))
var/obj/item/storage/box/matches/matchbox = new(fireworks_turf)
matchbox.pixel_y += 8
matchbox.pixel_x -= 3
// new /obj/item/storage/box/fireworks/dangerous(fireworks_turf) //dangerous version for extra holiday memes.
//lets mappers place notes on airlocks with custom info or a pre-made note from a path
/obj/effect/mapping_helpers/airlock_note_placer
name = "Airlock Note Placer"
late = TRUE
icon_state = "airlocknoteplacer"
var/note_info //for writing out custom notes without creating an extra paper subtype
var/note_name //custom note name
var/note_path //if you already have something wrote up in a paper subtype, put the path here
/obj/effect/mapping_helpers/airlock_note_placer/LateInitialize()
var/turf/turf = get_turf(src)
if(note_path && !istype(note_path, /obj/item/paper)) //don't put non-paper in the paper slot thank you
log_mapping("[src] at [x],[y] had an improper note_path path, could not place paper note.")
qdel(src)
if(locate(/obj/machinery/door/airlock) in turf)
var/obj/machinery/door/airlock/found_airlock = locate(/obj/machinery/door/airlock) in turf
if(note_path)
found_airlock.note = note_path
found_airlock.update_appearance()
qdel(src)
if(note_info)
var/obj/item/paper/paper = new /obj/item/paper(src)
if(note_name)
paper.name = note_name
paper.info = "[note_info]"
found_airlock.note = paper
paper.forceMove(found_airlock)
found_airlock.update_appearance()
qdel(src)
log_mapping("[src] at [x],[y] had no note_path or note_info, cannot place paper note.")
qdel(src)
log_mapping("[src] at [x],[y] could not find an airlock on current turf, cannot place paper note.")
qdel(src)
//This helper applies traits to things on the map directly.
/obj/effect/mapping_helpers/trait_injector
name = "Trait Injector"
icon_state = "trait"
late = TRUE
///Will inject into all fitting the criteria if false, otherwise first found.
var/first_match_only = TRUE
///Will inject into atoms of this type.
var/target_type
///Will inject into atoms with this name.
var/target_name
///Name of the trait, in the lower-case text (NOT the upper-case define) form.
var/trait_name
//Late init so everything is likely ready and loaded (no warranty)
/obj/effect/mapping_helpers/trait_injector/LateInitialize()
if(!GLOB.trait_name_map)
GLOB.trait_name_map = generate_trait_name_map()
if(!GLOB.trait_name_map.Find(trait_name))
CRASH("Wrong trait in [type] - [trait_name] is not a trait")
var/turf/target_turf = get_turf(src)
var/matches_found = 0
for(var/a in target_turf.GetAllContents())
var/atom/atom_on_turf = a
if(atom_on_turf == src)
continue
if(target_name && atom_on_turf.name != target_name)
continue
if(target_type && !istype(atom_on_turf,target_type))
continue
ADD_TRAIT(atom_on_turf, trait_name, MAPPING_HELPER_TRAIT)
matches_found++
if(first_match_only)
qdel(src)
return
if(!matches_found)
stack_trace("Trait mapper found no targets at ([x], [y], [z]). First Match Only: [first_match_only ? "true" : "false"] target type: [target_type] | target name: [target_name] | trait name: [trait_name]")
qdel(src)
/// Fetches an external dmi and applies to the target object
/obj/effect/mapping_helpers/custom_icon
name = "Custom Icon Helper"
icon_state = "trait"
late = TRUE
///Will inject into all fitting the criteria if false, otherwise first found.
var/first_match_only = TRUE
///Will inject into atoms of this type.
var/target_type
///Will inject into atoms with this name.
var/target_name
/// This is the var tha will be set with the fetched icon. In case you want to set some secondary icon sheets like inhands and such.
var/target_variable = "icon"
/// This should return raw dmi in response to http get request. For example: "https://github.com/tgstation/SS13-sprites/raw/master/mob/medu.dmi?raw=true"
var/icon_url
/obj/effect/mapping_helpers/custom_icon/LateInitialize()
///TODO put this injector stuff under common root
var/I = fetch_icon(icon_url)
var/turf/target_turf = get_turf(src)
var/matches_found = 0
for(var/a in target_turf.GetAllContents())
var/atom/atom_on_turf = a
if(atom_on_turf == src)
continue
if(target_name && atom_on_turf.name != target_name)
continue
if(target_type && !istype(atom_on_turf,target_type))
continue
atom_on_turf.vars[target_variable] = I
matches_found++
if(first_match_only)
qdel(src)
return
if(!matches_found)
stack_trace("[src] found no targets at ([x], [y], [z]). First Match Only: [first_match_only ? "true" : "false"] target type: [target_type] | target name: [target_name]")
qdel(src)
/obj/effect/mapping_helpers/custom_icon/proc/fetch_icon(url)
var/static/icon_cache = list()
var/static/query_in_progress = FALSE //We're using a single tmp file so keep it linear.
if(query_in_progress)
UNTIL(!query_in_progress)
if(icon_cache[url])
return icon_cache[url]
log_asset("Custom Icon Helper fetching dmi from: [url]")
var/datum/http_request/request = new()
var/file_name = "tmp/custom_map_icon.dmi"
request.prepare(RUSTG_HTTP_METHOD_GET, url , "", "", file_name)
query_in_progress = TRUE
request.begin_async()
UNTIL(request.is_complete())
var/datum/http_response/response = request.into_response()
if(response.errored || response.status_code != 200)
query_in_progress = FALSE
CRASH("Failed to fetch mapped custom icon from url [url], code: [response.status_code], error: [response.error]")
var/icon/I = new(file_name)
icon_cache[url] = I
query_in_progress = FALSE
return I
@@ -30,8 +30,8 @@
qdel(src)
/obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing)
var/list/baseturf_cache = thing.baseturfs
if(length(baseturf_cache))
if(length(thing.baseturfs))
var/list/baseturf_cache = thing.baseturfs.Copy()
for(var/i in baseturf_cache)
if(baseturf_to_replace[i])
baseturf_cache -= i
@@ -44,6 +44,8 @@
else
thing.PlaceOnBottom(null, baseturf)
/obj/effect/baseturf_helper/space
name = "space baseturf editor"
baseturf = /turf/open/space
@@ -79,4 +81,3 @@
/obj/effect/baseturf_helper/lava_land/surface
name = "lavaland baseturf editor"
baseturf = /turf/open/lava/smooth/lava_land_surface
+38 -3
View File
@@ -2,8 +2,9 @@
/obj/item/clothing/suit/hooded/explorer
name = "explorer suit"
desc = "An armoured suit for exploring harsh environments."
icon_state = "explorer"
item_state = "explorer"
icon_state = "explorer-normal"
item_state = "explorer-normal"
var/suit_type = "normal"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
cold_protection = CHEST|GROIN|LEGS|ARMS
min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
@@ -13,11 +14,15 @@
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/pickaxe)
resistance_flags = FIRE_PROOF
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC
no_t = TRUE
/obj/item/clothing/head/hooded/explorer
name = "explorer hood"
desc = "An armoured hood for exploring harsh environments."
icon_state = "explorer"
icon_state = "explorer-normal"
item_state = "explorer-normal"
var/suit_type = "normal"
var/basestate = "normal"
body_parts_covered = HEAD
flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS
min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT
@@ -33,10 +38,40 @@
/obj/item/clothing/suit/hooded/explorer/standard/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
/obj/item/clothing/suit/hooded/explorer/standard/proc/upgrade_icon(datum/source, amount, maxamount)
SIGNAL_HANDLER
if(amount)
name = "reinforced [initial(name)]"
suit_type = "normal_goliath"
if(amount == maxamount)
suit_type = "normal_goliath_full"
icon_state = "explorer-[suit_type]"
if(ishuman(loc))
var/mob/living/carbon/human/wearer = loc
if(wearer.wear_suit == src)
wearer.update_inv_wear_suit()
/obj/item/clothing/head/hooded/explorer/standard/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
/obj/item/clothing/head/hooded/explorer/standard/proc/upgrade_icon(datum/source, amount, maxamount)
SIGNAL_HANDLER
if(amount)
name = "reinforced [initial(name)]"
suit_type = "normal_goliath"
if(amount == maxamount)
suit_type = "normal_goliath_full"
icon_state = "explorer-[suit_type]"
if(ishuman(loc))
var/mob/living/carbon/human/wearer = loc
if(wearer.head == src)
wearer.update_inv_head()
/obj/item/clothing/mask/gas/explorer
name = "explorer gas mask"
@@ -3,12 +3,15 @@
/**
* Returns the thing in our active hand (whatever is in our active module-slot, in this case)
*
* Arguments
* * get_gripper - If the active module is a gripper, should we return the gripper or the contained item? (if the gripper contains nothing, returns the gripper anyways)
*/
/mob/living/silicon/robot/get_active_held_item()
/mob/living/silicon/robot/get_active_held_item(get_gripper = FALSE)
var/item = module_active
// snowflake handler for the gripper
if(istype(item, /obj/item/weapon/gripper))
var/obj/item/weapon/gripper/G = item
if(istype(item, /obj/item/gripper) && !get_gripper)
var/obj/item/gripper/G = item
if(G.wrapped)
if(G.wrapped.loc != G)
G.wrapped = null
@@ -284,9 +287,14 @@
/**
* Unequips the active held item, if there is one.
*
* Will always consider dropping gripper contents first.
*/
/mob/living/silicon/robot/proc/uneq_active()
if(module_active)
var/obj/item/gripper/gripper = get_active_held_item(TRUE)
if(istype(gripper) && gripper.drop_held())
return
unequip_module_from_slot(module_active, get_selected_module())
/**
@@ -302,11 +310,12 @@
* Checks if the item is currently in a slot.
*
* If the item is found in a slot, this returns TRUE. Otherwise, it returns FALSE
* Modified to accept items inside of grippers, used for `code\modules\tgui\states\hands.dm:27`
* Arguments
* * item_module - the item being checked
*/
/mob/living/silicon/robot/proc/activated(obj/item/item_module)
if(item_module in held_items)
if(get_active_held_item() == item_module || (item_module in held_items))
return TRUE
return FALSE
@@ -328,7 +328,7 @@
/obj/item/crowbar/cyborg,
/obj/item/healthanalyzer,
/obj/item/reagent_containers/borghypo,
/obj/item/weapon/gripper/medical,
/obj/item/gripper/medical,
/obj/item/reagent_containers/dropper,
/obj/item/reagent_containers/syringe,
/obj/item/surgical_drapes,
@@ -448,7 +448,7 @@
/obj/item/analyzer,
/obj/item/storage/part_replacer/cyborg,
/obj/item/holosign_creator/combifan,
/obj/item/weapon/gripper,
/obj/item/gripper,
/obj/item/lightreplacer/cyborg,
/obj/item/geiger_counter/cyborg,
/obj/item/assembly/signaler/cyborg,
@@ -923,7 +923,7 @@
/obj/item/gun/energy/kinetic_accelerator/cyborg,
/obj/item/gun/energy/plasmacutter/cyborg,
/obj/item/gps/cyborg,
/obj/item/weapon/gripper/mining,
/obj/item/gripper/mining,
/obj/item/cyborg_clamp,
/obj/item/stack/marker_beacon,
/obj/item/destTagger,
@@ -1075,7 +1075,7 @@
/obj/item/multitool/cyborg,
/obj/item/storage/part_replacer/cyborg,
/obj/item/holosign_creator/atmos,
/obj/item/weapon/gripper,
/obj/item/gripper,
/obj/item/lightreplacer/cyborg,
/obj/item/stack/sheet/metal/cyborg,
/obj/item/stack/sheet/glass/cyborg,
@@ -528,21 +528,7 @@ Difficulty: Very Hard
/obj/machinery/anomalous_crystal/emitter/ActivationReaction(mob/user, method)
if(..())
var/obj/item/projectile/P = new generated_projectile(get_turf(src))
P.setDir(dir)
switch(dir)
if(NORTH)
P.yo = 20
P.xo = 0
if(EAST)
P.yo = 0
P.xo = 20
if(WEST)
P.yo = 0
P.xo = -20
else
P.yo = -20
P.xo = 0
P.fire()
P.fire(angle2dir(dir))
/obj/machinery/anomalous_crystal/dark_reprise //Revives anyone nearby, but turns them into shadowpeople and renders them uncloneable, so the crystal is your only hope of getting up again if you go down.
observer_desc = "When activated, this crystal revives anyone nearby, but turns them into Shadowpeople and makes them unclonable, making the crystal their only hope of getting up again."
@@ -32,7 +32,7 @@ Key procs
/// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID.
var/id
/// Higher ones override lower priorities. This is NOT used for ID, ID must be unique, if it isn't unique the newer one overwrites automatically if overriding.
/// Determines order. Lower priorities are applied first.
var/priority = 0
var/flags = NONE
@@ -66,8 +66,10 @@ Key procs
if(!complex_calculation || (multiplicative_slowdown > 0)) // we aren't limiting how much things can slowdown.. yet.
return existing + multiplicative_slowdown
var/current_tiles = 10 / max(existing, world.tick_lag)
var/minimum_speed = 10 / min(current_tiles + max_tiles_per_second_boost, max(current_tiles, absolute_max_tiles_per_second))
return max(minimum_speed, existing + multiplicative_slowdown)
// multiplicative_slowdown is negative due to our first check
var/max_buff_to = max(existing + multiplicative_slowdown, 10 / absolute_max_tiles_per_second, 10 / (current_tiles + max_tiles_per_second_boost))
// never slow the user
return min(existing, max_buff_to)
GLOBAL_LIST_EMPTY(movespeed_modification_cache)
+3 -3
View File
@@ -7,7 +7,7 @@
/datum/movespeed_modifier/reagent/ephedrine
// strong painkiller effect that caps out at slightly above runspeed
multiplicative_slowdown = -1.5
priority = -100
priority = 500
complex_calculation = TRUE
absolute_max_tiles_per_second = 7
@@ -21,14 +21,14 @@
// extremely strong painkiller effect: allows user to run at old sprint speeds but not over by cancelling out slowdowns.
// however, will not make user go faster than that
multiplicative_slowdown = -4
priority = -100
priority = 500
complex_calculation = TRUE
absolute_max_tiles_per_second = 8
/datum/movespeed_modifier/reagent/methamphetamine
// very strong painkiller effect that caps out at slightly above runspeed
multiplicative_slowdown = -2.5
priority = -100
priority = 500
complex_calculation = TRUE
absolute_max_tiles_per_second = 7.5
@@ -19,14 +19,14 @@
/datum/movespeed_modifier/status_effect/tased
multiplicative_slowdown = 1.5
priority = 50
priority = 1500
/datum/movespeed_modifier/status_effect/domain
multiplicative_slowdown = 3
/datum/movespeed_modifier/status_effect/tased/no_combat_mode
multiplicative_slowdown = 8
priority = 100
priority = 1500
/datum/movespeed_modifier/status_effect/electrostaff
multiplicative_slowdown = 1
@@ -55,7 +55,7 @@
/datum/movespeed_modifier/status_effect/slime/light_pink
// decently good painkiller + speedup effect
blacklisted_movetypes = FLYING | FLOATING
priority = -150 // someday we really need to make these defines lmao
priority = 500 // someday we really need to make these defines lmao
multiplicative_slowdown = -2
complex_calculation = TRUE
absolute_max_tiles_per_second = 7
@@ -5,12 +5,6 @@
obj_flags = CAN_BE_HIT | UNIQUE_RENAME
circuit = /obj/item/circuitboard/machine/hydroponics/automagic
/obj/machinery/hydroponics/constructable/automagic/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/reagent_containers))
return FALSE //avoid fucky wuckies
..()
/obj/machinery/hydroponics/constructable/automagic/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
. = ..()
if(. == SUCCESSFUL_UNFASTEN)
+46 -38
View File
@@ -62,6 +62,7 @@
var/fermiIsReacting = FALSE //that prevents multiple reactions from occurring (i.e. add_reagent calls to process_reactions(), this stops any extra reactions.)
var/fermiReactID //instance of the chem reaction used during a fermireaction, kept here so it's cache isn't lost between loops/procs.
var/value_multiplier = DEFAULT_REAGENTS_VALUE //used for cargo reagents selling.
var/force_alt_taste = FALSE
/datum/reagents/New(maximum=100, new_flags = NONE, new_value = DEFAULT_REAGENTS_VALUE)
maximum_volume = maximum
@@ -1120,47 +1121,54 @@
. = locate(type) in cached_reagents
/datum/reagents/proc/generate_taste_message(minimum_percent=15)
// the lower the minimum percent, the more sensitive the message is.
var/list/out = list()
var/list/tastes = list() //descriptor = strength
if(minimum_percent <= 100)
for(var/datum/reagent/R in reagent_list)
if(!R.taste_mult)
continue
if(istype(R, /datum/reagent/consumable/nutriment))
var/list/taste_data = R.data
for(var/taste in taste_data)
var/ratio = taste_data[taste]
var/amount = ratio * R.taste_mult * R.volume
if(taste in tastes)
tastes[taste] += amount
else
tastes[taste] = amount
else
var/taste_desc = R.taste_description
var/taste_amount = R.volume * R.taste_mult
if(taste_desc in tastes)
tastes[taste_desc] += taste_amount
else
tastes[taste_desc] = taste_amount
//deal with percentages
// TODO it would be great if we could sort these from strong to weak
var/total_taste = counterlist_sum(tastes)
if(total_taste > 0)
for(var/taste_desc in tastes)
var/percent = tastes[taste_desc]/total_taste * 100
if(percent < minimum_percent)
if(!force_alt_taste)
// the lower the minimum percent, the more sensitive the message is.
var/list/tastes = list() //descriptor = strength
if(minimum_percent <= 100)
for(var/datum/reagent/R in reagent_list)
if(!R.taste_mult)
continue
var/intensity_desc = "a hint of"
if(ISINRANGE(percent, minimum_percent * 2, minimum_percent * 3)|| percent == 100)
intensity_desc = ""
else if(percent > minimum_percent * 3)
intensity_desc = "the strong flavor of"
if(intensity_desc != "")
out += "[intensity_desc] [taste_desc]"
if(istype(R, /datum/reagent/consumable/nutriment))
var/list/taste_data = R.data
for(var/taste in taste_data)
var/ratio = taste_data[taste]
var/amount = ratio * R.taste_mult * R.volume
if(taste in tastes)
tastes[taste] += amount
else
tastes[taste] = amount
else
out += "[taste_desc]"
var/taste_desc = R.taste_description
var/taste_amount = R.volume * R.taste_mult
if(taste_desc in tastes)
tastes[taste_desc] += taste_amount
else
tastes[taste_desc] = taste_amount
//deal with percentages
// TODO it would be great if we could sort these from strong to weak
var/total_taste = counterlist_sum(tastes)
if(total_taste > 0)
for(var/taste_desc in tastes)
var/percent = tastes[taste_desc]/total_taste * 100
if(percent < minimum_percent)
continue
var/intensity_desc = "a hint of"
if(ISINRANGE(percent, minimum_percent * 2, minimum_percent * 3)|| percent == 100)
intensity_desc = ""
else if(percent > minimum_percent * 3)
intensity_desc = "the strong flavor of"
if(intensity_desc != "")
out += "[intensity_desc] [taste_desc]"
else
out += "[taste_desc]"
else
// alternate taste is to force the taste of the atom if its a food item
if(my_atom && isfood(my_atom))
var/obj/item/reagent_containers/food/snacks/F = my_atom
out = F.tastes
return english_list(out, "something indescribable")
+33 -1
View File
@@ -32,47 +32,79 @@
/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)`
#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; }
/// Constants indicating unit test completion status
#define UNIT_TEST_PASSED 0
#define UNIT_TEST_FAILED 1
#define UNIT_TEST_SKIPPED 2
#define TEST_DEFAULT 1
#define TEST_DEL_WORLD INFINITY
/// A trait source when adding traits through unit tests
#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
#include "anchored_mobs.dm"
#include "bespoke_id.dm"
#include "binary_insert.dm"
// #include "bloody_footprints.dm"
// #include "breath.dm"
// #include "card_mismatch.dm"
#include "chain_pull_through_space.dm"
// #include "combat.dm"
#include "component_tests.dm"
// #include "connect_loc.dm"
// #include "confusion.dm"
// #include "crayons.dm"
// #include "create_and_destroy.dm"
// #include "designs.dm"
// #include "dynamic_ruleset_sanity.dm"
// #include "egg_glands.dm"
// #include "emoting.dm"
// #include "food_edibility_check.dm"
// #include "greyscale_config.dm"
// #include "heretic_knowledge.dm"
// #include "holidays.dm"
#include "initialize_sanity.dm"
// #include "hydroponics_harvest.dm"
// #include "keybinding_init.dm"
#include "machine_disassembly.dm"
#include "medical_wounds.dm"
#include "merge_type.dm"
// #include "metabolizing.dm"
// #include "ntnetwork_tests.dm"
// #include "outfit_sanity.dm"
// #include "pills.dm"
// #include "plantgrowth_tests.dm"
// #include "projectiles.dm"
// #include "rcd.dm"
#include "reagent_id_typos.dm"
// #include "reagent_mod_expose.dm"
// #include "reagent_mod_procs.dm"
#include "reagent_recipe_collisions.dm"
#include "resist.dm"
// #include "say.dm"
// #include "security_officer_distribution.dm"
// #include "serving_tray.dm"
// #include "siunit.dm"
#include "spawn_humans.dm"
#include "spawn_mobs.dm"
// #include "species_whitelists.dm"
// #include "stomach.dm"
// #include "strippable.dm"
#include "subsystem_init.dm"
#include "surgeries.dm"
#include "teleporters.dm"
#include "tgui_create_message.dm"
#include "timer_sanity.dm"
#include "unit_test.dm"
// #include "wizard.dm"
/// CIT TESTS
#include "character_saving.dm"
#ifdef REFERENCE_TRACKING //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter
#include "find_reference_sanity.dm"
#endif
#undef TEST_ASSERT
#undef TEST_ASSERT_EQUAL
#undef TEST_ASSERT_NOTEQUAL
@@ -1,6 +1,6 @@
/datum/unit_test/chain_pull_through_space
var/turf/open/space/space_tile
var/turf/claimed_tile
var/claimed_tile
var/mob/living/carbon/human/alice
var/mob/living/carbon/human/bob
var/mob/living/carbon/human/charlie
@@ -9,25 +9,25 @@
..()
// Create a space tile that goes to another z-level
claimed_tile = run_loc_bottom_left
claimed_tile = run_loc_floor_bottom_left.type
space_tile = new(locate(run_loc_bottom_left.x, run_loc_bottom_left.y, run_loc_bottom_left.z))
space_tile = new(locate(run_loc_floor_bottom_left.x, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
space_tile.destination_x = 100
space_tile.destination_y = 100
space_tile.destination_z = 5
// Create our list of humans, all adjacent to one another
alice = new(locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z))
alice = new(locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
alice.name = "Alice"
bob = new(locate(run_loc_bottom_left.x + 3, run_loc_bottom_left.y, run_loc_bottom_left.z))
bob = new(locate(run_loc_floor_bottom_left.x + 3, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
bob.name = "Bob"
charlie = new(locate(run_loc_bottom_left.x + 4, run_loc_bottom_left.y, run_loc_bottom_left.z))
charlie = new(locate(run_loc_floor_bottom_left.x + 4, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
charlie.name = "Charlie"
/datum/unit_test/chain_pull_through_space/Destroy()
space_tile.copyTurf(claimed_tile)
space_tile.ChangeTurf(claimed_tile)
qdel(alice)
qdel(bob)
qdel(charlie)
@@ -40,14 +40,14 @@
bob.start_pulling(charlie)
// Walk normally to the left, make sure we're still a chain
alice.Move(locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z))
if (bob.x != run_loc_bottom_left.x + 2)
alice.Move(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
if (bob.x != run_loc_floor_bottom_left.x + 2)
return Fail("During normal move, Bob was not at the correct x ([bob.x])")
if (charlie.x != run_loc_bottom_left.x + 3)
if (charlie.x != run_loc_floor_bottom_left.x + 3)
return Fail("During normal move, Charlie was not at the correct x ([charlie.x])")
// We're going through the space turf now that should teleport us
alice.Move(run_loc_bottom_left)
alice.Move(run_loc_floor_bottom_left)
if (alice.z != space_tile.destination_z)
return Fail("Alice did not teleport to the destination z-level. Current location: ([alice.x], [alice.y], [alice.z])")
@@ -0,0 +1,111 @@
///Used to test the completeness of the reference finder proc.
/datum/unit_test/find_reference_sanity
/atom/movable/ref_holder
var/atom/movable/ref_test/test
var/list/test_list = list()
var/list/test_assoc_list = list()
/atom/movable/ref_holder/Destroy()
test = null
test_list.Cut()
test_assoc_list.Cut()
return ..()
/atom/movable/ref_test
var/atom/movable/ref_test/self_ref
/atom/movable/ref_test/Destroy(force)
self_ref = null
return ..()
/datum/unit_test/find_reference_sanity/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Sanity check
victim.DoSearchVar(testbed, "Sanity Check", search_time = 1) //We increment search time to get around an optimization
TEST_ASSERT(!victim.found_refs.len, "The ref-tracking tool found a ref where none existed")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_baseline/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Set up for the first round of tests
testbed.test = victim
testbed.test_list += victim
testbed.test_assoc_list["baseline"] = victim
victim.DoSearchVar(testbed, "First Run", search_time = 2)
TEST_ASSERT(victim.found_refs["test"], "The ref-tracking tool failed to find a regular value")
TEST_ASSERT(victim.found_refs[testbed.test_list], "The ref-tracking tool failed to find a list entry")
TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list value")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_exotic/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Second round, bit harder this time
testbed.overlays += victim
testbed.vis_contents += victim
testbed.test_assoc_list[victim] = TRUE
victim.DoSearchVar(testbed, "Second Run", search_time = 3)
//This is another sanity check
TEST_ASSERT(!victim.found_refs[testbed.overlays], "The ref-tracking tool found an overlays entry? That shouldn't be possible")
TEST_ASSERT(victim.found_refs[testbed.vis_contents], "The ref-tracking tool failed to find a vis_contents entry")
TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list key")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_esoteric/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Let's get a bit esoteric
victim.self_ref = victim
var/list/to_find = list(victim)
testbed.test_list += list(to_find)
var/list/to_find_assoc = list(victim)
testbed.test_assoc_list["Nesting"] = to_find_assoc
victim.DoSearchVar(victim, "Third Run Self", search_time = 4)
victim.DoSearchVar(testbed, "Third Run Testbed", search_time = 4)
TEST_ASSERT(victim.found_refs["self_ref"], "The ref-tracking tool failed to find a self reference")
TEST_ASSERT(victim.found_refs[to_find], "The ref-tracking tool failed to find a nested list entry")
TEST_ASSERT(victim.found_refs[to_find_assoc], "The ref-tracking tool failed to find a nested assoc list entry")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_null_key_entry/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Calm before the storm
testbed.test_assoc_list = list(null = victim)
victim.DoSearchVar(testbed, "Fourth Run", search_time = 5)
TEST_ASSERT(testbed.test_assoc_list, "The ref-tracking tool failed to find a null key'd assoc list entry")
/datum/unit_test/find_reference_assoc_investigation/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Let's do some more complex assoc list investigation
var/list/to_find_in_key = list(victim)
testbed.test_assoc_list[to_find_in_key] = list("memes")
var/list/to_find_null_assoc_nested = list(victim)
testbed.test_assoc_list[null] = to_find_null_assoc_nested
victim.DoSearchVar(testbed, "Fifth Run", search_time = 6)
TEST_ASSERT(victim.found_refs[to_find_in_key], "The ref-tracking tool failed to find a nested assoc list key")
TEST_ASSERT(victim.found_refs[to_find_null_assoc_nested], "The ref-tracking tool failed to find a null key'd nested assoc list entry")
SSgarbage.should_save_refs = FALSE
@@ -1,11 +0,0 @@
/datum/unit_test/initialize_sanity/Run()
if(length(SSatoms.BadInitializeCalls))
Fail("Bad Initialize() calls detected. Please read logs.")
var/list/init_failures_to_text = list(
"[BAD_INIT_QDEL_BEFORE]" = "Qdeleted Before Initialized",
"[BAD_INIT_DIDNT_INIT]" = "Did Not Initialize",
"[BAD_INIT_SLEPT]" = "Initialize() Slept",
"[BAD_INIT_NO_HINT]" = "No Initialize() Hint Returned",
)
for(var/failure in SSatoms.BadInitializeCalls)
log_world("[failure]: [init_failures_to_text["[SSatoms.BadInitializeCalls[failure]]"]]") // You like stacked brackets?
-23
View File
@@ -1,23 +0,0 @@
/// Test to verify message mods are parsed correctly
/datum/unit_test/get_message_mods
var/mob/host_mob
/datum/unit_test/get_message_mods/Run()
host_mob = allocate(/mob/living/carbon/human)
test("Hello", "Hello", list())
test(";HELP", "HELP", list(MODE_HEADSET = TRUE))
test(";%Never gonna give you up", "Never gonna give you up", list(MODE_HEADSET = TRUE, MODE_SING = TRUE))
test(".s Gun plz", "Gun plz", list(RADIO_KEY = RADIO_KEY_SECURITY, RADIO_EXTENSION = RADIO_CHANNEL_SECURITY))
test("...What", "...What", list())
/datum/unit_test/get_message_mods/proc/test(message, expected_message, list/expected_mods)
var/list/mods = list()
TEST_ASSERT_EQUAL(host_mob.get_message_mods(message, mods), expected_message, "Chopped message was not what we expected. Message: [message]")
for (var/mod_key in mods)
TEST_ASSERT_EQUAL(mods[mod_key], expected_mods[mod_key], "The value for [mod_key] was not what we expected. Message: [message]")
expected_mods -= mod_key
if (expected_mods.len)
Fail("Some message mods were expected, but were not returned by get_message_mods: [json_encode(expected_mods)]. Message: [message]")
+1 -1
View File
@@ -1,5 +1,5 @@
/datum/unit_test/spawn_humans/Run()
var/locs = block(run_loc_bottom_left, run_loc_top_right)
var/locs = block(run_loc_floor_bottom_left, run_loc_floor_top_right)
for(var/I in 1 to 5)
new /mob/living/carbon/human(pick(locs))
+8
View File
@@ -0,0 +1,8 @@
///Unit test that spawns all mobs that can be spawned by golden slimes
/datum/unit_test/spawn_mobs
/datum/unit_test/spawn_mobs/Run()
for(var/_animal in typesof(/mob/living/simple_animal))
var/mob/living/simple_animal/animal = _animal
if (initial(animal.gold_core_spawnable) == HOSTILE_SPAWN || initial(animal.gold_core_spawnable) == FRIENDLY_SPAWN)
allocate(_animal)
+2 -2
View File
@@ -1,8 +1,8 @@
/datum/unit_test/auto_teleporter_linking/Run()
// Put down the teleporter machinery
var/obj/machinery/teleport/hub/hub = allocate(/obj/machinery/teleport/hub)
var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z))
var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z))
var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
TEST_ASSERT_EQUAL(hub.power_station, station, "Hub didn't link to the station")
TEST_ASSERT_EQUAL(station.teleporter_console, computer, "Station didn't link to the teleporter console")
@@ -0,0 +1,28 @@
/// Test that `TGUI_CREATE_MESSAGE` is correctly implemented
/datum/unit_test/tgui_create_message
/datum/unit_test/tgui_create_message/Run()
var/type = "something/here"
var/list/payload = list(
"name" = "Terry McTider",
"heads_caved" = 100,
"accomplishments" = list(
"nothing",
"literally nothing",
list(
"something" = "just kidding",
),
),
)
var/message = TGUI_CREATE_MESSAGE(type, payload)
// Ensure consistent output to compare by performing a round-trip.
var/output = json_encode(json_decode(url_decode(message)))
var/expected = json_encode(list(
"type" = type,
"payload" = payload,
))
TEST_ASSERT_EQUAL(expected, output, "TGUI_CREATE_MESSAGE didn't round trip properly")
+68 -41
View File
@@ -7,7 +7,7 @@ Call Fail() to fail the test (You should specify a reason)
You may use /New() and /Destroy() for setup/teardown respectively
You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing
You can use the run_loc_floor_bottom_left and run_loc_floor_top_right to get turfs for testing
*/
@@ -19,39 +19,46 @@ GLOBAL_VAR(test_log)
//Bit of metadata for the future maybe
var/list/procs_tested
/// The bottom left turf of the testing zone
var/turf/run_loc_bottom_left
/// The top right turf of the testing zone
var/turf/run_loc_top_right
/// The type of turf to allocate for the testing zone
var/test_turf_type = /turf/open/floor/plasteel
/// The bottom left floor turf of the testing zone
var/turf/run_loc_floor_bottom_left
/// The top right floor turf of the testing zone
var/turf/run_loc_floor_top_right
///The priority of the test, the larger it is the later it fires
var/priority = TEST_DEFAULT
//internal shit
var/focus = FALSE
var/succeeded = TRUE
var/list/allocated
var/list/fail_reasons
var/static/datum/turf_reservation/turf_reservation
var/static/datum/turf_reservation/reservation
/proc/cmp_unit_test_priority(datum/unit_test/a, datum/unit_test/b)
return initial(a.priority) - initial(b.priority)
/datum/unit_test/New()
if (isnull(turf_reservation))
turf_reservation = SSmapping.RequestBlockReservation(5, 5)
if (isnull(reservation))
reservation = SSmapping.RequestBlockReservation(5, 5)
for (var/turf/reserved_turf in turf_reservation.reserved_turfs)
reserved_turf.ChangeTurf(test_turf_type)
for (var/turf/reserved_turf in reservation.reserved_turfs)
reserved_turf.ChangeTurf(/turf/open/floor/plasteel)
allocated = new
run_loc_bottom_left = locate(turf_reservation.bottom_left_coords[1], turf_reservation.bottom_left_coords[2], turf_reservation.bottom_left_coords[3])
run_loc_top_right = locate(turf_reservation.top_right_coords[1], turf_reservation.top_right_coords[2], turf_reservation.top_right_coords[3])
run_loc_floor_bottom_left = locate(reservation.bottom_left_coords[1], reservation.bottom_left_coords[2], reservation.bottom_left_coords[3])
run_loc_floor_top_right = locate(reservation.top_right_coords[1], reservation.top_right_coords[2], reservation.top_right_coords[3])
TEST_ASSERT(isfloorturf(run_loc_floor_bottom_left), "run_loc_floor_bottom_left was not a floor ([run_loc_floor_bottom_left])")
TEST_ASSERT(isfloorturf(run_loc_floor_top_right), "run_loc_floor_top_right was not a floor ([run_loc_floor_top_right])")
/datum/unit_test/Destroy()
//clear the test area
for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right))
qdel(AM)
QDEL_LIST(allocated)
// clear the test area
for (var/turf/turf in block(locate(1, 1, run_loc_floor_bottom_left.z), locate(world.maxx, world.maxy, run_loc_floor_bottom_left.z)))
for (var/content in turf.contents)
if (istype(content, /obj/effect/landmark))
continue
qdel(content)
return ..()
/datum/unit_test/proc/Run()
@@ -70,44 +77,64 @@ GLOBAL_VAR(test_log)
/datum/unit_test/proc/allocate(type, ...)
var/list/arguments = args.Copy(2)
if (!arguments.len)
arguments = list(run_loc_bottom_left)
arguments = list(run_loc_floor_bottom_left)
else if (arguments[1] == null)
arguments[1] = run_loc_bottom_left
arguments[1] = run_loc_floor_bottom_left
var/instance = new type(arglist(arguments))
allocated += instance
return instance
/proc/RunUnitTest(test_path, list/test_results)
var/datum/unit_test/test = new test_path
GLOB.current_test = test
var/duration = REALTIMEOFDAY
test.Run()
duration = REALTIMEOFDAY - duration
GLOB.current_test = null
GLOB.failed_any_test |= !test.succeeded
var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [test_path] [duration / 10]s")
var/list/fail_reasons = test.fail_reasons
for(var/J in 1 to LAZYLEN(fail_reasons))
log_entry += "\tREASON #[J]: [fail_reasons[J]]"
var/message = log_entry.Join("\n")
log_test(message)
test_results[test_path] = list("status" = test.succeeded ? UNIT_TEST_PASSED : UNIT_TEST_FAILED, "message" = message, "name" = test_path)
qdel(test)
/proc/RunUnitTests()
CHECK_TICK
var/tests_to_run = subtypesof(/datum/unit_test)
var/list/tests_to_run = subtypesof(/datum/unit_test)
for (var/_test_to_run in tests_to_run)
var/datum/unit_test/test_to_run = _test_to_run
if (initial(test_to_run.focus))
tests_to_run = list(test_to_run)
break
for(var/I in tests_to_run)
var/datum/unit_test/test = new I
tests_to_run = sortTim(tests_to_run, /proc/cmp_unit_test_priority)
GLOB.current_test = test
var/duration = REALTIMEOFDAY
var/list/test_results = list()
test.Run()
for(var/unit_path in tests_to_run)
CHECK_TICK //We check tick first because the unit test we run last may be so expensive that checking tick will lock up this loop forever
RunUnitTest(unit_path, test_results)
duration = REALTIMEOFDAY - duration
GLOB.current_test = null
GLOB.failed_any_test |= !test.succeeded
var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [I] [duration / 10]s")
var/list/fail_reasons = test.fail_reasons
qdel(test)
for(var/J in 1 to LAZYLEN(fail_reasons))
log_entry += "\tREASON #[J]: [fail_reasons[J]]"
log_test(log_entry.Join("\n"))
CHECK_TICK
var/file_name = "data/unit_tests.json"
fdel(file_name)
file(file_name) << json_encode(test_results)
SSticker.force_ending = TRUE
//We have to call this manually because del_text can preceed us, and SSticker doesn't fire in the post game
SSticker.ready_for_reboot = TRUE
SSticker.standard_reboot()
// /datum/map_template/unit_tests
// name = "Unit Tests Zone"
// mappath = "_maps/templates/unit_tests.dmm"
+5 -1
View File
@@ -52,8 +52,12 @@ ANNOUNCE_ADMIN_LOGOUT
## Uncomment to have an admin message sent anytime an admin connects to a round in play, you can edit the messages in admin.dm
#ANNOUNCE_ADMIN_LOGIN
## Uncomment to enable global ban DB using the provided URL. The API should expect to receive a ckey at the end of the URL.
## More API details can be found here: https://centcom.melonmesa.com
CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search
#CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search
## Uncomment to enable source whitelisting, a comma-separated list (no spaces) of CentCom sources (sourceName).
## If enabled, only bans from these servers will be shown to admins using CentCom. The default sources list is an example.
#CENTCOM_SOURCE_WHITELIST Beestation MRP,TGMC,FTL13
## AUTOADMIN
## The default admin rank
+6
View File
@@ -491,3 +491,9 @@ ALLOW_CUSTOM_SKINTONES
## Allows pAI custom holoforms
PAI_CUSTOM_HOLOFORMS
## How long in seconds after which a hard delete is treated as causing lag. This can be a float and supports a precision as low as nanoseconds.
#HARD_DELETES_OVERRUN_THRESHOLD 0.5
## Once a typepath causes overrun from hard deletes this many times, stop hard deleting it on garbage collection failures. (set to 0 to disable)
#HARD_DELETES_OVERRUN_LIMIT 0
+9 -9
View File
@@ -8,20 +8,20 @@ export BYOND_MAJOR=514
export BYOND_MINOR=1556
#rust_g git tag
export RUST_G_VERSION=0.4.8
#auxmos git tag
export AUXMOS_VERSION=v0.2.3
export RUST_G_VERSION=0.4.10
#node version
export NODE_VERSION=12
export NODE_VERSION_PRECISE=12.20.0
export NODE_VERSION_PRECISE=12.22.4
# SpacemanDMM git tag
export SPACEMAN_DMM_VERSION=suite-1.6
# Extools git tag
export EXTOOLS_VERSION=v0.0.7
export SPACEMAN_DMM_VERSION=suite-1.7
# Python version for mapmerge and other tools
export PYTHON_VERSION=3.6.8
# Auxmos git tag
export AUXMOS_VERSION=v0.2.3
# Extools git tag
export EXTOOLS_VERSION=v0.0.7
+60 -15
View File
@@ -50,6 +50,66 @@
-->
<div class="commit sansserif">
<h2 class="date">11 September 2021</h2>
<h3 class="author">LetterN updated:</h3>
<ul class="changes bgimages16">
<li class="code_imp">Tickers, GC, MC, FS updates</li>
<li class="code_imp">rust_g update. It is default that we use their urlencode/unencode now.</li>
<li class="code_imp">updates CBT to juke</li>
<li class="bugfix">CI Cache works properly now</li>
</ul>
<h3 class="author">Putnam3145 updated:</h3>
<ul class="changes bgimages16">
<li class="bugfix">Removed some crashes</li>
</ul>
<h3 class="author">SandPoot updated:</h3>
<ul class="changes bgimages16">
<li class="rscadd">Cyborg grippers now have a preview of the item you are holding. tweak: Cyborg grippers no longer drop using alt-click, instead, you try to drop the gripper to drop the item, and then you can drop the gripper. tweak: You can now use the "Pick up" verb on the right click menu to take items with a cyborg gripper.</li>
<li class="bugfix">You should be able to access the interface of airlock electronics once again as a borg.</li>
<li class="refactor">Reworks a lot of stuff that was being used on the grippers.</li>
</ul>
<h3 class="author">timothyteakettle updated:</h3>
<ul class="changes bgimages16">
<li class="bugfix">arachnid legs now show up properly</li>
</ul>
<h3 class="author">zeroisthebiggay updated:</h3>
<ul class="changes bgimages16">
<li class="imageadd">reinforcing the mining hardsuit with goliath hide now makes the sprite cooler</li>
<li class="imageadd">The SWAT helmet is now consistent between its front, side and back sprites for coloration.</li>
<li class="imageadd">new riot armor sprites</li>
</ul>
<h2 class="date">10 September 2021</h2>
<h3 class="author">BlueWildrose updated:</h3>
<ul class="changes bgimages16">
<li class="rscadd">CTRL + (combat mode) Right Click - positional dropping and item rotation.</li>
</ul>
<h3 class="author">keronshb updated:</h3>
<ul class="changes bgimages16">
<li class="rscadd">Readds Reebe</li>
<li class="rscadd">Added the ability to dye your hair with gradients by using a hair dye spray.</li>
<li class="rscadd">The new Colorist quirk, allowing you to spawn with a hair dye spray.</li>
<li class="rscadd">Adds Hair gradients to preferences</li>
<li class="imageadd">Three new hair gradients, a pair of shorter fades and a spiky wave.</li>
<li class="bugfix">Adds viewers for mask of madness so it doesn't wallhack</li>
</ul>
<h3 class="author">zeroisthebiggay updated:</h3>
<ul class="changes bgimages16">
<li class="bugfix">Replaced the DNA probe's old sprite (Hypospray) with a unique sprite</li>
<li class="imageadd">added some icons and images for hyposprays and medipens so they stand out</li>
<li class="imageadd">added inhands for the cautery, retractor, drapes and hemostat.</li>
<li class="imageadd">New icon and sprites for the DNA probe</li>
<li class="imageadd">Emergency survival boxes now have an unique blue sprite and description to tell them apart from regular boxes.</li>
<li class="imageadd">Added craftable normal/extended emergency oxygen tank boxes to put your emergency oxygen tank collection inside.</li>
<li class="imageadd">Emergency first aid kits now look visually consistent with full first aid kits.</li>
<li class="imageadd">Ports new Hypospray, Combat Autoinjector, Pestle, Mortar and Dropper sprites from Shiptest!</li>
<li class="imageadd">Adds a new sprite for pill bottles!</li>
<li class="imageadd">new surgical tool sprites</li>
<li class="imageadd">The cyborg toolset is now all new and improved, with a new coat of paint!</li>
<li class="imageadd">Updates pride hammer sprites!</li>
<li class="imageadd">Replaced old Mjolnir sprites with new Mjolnir sprites.</li>
</ul>
<h2 class="date">08 September 2021</h2>
<h3 class="author">keronshb updated:</h3>
<ul class="changes bgimages16">
@@ -444,21 +504,6 @@
<li class="balance">replaces the seed machine with a biogen in the survival pod.</li>
<li class="bugfix">corrected an issue regarding wrong pair of gloves in the pod for DYI wiring .</li>
</ul>
<h2 class="date">10 July 2021</h2>
<h3 class="author">WanderingFox95 updated:</h3>
<ul class="changes bgimages16">
<li class="imageadd">Returns the wheelchair sprites to having, you know, wheels?</li>
<li class="rscadd">A motorized wheelchair addsound: Chairwhoosh.</li>
</ul>
<h2 class="date">09 July 2021</h2>
<h3 class="author">MrJWhit updated:</h3>
<ul class="changes bgimages16">
<li class="bugfix">Made a light not exist on the same tile as a door on pubby.</li>
<li class="bugfix">Makes the RD APC automatically connect to the powernet with the correct wire on pubby.</li>
<li class="rscadd">Added another wall for the AM engine so it doesn't space the airlock roundstart.</li>
</ul>
</div>
<b>GoonStation 13 Development Team</b>
+55
View File
@@ -29917,3 +29917,58 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- imageadd: Recolors the parade outfit for the Captain
- imageadd: Adds a toggle option for the parade outfits
- bugfix: Observers can no longer highlight your items
2021-09-10:
BlueWildrose:
- rscadd: CTRL + (combat mode) Right Click - positional dropping and item rotation.
keronshb:
- rscadd: Readds Reebe
- rscadd: Added the ability to dye your hair with gradients by using a hair dye
spray.
- rscadd: The new Colorist quirk, allowing you to spawn with a hair dye spray.
- rscadd: Adds Hair gradients to preferences
- imageadd: Three new hair gradients, a pair of shorter fades and a spiky wave.
- bugfix: Adds viewers for mask of madness so it doesn't wallhack
zeroisthebiggay:
- bugfix: Replaced the DNA probe's old sprite (Hypospray) with a unique sprite
- imageadd: added some icons and images for hyposprays and medipens so they stand
out
- imageadd: added inhands for the cautery, retractor, drapes and hemostat.
- imageadd: New icon and sprites for the DNA probe
- imageadd: Emergency survival boxes now have an unique blue sprite and description
to tell them apart from regular boxes.
- imageadd: Added craftable normal/extended emergency oxygen tank boxes to put your
emergency oxygen tank collection inside.
- imageadd: Emergency first aid kits now look visually consistent with full first
aid kits.
- imageadd: Ports new Hypospray, Combat Autoinjector, Pestle, Mortar and Dropper
sprites from Shiptest!
- imageadd: Adds a new sprite for pill bottles!
- imageadd: new surgical tool sprites
- imageadd: The cyborg toolset is now all new and improved, with a new coat of paint!
- imageadd: Updates pride hammer sprites!
- imageadd: Replaced old Mjolnir sprites with new Mjolnir sprites.
2021-09-11:
LetterN:
- code_imp: Tickers, GC, MC, FS updates
- code_imp: rust_g update. It is default that we use their urlencode/unencode now.
- code_imp: updates CBT to juke
- bugfix: CI Cache works properly now
Putnam3145:
- bugfix: Removed some crashes
SandPoot:
- rscadd: 'Cyborg grippers now have a preview of the item you are holding. tweak:
Cyborg grippers no longer drop using alt-click, instead, you try to drop the
gripper to drop the item, and then you can drop the gripper. tweak: You can
now use the "Pick up" verb on the right click menu to take items with a cyborg
gripper.'
- bugfix: You should be able to access the interface of airlock electronics once
again as a borg.
- refactor: Reworks a lot of stuff that was being used on the grippers.
timothyteakettle:
- bugfix: arachnid legs now show up properly
zeroisthebiggay:
- imageadd: reinforcing the mining hardsuit with goliath hide now makes the sprite
cooler
- imageadd: The SWAT helmet is now consistent between its front, side and back sprites
for coloration.
- imageadd: new riot armor sprites
@@ -1,7 +0,0 @@
author: "keronshb"
delete-after: True
changes:
- rscadd: "Added the ability to dye your hair with gradients by using a hair dye spray."
- rscadd: "The new Colorist quirk, allowing you to spawn with a hair dye spray."
- rscadd: "Adds Hair gradients to preferences"
- imageadd: "Three new hair gradients, a pair of shorter fades and a spiky wave."
@@ -1,4 +0,0 @@
author: "BlueWildrose"
delete-after: True
changes:
- rscadd: "CTRL + (combat mode) Right Click - positional dropping and item rotation."
@@ -1,4 +0,0 @@
author: "keronshb"
delete-after: True
changes:
- rscadd: "Readds Reebe"
@@ -1,13 +0,0 @@
author: "zeroisthebiggay"
delete-after: True
changes:
- bugfix: "Replaced the DNA probe's old sprite (Hypospray) with a unique sprite"
- imageadd: "added some icons and images for hyposprays and medipens so they stand out"
- imageadd: "added inhands for the cautery, retractor, drapes and hemostat."
- imageadd: "New icon and sprites for the DNA probe"
- imageadd: "Emergency survival boxes now have an unique blue sprite and description to tell them apart from regular boxes."
- imageadd: "Added craftable normal/extended emergency oxygen tank boxes to put your emergency oxygen tank collection inside."
- imageadd: "Emergency first aid kits now look visually consistent with full first aid kits."
- imageadd: "Ports new Hypospray, Combat Autoinjector, Pestle, Mortar and Dropper sprites from Shiptest!"
- imageadd: "Adds a new sprite for pill bottles!"
- imageadd: "new surgical tool sprites"
@@ -1,5 +0,0 @@
author: "zeroisthebiggay"
delete-after: True
changes:
- imageadd: "Updates pride hammer sprites!"
- imageadd: "Replaced old Mjolnir sprites with new Mjolnir sprites."
@@ -1,4 +0,0 @@
author: "zeroisthebiggay"
delete-after: True
changes:
- imageadd: "The cyborg toolset is now all new and improved, with a new coat of paint!"
@@ -1,4 +0,0 @@
author: "keronshb"
delete-after: True
changes:
- bugfix: "Adds viewers for mask of madness so it doesn't wallhack"
@@ -0,0 +1,4 @@
author: "timothyteakettle"
delete-after: True
changes:
- rscadd: "allows custom taste text and color on the custom ice cream setting in the ice cream vat"
@@ -0,0 +1,4 @@
author: "LetterN"
delete-after: True
changes:
- bugfix: "readded cwc theme in the index.js once more"
@@ -0,0 +1,4 @@
author: "LetterN"
delete-after: True
changes:
- bugfix: "lowers the audio volume of ark sfx"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 KiB

After

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 171 KiB

BIN
View File
Binary file not shown.
+2
View File
@@ -103,6 +103,7 @@
#include "code\__DEFINES\rockpaperscissors.dm"
#include "code\__DEFINES\role_preferences.dm"
#include "code\__DEFINES\rust_g.dm"
#include "code\__DEFINES\rust_g_overrides.dm"
#include "code\__DEFINES\say.dm"
#include "code\__DEFINES\security_levels.dm"
#include "code\__DEFINES\shuttles.dm"
@@ -665,6 +666,7 @@
#include "code\datums\helper_datums\events.dm"
#include "code\datums\helper_datums\getrev.dm"
#include "code\datums\helper_datums\icon_snapshot.dm"
#include "code\datums\helper_datums\stack_end_detector.dm"
#include "code\datums\helper_datums\teleport.dm"
#include "code\datums\looping_sounds\_looping_sound.dm"
#include "code\datums\looping_sounds\item_sounds.dm"
+1
View File
@@ -16,6 +16,7 @@ import './styles/themes/paper.scss';
import './styles/themes/retro.scss';
import './styles/themes/syndicate.scss';
import './styles/themes/wizard.scss';
import './styles/themes/clockcult.scss';
import { perf } from 'common/perf';
import { setupHotReloading } from 'tgui-dev-server/link/client';
+10
View File
@@ -13,6 +13,14 @@ This build script is the recommended way to compile the game, including not only
The script will skip build steps whose inputs have not changed since the last run.
## Getting list of available targets
You can get a list of all targets that you can build by running the following command:
```
tools/build/build --help
```
## Dependencies
- On Windows, `BUILD.bat` will automatically install a private (vendored) copy of Node.
@@ -22,3 +30,5 @@ The script will skip build steps whose inputs have not changed since the last ru
## Why?
We used to include compiled versions of the tgui JavaScript code in the Git repository so that the project could be compiled using BYOND only. These pre-compiled files tended to have merge conflicts for no good reason. Using a build script lets us avoid this problem, while keeping builds convenient for people who are not modifying tgui.
This build script is based on [Juke Build](https://github.com/stylemistake/juke-build) - please follow the link and read the documentation for the project to understand how it works and how to contribute to this build script.
-1
View File
@@ -1 +0,0 @@
This directory is used to store temporary files to create binaries on linux
-2
View File
@@ -1,6 +1,4 @@
#!/bin/sh
#Build TGUI
set -e
cd "$(dirname "$0")"
exec ../bootstrap/node build.js "$@"
+2 -1
View File
@@ -1 +1,2 @@
@"%~dp0\..\bootstrap\node" "%~dp0\build.js"
@echo off
"%~dp0\..\bootstrap\node.bat" --experimental-modules "%~dp0\build.js" %*
+268 -200
View File
@@ -1,225 +1,293 @@
#!/usr/bin/env node
/**
* Build script for /tg/station 13 codebase.
*
* This script uses Juke Build, read the docs here:
* https://github.com/stylemistake/juke-build
*
* @file
* @copyright 2020 Aleksej Komarov
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
// Change working directory to project root
process.chdir(require('path').resolve(__dirname, '../../'));
import fs from 'fs';
import { DreamDaemon, DreamMaker } from './lib/byond.js';
import { yarn } from './lib/yarn.js';
import Juke from './juke/index.js';
// Validate NodeJS version
const NODE_VERSION = parseInt(process.versions.node.match(/(\d+)/)[1]);
const NODE_VERSION_TARGET = parseInt(require('fs')
.readFileSync('dependencies.sh', 'utf-8')
.match(/NODE_VERSION=(\d+)/)[1]);
if (NODE_VERSION < NODE_VERSION_TARGET) {
console.error('Your current Node.js version is out of date.');
console.error('You have two options:');
console.error(' a) Go to https://nodejs.org/ and install the latest LTS release of Node.js');
console.error(' b) Uninstall Node.js (our build system automatically downloads one)');
process.exit(1);
}
Juke.chdir('../..', import.meta.url);
Juke.setup({ file: import.meta.url }).then((code) => process.exit(code));
const STANDARD_BUILD = "Standard Build"
const TGS_BUILD = "TGS Build"
const ALL_MAPS_BUILD = "CI All Maps Build"
const TEST_RUN_BUILD = "CI Integration Tests Build"
const NO_DM_BUILD = "Except DM Build"
const DME_NAME = 'tgstation';
let BUILD_MODE = STANDARD_BUILD;
if (process.env.CBT_BUILD_MODE) {
switch (process.env.CBT_BUILD_MODE) {
case "ALL_MAPS":
BUILD_MODE = ALL_MAPS_BUILD
break;
case "TEST_RUN":
BUILD_MODE = TEST_RUN_BUILD
break;
case "TGS":
BUILD_MODE = TGS_BUILD
break;
case "NO_DM":
BUILD_MODE = NO_DM_BUILD
break;
default:
BUILD_MODE = process.env.CBT_BUILD_MODE
break;
}
}
console.log(`Starting CBT in ${BUILD_MODE} mode.`)
export const DefineParameter = new Juke.Parameter({
type: 'string[]',
alias: 'D',
});
const DME_NAME = 'tgstation'
export const PortParameter = new Juke.Parameter({
type: 'string',
alias: 'p',
});
// Main
// --------------------------------------------------------
export const CiParameter = new Juke.Parameter({
type: 'boolean',
});
const { resolveGlob, stat } = require('./cbt/fs');
const { exec } = require('./cbt/process');
const { Task, runTasks } = require('./cbt/task');
const { regQuery } = require('./cbt/winreg');
const fs = require('fs');
export const DmMapsIncludeTarget = new Juke.Target({
executes: async () => {
const folders = [
...Juke.glob('_maps/RandomRuins/**/*.dmm'),
...Juke.glob('_maps/RandomZLevels/**/*.dmm'),
...Juke.glob('_maps/shuttles/**/*.dmm'),
...Juke.glob('_maps/templates/**/*.dmm'),
];
const content = folders
.map((file) => file.replace('_maps/', ''))
.map((file) => `#include "${file}"`)
.join('\n') + '\n';
fs.writeFileSync('_maps/templates.dm', content);
},
});
const yarn = args => {
const yarnPath = resolveGlob('./tgui/.yarn/releases/yarn-*.cjs')[0]
.replace('/tgui/', '/');
return exec('node', [yarnPath, ...args], {
cwd: './tgui',
});
};
export const DmTarget = new Juke.Target({
dependsOn: ({ get }) => [
get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget,
],
inputs: [
'_maps/map_files/generic/**',
'code/**',
'goon/**',
'html/**',
'icons/**',
'interface/**',
`${DME_NAME}.dme`,
],
outputs: [
`${DME_NAME}.dmb`,
`${DME_NAME}.rsc`,
],
parameters: [DefineParameter],
executes: async ({ get }) => {
const defines = get(DefineParameter);
if (defines.length > 0) {
Juke.logger.info('Using defines:', defines.join(', '));
}
await DreamMaker(`${DME_NAME}.dme`, {
defines: ['CBT', ...defines],
});
},
});
/** Installs all tgui dependencies */
const taskYarn = new Task('yarn')
// The following dependencies skip what could be considered an important
// step in Yarn: it verifies the integrity of cache. With this setup, if
// cache ever becomes corrupted, your only option is to clean build.
.depends('tgui/.yarn/+(cache|releases|plugins|sdks)/**/*')
.depends('tgui/**/package.json')
.depends('tgui/yarn.lock')
// Phony target (automatically created at the end of the task)
.provides('tgui/.yarn/install-target')
.build(() => yarn(['install']));
export const DmTestTarget = new Juke.Target({
dependsOn: ({ get }) => [
get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget,
],
executes: async ({ get }) => {
const defines = get(DefineParameter);
if (defines.length > 0) {
Juke.logger.info('Using defines:', defines.join(', '));
}
fs.copyFileSync(`${DME_NAME}.dme`, `${DME_NAME}.test.dme`);
await DreamMaker(`${DME_NAME}.test.dme`, {
defines: ['CBT', 'CIBUILDING', ...defines],
});
Juke.rm('data/logs/ci', { recursive: true });
await DreamDaemon(
`${DME_NAME}.test.dmb`,
'-close', '-trusted', '-verbose',
'-params', 'log-directory=ci'
);
Juke.rm('*.test.*');
try {
const cleanRun = fs.readFileSync('data/logs/ci/clean_run.lk', 'utf-8');
console.log(cleanRun);
}
catch (err) {
Juke.logger.error('Test run was not clean, exiting');
throw new Juke.ExitCode(1);
}
},
});
/** Builds svg fonts */
const taskTgfont = new Task('tgfont')
.depends('tgui/.yarn/install-target')
.depends('tgui/packages/tgfont/**/*.+(js|cjs|svg)')
.depends('tgui/packages/tgfont/package.json')
.provides('tgui/packages/tgfont/dist/tgfont.css')
.provides('tgui/packages/tgfont/dist/tgfont.eot')
.provides('tgui/packages/tgfont/dist/tgfont.woff2')
.build(() => yarn(['workspace', 'tgfont', 'build']));
export const YarnTarget = new Juke.Target({
inputs: [
'tgui/.yarn/+(cache|releases|plugins|sdks)/**/*',
'tgui/**/package.json',
'tgui/yarn.lock',
],
outputs: [
'tgui/.yarn/install-target',
],
executes: async () => {
await yarn('install');
},
});
/** Builds tgui */
const taskTgui = new Task('tgui')
.depends('tgui/.yarn/install-target')
.depends('tgui/webpack.config.js')
.depends('tgui/**/package.json')
.depends('tgui/packages/**/*.+(js|cjs|ts|tsx|scss)')
.provides('tgui/public/tgui.bundle.css')
.provides('tgui/public/tgui.bundle.js')
.provides('tgui/public/tgui-common.bundle.js')
.provides('tgui/public/tgui-panel.bundle.css')
.provides('tgui/public/tgui-panel.bundle.js')
.build(async () => {
await yarn(['run', 'webpack-cli', '--mode=production']);
});
export const TgFontTarget = new Juke.Target({
dependsOn: [YarnTarget],
inputs: [
'tgui/.yarn/install-target',
'tgui/packages/tgfont/**/*.+(js|cjs|svg)',
'tgui/packages/tgfont/package.json',
],
outputs: [
'tgui/packages/tgfont/dist/tgfont.css',
'tgui/packages/tgfont/dist/tgfont.eot',
'tgui/packages/tgfont/dist/tgfont.woff2',
],
executes: async () => {
await yarn('workspace', 'tgfont', 'build');
},
});
export const TguiTarget = new Juke.Target({
dependsOn: [YarnTarget],
inputs: [
'tgui/.yarn/install-target',
'tgui/webpack.config.js',
'tgui/**/package.json',
'tgui/packages/**/*.+(js|cjs|ts|tsx|scss)',
],
outputs: [
'tgui/public/tgui.bundle.css',
'tgui/public/tgui.bundle.js',
'tgui/public/tgui-panel.bundle.css',
'tgui/public/tgui-panel.bundle.js',
],
executes: async () => {
await yarn('webpack-cli', '--mode=production');
},
});
export const TguiEslintTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: async ({ args }) => {
await yarn(
'eslint', 'packages',
'--fix', '--ext', '.js,.cjs,.ts,.tsx',
...args
);
},
});
export const TguiTscTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: async () => {
await yarn('tsc');
},
});
export const TguiTestTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: async ({ args }) => {
await yarn('jest', ...args);
},
});
export const TguiLintTarget = new Juke.Target({
dependsOn: [YarnTarget, TguiEslintTarget, TguiTscTarget, TguiTestTarget],
});
export const TguiDevTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: async ({ args }) => {
await yarn('node', 'packages/tgui-dev-server/index.js', ...args);
},
});
export const TguiAnalyzeTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: async () => {
await yarn('webpack-cli', '--mode=production', '--analyze');
},
});
export const TestTarget = new Juke.Target({
dependsOn: [DmTestTarget, TguiTestTarget],
});
export const LintTarget = new Juke.Target({
dependsOn: [TguiLintTarget],
});
export const BuildTarget = new Juke.Target({
dependsOn: [TguiTarget, TgFontTarget, DmTarget],
});
export const ServerTarget = new Juke.Target({
dependsOn: [BuildTarget],
executes: async ({ get }) => {
const port = get(PortParameter) || '1337';
await DreamDaemon(`${DME_NAME}.dmb`, port, '-trusted');
},
});
export const AllTarget = new Juke.Target({
dependsOn: [TestTarget, LintTarget, BuildTarget],
});
/**
* Removes the immediate build junk to produce clean builds.
*/
export const CleanTarget = new Juke.Target({
executes: async () => {
Juke.rm('*.dmb');
Juke.rm('*.rsc');
Juke.rm('*.mdme');
Juke.rm('*.mdme*');
Juke.rm('*.m.*');
Juke.rm('_maps/templates.dm');
Juke.rm('tgui/public/.tmp', { recursive: true });
Juke.rm('tgui/public/*.map');
Juke.rm('tgui/public/*.chunk.*');
Juke.rm('tgui/public/*.bundle.*');
Juke.rm('tgui/public/*.hot-update.*');
Juke.rm('tgui/packages/tgfont/dist', { recursive: true });
Juke.rm('tgui/.yarn/cache', { recursive: true });
Juke.rm('tgui/.yarn/unplugged', { recursive: true });
Juke.rm('tgui/.yarn/webpack', { recursive: true });
Juke.rm('tgui/.yarn/build-state.yml');
Juke.rm('tgui/.yarn/install-state.gz');
Juke.rm('tgui/.yarn/install-target');
Juke.rm('tgui/.pnp.*');
},
});
/**
* Removes more junk at expense of much slower initial builds.
*/
export const DistCleanTarget = new Juke.Target({
dependsOn: [CleanTarget],
executes: async () => {
Juke.logger.info('Cleaning up data/logs');
Juke.rm('data/logs', { recursive: true });
Juke.logger.info('Cleaning up bootstrap cache');
Juke.rm('tools/bootstrap/.cache', { recursive: true });
Juke.logger.info('Cleaning up global yarn cache');
await yarn('cache', 'clean', '--all');
},
});
/**
* Prepends the defines to the .dme.
* Does not clean them up, as this is intended for TGS which
* clones new copies anyway.
*/
const taskPrependDefines = (...defines) => new Task('prepend-defines')
.build(async () => {
const dmeContents = fs.readFileSync(`${DME_NAME}.dme`);
const textToWrite = defines.map(define => `#define ${define}\n`);
fs.writeFileSync(`${DME_NAME}.dme`, `${textToWrite}\n${dmeContents}`);
});
const prependDefines = (...defines) => {
const dmeContents = fs.readFileSync(`${DME_NAME}.dme`);
const textToWrite = defines.map(define => `#define ${define}\n`);
fs.writeFileSync(`${DME_NAME}.dme`, `${textToWrite}\n${dmeContents}`);
};
const taskDm = (...injectedDefines) => new Task('dm')
.depends('_maps/map_files/generic/**')
.depends('code/**')
.depends('goon/**')
.depends('html/**')
.depends('icons/**')
.depends('interface/**')
.depends(process.platform === 'win32' ? 'auxmos.*' : 'libauxmos.*')
.depends('tgui/public/tgui.html')
.depends('tgui/public/*.bundle.*')
.depends(`${DME_NAME}.dme`)
.provides(`${DME_NAME}.dmb`)
.provides(`${DME_NAME}.rsc`)
.build(async () => {
const dmPath = await (async () => {
// Search in array of paths
const paths = [
...((process.env.DM_EXE && process.env.DM_EXE.split(',')) || []),
'C:\\Program Files\\BYOND\\bin\\dm.exe',
'C:\\Program Files (x86)\\BYOND\\bin\\dm.exe',
['reg', 'HKLM\\Software\\Dantom\\BYOND', 'installpath'],
['reg', 'HKLM\\SOFTWARE\\WOW6432Node\\Dantom\\BYOND', 'installpath'],
];
const isFile = path => {
try {
const fstat = stat(path);
return fstat && fstat.isFile();
}
catch (err) {}
return false;
};
for (let path of paths) {
// Resolve a registry key
if (Array.isArray(path)) {
const [type, ...args] = path;
path = await regQuery(...args);
}
if (!path) {
continue;
}
// Check if path exists
if (isFile(path)) {
return path;
}
if (isFile(path + '/dm.exe')) {
return path + '/dm.exe';
}
if (isFile(path + '/bin/dm.exe')) {
return path + '/bin/dm.exe';
}
}
// Default paths
return (
process.platform === 'win32' && 'dm.exe'
|| 'DreamMaker'
);
})();
if (injectedDefines.length) {
const injectedContent = injectedDefines
.map(x => `#define ${x}\n`)
.join('')
// Create mdme file
fs.writeFileSync(`${DME_NAME}.mdme`, injectedContent)
// Add the actual dme content
const dme_content = fs.readFileSync(`${DME_NAME}.dme`)
fs.appendFileSync(`${DME_NAME}.mdme`, dme_content)
await exec(dmPath, [`${DME_NAME}.mdme`]);
// Rename dmb
fs.renameSync(`${DME_NAME}.mdme.dmb`, `${DME_NAME}.dmb`)
// Rename rsc
fs.renameSync(`${DME_NAME}.mdme.rsc`, `${DME_NAME}.rsc`)
// Remove mdme
fs.unlinkSync(`${DME_NAME}.mdme`)
}
else {
await exec(dmPath, [`${DME_NAME}.dme`]);
}
});
export const TgsTarget = new Juke.Target({
dependsOn: [TguiTarget, TgFontTarget],
executes: async () => {
Juke.logger.info('Prepending TGS define');
prependDefines('TGS');
},
});
// Frontend
let tasksToRun = [
taskYarn,
taskTgfont,
taskTgui,
];
switch (BUILD_MODE) {
case STANDARD_BUILD:
tasksToRun.push(taskDm('CBT'));
break;
case TGS_BUILD:
tasksToRun.push(taskPrependDefines('TGS'));
break;
case ALL_MAPS_BUILD:
tasksToRun.push(taskDm('CBT','CIBUILDING','CITESTING','ALL_MAPS'));
break;
case TEST_RUN_BUILD:
tasksToRun.push(taskDm('CBT','CIBUILDING'));
break;
case NO_DM_BUILD:
break;
default:
console.error(`Unknown build mode : ${BUILD_MODE}`)
break;
}
const TGS_MODE = process.env.CBT_BUILD_MODE === 'TGS';
runTasks(tasksToRun);
export default TGS_MODE ? TgsTarget : BuildTarget;
-75
View File
@@ -1,75 +0,0 @@
#!/bin/sh
#Detect OS and use corresponding package manager for dependencies. Currently only works for arch, debian/ubuntu, and RHEL/fedora/CentOS
if [[ -f '/etc/arch-release' ]]; then
echo -ne '\n y' | sudo pacman --needed -Sy base-devel git curl nodejs unzip
fi
if [[ -f '/etc/debian version' ]]; then
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y build-essential git curl lib32z1 pkg-config libssl-dev:i386 libssl-dev nodejs unzip g++-multilib libc6-i386 libstdc++6:i386
fi
if [[ -f '/etc/centos-release' ]] || [[ -f '/etc/fedora-release' ]]; then #DNF should work for both of these
sudo dnf --refresh install make automake gcc gcc-c++ kernel-devel git curl unzip glibc-devel.i686 openssl-devel.i686 libgcc.i686 libstdc++-devel.i686
fi
cd binaries
#Install rust if not present
if ! [ -x "$has_cargo" ]; then
echo "Installing rust..."
curl https://sh.rustup.rs -sSf | sh -s -- -y
. ~/.profile
fi
#Download/update rust-g repo
if [ ! -d "rust-g" ]; then
echo "Cloning rust-g..."
git clone https://github.com/tgstation/rust-g
cd rust-g
~/.cargo/bin/rustup target add i686-unknown-linux-gnu
else
echo "Fetching rust-g..."
cd rust-g
git fetch
~/.cargo/bin/rustup target add i686-unknown-linux-gnu
fi
#Compile and move rust-g binary to repo root
echo "Deploying rust-g..."
git checkout "$RUST_G_VERSION"
env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --target=i686-unknown-linux-gnu
mv target/i686-unknown-linux-gnu/release/librust_g.so ../../../../librust_g.so
cd ..
#Download/update auxmos repo
if [ ! -d "auxmos" ]; then
echo "Cloning auxmos..."
git clone https://github.com/Putnam3145/auxmos
cd auxmos
~/.cargo/bin/rustup target add i686-unknown-linux-gnu
else
echo "Fetching auxmos..."
cd auxmos
git fetch
~/.cargo/bin/rustup target add i686-unknown-linux-gnu
fi
#Compile and move auxmos binary to repo root
echo "Deploying auxmos..."
git checkout "$AUXMOS_VERSION"
env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo rustc --release --target=i686-unknown-linux-gnu --features all_reaction_hooks,explosive_decompression -- -C target-cpu=native
mv target/i686-unknown-linux-gnu/release/libauxmos.so ../../../../libauxmos.so
cd ../..
#Install BYOND
cd ../..
./tools/ci/install_byond.sh
source $HOME/BYOND/byond/bin/byondsetup
cd tools/build
#Build TGUI
set -e
cd "$(dirname "$0")"
exec ../bootstrap/node build.js "$@"

Some files were not shown because too many files have changed in this diff Show More