diff --git a/code/__DEFINES/_versions.dm b/code/__DEFINES/_versions.dm new file mode 100644 index 00000000000..83a18de6f73 --- /dev/null +++ b/code/__DEFINES/_versions.dm @@ -0,0 +1,2 @@ +/// Version of RUST-G that this codebase wants +#define RUST_G_VERSION "0.5.0-P" diff --git a/code/__DEFINES/job.dm b/code/__DEFINES/job.dm index 029d15f4895..62920ed2a40 100644 --- a/code/__DEFINES/job.dm +++ b/code/__DEFINES/job.dm @@ -7,58 +7,59 @@ #define JOBCAT_ENGSEC (1<<0) -#define JOB_CAPTAIN (1<<0) -#define JOB_HOS (1<<1) -#define JOB_WARDEN (1<<2) +#define JOB_CAPTAIN (1<<0) +#define JOB_HOS (1<<1) +#define JOB_WARDEN (1<<2) #define JOB_DETECTIVE (1<<3) -#define JOB_OFFICER (1<<4) +#define JOB_OFFICER (1<<4) #define JOB_CHIEF (1<<5) #define JOB_ENGINEER (1<<6) #define JOB_ATMOSTECH (1<<7) -#define JOB_AI (1<<8) -#define JOB_CYBORG (1<<9) -#define JOB_CENTCOM (1<<10) +#define JOB_AI (1<<8) +#define JOB_CYBORG (1<<9) +#define JOB_CENTCOM (1<<10) #define JOB_SYNDICATE (1<<11) +#define JOB_JUDGE (1<<12) #define JOBCAT_MEDSCI (1<<1) -#define JOB_RD (1<<0) +#define JOB_RD (1<<0) #define JOB_SCIENTIST (1<<1) -#define JOB_CHEMIST (1<<2) -#define JOB_CMO (1<<3) -#define JOB_DOCTOR (1<<4) -#define JOB_GENETICIST (1<<5) -#define JOB_VIROLOGIST (1<<6) +#define JOB_CHEMIST (1<<2) +#define JOB_CMO (1<<3) +#define JOB_DOCTOR (1<<4) +#define JOB_GENETICIST (1<<5) +#define JOB_VIROLOGIST (1<<6) #define JOB_PSYCHIATRIST (1<<7) -#define JOB_ROBOTICIST (1<<8) +#define JOB_ROBOTICIST (1<<8) #define JOB_PARAMEDIC (1<<9) -#define JOB_CORONER (1<<10) +#define JOB_CORONER (1<<10) #define JOBCAT_SUPPORT (1<<2) -#define JOB_HOP (1<<0) +#define JOB_HOP (1<<0) #define JOB_BARTENDER (1<<1) #define JOB_BOTANIST (1<<2) #define JOB_CHEF (1<<3) -#define JOB_JANITOR (1<<4) +#define JOB_JANITOR (1<<4) #define JOB_LIBRARIAN (1<<5) #define JOB_QUARTERMASTER (1<<6) #define JOB_CARGOTECH (1<<7) #define JOB_MINER (1<<8) -#define JOB_LAWYER (1<<9) +#define JOB_LAWYER (1<<9) #define JOB_CHAPLAIN (1<<10) #define JOB_CLOWN (1<<11) #define JOB_MIME (1<<12) #define JOB_CIVILIAN (1<<13) #define JOB_EXPLORER (1<<14) -#define JOBCAT_KARMA (1<<3) +#define JOBCAT_KARMA (1<<3) #define JOB_NANO (1<<0) -#define JOB_BLUESHIELD (1<<1) -#define JOB_BARBER (1<<3) +#define JOB_BLUESHIELD (1<<1) +#define JOB_BARBER (1<<3) // #define JOB_MECHANIC (1<<4) // AA07 2021-10-02 - Removed: Kept for history sake -#define JOB_BRIGDOC (1<<5) -#define JOB_JUDGE (1<<6) -// #define JOB_PILOT (1<<7) // AA07 2021-10-02 - Removed: Kept for history sake +#define JOB_BRIGDOC (1<<5) +// #define JOB_JUDGE (1<<6) // AA07 2021-10-09 - Moved to ENGSEC (Non karma): Define kept for history sake +// #define JOB_PILOT (1<<7) // AA07 2021-10-02 - Removed: Kept for history sake diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm index 6a331a6131f..4ee84eda941 100644 --- a/code/__DEFINES/rust_g.dm +++ b/code/__DEFINES/rust_g.dm @@ -1,80 +1,130 @@ +// rust_g.dm - DM API for rust_g extension library +// +// To configure, create a `rust_g.config.dm` and set what you care about from +// the following options: +// +// #define RUST_G "path/to/rust_g" +// Override the .dll/.so detection logic with a fixed path or with detection +// logic of your own. +// +// #define RUSTG_OVERRIDE_BUILTINS +// Enable replacement rust-g functions for certain builtins. Off by default. + #ifndef RUST_G -/// Locator for the RUSTG DLL or SO depending on system type. Override if needed. -#define RUST_G (world.system_type == UNIX ? "./librust_g.so" : "./rust_g.dll") +// Default automatic RUST_G detection. +// On Windows, looks in the standard places for `rust_g.dll`. +// On Linux, looks in `.`, `$LD_LIBRARY_PATH`, and `~/.byond/bin` for either of +// `librust_g.so` (preferred) or `rust_g` (old). + +/* This comment bypasses grep checks */ /var/__rust_g + +/proc/__detect_rust_g() + if (world.system_type == UNIX) + if (fexists("./librust_g.so")) + // No need for LD_LIBRARY_PATH badness. + return __rust_g = "./librust_g.so" + else if (fexists("./rust_g")) + // Old dumb filename. + return __rust_g = "./rust_g" + else if (fexists("[world.GetConfig("env", "HOME")]/.byond/bin/rust_g")) + // Old dumb filename in `~/.byond/bin`. + return __rust_g = "rust_g" + else + // It's not in the current directory, so try others + return __rust_g = "librust_g.so" + else + return __rust_g = "rust_g.dll" + +#define RUST_G (__rust_g || __detect_rust_g()) #endif -// Gets the version of RUSTG +/// Gets the version of rust_g /proc/rustg_get_version() return call(RUST_G, "get_version")() -// Defines for internal job subsystem // -#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET" -#define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB" -#define RUSTG_JOB_ERROR "JOB PANICKED" +// Cellular Noise Operations // -// DMI related operations // +/** + * This proc generates a cellular automata noise grid which can be used in procedural generation methods. + * + * Returns a single string that goes row by row, with values of 1 representing an alive cell, and a value of 0 representing a dead cell. + * + * Arguments: + * * percentage: The chance of a turf starting closed + * * smoothing_iterations: The amount of iterations the cellular automata simulates before returning the results + * * birth_limit: If the number of neighboring cells is higher than this amount, a cell is born + * * death_limit: If the number of neighboring cells is lower than this amount, a cell dies + * * width: The width of the grid. + * * height: The height of the grid. + */ +#define rustg_cnoise_generate(percentage, smoothing_iterations, birth_limit, death_limit, width, height) \ + call(RUST_G, "cnoise_generate")(percentage, smoothing_iterations, birth_limit, death_limit, width, height) + +// DMI Operations // #define rustg_dmi_strip_metadata(fname) call(RUST_G, "dmi_strip_metadata")(fname) #define rustg_dmi_create_png(path, width, height, data) call(RUST_G, "dmi_create_png")(path, width, height, data) +#define rustg_dmi_resize_png(path, width, height, resizetype) call(RUST_G, "dmi_resize_png")(path, width, height, resizetype) -// Noise related operations // -#define rustg_noise_get_at_coordinates(seed, x, y) call(RUST_G, "noise_get_at_coordinates")(seed, x, y) - -// File related operations // +// File Operations // #define rustg_file_read(fname) call(RUST_G, "file_read")(fname) +#define rustg_file_exists(fname) call(RUST_G, "file_exists")(fname) #define rustg_file_write(text, fname) call(RUST_G, "file_write")(text, fname) #define rustg_file_append(text, fname) call(RUST_G, "file_append")(text, fname) #ifdef RUSTG_OVERRIDE_BUILTINS -#define file2text(fname) rustg_file_read(fname) -#define text2file(text, fname) rustg_file_append(text, fname) + #define file2text(fname) rustg_file_read("[fname]") + #define text2file(text, fname) rustg_file_append(text, "[fname]") #endif -// Git related operations // +// Git Operations // #define rustg_git_revparse(rev) call(RUST_G, "rg_git_revparse")(rev) #define rustg_git_commit_date(rev) call(RUST_G, "rg_git_commit_date")(rev) -// Hash related operations // +// Hashing Operations // #define rustg_hash_string(algorithm, text) call(RUST_G, "hash_string")(algorithm, text) #define rustg_hash_file(algorithm, fname) call(RUST_G, "hash_file")(algorithm, fname) +#define rustg_hash_generate_totp(seed) call(RUST_G, "generate_totp")(seed) +#define rustg_hash_generate_totp_tolerance(seed, tolerance) call(RUST_G, "generate_totp_tolerance")(seed, tolerance) #define RUSTG_HASH_MD5 "md5" #define RUSTG_HASH_SHA1 "sha1" #define RUSTG_HASH_SHA256 "sha256" #define RUSTG_HASH_SHA512 "sha512" +#define RUSTG_HASH_XXH64 "xxh64" +#define RUSTG_HASH_BASE64 "base64" #ifdef RUSTG_OVERRIDE_BUILTINS -#define md5(thing) (isfile(thing) ? rustg_hash_file(RUSTG_HASH_MD5, "[thing]") : rustg_hash_string(RUSTG_HASH_MD5, thing)) + #define md5(thing) (isfile(thing) ? rustg_hash_file(RUSTG_HASH_MD5, "[thing]") : rustg_hash_string(RUSTG_HASH_MD5, thing)) #endif -// Logging stuff // -#define rustg_log_write(fname, text) call(RUST_G, "log_write")(fname, text) -/proc/rustg_log_close_all() return call(RUST_G, "log_close_all")() - -// URL encoding stuff -#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 - -// HTTP library stuff // +// HTTP Operations // #define RUSTG_HTTP_METHOD_GET "get" #define RUSTG_HTTP_METHOD_PUT "put" #define RUSTG_HTTP_METHOD_DELETE "delete" #define RUSTG_HTTP_METHOD_PATCH "patch" #define RUSTG_HTTP_METHOD_HEAD "head" #define RUSTG_HTTP_METHOD_POST "post" - -// Commented out because this thing locks up the entire DD process when you use it -// DO NOT USE FOR THE LOVE OF GOD -// #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) /proc/rustg_create_async_http_client() return call(RUST_G, "start_http_client")() /proc/rustg_close_async_http_client() return call(RUST_G, "shutdown_http_client")() -// SQL stuff // +// Jobs Subsystem Operations // +#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET" +#define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB" +#define RUSTG_JOB_ERROR "JOB PANICKED" + +// JSON Operations // +#define rustg_json_is_valid(text) (call(RUST_G, "json_is_valid")(text) == "true") + +// Logging Operations // +#define rustg_log_write(fname, text) call(RUST_G, "log_write")(fname, text) +/proc/rustg_log_close_all() return call(RUST_G, "log_close_all")() + +// Noise Operations // +#define rustg_noise_get_at_coordinates(seed, x, y) call(RUST_G, "noise_get_at_coordinates")(seed, x, y) + +// SQL Opeartions // #define rustg_sql_connect_pool(options) call(RUST_G, "sql_connect_pool")(options) #define rustg_sql_query_async(handle, query, params) call(RUST_G, "sql_query_async")(handle, query, params) #define rustg_sql_query_blocking(handle, query, params) call(RUST_G, "sql_query_blocking")(handle, query, params) @@ -82,8 +132,19 @@ #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]") -// toml2json stuff // -#define rustg_toml2json(tomlfile) call(RUST_G, "toml2json")(tomlfile) +// TOML Operations // +#define rustg_read_toml_file(path) json_decode(call(RUST_G, "toml_file_to_json")(path) || "null") + +// Unzip Operations // +#define rustg_unzip_download_async(url, unzip_directory) call(RUST_G, "unzip_download_async")(url, unzip_directory) +#define rustg_unzip_check(job_id) call(RUST_G, "unzip_check")("[job_id]") + +// URL Encoder/Decoder Operations // +#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 -// RUSTG Version // -#define RUST_G_VERSION "0.4.5-P3" diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index c88dd0671b1..daf7bfe6b52 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -622,6 +622,13 @@ so as to remain in compliance with the most up-to-date laws." stone = null return ..() +/obj/screen/alert/notify_mapvote + name = "Map Vote" + desc = "Vote on which map you would like to play on next!" + icon_state = "map_vote" + +/obj/screen/alert/notify_mapvote/Click() + SSvote.browse_to(usr.client) //OBJECT-BASED diff --git a/code/controllers/configuration/configuration_core.dm b/code/controllers/configuration/configuration_core.dm index 81c2c6787bf..0550280527b 100644 --- a/code/controllers/configuration/configuration_core.dm +++ b/code/controllers/configuration/configuration_core.dm @@ -97,7 +97,7 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new()) var/config_file = "config/config.toml" if(!fexists(config_file)) config_file = "config/example/config.toml" // Fallback to example if user hasnt setup config properly - var/raw_json = rustg_toml2json(config_file) + var/raw_json = rustg_read_toml_file(config_file) raw_data = json_decode(raw_json) // Now pass through all our stuff @@ -141,7 +141,7 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new()) DIRECT_OUTPUT(world.log, "Overrides found for this instance. Loading them.") var/start = start_watch() // Time tracking - var/raw_json = rustg_toml2json(override_file) + var/raw_json = rustg_read_toml_file(override_file) raw_data = json_decode(raw_json) // Now safely load our overrides. diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 28f69ba1795..5bb1d0eb69f 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -408,12 +408,65 @@ SUBSYSTEM_DEF(ticker) if(player.mind.assigned_role != player.mind.special_role) SSjobs.AssignRank(player, player.mind.assigned_role, FALSE) SSjobs.EquipRank(player, player.mind.assigned_role, FALSE) - EquipCustomItems(player) + equip_cuis(player) + if(captainless) for(var/mob/M in GLOB.player_list) if(!isnewplayer(M)) to_chat(M, "Captainship not forced on anyone.") +/datum/controller/subsystem/ticker/proc/equip_cuis(mob/living/carbon/human/H) + if(!H.client) + return // If they are spawning without a client (somehow), they *cant* have a CUI list + for(var/datum/custom_user_item/cui in H.client.cui_entries) + // Skip items with invalid character names + if((cui.characer_name != H.real_name) && !cui.all_characters_allowed) + continue + + var/ok = FALSE + + if(!cui.all_jobs_allowed) + var/alt_blocked = FALSE + if(H.mind.role_alt_title) + if(!(H.mind.role_alt_title in cui.allowed_jobs)) + alt_blocked = TRUE + if(!(H.mind.assigned_role in cui.allowed_jobs) || alt_blocked) + continue + + var/obj/item/I = new cui.object_typepath() + var/name_override = cui.item_name_override + var/desc_override = cui.item_desc_override + + if(name_override) + I.name = name_override + if(desc_override) + I.desc = desc_override + + if(istype(H.back, /obj/item/storage)) // Try to place it in something on the mob's back + var/obj/item/storage/S = H.back + if(length(S.contents) < S.storage_slots) + I.forceMove(H.back) + ok = TRUE + to_chat(H, "Your [I.name] has been added to your [H.back.name].") + + if(!ok) + for(var/obj/item/storage/S in H.contents) // Try to place it in any item that can store stuff, on the mob. + if(length(S.contents) < S.storage_slots) + I.forceMove(S) + ok = TRUE + to_chat(H, "Your [I.name] has been added to your [S.name].") + break + + if(!ok) // Finally, since everything else failed, place it on the ground + var/turf/T = get_turf(H) + if(T) + I.forceMove(T) + to_chat(H, "Your [I.name] is on the [T.name] below you.") + else + to_chat(H, "Your [I.name] couldnt spawn anywhere on you or even on the floor below you. Please file a bug report.") + qdel(I) + + /datum/controller/subsystem/ticker/proc/send_tip_of_the_round() var/m if(selected_tip) diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 0185aa5931b..a93d7e443c8 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -266,8 +266,12 @@ SUBSYSTEM_DEF(vote) Click here or type vote to place your vote. You have [GLOB.configuration.vote.vote_time / 10] seconds to vote."}) switch(vote_type) - if("crew transfer", "gamemode", "custom", "map") + if("crew transfer", "gamemode", "custom") SEND_SOUND(world, sound('sound/ambience/alarm4.ogg')) + if("map") + SEND_SOUND(world, sound('sound/ambience/alarm4.ogg')) + for(var/mob/M in GLOB.player_list) + M.throw_alert("Map Vote", /obj/screen/alert/notify_mapvote, timeout_override = GLOB.configuration.vote.vote_time) if(mode == "gamemode" && SSticker.ticker_going) SSticker.ticker_going = FALSE to_chat(world, "Round start has been delayed.") diff --git a/code/datums/action.dm b/code/datums/action.dm index be7f82494f9..4ba27fb037d 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -423,6 +423,13 @@ /datum/action/item_action/remove_badge name = "Remove Holobadge" + +// Clown Acrobat Shoes +/datum/action/item_action/slipping + name = "Tactical Slip" + desc = "Activates the clown shoes' ankle-stimulating module, allowing the user to do a short slip forward going under anyone." + button_icon_state = "clown" + // Jump boots /datum/action/item_action/bhop name = "Activate Jump Boots" diff --git a/code/datums/custom_user_item.dm b/code/datums/custom_user_item.dm new file mode 100644 index 00000000000..9680a990488 --- /dev/null +++ b/code/datums/custom_user_item.dm @@ -0,0 +1,66 @@ +/** + * # Custom User Item + * + * Holder for CUIs + * + * This datum is a holder that is essentially a "model" of the `customuseritems` + * database table, and is used for giving people their CUIs on spawn. + * It is instanced as part of the client data loading framework on the client. + * + */ +/datum/custom_user_item + /// Can this be used on all characters? + var/all_characters_allowed = FALSE + /// Name of the character that can have this item. + var/characer_name + /// Are all jobs allowed? + var/all_jobs_allowed = FALSE + /// List of allowed jobs + var/list/allowed_jobs = list() + /// Custom item typepath + var/object_typepath + /// Custom item name override + var/item_name_override + /// Custom item description override + var/item_desc_override + /// Raw job mask + var/raw_job_mask + + +/** + * CUI Info Parser + * + * Parses all the raw info into usable stuff, and also does validity checks + * Returns TRUE if its a valid item, and FALSE if not + * + * Arguments: + * * owning_ckey - Player who owns this item. Used for logging purposes. + */ +/datum/custom_user_item/proc/parse_info(owning_ckey) + . = FALSE // Setting this here means it will return false even if it runtimes + + // Sort path + if(!object_typepath || !ispath(object_typepath)) + stack_trace("Incorrect database entry found in table 'customuseritems' path value is [object_typepath ? object_typepath : "(NULL)"], which doesnt exist. Ask the host to look at CUI entries for [owning_ckey]") + return + + // Sort job mask + if(raw_job_mask == "*") + all_jobs_allowed = TRUE + else + var/list/local_allowed_jobs = splittext(raw_job_mask, ",") + for(var/i in 1 to length(local_allowed_jobs)) + if(istext(local_allowed_jobs[i])) + local_allowed_jobs[i] = trim(local_allowed_jobs[i]) + + allowed_jobs = local_allowed_jobs + + // Sort character name + if(characer_name == "*") + all_characters_allowed = TRUE + + return TRUE + + +/datum/custom_user_item/vv_edit_var(var_name, var_value) + return FALSE // fuck off diff --git a/code/datums/http.dm b/code/datums/http.dm index e7ba1620854..6679b859c65 100644 --- a/code/datums/http.dm +++ b/code/datums/http.dm @@ -19,6 +19,8 @@ var/headers /// URL that the request is being sent to var/url + /// If present, response body will be saved to this file. + var/output_file /// The raw response, which will be decoeded into a [/datum/http_response] var/_raw_response /// Callback for executing after async requests. Will be called with an argument of [/datum/http_response] as first argument @@ -42,7 +44,7 @@ THE METHODS IN THIS FILE ARE TO BE USED BY THE SUBSYSTEM AS A MANGEMENT HUB * * _body - The body of the request, if applicable * * _headers - Associative list of HTTP headers to send, if applicab;e */ -/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 @@ -51,6 +53,7 @@ THE METHODS IN THIS FILE ARE TO BE USED BY THE SUBSYSTEM AS A MANGEMENT HUB method = _method url = _url body = _body + output_file = _output_file /** * Blocking executor @@ -60,7 +63,7 @@ THE METHODS IN THIS FILE ARE TO BE USED BY THE SUBSYSTEM AS A MANGEMENT HUB */ /datum/http_request/proc/execute_blocking() CRASH("Attempted to execute a blocking HTTP request") - // _raw_response = rustg_http_request_blocking(method, url, body, headers) + // _raw_response = rustg_http_request_blocking(method, url, body, headers, build_options()) /** * Async execution starter @@ -73,7 +76,7 @@ THE METHODS IN THIS FILE ARE TO BE USED BY THE SUBSYSTEM AS A MANGEMENT HUB 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))) _raw_response = "Proc error: [id]" @@ -81,6 +84,16 @@ THE METHODS IN THIS FILE ARE TO BE USED BY THE SUBSYSTEM AS A MANGEMENT HUB else in_progress = TRUE +/** + * Options builder + * + * Builds options for if we want to download files with SShttp + */ +/datum/http_request/proc/build_options() + if(output_file) + return json_encode(list("output_filename" = output_file, "body_filename" = null)) + return null + /** * Async completion checker * diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm index 5aeb241d26a..68a31f59238 100644 --- a/code/datums/supplypacks.dm +++ b/code/datums/supplypacks.dm @@ -1456,6 +1456,12 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY containertype = /obj/structure/closet/crate/secure containername = "shaft miner starter kit" +/datum/supply_packs/misc/carpet + name = "Carpet Crate" + cost = 20 + contains = list(/obj/item/stack/tile/carpet/twenty) + containername = "carpet crate" + ///////////// Paper Work diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm index 4e59d8ad6dd..12e2f1f71e1 100644 --- a/code/datums/uplink_item.dm +++ b/code/datums/uplink_item.dm @@ -167,18 +167,19 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) //Clown /datum/uplink_item/jobspecific/clowngrenade name = "Banana Grenade" - desc = "A grenade that explodes into HONK! brand banana peels that are genetically modified to be extra slippery and extrude caustic acid when stepped on" + desc = "A grenade that explodes into HONK! brand banana peels that are genetically modified to be extra slippery and extrude caustic acid when stepped on." reference = "BG" item = /obj/item/grenade/clown_grenade cost = 5 job = list("Clown") -/datum/uplink_item/jobspecific/clownmagboots - name = "Clown Magboots" - desc = "A pair of modified clown shoes fitted with an advanced magnetic traction system. Look and sound exactly like regular clown shoes unless closely inspected." - reference = "CM" - item = /obj/item/clothing/shoes/magboots/clown +/datum/uplink_item/jobspecific/clownslippers + name = "Clown Acrobatic Shoes" + desc = "A pair of modified clown shoes fitted with a built-in propulsion system that allows the user to perform a short slip below anyone. Turning on the waddle dampeners removes the slowdown on the shoes." + reference = "CAS" + item = /obj/item/clothing/shoes/clown_shoes/slippers cost = 3 + surplus = 75 job = list("Clown") /datum/uplink_item/jobspecific/trick_revolver diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm index 4f0552cbefa..3b07125cb5a 100644 --- a/code/game/gamemodes/malfunction/Malf_Modules.dm +++ b/code/game/gamemodes/malfunction/Malf_Modules.dm @@ -786,3 +786,24 @@ /datum/AI_Module/large/cameracrack/upgrade(mob/living/silicon/ai/AI) if(AI.builtInCamera) QDEL_NULL(AI.builtInCamera) + +/datum/AI_Module/large/engi_upgrade + module_name = "Engineering Cyborg Emitter Upgrade" + mod_pick_name = "emitter" + description = "Downloads firmware that activates the built in emitter in all engineering cyborgs linked to you. Cyborgs built after this upgrade will have it pre-installed." + cost = 50 // IDK look into this + one_purchase = TRUE + upgrade = TRUE + unlock_text = "Firmware downloaded. Bugs removed. Built in emitters operating at 73% efficiency." + unlock_sound = 'sound/items/rped.ogg' + +/datum/AI_Module/large/engi_upgrade/upgrade(mob/living/silicon/ai/AI) + AI.purchased_modules += /obj/item/robot_module/engineering + log_game("[key_name(usr)] purchased emitters for all engineering cyborgs.") + message_admins("[key_name_admin(usr)] purchased emitters for all engineering cyborgs!") + for(var/mob/living/silicon/robot/R in AI.connected_robots) + if(!istype(R.module, /obj/item/robot_module/engineering)) + continue + R.module.malfhacked = TRUE + R.module.rebuild_modules() + to_chat(R, "New firmware downloaded. Emitter is now online.") diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 47c41d841fe..9dff5725b81 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -205,6 +205,15 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) /datum/objective/protect/mindslave //subytpe for mindslave implants +/datum/objective/protect/mindslave/on_target_cryo() + if(owner?.current) + to_chat(owner.current, "
You notice that your master has entered cryogenic storage, and revert to your normal self, until they return again. You are no longer a mindslave!") + SEND_SOUND(owner.current, sound('sound/ambience/alarm4.ogg')) + owner.remove_antag_datum(/datum/antagonist/mindslave) + SSticker.mode.implanted.Remove(owner) + log_admin("[key_name(owner.current)]'s mindslave master has cryo'd, and is no longer a mindslave.") + message_admins("[key_name_admin(owner.current)]'s mindslave master has cryo'd, and is no longer a mindslave.") //Since they were on antag hud earlier, this feels important to log + qdel(src) /datum/objective/hijack martyr_compatible = 0 //Technically you won't get both anyway. diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm index 0eef296334d..927b9472ad4 100644 --- a/code/game/gamemodes/wizard/spellbook.dm +++ b/code/game/gamemodes/wizard/spellbook.dm @@ -343,6 +343,21 @@ playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1) return TRUE +/datum/spellbook_entry/summon/slience_ghosts + name = "Silence Ghosts" + desc = "Tired of people talking behind your back, and spooking you? Why not silence them, and make the dead deader." + cost = 2 + log_name = "SLG" + is_ragin_restricted = TRUE //Salt needs to flow here, to be honest + +/datum/spellbook_entry/summon/slience_ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + new /datum/event/wizard/ghost_mute() + active = TRUE + to_chat(user, "You have silenced all ghosts!") + playsound(get_turf(user), 'sound/effects/ghost.ogg', 50, 1) + message_admins("[key_name_admin(usr)] silenced all ghosts as a wizard! (Deadchat is now DISABLED)") + return TRUE + /datum/spellbook_entry/summon/guns name = "Summon Guns" desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first." diff --git a/code/game/jobs/job/supervisor.dm b/code/game/jobs/job/supervisor.dm index fa0fb892cae..46ed46f5ae2 100644 --- a/code/game/jobs/job/supervisor.dm +++ b/code/game/jobs/job/supervisor.dm @@ -194,16 +194,18 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca /datum/job/judge title = "Magistrate" flag = JOB_JUDGE - department_flag = JOBCAT_KARMA + department_flag = JOBCAT_ENGSEC total_positions = 1 spawn_positions = 1 supervisors = "the Nanotrasen Supreme Court" department_head = list("Captain") selection_color = "#ddddff" - req_admin_notify = 1 - is_legal = 1 + req_admin_notify = TRUE + is_legal = TRUE transfer_allowed = FALSE minimal_player_age = 30 + exp_requirements = 6000 // 100 hours baby + exp_type = EXP_TYPE_SECURITY access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_FORENSICS_LOCKERS, ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_EVA, ACCESS_HEADS, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE, diff --git a/code/game/jobs/jobs.dm b/code/game/jobs/jobs.dm index 5fd46e11434..c15c59303b7 100644 --- a/code/game/jobs/jobs.dm +++ b/code/game/jobs/jobs.dm @@ -96,8 +96,7 @@ GLOBAL_LIST_INIT(whitelisted_positions, list( "Blueshield", "Nanotrasen Representative", "Barber", - "Brig Physician", - "Magistrate" + "Brig Physician" )) diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index c4454baf3c7..0c6342592b3 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -322,6 +322,8 @@ if(shock(user, 100)) return if(!is_operational()) + if(user.a_intent != INTENT_HELP) + return ..() if(panel_open) to_chat(usr, "Close the maintenance panel first.") else diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm index 720b164e7d6..e0c9f4ee18c 100644 --- a/code/game/objects/items/stacks/tiles/tile_types.dm +++ b/code/game/objects/items/stacks/tiles/tile_types.dm @@ -88,6 +88,70 @@ /obj/item/stack/tile/carpet/black/twenty amount = 20 +/obj/item/stack/tile/carpet/blue + name = "blue carpet" + icon_state = "tile-carpet-blue" + turf_type = /turf/simulated/floor/carpet/blue + +/obj/item/stack/tile/carpet/blue/twenty + amount = 20 + +/obj/item/stack/tile/carpet/cyan + name = "cyan carpet" + icon_state = "tile-carpet-cyan" + turf_type = /turf/simulated/floor/carpet/cyan + +/obj/item/stack/tile/carpet/cyan/twenty + amount = 20 + +/obj/item/stack/tile/carpet/green + name = "green carpet" + icon_state = "tile-carpet-green" + turf_type = /turf/simulated/floor/carpet/green + +/obj/item/stack/tile/carpet/green/twenty + amount = 20 + +/obj/item/stack/tile/carpet/orange + name = "orange carpet" + icon_state = "tile-carpet-orange" + turf_type = /turf/simulated/floor/carpet/orange + +/obj/item/stack/tile/carpet/orange/twenty + amount = 20 + +/obj/item/stack/tile/carpet/purple + name = "purple carpet" + icon_state = "tile-carpet-purple" + turf_type = /turf/simulated/floor/carpet/purple + +/obj/item/stack/tile/carpet/purple/twenty + amount = 20 + +/obj/item/stack/tile/carpet/red + name = "red carpet" + icon_state = "tile-carpet-red" + turf_type = /turf/simulated/floor/carpet/red + +/obj/item/stack/tile/carpet/red/twenty + amount = 20 + +/obj/item/stack/tile/carpet/royalblack + name = "royal black carpet" + icon_state = "tile-carpet-royalblack" + turf_type = /turf/simulated/floor/carpet/royalblack + +/obj/item/stack/tile/carpet/royalblack/twenty + amount = 20 + +/obj/item/stack/tile/carpet/royalblue + name = "royal blue carpet" + icon_state = "tile-carpet-royalblue" + turf_type = /turf/simulated/floor/carpet/royalblue + +/obj/item/stack/tile/carpet/royalblue/twenty + amount = 20 + //Plasteel /obj/item/stack/tile/plasteel name = "floor tiles" diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm index c89fd8aeb0d..0894c00cf2e 100644 --- a/code/game/objects/items/weapons/storage/bags.dm +++ b/code/game/objects/items/weapons/storage/bags.dm @@ -371,6 +371,7 @@ icon = 'icons/obj/food/containers.dmi' icon_state = "tray" desc = "A metal tray to lay food on." + storage_slots = 8 force = 5 throwforce = 10 throw_speed = 3 diff --git a/code/game/turfs/simulated/floor/fancy_floor.dm b/code/game/turfs/simulated/floor/fancy_floor.dm index 0b8938fc204..86a1e119dad 100644 --- a/code/game/turfs/simulated/floor/fancy_floor.dm +++ b/code/game/turfs/simulated/floor/fancy_floor.dm @@ -68,6 +68,9 @@ playsound(src, 'sound/effects/shovel_dig.ogg', 50, 1) make_plating() + +//Carpets + /turf/simulated/floor/carpet name = "carpet" icon = 'icons/turf/floors/carpet.dmi' @@ -112,6 +115,48 @@ floor_tile = /obj/item/stack/tile/carpet/black canSmoothWith = list(/turf/simulated/floor/carpet/black) +/turf/simulated/floor/carpet/blue + icon = 'icons/turf/floors/carpet_blue.dmi' + floor_tile = /obj/item/stack/tile/carpet/blue + canSmoothWith = list(/turf/simulated/floor/carpet/blue) + +/turf/simulated/floor/carpet/cyan + icon = 'icons/turf/floors/carpet_cyan.dmi' + floor_tile = /obj/item/stack/tile/carpet/cyan + canSmoothWith = list(/turf/simulated/floor/carpet/cyan) + +/turf/simulated/floor/carpet/green + icon = 'icons/turf/floors/carpet_green.dmi' + floor_tile = /obj/item/stack/tile/carpet/green + canSmoothWith = list(/turf/simulated/floor/carpet/green) + +/turf/simulated/floor/carpet/orange + icon = 'icons/turf/floors/carpet_orange.dmi' + floor_tile = /obj/item/stack/tile/carpet/orange + canSmoothWith = list(/turf/simulated/floor/carpet/orange) + +/turf/simulated/floor/carpet/purple + icon = 'icons/turf/floors/carpet_purple.dmi' + floor_tile = /obj/item/stack/tile/carpet/purple + canSmoothWith = list(/turf/simulated/floor/carpet/purple) + +/turf/simulated/floor/carpet/red + icon = 'icons/turf/floors/carpet_red.dmi' + floor_tile = /obj/item/stack/tile/carpet/red + canSmoothWith = list(/turf/simulated/floor/carpet/red) + +/turf/simulated/floor/carpet/royalblack + icon = 'icons/turf/floors/carpet_royalblack.dmi' + floor_tile = /obj/item/stack/tile/carpet/royalblack + canSmoothWith = list(/turf/simulated/floor/carpet/royalblack) + +/turf/simulated/floor/carpet/royalblue + icon = 'icons/turf/floors/carpet_royalblue.dmi' + floor_tile = /obj/item/stack/tile/carpet/royalblue + canSmoothWith = list(/turf/simulated/floor/carpet/royalblue) + +//End of carpets + /turf/simulated/floor/fakespace icon = 'icons/turf/space.dmi' icon_state = "0" diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index a78f3219e47..c4b14311986 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -706,8 +706,9 @@ GLOBAL_LIST_INIT(admin_verbs_ticket, list( if(istext(flags)) flags = text2num(flags) + var/client/check_client = GLOB.directory[ckey] // Do a little check here - if(GLOB.configuration.system.is_production && (flags & R_ADMIN) && prefs._2fa_status == _2FA_DISABLED) // If they are an admin and their 2FA is disabled + if(GLOB.configuration.system.is_production && (flags & R_ADMIN) && check_client.prefs._2fa_status == _2FA_DISABLED) // If they are an admin and their 2FA is disabled to_chat(src,"You do not have 2FA enabled. Admin verbs will be unavailable until you have enabled 2FA.") // Very fucking obvious qdel(admin_read) return diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index f68e20b1903..7c011962c08 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -117,10 +117,16 @@ /// Client's pAI save var/datum/pai_save/pai_save + /// List of the clients CUIs + var/list/datum/custom_user_item/cui_entries = list() + /client/vv_edit_var(var_name, var_value) switch(var_name) // I know we will never be in a world where admins are editing client vars to let people bypass TOS // But guess what, if I have the ability to overengineer something, I am going to do it if("tos_consent") return FALSE + // Dont fuck with this + if("cui_entries") + return FALSE return ..() diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index fa5ae0b4d37..6f8d489baf6 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -149,8 +149,6 @@ karma_purchase(karma,30,"job","Nanotrasen Representative") if("4") karma_purchase(karma,30,"job","Blueshield") - if("5") - karma_purchase(karma,45,"job","Magistrate") return if(href_list["KarmaBuy2"]) var/karma=verify_karma() diff --git a/code/modules/client/login_processing/45-cuis.dm b/code/modules/client/login_processing/45-cuis.dm new file mode 100644 index 00000000000..88bfca44638 --- /dev/null +++ b/code/modules/client/login_processing/45-cuis.dm @@ -0,0 +1,20 @@ +/datum/client_login_processor/cuis + priority = 45 + +/datum/client_login_processor/cuis/get_query(client/C) + var/datum/db_query/query = SSdbcore.NewQuery("SELECT cuiRealName, cuiPath, cuiItemName, cuiDescription, cuiJobMask FROM customuseritems WHERE cuiCKey=:ckey", list( + "ckey" = C.ckey + )) + return query + +/datum/client_login_processor/cuis/process_result(datum/db_query/Q, client/C) + while(Q.NextRow()) + var/datum/custom_user_item/cui = new() + cui.characer_name = Q.item[1] + cui.object_typepath = text2path(Q.item[2]) + cui.item_name_override = Q.item[3] + cui.item_desc_override = Q.item[4] + cui.raw_job_mask = Q.item[5] + + if(cui.parse_info(C.ckey)) + C.cui_entries += cui diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index 329bea01f9d..4c23bbdff38 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -96,6 +96,9 @@ if(user.get_active_hand() != src) to_chat(user, "You must hold [src] in your hand to do this.") return + toggle_waddle(user) + +/obj/item/clothing/shoes/clown_shoes/proc/toggle_waddle(mob/living/user) if(!enabled_waddle) to_chat(user, "You switch off the waddle dampeners!") enabled_waddle = TRUE @@ -111,6 +114,45 @@ desc = "Standard-issue shoes of the wizarding class clown. Damn they're huge! And powerful! Somehow." magical = TRUE +/obj/item/clothing/shoes/clown_shoes/slippers + actions_types = list(/datum/action/item_action/slipping) + var/slide_distance = 6 + var/recharging_rate = 8 SECONDS + var/recharging_time = 0 + +/obj/item/clothing/shoes/clown_shoes/slippers/item_action_slot_check(slot, mob/user) + if(slot == slot_shoes) + return TRUE + +/obj/item/clothing/shoes/clown_shoes/slippers/ui_action_click(mob/user, action) + if(recharging_time > world.time) + to_chat(user, "The boot's internal propulsion needs to recharge still!") + return + var/prev_dir = user.dir + var/prev_pass_flags = user.pass_flags + user.pass_flags |= PASSMOB + user.Weaken(2) + user.dir = prev_dir + playsound(src, 'sound/effects/stealthoff.ogg', 50, TRUE, 1) + recharging_time = world.time + recharging_rate + user.visible_message("[user] slips forward!") + for(var/i in 1 to slide_distance) + step(user, user.dir) + sleep(1) + user.SetWeakened(0) + user.pass_flags = prev_pass_flags + + +/obj/item/clothing/shoes/clown_shoes/slippers/toggle_waddle(mob/living/user) + if(!enabled_waddle) + to_chat(user, "You switch off the waddle dampeners!") + enabled_waddle = TRUE + slowdown = initial(slowdown) + else + to_chat(user, "You switch on the waddle dampeners, [src] no longer slow you down!") + enabled_waddle = FALSE + slowdown = SHOES_SLOWDOWN + /obj/item/clothing/shoes/jackboots name = "jackboots" desc = "Nanotrasen-issue Security combat boots for combat scenarios or combat situations. All combat, all the time." diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm index 25ac77e90f8..e63e785edbb 100644 --- a/code/modules/crafting/recipes.dm +++ b/code/modules/crafting/recipes.dm @@ -373,7 +373,71 @@ result = list(/obj/item/stack/tile/carpet/black) time = 20 reqs = list(/obj/item/stack/tile/carpet = 1) - pathtools = list(/obj/item/toy/crayon) + pathtools = list(/obj/item/toy/crayon/black) + category = CAT_MISC + +/datum/crafting_recipe/bluecarpet + name = "Black Carpet" + result = list(/obj/item/stack/tile/carpet/blue) + time = 20 + reqs = list(/obj/item/stack/tile/carpet = 1) + pathtools = list(/obj/item/toy/crayon/blue) + category = CAT_MISC + +/datum/crafting_recipe/cyancarpet + name = "Cyan Carpet" + result = list(/obj/item/stack/tile/carpet/blue) + time = 20 + reqs = list(/obj/item/stack/tile/carpet = 1) + pathtools = list(/obj/item/toy/crayon/blue, /obj/item/toy/crayon/green) + category = CAT_MISC + +/datum/crafting_recipe/greencarpet + name = "Green Carpet" + result = list(/obj/item/stack/tile/carpet/green) + time = 20 + reqs = list(/obj/item/stack/tile/carpet = 1) + pathtools = list(/obj/item/toy/crayon/green) + category = CAT_MISC + +/datum/crafting_recipe/orangecarpet + name = "Orange Carpet" + result = list(/obj/item/stack/tile/carpet/orange) + time = 20 + reqs = list(/obj/item/stack/tile/carpet = 1) + pathtools = list(/obj/item/toy/crayon/yellow, /obj/item/toy/crayon/red) + category = CAT_MISC + +/datum/crafting_recipe/purplecarpet + name = "Purple Carpet" + result = list(/obj/item/stack/tile/carpet/purple) + time = 20 + reqs = list(/obj/item/stack/tile/carpet = 1) + pathtools = list(/obj/item/toy/crayon/red, /obj/item/toy/crayon/blue) + category = CAT_MISC + +/datum/crafting_recipe/redcarpet + name = "Red Carpet" + result = list(/obj/item/stack/tile/carpet/purple) + time = 20 + reqs = list(/obj/item/stack/tile/carpet = 1) + pathtools = list(/obj/item/toy/crayon/red) + category = CAT_MISC + +/datum/crafting_recipe/royalblackcarpet + name = "Royal Black Carpet" + result = list(/obj/item/stack/tile/carpet/royalblack = 10) + time = 20 + reqs = list(/obj/item/stack/tile/carpet/black = 10, + /obj/item/stack/sheet/mineral/gold = 2) + category = CAT_MISC + +/datum/crafting_recipe/royalbluecarpet + name = "Royal Blue Carpet" + result = list(/obj/item/stack/tile/carpet/royalblue = 10) + time = 20 + reqs = list(/obj/item/stack/tile/carpet/blue = 10, + /obj/item/stack/sheet/mineral/gold = 2) category = CAT_MISC /datum/crafting_recipe/showercurtain diff --git a/code/modules/customitems/item_spawning.dm b/code/modules/customitems/item_spawning.dm deleted file mode 100644 index 5f998861cf6..00000000000 --- a/code/modules/customitems/item_spawning.dm +++ /dev/null @@ -1,101 +0,0 @@ -/proc/EquipCustomItems(mob/living/carbon/human/M) - if(!SSdbcore.IsConnected()) - return - - // Grab the info we want. - var/datum/db_query/query = SSdbcore.NewQuery({" - SELECT cuiPath, cuiPropAdjust, cuiJobMask, cuiDescription, cuiItemName FROM customuseritems - WHERE cuiCKey=:ckey AND (cuiRealName=:realname OR cuiRealName='*')"}, list( - "ckey" = M.ckey, - "realname" = M.real_name - )) - if(!query.warn_execute(async = FALSE)) // Dont make this async. Youll make roundstart slow. Trust me. - qdel(query) - return - - while(query.NextRow()) - var/path = text2path(query.item[1]) - var/propadjust = query.item[2] - var/jobmask = query.item[3] - var/ok = 0 - if(!path || !ispath(path)) - log_debug("Incorrect database entry found in table 'customuseritems' path value = [path], cuiPath is null. cuiCKey='[M.ckey]' AND (cuiRealName='[M.real_name]' OR cuiRealName='*'") - continue - if(jobmask != "*") - var/list/allowed_jobs = splittext(jobmask,",") - for(var/i = 1, i <= allowed_jobs.len, i++) - if(istext(allowed_jobs[i])) - allowed_jobs[i] = trim(allowed_jobs[i]) - var/alt_blocked = 0 - if(M.mind.role_alt_title) - if(!(M.mind.role_alt_title in allowed_jobs)) - alt_blocked = 1 - if(!(M.mind.assigned_role in allowed_jobs) || alt_blocked) - continue - - var/obj/item/Item = new path() - var/description = query.item[4] - var/newname = query.item[5] - if(istype(Item,/obj/item/card/id)) - var/obj/item/card/id/I = Item - for(var/obj/item/card/id/C in M) - //default settings - I.name = "[M.real_name]'s ID Card ([M.mind.role_alt_title ? M.mind.role_alt_title : M.mind.assigned_role])" - I.registered_name = M.real_name - I.access = C.access - I.assignment = C.assignment - I.blood_type = C.blood_type - I.dna_hash = C.dna_hash - I.fingerprint_hash = C.fingerprint_hash - qdel(C) - ok = M.equip_or_collect(I, slot_wear_id, 0) //if 1, last argument deletes on fail - break - else if(istype(M.back, /obj/item/storage)) // Try to place it in something on the mob's back - var/obj/item/storage/S = M.back - if(S.contents.len < S.storage_slots) - Item.loc = M.back - ok = 1 - to_chat(M, "Your [Item.name] has been added to your [M.back.name].") - if(ok == 0) - for(var/obj/item/storage/S in M.contents) // Try to place it in any item that can store stuff, on the mob. - if(S.contents.len < S.storage_slots) - Item.loc = S - ok = 1 - to_chat(M, "Your [Item.name] has been added to your [S.name].") - break - if(description) - Item.desc = description - if(newname) - Item.name = newname - - if(ok == 0) // Finally, since everything else failed, place it on the ground - Item.loc = get_turf(M.loc) - - HackProperties(Item,propadjust) - M.regenerate_icons() - qdel(query) - -// This is hacky, but since it's difficult as fuck to make a proper parser in BYOND without killing the server, here it is. - N3X -/proc/HackProperties(mob/living/carbon/human/M, obj/item/I, script) - var/list/statements = splittext(script,";") - if(statements.len == 0) - return - for(var/statement in statements) - var/list/assignmentChunks = splittext(statement,"=") - var/varname = assignmentChunks[1] - var/list/typeChunks=splittext(script,":") - var/desiredType=typeChunks[1] - switch(desiredType) - if("string") - var/output = typeChunks[2] - output = replacetext(output,"{REALNAME}", M.real_name) - output = replacetext(output,"{ROLE}", M.mind.assigned_role) - output = replacetext(output,"{ROLE_ALT}", "[M.mind.role_alt_title ? M.mind.role_alt_title : M.mind.assigned_role]") - I.vars[varname]=output - if("number") - I.vars[varname]=text2num(typeChunks[2]) - if("icon") - if(typeChunks.len==2) - I.vars[varname]=new /icon(typeChunks[2]) - if(typeChunks.len==3) - I.vars[varname]=new /icon(typeChunks[2],typeChunks[3]) diff --git a/code/modules/events/wizard/ghost.dm b/code/modules/events/wizard/ghost.dm index 11529e3080d..e703799ff02 100644 --- a/code/modules/events/wizard/ghost.dm +++ b/code/modules/events/wizard/ghost.dm @@ -3,3 +3,12 @@ /datum/event/wizard/ghost/start() var/msg = "You suddenly feel extremely obvious..." set_observer_default_invisibility(0, msg) + +/datum/event/wizard/ghost_mute //The spook is silent + +/datum/event/wizard/ghost_mute/start() + GLOB.dsay_enabled = FALSE + var/sound/S = sound('sound/hallucinations/wail.ogg') + for(var/mob/dead/observer/silenced in GLOB.player_list) + to_chat(silenced, "Magical forces wrap around your spectral form. You can no longer speak to other ghosts!") + SEND_SOUND(silenced, S) diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm index 87e17efd10b..599eef5b2f0 100644 --- a/code/modules/games/cards.dm +++ b/code/modules/games/cards.dm @@ -200,11 +200,12 @@ deckshuffle() /obj/item/deck/verb/verb_shuffle() - set category = "Object" - set name = "Shuffle" - set desc = "Shuffle the cards in the deck." - set src in view(1) - deckshuffle() + if(!isobserver(usr)) + set category = "Object" + set name = "Shuffle" + set desc = "Shuffle the cards in the deck." + set src in view(1) + deckshuffle() /obj/item/deck/proc/deckshuffle() var/mob/living/user = usr diff --git a/code/modules/karma/karma.dm b/code/modules/karma/karma.dm index f684a8b261c..14878faf54a 100644 --- a/code/modules/karma/karma.dm +++ b/code/modules/karma/karma.dm @@ -275,10 +275,6 @@ GLOBAL_LIST_EMPTY(karma_spenders) dat += "Unlock Blueshield -- 30KP
" else dat += "Blueshield - Unlocked
" - if(!("Magistrate" in joblist)) - dat += "Unlock Magistrate -- 45KP
" - else - dat+= "Magistrate - Unlocked
" if(1) // Species Unlocks if(!("Machine" in specieslist)) @@ -349,6 +345,9 @@ GLOBAL_LIST_EMPTY(karma_spenders) if("Security Pod Pilot" in purchased) refundable += "Security Pod Pilot" dat += "Refund Security Pod Pilot -- 30KP
" + if("Magistrate" in purchased) + refundable += "Magistrate" + dat += "Refund Magistrate -- 45KP
" if(!refundable.len) dat += "You do not have any refundable karma purchases.
" @@ -515,6 +514,8 @@ GLOBAL_LIST_EMPTY(karma_spenders) cost = 30 if("Nanotrasen Recruiter") cost = 10 + if("Magistrate") + cost = 45 else to_chat(usr, "That job is not refundable.") return diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 47c383e1a33..943f894b151 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -79,6 +79,9 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( var/obj/machinery/power/apc/malfhack = null var/explosive = 0 //does the AI explode when it dies? + /// List of modules the AI has purchased malf upgrades for. + var/list/purchased_modules = list() + var/mob/living/silicon/ai/parent = null var/camera_light_on = 0 var/list/obj/machinery/camera/lit_cameras = list() diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 677f67ebf13..768bbbc65f9 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -26,7 +26,8 @@ "Parrot" = "parrot", "Box Bot" = "boxbot", "Spider Bot" = "spiderbot", - "Fairy" = "fairy" + "Fairy" = "fairy", + "Snake" = "snake" ) var/global/list/possible_say_verbs = list( @@ -35,7 +36,8 @@ "Beep" = list("beeps","beeps loudly","boops"), "Chirp" = list("chirps","chirrups","cheeps"), "Feline" = list("purrs","yowls","meows"), - "Canine" = list("yaps","barks","growls") + "Canine" = list("yaps","barks","growls"), + "Hiss" = list("hisses","hisses","hisses") ) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 8affd317c6a..60d13a88ce5 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1264,6 +1264,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( connected_ai = AI connected_ai.connected_robots |= src notify_ai(1) + if(module) + module.rebuild_modules() //This way, if a borg gets linked to a malf AI that has upgrades, they get their upgrades. sync() /mob/living/silicon/robot/adjustOxyLoss(amount) diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index ab7c4ff9562..3c202a35029 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -6,12 +6,17 @@ item_state = "electronic" flags = CONDUCT + /// Has the AI hacked the borg module, allowing access to the malf AI exclusive item. + var/malfhacked = FALSE + /// A list of all currently usable and created modules the robot currently has access too. var/list/modules = list() /// A list of module-specific, non-emag modules the borg will gain when this module is chosen. var/list/basic_modules = list() /// A list of modules the robot gets when emagged. var/list/emag_modules = list() + /// A list of modules that the robot gets when malf AI buys it. + var/list/malf_modules = list() /// A list of modules that require special recharge handling. Examples include things like flashes, sprays and welding tools. var/list/special_rechargables = list() /// A list of all "energy stacks", i.e metal, glass, brute kits, splints, etc. @@ -43,12 +48,17 @@ emag_modules += I emag_modules -= i + for(var/i in malf_modules) + var/obj/item/I = new i(src) + malf_modules += I + malf_modules -= i + // Flashes need a special recharge, and since basically every module uses it, add it here. // Even if the module doesn't use a flash, it wont cause any issues to have it in this list. special_rechargables += /obj/item/flash/cyborg // This is done so we can loop through this list later and call cyborg_recharge() on the items while the borg is recharging. - var/all_modules = basic_modules | emag_modules + var/all_modules = basic_modules | emag_modules | malf_modules for(var/path in special_rechargables) var/obj/item/I = locate(path) in all_modules if(I) // If it exists, add the object reference. @@ -70,6 +80,7 @@ QDEL_LIST(modules) QDEL_LIST(basic_modules) QDEL_LIST(emag_modules) + QDEL_LIST(malf_modules) QDEL_LIST(storages) QDEL_LIST(special_rechargables) return ..() @@ -88,6 +99,7 @@ var/list/lists = list( basic_modules, emag_modules, + malf_modules, storages, special_rechargables ) @@ -155,13 +167,17 @@ return I /** - * Builds the usable module list from the modules we have in `basic_modules` and `emag_modules` + * Builds the usable module list from the modules we have in `basic_modules`, `emag_modules` and `malf_modules` */ /obj/item/robot_module/proc/rebuild_modules() var/mob/living/silicon/robot/R = loc R.uneq_all() modules = list() + if(!malfhacked && R.connected_ai) + if(type in R.connected_ai.purchased_modules) + malfhacked = TRUE + // By this point these lists should only contain items. It's safe to use typeless loops here. for(var/item in basic_modules) add_module(item, FALSE) @@ -170,6 +186,10 @@ for(var/item in emag_modules) add_module(item, FALSE) + if(malfhacked) + for(var/item in malf_modules) + add_module(item, FALSE) + if(R.hud_used) R.hud_used.update_robot_modules_display() @@ -354,8 +374,9 @@ /obj/item/stack/sheet/glass/cyborg, /obj/item/stack/sheet/rglass/cyborg ) - emag_modules = list(/obj/item/borg/stun) - special_rechargables = list(/obj/item/extinguisher, /obj/item/weldingtool/largetank/cyborg) + emag_modules = list(/obj/item/borg/stun, /obj/item/restraints/handcuffs/cable/zipties/cyborg) + malf_modules = list(/obj/item/gun/energy/emitter/cyborg) + special_rechargables = list(/obj/item/extinguisher, /obj/item/weldingtool/largetank/cyborg, /obj/item/gun/energy/emitter/cyborg) /obj/item/robot_module/engineering/handle_death(mob/living/silicon/robot/R, gibbed) var/obj/item/gripper/G = locate(/obj/item/gripper) in modules @@ -400,7 +421,7 @@ /obj/item/holosign_creator, /obj/item/extinguisher/mini ) - emag_modules = list(/obj/item/reagent_containers/spray/cyborg_lube) + emag_modules = list(/obj/item/reagent_containers/spray/cyborg_lube, /obj/item/restraints/handcuffs/cable/zipties/cyborg) special_rechargables = list( /obj/item/lightreplacer, /obj/item/reagent_containers/spray/cyborg_lube, @@ -471,7 +492,7 @@ /obj/item/storage/bag/tray/cyborg, /obj/item/reagent_containers/food/drinks/shaker ) - emag_modules = list(/obj/item/reagent_containers/food/drinks/cans/beer/sleepy_beer) + emag_modules = list(/obj/item/reagent_containers/food/drinks/cans/beer/sleepy_beer, /obj/item/restraints/handcuffs/cable/zipties/cyborg) special_rechargables = list( /obj/item/reagent_containers/food/condiment/enzyme, /obj/item/reagent_containers/food/drinks/cans/beer/sleepy_beer @@ -530,7 +551,7 @@ /obj/item/gun/energy/kinetic_accelerator/cyborg, /obj/item/gps/cyborg ) - emag_modules = list(/obj/item/borg/stun, /obj/item/pickaxe/drill/cyborg/diamond) + emag_modules = list(/obj/item/borg/stun, /obj/item/pickaxe/drill/cyborg/diamond, /obj/item/restraints/handcuffs/cable/zipties/cyborg) special_rechargables = list(/obj/item/extinguisher/mini, /obj/item/weldingtool/mini) // Replace their normal drill with a diamond drill. diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 04aca83e512..9370829c746 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -359,7 +359,7 @@ character.buckled.dir = character.dir character = SSjobs.EquipRank(character, rank, 1) //equips the human - EquipCustomItems(character) + SSticker.equip_cuis(character) // Gives them their CUIs SSticker.mode.latespawn(character) diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 25c83556882..dd5f68eb739 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -140,6 +140,7 @@ var/on = 0 var/brightness_on = 2 light_color = LIGHT_COLOR_RED + armour_penetration = 20 /obj/item/pen/edagger/attack_self(mob/living/user) if(on) diff --git a/code/modules/projectiles/ammunition/energy.dm b/code/modules/projectiles/ammunition/energy.dm index 18ed273ff23..049bf13c174 100644 --- a/code/modules/projectiles/ammunition/energy.dm +++ b/code/modules/projectiles/ammunition/energy.dm @@ -271,6 +271,17 @@ delay = 50 select_name = "snipe" +/obj/item/ammo_casing/energy/emitter + projectile_type = /obj/item/projectile/beam/emitter + muzzle_flash_color = LIGHT_COLOR_GREEN + fire_sound = 'sound/weapons/emitter.ogg' + e_cost = 100 + delay = 2 SECONDS // Lasers fire twice every second for 40 dps, this fires every 2 seconds for 15 dps. Seems fair, since every cyborg will have this with more shots? + select_name = "emitter" + +/obj/item/ammo_casing/energy/emitter/cyborg + e_cost = 500 // about 28 shots on an engineering borg from a borging machine, assuming some power is used for lights / movement. May need to change. + /obj/item/ammo_casing/energy/bsg projectile_type = /obj/item/projectile/energy/bsg muzzle_flash_color = LIGHT_COLOR_DARKBLUE diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index b205dc44797..9d7b9bde281 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -141,6 +141,29 @@ name = "cyborg immolator cannon" ammo_type = list(/obj/item/ammo_casing/energy/immolator/scatter/cyborg, /obj/item/ammo_casing/energy/immolator/strong/cyborg) // scatter is default, because it is more useful +/obj/item/gun/energy/emitter + name = "mobile emitter" + desc = "An emitter removed from its base, and attached to a laser cannon frame." + icon_state = "emittercannon" + item_state = "laser" + w_class = WEIGHT_CLASS_BULKY + shaded_charge = TRUE + can_holster = FALSE + origin_tech = "combat=4;magnets=4;powerstorage=3" + ammo_type = list(/obj/item/ammo_casing/energy/emitter) + ammo_x_offset = 3 + +/obj/item/gun/energy/emitter/cyborg + name = "mounted emitter" + desc = "An emitter built into to your cyborg frame, draining charge from your cell." + ammo_type = list(/obj/item/ammo_casing/energy/emitter/cyborg) + +/obj/item/gun/energy/emitter/cyborg/newshot() + ..() + robocharge() + +/obj/item/gun/energy/emitter/cyborg/emp_act() + return ////////Laser Tag//////////////////// diff --git a/icons/mob/pai.dmi b/icons/mob/pai.dmi index 6d5a2003b8c..e76b13409d2 100644 Binary files a/icons/mob/pai.dmi and b/icons/mob/pai.dmi differ diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index 9ef222c0bff..3354c42a5b7 100644 Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ diff --git a/icons/obj/guns/energy.dmi b/icons/obj/guns/energy.dmi index b4f2c2204ab..f2a1917a06e 100644 Binary files a/icons/obj/guns/energy.dmi and b/icons/obj/guns/energy.dmi differ diff --git a/icons/obj/tiles.dmi b/icons/obj/tiles.dmi index 027f074a790..d7712e6ccb9 100644 Binary files a/icons/obj/tiles.dmi and b/icons/obj/tiles.dmi differ diff --git a/icons/turf/floors/carpet_blue.dmi b/icons/turf/floors/carpet_blue.dmi new file mode 100644 index 00000000000..cd3e7f01aa8 Binary files /dev/null and b/icons/turf/floors/carpet_blue.dmi differ diff --git a/icons/turf/floors/carpet_cyan.dmi b/icons/turf/floors/carpet_cyan.dmi new file mode 100644 index 00000000000..a1fbb6f9487 Binary files /dev/null and b/icons/turf/floors/carpet_cyan.dmi differ diff --git a/icons/turf/floors/carpet_green.dmi b/icons/turf/floors/carpet_green.dmi new file mode 100644 index 00000000000..bd945c45e17 Binary files /dev/null and b/icons/turf/floors/carpet_green.dmi differ diff --git a/icons/turf/floors/carpet_orange.dmi b/icons/turf/floors/carpet_orange.dmi new file mode 100644 index 00000000000..316dd135e22 Binary files /dev/null and b/icons/turf/floors/carpet_orange.dmi differ diff --git a/icons/turf/floors/carpet_purple.dmi b/icons/turf/floors/carpet_purple.dmi new file mode 100644 index 00000000000..8cc886139cc Binary files /dev/null and b/icons/turf/floors/carpet_purple.dmi differ diff --git a/icons/turf/floors/carpet_red.dmi b/icons/turf/floors/carpet_red.dmi new file mode 100644 index 00000000000..2327b49a017 Binary files /dev/null and b/icons/turf/floors/carpet_red.dmi differ diff --git a/icons/turf/floors/carpet_royalblack.dmi b/icons/turf/floors/carpet_royalblack.dmi new file mode 100644 index 00000000000..77418dc06f8 Binary files /dev/null and b/icons/turf/floors/carpet_royalblack.dmi differ diff --git a/icons/turf/floors/carpet_royalblue.dmi b/icons/turf/floors/carpet_royalblue.dmi new file mode 100644 index 00000000000..bc2014c9ac6 Binary files /dev/null and b/icons/turf/floors/carpet_royalblue.dmi differ diff --git a/librust_g.so b/librust_g.so index 1f059e0b2ce..63d3e053d9e 100644 Binary files a/librust_g.so and b/librust_g.so differ diff --git a/paradise.dme b/paradise.dme index 5488d42c65c..b7db391ce27 100644 --- a/paradise.dme +++ b/paradise.dme @@ -23,6 +23,7 @@ #include "code\__DEFINES\_spacemandmm.dm" #include "code\__DEFINES\_tgs_defines.dm" #include "code\__DEFINES\_tick.dm" +#include "code\__DEFINES\_versions.dm" #include "code\__DEFINES\access.dm" #include "code\__DEFINES\admin.dm" #include "code\__DEFINES\antagonists.dm" @@ -278,6 +279,7 @@ #include "code\datums\callback.dm" #include "code\datums\chatmessage.dm" #include "code\datums\click_intercept.dm" +#include "code\datums\custom_user_item.dm" #include "code\datums\datacore.dm" #include "code\datums\datum.dm" #include "code\datums\datumvars.dm" @@ -1390,6 +1392,7 @@ #include "code\modules\client\login_processing\38-alts_cid.dm" #include "code\modules\client\login_processing\39-cid_count.dm" #include "code\modules\client\login_processing\40-pai_save.dm" +#include "code\modules\client\login_processing\45-cuis.dm" #include "code\modules\client\login_processing\__client_login_processor.dm" #include "code\modules\client\preference\character.dm" #include "code\modules\client\preference\link_processing.dm" @@ -1484,7 +1487,6 @@ #include "code\modules\crafting\recipes.dm" #include "code\modules\crafting\tailoring.dm" #include "code\modules\customitems\item_defines.dm" -#include "code\modules\customitems\item_spawning.dm" #include "code\modules\detective_work\detective_work.dm" #include "code\modules\detective_work\evidence.dm" #include "code\modules\detective_work\footprints_and_rag.dm" diff --git a/rust_g.dll b/rust_g.dll index ed4ac5bc38b..a4b6f9074bd 100644 Binary files a/rust_g.dll and b/rust_g.dll differ diff --git a/tools/ci/validate_rustg_windows.py b/tools/ci/validate_rustg_windows.py index 787bafb9a60..1c3c883ae5e 100644 --- a/tools/ci/validate_rustg_windows.py +++ b/tools/ci/validate_rustg_windows.py @@ -37,7 +37,7 @@ else: fail("RUSTG DM header file does NOT exist") # Parse the version from the DM file -f = open("code/__DEFINES/rust_g.dm", "r") +f = open("code/__DEFINES/_versions.dm", "r") lines = f.readlines() f.close() dm_version = None @@ -92,16 +92,16 @@ else: # Make sure we can parse TOML string_array = c_char_p * 1 sa = string_array(bytes(ci_toml_file_location, "ascii")) -rustg_dll.toml2json.argtypes = [c_int, c_char_p * 1] +rustg_dll.toml_file_to_json.argtypes = [c_int, c_char_p * 1] # Set args for JSON retrieval -rustg_dll.toml2json.restype = c_char_p +rustg_dll.toml_file_to_json.restype = c_char_p try: # Run it - output_json = rustg_dll.toml2json(1, sa).decode() + output_json = rustg_dll.toml_file_to_json(1, sa).decode() json.loads(output_json) - success("toml2json conversion successful") + success("toml_file_to_json successful") except Exception: - fail("Failed to convert toml2json") + fail("Failed to run toml_file_to_json") exit(0) # Success