diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8d7b4133840c..e2390c9fe817 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -89,6 +89,19 @@ ], "group": "build", "label": "tgui: sonar" + }, + { + "type": "shell", + "command": "bin/tgfont", + "windows": { + "command": ".\\bin\\tgfont.cmd" + }, + "problemMatcher": [ + "$tsc", + "$eslint-stylish" + ], + "group": "build", + "label": "tgui: rebuild tgfont" } ] } diff --git a/bin/tgfont.cmd b/bin/tgfont.cmd new file mode 100644 index 000000000000..b768c81d653a --- /dev/null +++ b/bin/tgfont.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error tg-font %* diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm index 73b10d700247..e6e1e64e9c7e 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm @@ -74,3 +74,7 @@ /// From /mob/living/carbon/xenomorph/proc/handle_crit() #define COMSIG_XENO_ENTER_CRIT "xeno_entering_critical" + +/// From /mob/living/carbon/xenomorph/proc/hivemind_talk(): (message) +#define COMSIG_XENO_TRY_HIVEMIND_TALK "xeno_try_hivemind_talk" + #define COMPONENT_OVERRIDE_HIVEMIND_TALK (1<<0) diff --git a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm index 710e4d9ae20a..61eb757e9c4d 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm @@ -109,6 +109,12 @@ #define COMSIG_MOB_EMOTED(emote_key) "mob_emoted_[emote_key]" +#define COMSIG_MOB_TRY_EMOTE "mob_try_emote" + #define COMPONENT_OVERRIDE_EMOTE (1<<0) + +#define COMSIG_MOB_TRY_POINT "mob_try_point" + #define COMPONENT_OVERRIDE_POINT (1<<0) + //from /mob/living/set_stat() #define COMSIG_MOB_STAT_SET_ALIVE "mob_stat_set_alive" //from /mob/living/set_stat() diff --git a/code/__DEFINES/keybinding.dm b/code/__DEFINES/keybinding.dm index f4503aeea5d5..4b8fd9bd58b3 100644 --- a/code/__DEFINES/keybinding.dm +++ b/code/__DEFINES/keybinding.dm @@ -165,6 +165,8 @@ #define COMSIG_KB_XENO_HIVE_STATUS "keybinding_hive_status" #define COMSIG_KB_XENO_HIDE "keybinding_hide" #define COMSIG_KB_XENO_EVOLVE "keybinding_evolve" +#define COMSIG_KB_XENO_PURCHASE_STRAIN "keybinding_purchase_strain" + // Yautja #define COMSIG_KB_YAUTJA_BUTCHER "keybinding_yautja_butcher" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 7ec2ab8b975e..e2bd868f9a80 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -309,77 +309,6 @@ #define CAN_HOLD_TWO_HANDS 1 #define CAN_HOLD_ONE_HAND 2 -// ------------ // -// STRAIN FLAGS // -// ------------ // - -// Queen strain flags -#define QUEEN_NORMAL "Normal" - -// Facehugger strain flags -#define FACEHUGGER_NORMAL "Normal" -#define FACEHUGGER_WATCHER "Watcher" - -// Drone strain flags -#define DRONE_NORMAL "Normal" -#define DRONE_HEALER "Healer" -#define DRONE_GARDENER "Gardener" - -// Hivelord strain flags -#define HIVELORD_NORMAL "Normal" -#define HIVELORD_RESIN_WHISPERER "Resin Whisperer" - -// Carrier strain flags -#define CARRIER_NORMAL "Normal" -#define CARRIER_EGGSAC "Eggsac" - -// Burrower strain flags -#define BURROWER_NORMAL "Normal" -#define BURROWER_TREMOR "Tremor" - -// Sentinel strain flags -#define SENTINEL_NORMAL "Normal" - -// Spitter strain flags -#define SPITTER_NORMAL "Normal" - -// Boiler strain flags -#define BOILER_NORMAL "Normal" -#define BOILER_TRAPPER "Trapper" - -// Runner strain flags -#define RUNNER_NORMAL "Normal" -#define RUNNER_ACIDER "Acider" - -// Lurker strain flags -#define LURKER_NORMAL "Normal" -#define LURKER_VAMPIRE "Vampire" -// Ravager strain flags -#define RAVAGER_NORMAL "Normal" -#define RAVAGER_HEDGEHOG "Hedgehog" -#define RAVAGER_BERSERKER "Berserker" - -// Defender strain flags -#define DEFENDER_NORMAL "Normal" -#define DEFENDER_STEELCREST "Steelcrest" - -// Warrior strain flags -#define WARRIOR_NORMAL "Normal" - -// Crusher strain flags -#define CRUSHER_NORMAL "Normal" -#define CRUSHER_CHARGER "Charger" - -// Praetorian strain flags -#define PRAETORIAN_NORMAL "Normal" -#define PRAETORIAN_VANGUARD "Vanguard" -#define PRAETORIAN_DANCER "Dancer" -#define PRAETORIAN_WARDEN "Warden" -#define PRAETORIAN_OPPRESSOR "Oppressor" - -// Hellhound strain flags -#define HELLHOUND_NORMAL "Normal" - GLOBAL_LIST_INIT(default_onmob_icons, list( WEAR_L_HAND = 'icons/mob/humans/onmob/items_lefthand_0.dmi', WEAR_R_HAND = 'icons/mob/humans/onmob/items_righthand_0.dmi', @@ -452,4 +381,3 @@ GLOBAL_LIST_INIT(default_xeno_onmob_icons, list( #define MOBILITY_FLAGS_DEFAULT (MOBILITY_MOVE | MOBILITY_STAND) #define MOBILITY_FLAGS_CARBON_DEFAULT (MOBILITY_MOVE | MOBILITY_STAND | MOBILITY_REST | MOBILITY_LIEDOWN) #define MOBILITY_FLAGS_REST_CAPABLE_DEFAULT (MOBILITY_MOVE | MOBILITY_STAND | MOBILITY_REST | MOBILITY_LIEDOWN) - diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 96335a3c1acf..47aa0e732c76 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -146,8 +146,9 @@ #define SS_INIT_DATABASE -27 #define SS_INIT_ENTITYMANAGER -28 #define SS_INIT_PLAYTIME -29 -#define SS_INIT_PREDSHIPS -30 -#define SS_INIT_OBJECTIVES -31 +#define SS_INIT_STICKY -30 +#define SS_INIT_PREDSHIPS -31 +#define SS_INIT_OBJECTIVES -32 #define SS_INIT_MINIMAP -34 #define SS_INIT_STATPANELS -98 #define SS_INIT_CHAT -100 //Should be last to ensure chat remains smooth during init. diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm index 2761bc12acf0..6f6e4eef7c20 100644 --- a/code/__DEFINES/xeno.dm +++ b/code/__DEFINES/xeno.dm @@ -361,6 +361,48 @@ #define RESIN_CONSTRUCTION_NO_MAX -1 +// -------------- // +// STRAIN DEFINES // +// -------------- // + +// Facehugger strain flags +#define FACEHUGGER_WATCHER "Watcher" + +// Drone strain flags +#define DRONE_HEALER "Healer" +#define DRONE_GARDENER "Gardener" + +// Hivelord strain flags +#define HIVELORD_RESIN_WHISPERER "Resin Whisperer" + +// Carrier strain flags +#define CARRIER_EGGSAC "Eggsac" + +// Boiler strain flags +#define BOILER_TRAPPER "Trapper" + +// Runner strain flags +#define RUNNER_ACIDER "Acider" + +// Lurker strain flags +#define LURKER_VAMPIRE "Vampire" + +// Ravager strain flags +#define RAVAGER_HEDGEHOG "Hedgehog" +#define RAVAGER_BERSERKER "Berserker" + +// Defender strain flags +#define DEFENDER_STEELCREST "Steelcrest" + +// Crusher strain flags +#define CRUSHER_CHARGER "Charger" + +// Praetorian strain flags +#define PRAETORIAN_VANGUARD "Vanguard" +#define PRAETORIAN_DANCER "Dancer" +#define PRAETORIAN_WARDEN "Warden" +#define PRAETORIAN_OPPRESSOR "Oppressor" + ///////////////////////////////////////////////////////////////////////////////////// // // Modifiers diff --git a/code/__HELPERS/logging.dm b/code/__HELPERS/logging.dm index c20db3da303f..d6c18c8a93be 100644 --- a/code/__HELPERS/logging.dm +++ b/code/__HELPERS/logging.dm @@ -209,10 +209,10 @@ GLOBAL_VAR_INIT(log_end, world.system_type == UNIX ? ascii2text(13) : "") WRITE_LOG(GLOB.world_game_log, "MISC: [text]") GLOB.STUI?.debug.Add("\[[time]]MISC: [text]") -/proc/log_mutator(text) - if(!GLOB.mutator_logs) +/proc/log_strain(text) + if(!GLOB.strain_logs) return - WRITE_LOG(GLOB.mutator_logs, "[text]") + WRITE_LOG(GLOB.strain_logs, "[text]") /proc/log_hiveorder(text) var/time = time_stamp() diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm index 1f3404f403f2..7d9cd3324067 100644 --- a/code/_globalvars/global_lists.dm +++ b/code/_globalvars/global_lists.dm @@ -166,9 +166,6 @@ GLOBAL_LIST_INIT(language_keys, setup_language_keys()) //table of say codes for GLOBAL_REFERENCE_LIST_INDEXED(origins, /datum/origin, name) GLOBAL_LIST_INIT(player_origins, USCM_ORIGINS) -//Xeno mutators -GLOBAL_REFERENCE_LIST_INDEXED_SORTED(xeno_mutator_list, /datum/xeno_mutator, name) - //Xeno hives GLOBAL_LIST_INIT_TYPED(hive_datum, /datum/hive_status, list( XENO_HIVE_NORMAL = new /datum/hive_status(), diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 385cbcb8d446..83929ecf8803 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -491,8 +491,6 @@ This maintains a list of ip addresses that are able to bypass topic filtering. /datum/config_entry/flag/respawn -/datum/config_entry/flag/ToRban - /datum/config_entry/flag/ooc_country_flags /datum/config_entry/flag/record_rounds @@ -533,6 +531,8 @@ This maintains a list of ip addresses that are able to bypass topic filtering. /datum/config_entry/string/round_results_webhook_url +/datum/config_entry/string/important_log_channel + /// InfluxDB v2 Host to connect to for sending statistics (over HTTP API) /datum/config_entry/string/influxdb_host /// InfluxDB v2 Bucket to send staistics to diff --git a/code/controllers/subsystem/stickyban.dm b/code/controllers/subsystem/stickyban.dm new file mode 100644 index 000000000000..48e934addc1a --- /dev/null +++ b/code/controllers/subsystem/stickyban.dm @@ -0,0 +1,284 @@ +SUBSYSTEM_DEF(stickyban) + name = "Sticky Ban" + init_order = SS_INIT_STICKY + flags = SS_NO_FIRE + +/datum/controller/subsystem/stickyban/Initialize() + var/list/all_bans = world.GetConfig("ban") + + for(var/existing_ban in all_bans) + var/list/ban_data = params2list(world.GetConfig("ban", existing_ban)) + INVOKE_ASYNC(src, PROC_REF(import_sticky), existing_ban, ban_data) + + return SS_INIT_SUCCESS + +/** + * Returns a list of [/datum/view_record/stickyban]s, or null, if no stickybans are found. All arguments are optional, but you should pass at least one if you want any results. + */ +/datum/controller/subsystem/stickyban/proc/check_for_sticky_ban(ckey, address, computer_id) + var/list/stickyban_ids = list() + + for(var/datum/view_record/stickyban_matched_ckey/matched_ckey as anything in get_impacted_ckey_records(ckey)) + stickyban_ids += matched_ckey.linked_stickyban + + for(var/datum/view_record/stickyban_matched_cid/matched_cid as anything in get_impacted_cid_records(computer_id)) + stickyban_ids += matched_cid.linked_stickyban + + for(var/datum/view_record/stickyban_matched_ip/matched_ip as anything in get_impacted_ip_records(address)) + stickyban_ids += matched_ip.linked_stickyban + + if(!length(stickyban_ids)) + return FALSE + + var/list/datum/view_record/stickyban/stickies = DB_VIEW(/datum/view_record/stickyban, + DB_AND( + DB_COMP("id", DB_IN, stickyban_ids), + DB_COMP("active", DB_EQUALS, TRUE) + ) + ) + + for(var/datum/view_record/stickyban/current_sticky in stickies) + if(length(get_whitelisted_ckey_records(current_sticky.id, ckey))) + stickies -= current_sticky + + if(!length(stickies)) + return FALSE + + return stickies + +/** + * Associates an existing stickyban with a new match, either of a ckey, address, or computer_id. Or all three. + * + * Arguments: + * - existing_ban_id, int, required + * - ckey, string, optional + * - address, string, optional + * - computer_id, string, optional + */ +/datum/controller/subsystem/stickyban/proc/match_sticky(existing_ban_id, ckey, address, computer_id) + if(!existing_ban_id) + return + + if(ckey) + add_matched_ckey(existing_ban_id, ckey) + + if(address) + add_matched_ip(existing_ban_id, address) + + if(computer_id) + add_matched_cid(existing_ban_id, computer_id) + +/** + * Adds a new tracked stickyban, and returns a [/datum/entity/stickyban] if it was successful. Blocking, sleeps. + */ +/datum/controller/subsystem/stickyban/proc/add_stickyban(identifier, reason, message, datum/entity/player/banning_admin, override_date) + var/datum/entity/stickyban/new_sticky = DB_ENTITY(/datum/entity/stickyban) + new_sticky.identifier = identifier + new_sticky.reason = reason + new_sticky.message = message + + if(banning_admin) + new_sticky.adminid = banning_admin.id + + new_sticky.date = override_date ? override_date : "[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]" + new_sticky.save() + new_sticky.sync() + + return new_sticky + +/// Adds a ckey match to the specified sticky ban. +/datum/controller/subsystem/stickyban/proc/add_matched_ckey(existing_ban_id, key) + key = ckey(key) + + if(length(DB_VIEW(/datum/view_record/stickyban_matched_ckey, + DB_AND( + DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id), + DB_COMP("ckey", DB_EQUALS, key) + ) + ))) + return + + var/datum/entity/stickyban_matched_ckey/matched_ckey = DB_ENTITY(/datum/entity/stickyban_matched_ckey) + + matched_ckey.ckey = key + matched_ckey.linked_stickyban = existing_ban_id + + matched_ckey.save() + +/// Adds an IP match to the specified stickyban. +/datum/controller/subsystem/stickyban/proc/add_matched_ip(existing_ban_id, ip) + if(length(DB_VIEW(/datum/view_record/stickyban_matched_ip, + DB_AND( + DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id), + DB_COMP("ip", DB_EQUALS, ip) + ) + ))) + return + + var/datum/entity/stickyban_matched_ip/matched_ip = DB_ENTITY(/datum/entity/stickyban_matched_ip) + + matched_ip.ip = ip + matched_ip.linked_stickyban = existing_ban_id + + matched_ip.save() + +/// Adds a CID match to the specified stickyban. +/datum/controller/subsystem/stickyban/proc/add_matched_cid(existing_ban_id, cid) + if(length(DB_VIEW(/datum/view_record/stickyban_matched_cid, + DB_AND( + DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id), + DB_COMP("cid", DB_EQUALS, cid) + ) + ))) + return + + + var/datum/entity/stickyban_matched_cid/matched_cid = DB_ENTITY(/datum/entity/stickyban_matched_cid) + + matched_cid.cid = cid + matched_cid.linked_stickyban = existing_ban_id + + matched_cid.save() + +/// Whitelists a specific CKEY to the specified stickyban, which will allow connection, even with matching CIDs and IPs. +/datum/controller/subsystem/stickyban/proc/whitelist_ckey(existing_ban_id, key) + key = ckey(key) + + if(!key) + return + + var/id_to_select + + var/list/datum/view_record/stickyban_matched_ckey/existing_matches = DB_VIEW(/datum/view_record/stickyban_matched_ckey, + DB_AND( + DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id), + DB_COMP("ckey", DB_EQUALS, key) + ) + ) + + if(length(existing_matches)) + var/datum/view_record/stickyban_matched_ckey/match = existing_matches[1] + id_to_select = match.id + + var/datum/entity/stickyban_matched_ckey/whitelisted_ckey = DB_ENTITY(/datum/entity/stickyban_matched_ckey, id_to_select) + + whitelisted_ckey.ckey = key + whitelisted_ckey.linked_stickyban = existing_ban_id + whitelisted_ckey.whitelisted = TRUE + + whitelisted_ckey.save() + +/** + * Returns a [/list] of [/datum/view_record/stickyban_matched_ckey] where the ckey provided has not been + * whitelisted from the stickyban, and would be prevented from joining - provided that the stickyban itself + * remains active. + */ +/datum/controller/subsystem/stickyban/proc/get_impacted_ckey_records(key) + key = ckey(key) + + return DB_VIEW(/datum/view_record/stickyban_matched_ckey, + DB_AND( + DB_COMP("ckey", DB_EQUALS, key), + DB_COMP("whitelisted", DB_EQUALS, FALSE) + ) + ) + +/** + * Returns a [/list] of [/datum/view_record/stickyban_matched_ckey] which have been manually whitelisted by an admin and matches the provided existing_ban_id and key. + */ +/datum/controller/subsystem/stickyban/proc/get_whitelisted_ckey_records(existing_ban_id, key) + key = ckey(key) + + return DB_VIEW(/datum/view_record/stickyban_matched_ckey, + DB_AND( + DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id), + DB_COMP("ckey", DB_EQUALS, key), + DB_COMP("whitelisted", DB_EQUALS, TRUE), + ) + ) + +/** + * Returns a [/list] of [/datum/view_record/stickyban_matched_cid] where the impacted CID matches the CID provided. + * Connections matching this CID will be blocked - provided the linked stickyban is active. + */ +/datum/controller/subsystem/stickyban/proc/get_impacted_cid_records(cid) + return DB_VIEW(/datum/view_record/stickyban_matched_cid, + DB_COMP("cid", DB_EQUALS, cid) + ) + +/** + * Returns a [/list] of [/datum/view_record/stickyban_matched_ip] where the impacted IP matches the IP provided. + * Connections matchin this IP will be blocked - provided the linked stickyban is active. + */ +/datum/controller/subsystem/stickyban/proc/get_impacted_ip_records(ip) + return DB_VIEW(/datum/view_record/stickyban_matched_ip, + DB_COMP("ip", DB_EQUALS, ip) + ) + +/// Legacy import from pager bans to database bans. +/datum/controller/subsystem/stickyban/proc/import_sticky(identifier, list/ban_data) + WAIT_DB_READY + + if(ban_data["type"] != "sticky") + handle_old_perma(identifier, ban_data) + return + + if(!ban_data["message"]) + ban_data["message"] = "Evasion" + + add_stickyban(identifier, ban_data["reason"], ban_data["message"], override_date = "LEGACY") + +/** + * We abuse the on_insert from ndatabase here to ensure we have the synced ID of the new stickyban when applying a *lot* of associated bans. If we don't have a matching pager ban with the new sticky's identifier, we stop. + */ +/datum/entity_meta/stickyban/on_insert(datum/entity/stickyban/new_sticky) + var/list/ban_data = params2list(world.GetConfig("ban", new_sticky.identifier)) + + if(!length(ban_data)) + return + + var/list/whitelisted = list() + if(ban_data["whitelist"]) + whitelisted = splittext(ban_data["whitelist"], ",") + for(var/key in whitelisted) + SSstickyban.whitelist_ckey(new_sticky.id, key) + + if(ban_data["keys"]) + var/list/keys = splittext(ban_data["keys"], ",") + keys -= whitelisted + for(var/key in keys) + SSstickyban.add_matched_ckey(new_sticky.id, key) + + if(ban_data["computer_id"]) + var/list/cids = splittext(ban_data["computer_id"], ",") + for(var/cid in cids) + SSstickyban.add_matched_cid(new_sticky.id, cid) + + if(ban_data["IP"]) + var/list/ips = splittext(ban_data["IP"], ",") + for(var/ip in ips) + SSstickyban.add_matched_ip(new_sticky.id, ip) + + world.SetConfig("ban", new_sticky.identifier, null) + +/// Imports permabans from the old ban.txt, and does *not* ban people that have been whitelisted. +/datum/controller/subsystem/stickyban/proc/handle_old_perma(identifier, list/ban_data) + var/list/keys_to_ban = list() + + keys_to_ban += splittext(ban_data["keys"], ",") + + for(var/x in 1 to length(keys_to_ban)) + keys_to_ban[x] = ckey(keys_to_ban[x]) + + var/list/keys = splittext(ban_data["whitelist"], ",") + for(var/key in keys) + keys_to_ban -= ckey(key) + + for(var/key in keys_to_ban) + var/datum/entity/player/player_entity = get_player_from_key(key) + if(!player_entity) + continue + + INVOKE_ASYNC(player_entity, TYPE_PROC_REF(/datum/entity/player, add_perma_ban), ban_data["message"]) + + world.SetConfig("ban", identifier, null) diff --git a/code/datums/ammo/bullet/rifle.dm b/code/datums/ammo/bullet/rifle.dm index 0be6f1db8ff4..ab30599eeb12 100644 --- a/code/datums/ammo/bullet/rifle.dm +++ b/code/datums/ammo/bullet/rifle.dm @@ -169,16 +169,16 @@ shell_speed = AMMO_SPEED_TIER_6 /datum/ammo/bullet/rifle/m4ra/impact/on_hit_mob(mob/M, obj/projectile/P) - knockback(M, P, 32) // Can knockback basically at max range + knockback(M, P, 32) // Can knockback basically at max range max range is 24 tiles... /datum/ammo/bullet/rifle/m4ra/impact/knockback_effects(mob/living/living_mob, obj/projectile/fired_projectile) if(iscarbonsizexeno(living_mob)) var/mob/living/carbon/xenomorph/target = living_mob to_chat(target, SPAN_XENODANGER("You are shaken and slowed by the sudden impact!")) - target.KnockDown(0.5) // purely for visual effect, noone actually cares - target.Stun(0.5) - target.apply_effect(2, SUPERSLOW) - target.apply_effect(5, SLOW) + target.KnockDown(0.5-fired_projectile.distance_travelled/100) // purely for visual effect, noone actually cares + target.Stun(0.5-fired_projectile.distance_travelled/100) + target.apply_effect(2-fired_projectile.distance_travelled/20, SUPERSLOW) + target.apply_effect(5-fired_projectile.distance_travelled/10, SLOW) else if(!isyautja(living_mob)) //Not predators. living_mob.apply_effect(1, SUPERSLOW) diff --git a/code/datums/components/temporary_mute.dm b/code/datums/components/temporary_mute.dm new file mode 100644 index 000000000000..0fed49c4778e --- /dev/null +++ b/code/datums/components/temporary_mute.dm @@ -0,0 +1,90 @@ +/datum/component/temporary_mute + dupe_mode = COMPONENT_DUPE_UNIQUE + /// A message to tell the user when they attempt to speak, if any + var/on_speak_message = "" + /// A message to tell the user when they attempt to emote, if any + var/on_emote_message = "" + /// A message to tell the user when they become no longer mute, if any + var/on_unmute_message = "" + /// How long after the component's initialization it should be deleted. -1 means it will never delete + var/time_until_unmute = 3 MINUTES + +/datum/component/temporary_mute/Initialize(on_speak_message = "", on_emote_message = "", on_unmute_message = "", time_until_unmute = 3 MINUTES) + . = ..() + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + + src.on_speak_message = on_speak_message + src.on_emote_message = on_emote_message + src.on_unmute_message = on_unmute_message + src.time_until_unmute = time_until_unmute + if(time_until_unmute != -1) + QDEL_IN(src, time_until_unmute) + +/datum/component/temporary_mute/RegisterWithParent() + ..() + RegisterSignal(parent, COMSIG_LIVING_SPEAK, PROC_REF(on_speak)) + RegisterSignal(parent, COMSIG_XENO_TRY_HIVEMIND_TALK, PROC_REF(on_hivemind)) + RegisterSignal(parent, COMSIG_MOB_TRY_EMOTE, PROC_REF(on_emote)) + RegisterSignal(parent, COMSIG_MOB_TRY_POINT, PROC_REF(on_point)) + +/datum/component/temporary_mute/UnregisterFromParent() + ..() + if(parent) + UnregisterSignal(parent, COMSIG_LIVING_SPEAK) + UnregisterSignal(parent, COMSIG_XENO_TRY_HIVEMIND_TALK) + UnregisterSignal(parent, COMSIG_MOB_TRY_EMOTE) + UnregisterSignal(parent, COMSIG_MOB_TRY_POINT) + if(on_unmute_message) + to_chat(parent, SPAN_NOTICE(on_unmute_message)) + +/datum/component/temporary_mute/proc/on_speak( + mob/user, + message, + datum/language/speaking = null, + verb = "says", + alt_name = "", + italics = FALSE, + message_range = GLOB.world_view_size, + sound/speech_sound, + sound_vol, + nolog = FALSE, + message_mode = null +) + SIGNAL_HANDLER + + if(!nolog) + log_say("[user.name != "Unknown" ? user.name : "([user.real_name])"] attempted to say the following before their spawn mute ended: [message] (CKEY: [user.key]) (JOB: [user.job])") + if(on_speak_message) + to_chat(parent, SPAN_BOLDNOTICE(on_speak_message)) + return COMPONENT_OVERRIDE_SPEAK + +/datum/component/temporary_mute/proc/on_hivemind(mob/user, message) + SIGNAL_HANDLER + + log_say("[user.name != "Unknown" ? user.name : "([user.real_name])"] attempted to hivemind the following before their spawn mute ended: [message] (CKEY: [user.key]) (JOB: [user.job])") + if(on_speak_message) + to_chat(parent, SPAN_BOLDNOTICE(on_speak_message)) + return COMPONENT_OVERRIDE_HIVEMIND_TALK + +/datum/component/temporary_mute/proc/on_emote(mob/user, datum/emote/current_emote, act, m_type, param, intentional) + SIGNAL_HANDLER + + // Allow involuntary emotes or non-custom emotes + if(!intentional) + return + if(!param && !istype(current_emote, /datum/emote/custom)) + return + + log_say("[user.name != "Unknown" ? user.name : "([user.real_name])"] attempted to emote the following before their spawn mute ended: [param] (CKEY: [user.key]) (JOB: [user.job])") + if(on_emote_message) + to_chat(parent, SPAN_BOLDNOTICE(on_emote_message)) + return COMPONENT_OVERRIDE_EMOTE + +/datum/component/temporary_mute/proc/on_point(mob/user, atom/target) + SIGNAL_HANDLER + + log_say("[user.name != "Unknown" ? user.name : "([user.real_name])"] attempted to point at the following before their spawn mute ended: [target] (CKEY: [user.key]) (JOB: [user.job])") + if(on_emote_message) + to_chat(parent, SPAN_BOLDNOTICE(on_emote_message)) + return COMPONENT_OVERRIDE_POINT diff --git a/code/datums/emergency_calls/goons.dm b/code/datums/emergency_calls/goons.dm index 8a0b00968807..fb491a2f43c4 100644 --- a/code/datums/emergency_calls/goons.dm +++ b/code/datums/emergency_calls/goons.dm @@ -96,6 +96,45 @@ to_chat(backstory_human, SPAN_BOLD("You heard about the original distress signal ages ago, but you have only just gotten permission from corporate to enter the area.")) to_chat(backstory_human, SPAN_BOLD("Ensure no damage is incurred against Weyland-Yutani. Make sure the researcher is kept safe and follow their instructions.")) +/datum/emergency_call/goon/bodyguard + name = "Weyland-Yutani Goon (Executive Bodyguard Detail)" + mob_max = 1 + mob_min = 1 + +/datum/emergency_call/goon/bodyguard/New() + ..() + dispatch_message = "[MAIN_SHIP_NAME], this is a Weyland-Yutani Corporate Security Protection Detail shuttle inbound to the Liaison's Beacon." + objectives = "Protect the Corporate Liaison and follow his commands, unless it goes against Company policy. Do not damage Wey-Yu property." + +/datum/emergency_call/goon/bodyguard/create_member(datum/mind/M, turf/override_spawn_loc) + var/turf/spawn_loc = override_spawn_loc ? override_spawn_loc : get_spawn_point() + + if(!istype(spawn_loc)) + return //Didn't find a useable spawn point. + + var/mob/living/carbon/human/mob = new(spawn_loc) + M.transfer_to(mob, TRUE) + + if(!leader && HAS_FLAG(mob.client.prefs.toggles_ert, PLAY_LEADER) && check_timelock(mob.client, JOB_SQUAD_LEADER, time_required_for_job)) + leader = mob + to_chat(mob, SPAN_ROLE_HEADER("You are a Weyland-Yutani Corporate Security Lead!")) + arm_equipment(mob, /datum/equipment_preset/goon/lead, TRUE, TRUE) + else + to_chat(mob, SPAN_ROLE_HEADER("You are a Weyland-Yutani Corporate Security Officer!")) + arm_equipment(mob, /datum/equipment_preset/goon/standard, TRUE, TRUE) + + print_backstory(mob) + + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), mob, SPAN_BOLD("Objectives: [objectives]")), 1 SECONDS) + +/datum/emergency_call/goon/bodyguard/print_backstory(mob/living/carbon/human/M) + to_chat(M, SPAN_BOLD("You were born [pick(75;"in Europe", 15;"in Asia", 10;"on Mars")] to a poor family.")) + to_chat(M, SPAN_BOLD("Joining the ranks of Weyland-Yutani was all you could do to keep yourself and your loved ones fed.")) + to_chat(M, SPAN_BOLD("You have no idea what a xenomorph is.")) + to_chat(M, SPAN_BOLD("You are a simple security officer employed by Weyland-Yutani to guard their Executives from all Divisions alike.")) + to_chat(M, SPAN_BOLD("You were sent to act as the Executives bodyguard on the [MAIN_SHIP_NAME], you have gotten permission from corporate to enter the area.")) + to_chat(M, SPAN_BOLD("Ensure no damage is incurred against Weyland-Yutani. Make sure the CL is safe.")) + /datum/emergency_call/goon/platoon name = "Weyland-Yutani Corporate Security (Platoon)" mob_min = 8 diff --git a/code/datums/emergency_calls/inspection.dm b/code/datums/emergency_calls/inspection.dm index f0400528ac31..d2efcf5fe275 100644 --- a/code/datums/emergency_calls/inspection.dm +++ b/code/datums/emergency_calls/inspection.dm @@ -164,6 +164,42 @@ new /obj/item/storage/box/handcuffs(drop_spawn) new /obj/item/storage/box/handcuffs(drop_spawn) +/datum/emergency_call/inspection_wy/lawyer + name = "Lawyers - Corporate" + mob_max = 2 + mob_min = 1 + name_of_spawn = /obj/effect/landmark/ert_spawns/distress_pmc + item_spawn = /obj/effect/landmark/ert_spawns/distress_pmc/item + probability = 0 + +/datum/emergency_call/inspection_wy/lawyer/New() + ..() + objectives = "Make sure the crew of the [MAIN_SHIP_NAME] is aware of your presence. Investigate who the Corporate Liaison reported for breaking their contract and any review other Company assets and make sure they remain loyal to the Company. Make a detailed report back to Corporate." + +/datum/emergency_call/inspection_wy/lawyer/create_member(datum/mind/M, turf/override_spawn_loc) + var/turf/T = override_spawn_loc ? override_spawn_loc : get_spawn_point() + + if(!istype(T)) + return FALSE + + var/mob/living/carbon/human/H = new(T) + M.transfer_to(H, TRUE) + + if(!leader && HAS_FLAG(H.client.prefs.toggles_ert, PLAY_LEADER) && check_timelock(H.client, list(JOB_SQUAD_LEADER), time_required_for_job)) + leader = H + arm_equipment(H, /datum/equipment_preset/wy/exec_supervisor/lawyer, TRUE, TRUE) + to_chat(H, SPAN_ROLE_HEADER("You are a Weyland-Yutani Lead Corporate Attorney!")) + to_chat(H, SPAN_ROLE_BODY("While officially the Corporate Affairs Division does mundane paperwork for Weyland-Yutani, in practice you serve as both official and unofficial investigators into conduct of Company and non-Company personnel. You are being dispatched to the [MAIN_SHIP_NAME] to make sure that the USCM abides by it's signed contracts provided by the local Liaison and that they have not forgotten the real hand that feeds them.")) + to_chat(H, SPAN_ROLE_BODY("Remember the USCM personnel on the ship may not appreciate your presence there. Should the Liaison be in jail, you are to act as legal counsel in any way. Your basic duty is to make a detailed report of anything involving the Liaison, any other WY personnel and of course any contract violations on board the ship.")) + to_chat(H, SPAN_WARNING("You are to avoid open conflict with the Marines. Retreat and make a report if they are outright hostile. Ahelp if you have any more questions or wish to release this character for other players.")) + else + arm_equipment(H, /datum/equipment_preset/wy/exec_spec/lawyer, TRUE, TRUE) + to_chat(H, SPAN_ROLE_HEADER("You are a Weyland-Yutani Corporate Attorney!")) + to_chat(H, SPAN_ROLE_BODY("While officially the Corporate Affairs Division does mundane paperwork for Weyland-Yutani, in practice you serve as both official and unofficial investigators into conduct of Company and non-Company personnel. The Lead Attorney is in charge, your duty is to provide counsel and any other form of assistance you can render to make sure your mission is a success.")) + to_chat(H, SPAN_ROLE_BODY("Remember that the USCM, or at least some parts of it, may be hostile towards your presence on the ship. You and the Lead Attorney are to avoid open conflict with the Marines. Your main priority is making sure that you both survive to write the report the Company is due.")) + to_chat(H, SPAN_WARNING("You are to avoid open conflict with the Marines. Retreat and make a report if they are outright hostile. Ahelp if you have any more questions or wish to release this character for other players.")) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), H, SPAN_BOLD("Objectives: [objectives]")), 1 SECONDS) + // Colonial Marshals - UA Law Enforcement / Investigative Federal Agents which usually watch over Colonies. Also a good option for prisoner transfers, investigating corporate corruption, survivor rescues, or illict trade practices(black market). /datum/emergency_call/inspection_cmb diff --git a/code/datums/emergency_calls/pizza.dm b/code/datums/emergency_calls/pizza.dm index a35ce584c68d..d80ea7e21139 100644 --- a/code/datums/emergency_calls/pizza.dm +++ b/code/datums/emergency_calls/pizza.dm @@ -8,7 +8,7 @@ objectives = "Make sure you get a tip!" shuttle_id = "Distress_Small" name_of_spawn = /obj/effect/landmark/ert_spawns/distress_pizza - probability = 0 + probability = 1 /datum/emergency_call/pizza/create_member(datum/mind/M, turf/override_spawn_loc) var/turf/spawn_loc = override_spawn_loc ? override_spawn_loc : get_spawn_point() diff --git a/code/datums/emergency_calls/upp.dm b/code/datums/emergency_calls/upp.dm index d68171981355..036e6f9e8b77 100644 --- a/code/datums/emergency_calls/upp.dm +++ b/code/datums/emergency_calls/upp.dm @@ -99,18 +99,20 @@ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), H, SPAN_BOLD("Objectives: [objectives]")), 1 SECONDS) -/datum/emergency_call/upp/hostile +/datum/emergency_call/upp/hostile //if admins want to specifically call in friendly ones name = "UPP Naval Infantry (Squad) (Hostile)" hostility = TRUE + probability = 0 /datum/emergency_call/upp/hostile/New() ..() arrival_message = "[MAIN_SHIP_NAME] t*is i* UP* d^sp^*ch`. STr*&e teaM, #*u are cLe*% for a*pr*%^h. Pr*mE a*l wE*p^ns and pR*epr# t% r@nd$r a(tD." objectives = "Eliminate the UA Forces to ensure the UPP presence in this sector is continued. Listen to your superior officers and take over the [MAIN_SHIP_NAME] at all costs." -/datum/emergency_call/upp/friendly +/datum/emergency_call/upp/friendly //ditto name = "UPP Naval Infantry (Squad) (Friendly)" hostility = FALSE + probability = 0 /datum/emergency_call/upp/friendly/New() ..() diff --git a/code/datums/entities/player.dm b/code/datums/entities/player.dm index 2973a174858f..f0b601652fe0 100644 --- a/code/datums/entities/player.dm +++ b/code/datums/entities/player.dm @@ -305,6 +305,36 @@ BSQL_PROTECT_DATUM(/datum/entity/player) return TRUE +/// Permanently bans this user, with the provided reason. The banner ([/datum/entity/player]) argument is optional, as this can be done without admin intervention. +/datum/entity/player/proc/add_perma_ban(reason, internal_reason, datum/entity/player/banner) + if(is_permabanned) + return FALSE + + is_permabanned = TRUE + permaban_date = "[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]" + permaban_reason = reason + + if(banner) + permaban_admin_id = banner.id + message_admins("[key_name_admin(banner.owning_client)] has permanently banned [ckey] for '[reason]'.") + var/datum/tgs_chat_embed/field/reason_embed + if(internal_reason) + reason_embed = new("Permaban Reason", internal_reason) + important_message_external("[banner.owning_client] has permanently banned [ckey] for '[reason]'.", "Permaban Placed", reason_embed ? list(reason_embed) : null) + + add_note("Permanently banned | [reason]", FALSE, NOTE_ADMIN, TRUE) + if(internal_reason) + add_note("Internal reason: [internal_reason]", TRUE, NOTE_ADMIN) + + if(owning_client) + to_chat_forced(owning_client, SPAN_LARGE("You have been permanently banned by [banner.ckey].\nReason: [reason].")) + to_chat_forced(owning_client, SPAN_LARGE("This is a permanent ban. It will not be removed.")) + QDEL_NULL(owning_client) + + save() + + return TRUE + /datum/entity/player/proc/auto_unban() if(!is_time_banned) return @@ -447,54 +477,45 @@ BSQL_PROTECT_DATUM(/datum/entity/player) record_login_triplet(player.ckey, address, computer_id) player_data.sync() -/datum/entity/player/proc/check_ban(computer_id, address) +/datum/entity/player/proc/check_ban(computer_id, address, is_telemetry) . = list() - var/list/linked_bans = check_for_sticky_ban(address, computer_id) - if(islist(linked_bans)) - var/datum/view_record/stickyban_list_view/SLW = LAZYACCESS(linked_bans, 1) - if(SLW) - var/reason = "" - - if(SLW.address == address) - reason += "IP Address Matches; " - if(SLW.computer_id == computer_id) - reason += "CID Matches; " - if(SLW.ckey == ckey) - reason += "Ckey Matches; " - - var/source_id = SLW.linked_stickyban - var/source_reason = SLW.linked_reason - var/source_ckey = SLW.linked_ckey - if(!source_id) - source_id = "[SLW.entry_id]" - source_reason = SLW.reason - source_ckey = SLW.ckey - - log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Stickybanned (Linked to [source_ckey]; Reason: [source_reason])") - message_admins("Failed Login: [ckey] (IP: [last_known_ip], CID: [last_known_cid]) - Stickybanned (Linked to ckey [source_ckey]; Reason: [source_reason])") - - DB_FILTER(/datum/entity/player_sticky_ban, - DB_AND( - DB_COMP("ckey", DB_EQUALS, ckey), - DB_COMP("address", DB_EQUALS, address), - DB_COMP("computer_id", DB_EQUALS, computer_id) - ), CALLBACK(src, PROC_REF(process_stickyban), address, computer_id, source_id, reason, null)) - - .["desc"] = "\nReason: Stickybanned\nExpires: PERMANENT" - .["reason"] = "ckey/id" - return . + var/list/datum/view_record/stickyban/all_stickies = SSstickyban.check_for_sticky_ban(ckey, address, computer_id) + + if(length(all_stickies)) + var/datum/view_record/stickyban/sticky = all_stickies[1] + + if(!is_telemetry) + log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Stickybanned (Reason: [sticky.reason])") + message_admins("Failed Login: [ckey] (IP: [last_known_ip], CID: [last_known_cid]) - Stickybanned (Reason: [sticky.reason])") + + var/appeal + if(CONFIG_GET(string/banappeals)) + appeal = "\nFor more information on your ban, or to appeal, head to [CONFIG_GET(string/banappeals)]" + + .["desc"] = "\nReason: Stickybanned - [sticky.message] Identifier: [sticky.identifier]\n[appeal]" + .["reason"] = "ckey/id" + + if(!is_telemetry) + SSstickyban.match_sticky(sticky.id, ckey, address, computer_id) + return + if(!is_time_banned && !is_permabanned) return null + var/appeal if(CONFIG_GET(string/banappeals)) appeal = "\nFor more information on your ban, or to appeal, head to [CONFIG_GET(string/banappeals)]" if(is_permabanned) - permaban_admin.sync() - log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Banned [permaban_reason]") - message_admins("Failed Login: [ckey] id:[last_known_cid] ip:[last_known_ip] - Banned [permaban_reason]") - .["desc"] = "\nReason: [permaban_reason]\nExpires: PERMANENT\nBy: [permaban_admin.ckey][appeal]" + var/banner = "Host" + if(permaban_admin_id) + var/datum/view_record/players/banning_admin = locate() in DB_VIEW(/datum/view_record/players, DB_COMP("id", DB_EQUALS, permaban_admin_id)) + banner = banning_admin.ckey + if(!is_telemetry) + log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Banned [permaban_reason]") + message_admins("Failed Login: [ckey] id:[last_known_cid] ip:[last_known_ip] - Banned [permaban_reason]") + .["desc"] = "\nReason: [permaban_reason]\nExpires: PERMANENT\nBy: [banner][appeal]" .["reason"] = "ckey/id" return . if(is_time_banned) @@ -509,8 +530,9 @@ BSQL_PROTECT_DATUM(/datum/entity/player) timeleftstring = "[round(time_left / 60, 0.1)] Hours" else timeleftstring = "[time_left] Minutes" - log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Banned [time_ban_reason]") - message_admins("Failed Login: [ckey] id:[last_known_cid] ip:[last_known_ip] - Banned [time_ban_reason]") + if(!is_telemetry) + log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Banned [time_ban_reason]") + message_admins("Failed Login: [ckey] id:[last_known_cid] ip:[last_known_ip] - Banned [time_ban_reason]") .["desc"] = "\nReason: [time_ban_reason]\nExpires: [timeleftstring]\nBy: [time_ban_admin.ckey][appeal]" .["reason"] = "ckey/id" return . @@ -681,7 +703,6 @@ BSQL_PROTECT_DATUM(/datum/entity/player) parent_entity = /datum/entity/player child_entity = /datum/entity/player child_field = "permaban_admin_id" - parent_name = "permabanning_admin" /datum/view_record/players diff --git a/code/datums/entities/player_sticky_ban.dm b/code/datums/entities/player_sticky_ban.dm index d79befddb04e..752334e8e001 100644 --- a/code/datums/entities/player_sticky_ban.dm +++ b/code/datums/entities/player_sticky_ban.dm @@ -1,94 +1,133 @@ -/datum/entity/player_sticky_ban - var/player_id - var/admin_id +BSQL_PROTECT_DATUM(/datum/entity/stickyban) + +/datum/entity/stickyban + var/identifier var/reason + var/message var/date - var/ckey - var/address - var/computer_id - - var/linked_stickyban - -BSQL_PROTECT_DATUM(/datum/entity/player_sticky_ban) + var/active = TRUE + var/adminid -/datum/entity_meta/player_sticky_ban - entity_type = /datum/entity/player_sticky_ban - table_name = "player_sticky_bans" +/datum/entity_meta/stickyban + entity_type = /datum/entity/stickyban + table_name = "stickyban" field_types = list( - "player_id"=DB_FIELDTYPE_BIGINT, - "admin_id"=DB_FIELDTYPE_BIGINT, - "reason"=DB_FIELDTYPE_STRING_MAX, - "date"=DB_FIELDTYPE_STRING_LARGE, - "address"=DB_FIELDTYPE_STRING_LARGE, - "ckey" = DB_FIELDTYPE_STRING_LARGE, - "computer_id"=DB_FIELDTYPE_STRING_LARGE, - "linked_stickyban"=DB_FIELDTYPE_BIGINT, + "identifier" = DB_FIELDTYPE_STRING_LARGE, + "reason" = DB_FIELDTYPE_STRING_LARGE, + "message" = DB_FIELDTYPE_STRING_LARGE, + "date" = DB_FIELDTYPE_STRING_LARGE, + "active" = DB_FIELDTYPE_INT, + "adminid" = DB_FIELDTYPE_BIGINT, ) +/datum/view_record/stickyban + var/id + var/identifier + var/reason + var/message + var/date + var/active + var/admin -/datum/entity_link/linked_sticky_bans - parent_entity = /datum/entity/player_sticky_ban - child_entity = /datum/entity/player_sticky_ban - child_field = "linked_stickyban" - - parent_name = "linked_ban" - child_name = "linked_bans" - -/datum/entity_link/player_to_player_sticky_bans - parent_entity = /datum/entity/player - child_entity = /datum/entity/player_sticky_ban - child_field = "player_id" - - parent_name = "player" - child_name = "stickybans" +/datum/entity_view_meta/stickyban + root_record_type = /datum/entity/stickyban + destination_entity = /datum/view_record/stickyban + fields = list( + "id", + "identifier", + "reason", + "message", + "date", + "active", + "admin" = DB_CASE(DB_COMP("adminid", DB_ISNOT), "stickybanning_admin.ckey", DB_CONST("AdminBot")) + ) -/datum/entity_link/admin_to_player_sticky_bans +/datum/entity_link/stickyban_to_banning_admin parent_entity = /datum/entity/player - child_entity = /datum/entity/player_sticky_ban - child_field = "admin_id" + child_entity = /datum/entity/stickyban + child_field = "adminid" + parent_name = "stickybanning_admin" - parent_name = "admin" +/datum/entity/stickyban_matched_ckey + var/ckey + var/linked_stickyban + var/whitelisted = FALSE -/datum/view_record/stickyban_list_view - var/entry_id - var/player_id - var/admin_id +/datum/entity_meta/stickyban_matched_ckey + entity_type = /datum/entity/stickyban_matched_ckey + table_name = "stickyban_matched_ckey" + field_types = list( + "ckey" = DB_FIELDTYPE_STRING_LARGE, + "linked_stickyban" = DB_FIELDTYPE_BIGINT, + "whitelisted" = DB_FIELDTYPE_INT, + ) - var/reason - var/date - var/address - var/computer_id +/datum/view_record/stickyban_matched_ckey + var/id var/ckey + var/linked_stickyban var/whitelisted +/datum/entity_view_meta/stickyban_matched_ckey + root_record_type = /datum/entity/stickyban_matched_ckey + destination_entity = /datum/view_record/stickyban_matched_ckey + fields = list( + "id", + "ckey", + "linked_stickyban", + "whitelisted", + ) + + +/datum/entity/stickyban_matched_cid + var/cid var/linked_stickyban - var/linked_ckey - var/linked_reason - var/admin_ckey - var/linked_admin_ckey +/datum/entity_meta/stickyban_matched_cid + entity_type = /datum/entity/stickyban_matched_cid + table_name = "stickyban_matched_cid" + field_types = list( + "cid" = DB_FIELDTYPE_STRING_LARGE, + "linked_stickyban" = DB_FIELDTYPE_BIGINT, + ) +/datum/view_record/stickyban_matched_cid + var/id + var/cid + var/linked_stickyban -/datum/entity_view_meta/stickyban_list_view - root_record_type = /datum/entity/player_sticky_ban - destination_entity = /datum/view_record/stickyban_list_view +/datum/entity_view_meta/stickyban_matched_cid + root_record_type = /datum/entity/stickyban_matched_cid + destination_entity = /datum/view_record/stickyban_matched_cid fields = list( - "entry_id" = "id", - "player_id", - "admin_id", + "id", + "cid", + "linked_stickyban", + ) - "reason", - "date", - "address", - "computer_id", - "ckey" = "player.ckey", - "whitelisted" = "player.stickyban_whitelisted", - "linked_stickyban", - "linked_ckey" = "linked_ban.player.ckey", - "linked_reason" = "linked_ban.reason", +/datum/entity/stickyban_matched_ip + var/ip + var/linked_stickyban - "admin_ckey" = "admin.ckey", - "linked_admin_ckey" = "linked_ban.admin.ckey" +/datum/entity_meta/stickyban_matched_ip + entity_type = /datum/entity/stickyban_matched_ip + table_name = "stickyban_matched_ip" + field_types = list( + "ip" = DB_FIELDTYPE_STRING_LARGE, + "linked_stickyban" = DB_FIELDTYPE_BIGINT, + ) + +/datum/view_record/stickyban_matched_ip + var/id + var/ip + var/linked_stickyban + +/datum/entity_view_meta/stickyban_matched_ip + root_record_type = /datum/entity/stickyban_matched_ip + destination_entity = /datum/view_record/stickyban_matched_ip + fields = list( + "id", + "ip", + "linked_stickyban", ) - order_by = list("entry_id" = DB_ORDER_BY_DESC) diff --git a/code/datums/keybinding/xenomorph.dm b/code/datums/keybinding/xenomorph.dm index cef04d01a75c..9fa525d50002 100644 --- a/code/datums/keybinding/xenomorph.dm +++ b/code/datums/keybinding/xenomorph.dm @@ -218,3 +218,18 @@ name = "evolve" full_name = "Evolve" keybind_signal = COMSIG_KB_XENO_EVOLVE + +/datum/keybinding/xenomorph/purchase_strain + hotkey_keys = list("Unbound") + classic_keys = list("Unbound") + name = "purchase_strain" + full_name = "Purchase Strain" + keybind_signal = COMSIG_KB_XENO_PURCHASE_STRAIN + +/datum/keybinding/xenomorph/purchase_strain/down(client/user) + . = ..() + if(.) + return + + var/mob/living/carbon/xenomorph/current_xeno = user?.mob + current_xeno.purchase_strain() diff --git a/code/datums/supply_packs/spec_ammo.dm b/code/datums/supply_packs/spec_ammo.dm index e20a5de865a3..f0eb5ab9cea2 100644 --- a/code/datums/supply_packs/spec_ammo.dm +++ b/code/datums/supply_packs/spec_ammo.dm @@ -157,13 +157,11 @@ group = "Weapons Specialist Ammo" /datum/supply_packs/ammo_scout_incendiary - name = "M4RA Scout Incendiary Magazine Crate (x5)" + name = "M4RA Scout Incendiary Magazine Crate (x3)" contains = list( /obj/item/ammo_magazine/rifle/m4ra/custom/incendiary, /obj/item/ammo_magazine/rifle/m4ra/custom/incendiary, /obj/item/ammo_magazine/rifle/m4ra/custom/incendiary, - /obj/item/ammo_magazine/rifle/m4ra/custom/incendiary, - /obj/item/ammo_magazine/rifle/m4ra/custom/incendiary, ) cost = 30 containertype = /obj/structure/closet/crate/ammo @@ -171,13 +169,11 @@ group = "Weapons Specialist Ammo" /datum/supply_packs/ammo_scout_impact - name = "M4RA Scout Impact Magazine Crate (x5)" + name = "M4RA Scout Impact Magazine Crate (x3)" contains = list( /obj/item/ammo_magazine/rifle/m4ra/custom/impact, /obj/item/ammo_magazine/rifle/m4ra/custom/impact, /obj/item/ammo_magazine/rifle/m4ra/custom/impact, - /obj/item/ammo_magazine/rifle/m4ra/custom/impact, - /obj/item/ammo_magazine/rifle/m4ra/custom/impact, ) cost = 30 containertype = /obj/structure/closet/crate/ammo diff --git a/code/datums/tutorial/_tutorial.dm b/code/datums/tutorial/_tutorial.dm index 5423453bbdb9..7dd7ac85c04d 100644 --- a/code/datums/tutorial/_tutorial.dm +++ b/code/datums/tutorial/_tutorial.dm @@ -52,6 +52,7 @@ GLOBAL_LIST_EMPTY_TYPED(ongoing_tutorials, /datum/tutorial) reservation = SSmapping.RequestBlockReservation(initial(tutorial_template.width), initial(tutorial_template.height)) if(!reservation) + abort_tutorial() return FALSE var/turf/bottom_left_corner_reservation = locate(reservation.bottom_left_coords[1], reservation.bottom_left_coords[2], reservation.bottom_left_coords[3]) diff --git a/code/datums/tutorial/_tutorial_menu.dm b/code/datums/tutorial/_tutorial_menu.dm index 42eb3f6aabfa..3c7a28e77b92 100644 --- a/code/datums/tutorial/_tutorial_menu.dm +++ b/code/datums/tutorial/_tutorial_menu.dm @@ -71,7 +71,7 @@ path = text2path(params["tutorial_path"]) - if(!path || !isnewplayer(usr)) + if(!ispath(path, /datum/tutorial) || !isnewplayer(usr)) return if(HAS_TRAIT(usr, TRAIT_IN_TUTORIAL) || istype(get_area(usr), /area/misc/tutorial)) @@ -79,5 +79,6 @@ return path = new path - path.start_tutorial(usr) - return TRUE + if(path.start_tutorial(usr)) + ui.close() + return TRUE diff --git a/code/datums/tutorial/xenomorph/_xenomorph.dm b/code/datums/tutorial/xenomorph/_xenomorph.dm index bd85cdb35f44..caa33d8eed43 100644 --- a/code/datums/tutorial/xenomorph/_xenomorph.dm +++ b/code/datums/tutorial/xenomorph/_xenomorph.dm @@ -22,6 +22,10 @@ // We don't want people talking to other xenomorphs across tutorials new_character.can_hivemind_speak = FALSE + // No age prefix or HUD element + new_character.age = XENO_NO_AGE + new_character.show_age_prefix = FALSE + new_character.generate_name() tutorial_mob = new_character xeno = new_character diff --git a/code/datums/tutorial/xenomorph/xenomorph_basic.dm b/code/datums/tutorial/xenomorph/xenomorph_basic.dm index 0415977835aa..965f7b55d3c0 100644 --- a/code/datums/tutorial/xenomorph/xenomorph_basic.dm +++ b/code/datums/tutorial/xenomorph/xenomorph_basic.dm @@ -4,6 +4,7 @@ name = "Xenomorph - Basic" desc = "A tutorial to get you acquainted with the very basics of how to play a xenomorph." icon_state = "xeno" + tutorial_id = "xeno_basic_1" tutorial_template = /datum/map_template/tutorial/s12x12 starting_xenomorph_type = /mob/living/carbon/xenomorph/drone @@ -20,6 +21,7 @@ xeno.plasma_max = 0 xeno.melee_damage_lower = 40 xeno.melee_damage_upper = 40 + xeno.lock_evolve = TRUE message_to_player("Welcome to the Xenomorph basic tutorial. You are [xeno.name], a drone, the workhorse of the hive.") @@ -105,7 +107,7 @@ UnregisterSignal(human_dummy, COMSIG_MOB_DEATH) message_to_player("Well done. Killing humans is one of many ways to help the hive.") - message_to_player("Another way is to capture them. This will grow a new xenomorph inside them which will eventually burst into a new playable xenomorph!") + message_to_player("Another way is to capture them. This will grow a new xenomorph inside them which will eventually burst into a new playable xenomorph!") addtimer(CALLBACK(human_dummy, TYPE_PROC_REF(/mob/living, rejuvenate)), 8 SECONDS) addtimer(CALLBACK(src, PROC_REF(proceed_to_tackle_phase)), 10 SECONDS) @@ -227,3 +229,5 @@ /datum/tutorial/xenomorph/basic/init_map() loc_from_corner(9,0).ChangeTurf(/turf/closed/wall/resin/tutorial) + +#undef WAITING_HEALTH_THRESHOLD diff --git a/code/datums/xeno_shields/shield_types/vanguard_shield.dm b/code/datums/xeno_shields/shield_types/vanguard_shield.dm index 5b9eebc04ab8..21d9fb12cfd7 100644 --- a/code/datums/xeno_shields/shield_types/vanguard_shield.dm +++ b/code/datums/xeno_shields/shield_types/vanguard_shield.dm @@ -50,7 +50,6 @@ if (!istype(linked_xeno)) return - if (linked_xeno.mutation_type == PRAETORIAN_VANGUARD) - var/datum/behavior_delegate/praetorian_vanguard/BD = linked_xeno.behavior_delegate - if (istype(BD)) - BD.last_combat_time = world.time + var/datum/behavior_delegate/praetorian_vanguard/behavior = linked_xeno.behavior_delegate + if (istype(behavior)) + behavior.last_combat_time = world.time diff --git a/code/defines/procs/admin.dm b/code/defines/procs/admin.dm index 1e4f02e95cc0..d48c02127ea9 100644 --- a/code/defines/procs/admin.dm +++ b/code/defines/procs/admin.dm @@ -1,3 +1,15 @@ -/proc/log_and_message_admins(message as text) - log_admin("[key_name(usr)] [message]") - message_admins("[key_name(usr)] [message]") +/proc/important_message_external(message, title, list/datum/tgs_chat_embed/field/fields) + if(CONFIG_GET(string/important_log_channel)) + var/datum/tgs_message_content/to_send = new("") + + var/datum/tgs_chat_embed/structure/embed = new() + embed.title = title ? title : "Important Log" + embed.description = message + embed.timestamp = time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss") + embed.colour = "#ED2939" + if(length(fields)) + embed.fields = fields + + to_send.embed = embed + + send2chat(to_send, CONFIG_GET(string/important_log_channel)) diff --git a/code/game/gamemodes/colonialmarines/colonialmarines.dm b/code/game/gamemodes/colonialmarines/colonialmarines.dm index 6dfe50542b00..192277a250cc 100644 --- a/code/game/gamemodes/colonialmarines/colonialmarines.dm +++ b/code/game/gamemodes/colonialmarines/colonialmarines.dm @@ -372,10 +372,16 @@ var/living = headcount["total_headcount"] if ((headcount["WY_headcount"] / living) > MAJORITY) musical_track = pick('sound/theme/lastmanstanding_wy.ogg') + log_game("3rd party victory: Weyland-Yutani") + message_admins("3rd party victory: Weyland-Yutani") else if ((headcount["UPP_headcount"] / living) > MAJORITY) musical_track = pick('sound/theme/lastmanstanding_upp.ogg') + log_game("3rd party victory: Union of Progressive Peoples") + message_admins("3rd party victory: Union of Progressive Peoples") else if ((headcount["CLF_headcount"] / living) > MAJORITY) musical_track = pick('sound/theme/lastmanstanding_clf.ogg') + log_game("3rd party victory: Colonial Liberation Front") + message_admins("3rd party victory: Colonial Liberation Front") else if ((headcount["marine_headcount"] / living) > MAJORITY) musical_track = pick('sound/theme/neutral_melancholy2.ogg') //This is the theme song for Colonial Marines the game, fitting else diff --git a/code/game/machinery/medical_pod/sleeper.dm b/code/game/machinery/medical_pod/sleeper.dm index 84ef2f579ba1..5868df901c6a 100644 --- a/code/game/machinery/medical_pod/sleeper.dm +++ b/code/game/machinery/medical_pod/sleeper.dm @@ -208,6 +208,14 @@ var/amount = text2num(params["amount"]) if(!length(chemical) || amount <= 0) return + if(!(amount in connected.amounts)) + log_debug("[amount] is an invalid amount to inject in [src]!") + return + if(!(chemical in connected.available_chemicals)) + log_debug("[chemical] is not available to inject in [src]!") + return + if(connected.occupant.reagents && connected.occupant.reagents.get_reagent_amount(chemical) + amount > connected.max_chem) + return if(connected.occupant.health > connected.min_health || (chemical in connected.emergency_chems)) connected.inject_chemical(usr, chemical, amount) else diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm index d9aa42dbd1f1..96e1635bb0b8 100644 --- a/code/game/machinery/newscaster.dm +++ b/code/game/machinery/newscaster.dm @@ -1,742 +1,12 @@ -//############################################## -//################### NEWSCASTERS BE HERE! #### -//###-Agouri################################### - -/datum/feed_message - var/author ="" - var/body ="" - var/message_type ="Story" - //var/parent_channel - var/backup_body ="" - var/backup_author ="" - var/is_admin_message = 0 - var/icon/img = null - var/icon/backup_img - -/datum/feed_channel - var/channel_name="" - var/list/datum/feed_message/messages = list() - //var/message_count = 0 - var/locked=0 - var/author="" - var/backup_author="" - var/censored=0 - var/is_admin_channel=0 - //var/page = null //For newspapers - -/datum/feed_message/proc/clear() - src.author = "" - src.body = "" - src.backup_body = "" - src.backup_author = "" - src.img = null - src.backup_img = null - -/datum/feed_channel/proc/clear() - src.channel_name = "" - src.messages = list() - src.locked = 0 - src.author = "" - src.backup_author = "" - src.censored = 0 - src.is_admin_channel = 0 - -/datum/feed_network - var/list/datum/feed_channel/network_channels = list() - var/datum/feed_message/wanted_issue - -GLOBAL_DATUM_INIT(news_network, /datum/feed_network, new()) //The global news-network, which is coincidentally a global list. - -GLOBAL_LIST_INIT_TYPED(allCasters, /obj/structure/machinery/newscaster, list()) //Global list that will contain reference to all newscasters in existence. - /obj/structure/machinery/newscaster name = "newscaster" desc = "A standard Weyland-Yutani-licensed newsfeed handler for use in commercial space stations. All the news you absolutely have no use for, in one place!" icon = 'icons/obj/structures/machinery/terminals.dmi' icon_state = "newscaster_normal" - var/isbroken = FALSE //1 if someone banged it with something heavy - var/ispowered = TRUE //starts powered, changes with power_change() - //var/list/datum/feed_channel/channel_list = list() //This list will contain the names of the feed channels. Each name will refer to a data region where the messages of the feed channels are stored. - //OBSOLETE: We're now using a global news network - var/screen = 0 //Or maybe I'll make it into a list within a list afterwards... whichever I prefer, go fuck yourselves :3 - // 0 = welcome screen - main menu - // 1 = view feed channels - // 2 = create feed channel - // 3 = create feed story - // 4 = feed story submited sucessfully - // 5 = feed channel created successfully - // 6 = ERROR: Cannot create feed story - // 7 = ERROR: Cannot create feed channel - // 8 = print newspaper - // 9 = viewing channel feeds - // 10 = censor feed story - // 11 = censor feed channel - //Holy shit this is outdated, made this when I was still starting newscasters :3 - var/paper_remaining = 0 - var/securityCaster = 0 - // 0 = Caster cannot be used to issue wanted posters - // 1 = the opposite - var/unit_no = 0 //Each newscaster has a unit number - //var/datum/feed_message/wanted //We're gonna use a feed_message to store data of the wanted person because fields are similar - //var/wanted_issue = 0 //OBSOLETE - // 0 = there's no WANTED issued, we don't need a special icon_state - // 1 = Guess what. - var/alert_delay = 500 - var/alert = 0 - // 0 = there hasn't been a news/wanted update in the last alert_delay - // 1 = there has - var/scanned_user = "Unknown" //Will contain the name of the person who currently uses the newscaster - var/msg = ""; //Feed message - var/obj/item/photo/photo = null - var/channel_name = ""; //the feed channel which will be receiving the feed, or being created - var/c_locked=0; //Will our new channel be locked to public submissions? - var/hitstaken = 0 //Death at 3 hits from an item with force>=15 - var/datum/feed_channel/viewing_channel = null anchored = TRUE - -/obj/structure/machinery/newscaster/security_unit //Security unit +/obj/structure/machinery/newscaster/security_unit name = "Security Newscaster" - securityCaster = 1 - -/obj/structure/machinery/newscaster/security_unit/New() //Constructor, ho~ - GLOB.allCasters += src - src.paper_remaining = 15 // Will probably change this to something better - for(var/obj/structure/machinery/newscaster/NEWSCASTER in GLOB.allCasters) // Let's give it an appropriate unit number - src.unit_no++ - src.update_icon() //for any custom ones on the map... - ..() //I just realised the newscasters weren't in the global machines list. The superconstructor call will tend to that - -/obj/structure/machinery/newscaster/security_unit/Destroy() - GLOB.allCasters -= src - return ..() - -/obj/structure/machinery/newscaster/update_icon() - if(!ispowered || isbroken) - icon_state = "newscaster_off" - if(isbroken) //If the thing is smashed, add crack overlay on top of the unpowered sprite. - src.overlays.Cut() - src.overlays += image(src.icon, "crack3") - return - - src.overlays.Cut() //reset overlays - - if(GLOB.news_network.wanted_issue) //wanted icon state, there can be no overlays on it as it's a priority message - icon_state = "newscaster_wanted" - return - - if(alert) //new message alert overlay - src.overlays += "newscaster_alert" - - if(hitstaken > 0) //Cosmetic damage overlay - src.overlays += image(src.icon, "crack[hitstaken]") - - icon_state = "newscaster_normal" - return - -/obj/structure/machinery/newscaster/power_change() - if(isbroken) //Broken shit can't be powered. - return - ..() - if( !(stat & NOPOWER) ) - src.ispowered = TRUE - src.update_icon() - else - spawn(rand(0, 15)) - src.ispowered = FALSE - src.update_icon() - - -/obj/structure/machinery/newscaster/ex_act(severity) - switch(severity) - if(0 to EXPLOSION_THRESHOLD_LOW) - if(prob(50)) - src.isbroken=1 - src.update_icon() - return - if(EXPLOSION_THRESHOLD_LOW to EXPLOSION_THRESHOLD_MEDIUM) - src.isbroken=1 - if(prob(50)) - deconstruct(FALSE) - else - src.update_icon() //can't place it above the return and outside the if-else. or we might get runtimes of null.update_icon() if(prob(50)) goes in. - return - if(EXPLOSION_THRESHOLD_MEDIUM to INFINITY) - deconstruct(FALSE) - return - return - -/obj/structure/machinery/newscaster/attack_remote(mob/user as mob) - return src.attack_hand(user) - -/obj/structure/machinery/newscaster/attack_hand(mob/user as mob) //########### THE MAIN BEEF IS HERE! And in the proc below this...############ - if(!src.ispowered || src.isbroken) - return - if(istype(user, /mob/living/carbon/human) || istype(user,/mob/living/silicon) ) - var/mob/living/human_or_robot_user = user - var/dat - dat = text("Newscaster

Newscaster Unit #[src.unit_no]

") - - src.scan_user(human_or_robot_user) //Newscaster scans you - - switch(screen) - if(0) - dat += "Welcome to Newscasting Unit #[src.unit_no].
Interface & News networks Operational." - dat += "
Property of Weyland-Yutani" - if(GLOB.news_network.wanted_issue) - dat+= "
Read Wanted Issue" - dat+= "

Create Feed Channel" - dat+= "
View Feed Channels" - dat+= "
Submit new Feed story" - dat+= "
Print newspaper" - dat+= "
Re-scan User" - dat+= "

Exit" - if(src.securityCaster) - var/wanted_already = 0 - if(GLOB.news_network.wanted_issue) - wanted_already = 1 - - dat+="
Feed Security functions:
" - dat+="
[(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue" - dat+="
Censor Feed Stories" - dat+="
Mark Feed Channel with Weyland-Yutani D-Notice" - dat+="

The newscaster recognises you as: [src.scanned_user]" - if(1) - dat+= "Station Feed Channels
" - if(!length(GLOB.news_network.network_channels) ) - dat+="No active channels found..." - else - for(var/datum/feed_channel/CHANNEL in GLOB.news_network.network_channels) - if(CHANNEL.is_admin_channel) - dat+="[CHANNEL.channel_name]
" - else - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : null]
" - /*for(var/datum/feed_channel/CHANNEL in src.channel_list) - dat+="[CHANNEL.channel_name]:
\[created by: [CHANNEL.author]\]

" - if(!length(CHANNEL.messages) ) - dat+="No feed messages found in channel...

" - else - for(var/datum/feed_message/MESSAGE in CHANNEL.messages) - dat+="-[MESSAGE.body]
\[[MESSAGE.message_type] by [MESSAGE.author]\]
"*/ - - dat+="

Refresh" - dat+="
Back" - if(2) - dat+="Creating new Feed Channel..." - dat+="
Channel Name: [src.channel_name]
" - dat+="Channel Author: [src.scanned_user]
" - dat+="Will Accept Public Feeds: [(src.c_locked) ? ("NO") : ("YES")]

" - dat+="
Submit

Cancel
" - if(3) - dat+="Creating new Feed Message..." - dat+="
Receiving Channel: [src.channel_name]
" //MARK - dat+="Message Author: [src.scanned_user]
" - dat+="Message Body: [src.msg]
" - dat+="Attach Photo: [(src.photo ? "Photo Attached" : "No Photo")]
" - dat+="
Submit

Cancel
" - if(4) - dat+="Feed story successfully submitted to [src.channel_name].

" - dat+="
Return
" - if(5) - dat+="Feed Channel [src.channel_name] created successfully.

" - dat+="
Return
" - if(6) - dat+="ERROR: Could not submit Feed story to Network.

" - if(src.channel_name=="") - dat+="�Invalid receiving channel name.
" - if(src.scanned_user=="Unknown") - dat+="�Channel author unverified.
" - if(src.msg == "" || src.msg == "\[REDACTED\]") - dat+="�Invalid message body.
" - - dat+="
Return
" - if(7) - dat+="ERROR: Could not submit Feed Channel to Network.

" - //var/list/existing_channels = list() //Let's get dem existing channels - OBSOLETE - var/list/existing_authors = list() - for(var/datum/feed_channel/FC in GLOB.news_network.network_channels) - //existing_channels += FC.channel_name //OBSOLETE - if(FC.author == "\[REDACTED\]") - existing_authors += FC.backup_author - else - existing_authors += FC.author - if(src.scanned_user in existing_authors) - dat+="�There already exists a Feed channel under your name.
" - if(src.channel_name=="" || src.channel_name == "\[REDACTED\]") - dat+="�Invalid channel name.
" - var/check = 0 - for(var/datum/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == src.channel_name) - check = 1 - break - if(check) - dat+="�Channel name already in use.
" - if(src.scanned_user=="Unknown") - dat+="�Channel author unverified.
" - dat+="
Return
" - if(8) - var/total_num=length(GLOB.news_network.network_channels) - var/active_num=total_num - var/message_num=0 - for(var/datum/feed_channel/FC in GLOB.news_network.network_channels) - if(!FC.censored) - message_num += length(FC.messages) //Dont forget, datum/feed_channel's var messages is a list of datum/feed_message - else - active_num-- - dat+="Network currently serves a total of [total_num] Feed channels, [active_num] of which are active, and a total of [message_num] Feed Stories." //TODO: CONTINUE - dat+="

Liquid Paper remaining: [(src.paper_remaining) *100 ] cm^3" - dat+="

Print Paper" - dat+="
Cancel" - if(9) - dat+="[src.viewing_channel.channel_name]: \[created by: [src.viewing_channel.author]\]
" - if(src.viewing_channel.censored) - dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Weyland-Yutani D-Notice.
" - dat+="No further feed story additions are allowed while the D-Notice is in effect.

" - else - if(!length(src.viewing_channel.messages) ) - dat+="No feed messages found in channel...
" - else - var/i = 0 - for(var/datum/feed_message/MESSAGE in src.viewing_channel.messages) - i++ - dat+="-[MESSAGE.body]
" - if(MESSAGE.img) - usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") - dat+="

" - dat+="\[[MESSAGE.message_type] by [MESSAGE.author]\]
" - dat+="

Refresh" - dat+="
Back" - if(10) - dat+="Weyland-Yutani Feed Censorship Tool
" - dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
" - dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.
" - dat+="
Select Feed channel to get Stories from:
" - if(!length(GLOB.news_network.network_channels)) - dat+="No feed channels found active...
" - else - for(var/datum/feed_channel/CHANNEL in GLOB.news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : null]
" - dat+="
Cancel" - if(11) - dat+="Weyland-Yutani D-Notice Handler
" - dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's" - dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed" - dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
" - if(!length(GLOB.news_network.network_channels)) - dat+="No feed channels found active...
" - else - for(var/datum/feed_channel/CHANNEL in GLOB.news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : null]
" - - dat+="
Back" - if(12) - dat+="[src.viewing_channel.channel_name]: \[ created by: [src.viewing_channel.author] \]
" - dat+="[(src.viewing_channel.author=="\[REDACTED\]") ? ("Undo Author censorship") : ("Censor channel Author")]
" - - - if(!length(src.viewing_channel.messages) ) - dat+="No feed messages found in channel...
" - else - for(var/datum/feed_message/MESSAGE in src.viewing_channel.messages) - dat+="-[MESSAGE.body]
\[[MESSAGE.message_type] by [MESSAGE.author]\]
" - dat+="[(MESSAGE.body == "\[REDACTED\]") ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.author == "\[REDACTED\]") ? ("Undo Author Censorship") : ("Censor message Author")]
" - dat+="
Back" - if(13) - dat+="[src.viewing_channel.channel_name]: \[ created by: [src.viewing_channel.author] \]
" - dat+="Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
" - if(src.viewing_channel.censored) - dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Weyland-Yutani D-Notice.
" - dat+="No further feed story additions are allowed while the D-Notice is in effect.

" - else - if(!length(src.viewing_channel.messages) ) - dat+="No feed messages found in channel...
" - else - for(var/datum/feed_message/MESSAGE in src.viewing_channel.messages) - dat+="-[MESSAGE.body]
\[[MESSAGE.message_type] by [MESSAGE.author]\]
" - - dat+="
Back" - if(14) - dat+="Wanted Issue Handler:" - var/wanted_already = 0 - var/end_param = 1 - if(GLOB.news_network.wanted_issue) - wanted_already = 1 - end_param = 2 - - if(wanted_already) - dat+="
A wanted issue is already in Feed Circulation. You can edit or cancel it below.
" - dat+="
" - dat+="Criminal Name: [src.channel_name]
" - dat+="Description: [src.msg]
" - dat+="Attach Photo: [(src.photo ? "Photo Attached" : "No Photo")]
" - if(wanted_already) - dat+="Wanted Issue created by: [GLOB.news_network.wanted_issue.backup_author]
" - else - dat+="Wanted Issue will be created under prosecutor: [src.scanned_user]
" - dat+="
[(wanted_already) ? ("Edit Issue") : ("Submit")]" - if(wanted_already) - dat+="
Take down Issue" - dat+="
Cancel" - if(15) - dat+="Wanted issue for [src.channel_name] is now in Network Circulation.

" - dat+="
Return
" - if(16) - dat+="ERROR: Wanted Issue rejected by Network.

" - if(src.channel_name=="" || src.channel_name == "\[REDACTED\]") - dat+="�Invalid name for person wanted.
" - if(src.scanned_user=="Unknown") - dat+="�Issue author unverified.
" - if(src.msg == "" || src.msg == "\[REDACTED\]") - dat+="�Invalid description.
" - dat+="
Return
" - if(17) - dat+="Wanted Issue successfully deleted from Circulation
" - dat+="
Return
" - if(18) - dat+="-- STATIONWIDE WANTED ISSUE --
\[Submitted by: [GLOB.news_network.wanted_issue.backup_author]\]
" - dat+="Criminal: [GLOB.news_network.wanted_issue.author]
" - dat+="Description: [GLOB.news_network.wanted_issue.body]
" - dat+="Photo:: " - if(GLOB.news_network.wanted_issue.img) - usr << browse_rsc(GLOB.news_network.wanted_issue.img, "tmp_photow.png") - dat+="
" - else - dat+="None" - dat+="

Back
" - if(19) - dat+="Wanted issue for [src.channel_name] successfully edited.

" - dat+="
Return
" - if(20) - dat+="Printing successful. Please receive your newspaper from the bottom of the machine.

" - dat+="Return" - if(21) - dat+="Unable to print newspaper. Insufficient paper. Please notify maintenance personnel to refill machine storage.

" - dat+="Return" - else - dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" - - - human_or_robot_user << browse(dat, "window=newscaster_main;size=400x600") - onclose(human_or_robot_user, "newscaster_main") - - /*if(src.isbroken) //debugging shit - return - src.hitstaken++ - if(src.hitstaken==3) - src.isbroken = TRUE - src.update_icon()*/ - - -/obj/structure/machinery/newscaster/Topic(href, href_list) - if(..()) - return - if ((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(src.loc, /turf))) || (isRemoteControlling(usr))) - usr.set_interaction(src) - if(href_list["set_channel_name"]) - src.channel_name = strip_html(input(usr, "Provide a Feed Channel Name", "Network Channel Handler", "")) - while (findtext(src.channel_name," ") == 1) - src.channel_name = copytext(src.channel_name,2,length(src.channel_name)+1) - src.updateUsrDialog() - //src.update_icon() - - else if(href_list["set_channel_lock"]) - src.c_locked = !src.c_locked - src.updateUsrDialog() - //src.update_icon() - - else if(href_list["submit_new_channel"]) - //var/list/existing_channels = list() //OBSOLETE - var/list/existing_authors = list() - for(var/datum/feed_channel/FC in GLOB.news_network.network_channels) - //existing_channels += FC.channel_name - if(FC.author == "\[REDACTED\]") - existing_authors += FC.backup_author - else - existing_authors +=FC.author - var/check = 0 - for(var/datum/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == src.channel_name) - check = 1 - break - if(src.channel_name == "" || src.channel_name == "\[REDACTED\]" || src.scanned_user == "Unknown" || check || (src.scanned_user in existing_authors) ) - src.screen=7 - else - var/choice = alert("Please confirm Feed channel creation","Network Channel Handler","Confirm","Cancel") - if(choice=="Confirm") - var/datum/feed_channel/newChannel = new /datum/feed_channel - newChannel.channel_name = src.channel_name - newChannel.author = src.scanned_user - newChannel.locked = c_locked - GLOB.news_network.network_channels += newChannel //Adding channel to the global network - src.screen=5 - src.updateUsrDialog() - //src.update_icon() - - else if(href_list["set_channel_receiving"]) - //var/list/datum/feed_channel/available_channels = list() - var/list/available_channels = list() - for(var/datum/feed_channel/F in GLOB.news_network.network_channels) - if( (!F.locked || F.author == scanned_user) && !F.censored) - available_channels += F.channel_name - src.channel_name = strip_html(tgui_input_list(usr, "Choose receiving Feed Channel", "Network Channel Handler", available_channels )) - src.updateUsrDialog() - - else if(href_list["set_new_message"]) - src.msg = strip_html(input(usr, "Write your Feed story", "Network Channel Handler", "")) - while (findtext(src.msg," ") == 1) - src.msg = copytext(src.msg,2,length(src.msg)+1) - src.updateUsrDialog() - - else if(href_list["set_attachment"]) - AttachPhoto(usr) - src.updateUsrDialog() - - else if(href_list["submit_new_message"]) - if(src.msg =="" || src.msg=="\[REDACTED\]" || src.scanned_user == "Unknown" || src.channel_name == "" ) - src.screen=6 - else - var/datum/feed_message/newMsg = new /datum/feed_message - newMsg.author = src.scanned_user - newMsg.body = src.msg - if(photo) - newMsg.img = photo.img - for(var/datum/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == src.channel_name) - FC.messages += newMsg //Adding message to the network's appropriate feed_channel - break - src.screen=4 - for(var/obj/structure/machinery/newscaster/NEWSCASTER in GLOB.allCasters) - NEWSCASTER.newsAlert(src.channel_name) - - src.updateUsrDialog() - - else if(href_list["create_channel"]) - src.screen=2 - src.updateUsrDialog() - - else if(href_list["create_feed_story"]) - src.screen=3 - src.updateUsrDialog() - - else if(href_list["menu_paper"]) - src.screen=8 - src.updateUsrDialog() - else if(href_list["print_paper"]) - if(!src.paper_remaining) - src.screen=21 - else - src.print_paper() - src.screen = 20 - src.updateUsrDialog() - - else if(href_list["menu_censor_story"]) - src.screen=10 - src.updateUsrDialog() - - else if(href_list["menu_censor_channel"]) - src.screen=11 - src.updateUsrDialog() - - else if(href_list["menu_wanted"]) - var/already_wanted = 0 - if(GLOB.news_network.wanted_issue) - already_wanted = 1 - - if(already_wanted) - src.channel_name = GLOB.news_network.wanted_issue.author - src.msg = GLOB.news_network.wanted_issue.body - src.screen = 14 - src.updateUsrDialog() - - else if(href_list["set_wanted_name"]) - src.channel_name = strip_html(input(usr, "Provide the name of the Wanted person", "Network Security Handler", "")) - while (findtext(src.channel_name," ") == 1) - src.channel_name = copytext(src.channel_name,2,length(src.channel_name)+1) - src.updateUsrDialog() - - else if(href_list["set_wanted_desc"]) - src.msg = strip_html(input(usr, "Provide the a description of the Wanted person and any other details you deem important", "Network Security Handler", "")) - while (findtext(src.msg," ") == 1) - src.msg = copytext(src.msg,2,length(src.msg)+1) - src.updateUsrDialog() - - else if(href_list["submit_wanted"]) - var/input_param = text2num(href_list["submit_wanted"]) - if(src.msg == "" || src.channel_name == "" || src.scanned_user == "Unknown") - src.screen = 16 - else - var/choice = alert("Please confirm Wanted Issue [(input_param==1) ? ("creation.") : ("edit.")]","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - if(input_param==1) //If input_param == 1 we're submitting a new wanted issue. At 2 we're just editing an existing one. See the else below - var/datum/feed_message/WANTED = new /datum/feed_message - WANTED.author = src.channel_name - WANTED.body = src.msg - WANTED.backup_author = src.scanned_user //I know, a bit wacky - if(photo) - WANTED.img = photo.img - GLOB.news_network.wanted_issue = WANTED - for(var/obj/structure/machinery/newscaster/NEWSCASTER in GLOB.allCasters) - NEWSCASTER.newsAlert() - NEWSCASTER.update_icon() - src.screen = 15 - else - if(GLOB.news_network.wanted_issue.is_admin_message) - alert("The wanted issue has been distributed by a Weyland-Yutani higherup. You cannot edit it.","Ok") - return - GLOB.news_network.wanted_issue.author = src.channel_name - GLOB.news_network.wanted_issue.body = src.msg - GLOB.news_network.wanted_issue.backup_author = src.scanned_user - if(photo) - GLOB.news_network.wanted_issue.img = photo.img - src.screen = 19 - - src.updateUsrDialog() - - else if(href_list["cancel_wanted"]) - if(GLOB.news_network.wanted_issue.is_admin_message) - alert("The wanted issue has been distributed by a Weyland-Yutani higherup. You cannot take it down.","Ok") - return - var/choice = alert("Please confirm Wanted Issue removal","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - GLOB.news_network.wanted_issue = null - for(var/obj/structure/machinery/newscaster/NEWSCASTER in GLOB.allCasters) - NEWSCASTER.update_icon() - src.screen=17 - src.updateUsrDialog() - - else if(href_list["view_wanted"]) - src.screen=18 - src.updateUsrDialog() - else if(href_list["censor_channel_author"]) - var/datum/feed_channel/FC = locate(href_list["censor_channel_author"]) - if(FC.is_admin_channel) - alert("This channel was created by a Weyland-Yutani Officer. You cannot censor it.","Ok") - return - if(FC.author != "\[REDACTED\]") - FC.backup_author = FC.author - FC.author = "\[REDACTED\]" - else - FC.author = FC.backup_author - src.updateUsrDialog() - - else if(href_list["censor_channel_story_author"]) - var/datum/feed_message/MSG = locate(href_list["censor_channel_story_author"]) - if(MSG.is_admin_message) - alert("This message was created by a Weyland-Yutani Officer. You cannot censor its author.","Ok") - return - if(MSG.author != "\[REDACTED\]") - MSG.backup_author = MSG.author - MSG.author = "\[REDACTED\]" - else - MSG.author = MSG.backup_author - src.updateUsrDialog() - - else if(href_list["censor_channel_story_body"]) - var/datum/feed_message/MSG = locate(href_list["censor_channel_story_body"]) - if(MSG.is_admin_message) - alert("This channel was created by a Weyland-Yutani Officer. You cannot censor it.","Ok") - return - if(MSG.img != null) - MSG.backup_img = MSG.img - MSG.img = null - else - MSG.img = MSG.backup_img - if(MSG.body != "\[REDACTED\]") - MSG.backup_body = MSG.body - MSG.body = "\[REDACTED\]" - else - MSG.body = MSG.backup_body - src.updateUsrDialog() - - else if(href_list["pick_d_notice"]) - var/datum/feed_channel/FC = locate(href_list["pick_d_notice"]) - src.viewing_channel = FC - src.screen=13 - src.updateUsrDialog() - - else if(href_list["toggle_d_notice"]) - var/datum/feed_channel/FC = locate(href_list["toggle_d_notice"]) - if(FC.is_admin_channel) - alert("This channel was created by a Weyland-Yutani Officer. You cannot place a D-Notice upon it.","Ok") - return - FC.censored = !FC.censored - src.updateUsrDialog() - - else if(href_list["view"]) - src.screen=1 - src.updateUsrDialog() - - else if(href_list["setScreen"]) //Brings us to the main menu and resets all fields~ - src.screen = text2num(href_list["setScreen"]) - if (src.screen == 0) - src.scanned_user = "Unknown"; - msg = ""; - src.c_locked=0; - channel_name=""; - src.viewing_channel = null - src.updateUsrDialog() - - else if(href_list["show_channel"]) - var/datum/feed_channel/FC = locate(href_list["show_channel"]) - src.viewing_channel = FC - src.screen = 9 - src.updateUsrDialog() - - else if(href_list["pick_censor_channel"]) - var/datum/feed_channel/FC = locate(href_list["pick_censor_channel"]) - src.viewing_channel = FC - src.screen = 12 - src.updateUsrDialog() - - else if(href_list["refresh"]) - src.updateUsrDialog() - - -/obj/structure/machinery/newscaster/attackby(obj/item/I as obj, mob/user as mob) - - if (src.isbroken) - playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 25, 1) - for (var/mob/O in hearers(5, src.loc)) - O.show_message("[user.name] further abuses the shattered [src.name].", SHOW_MESSAGE_VISIBLE) - else - if(!(I.flags_item & NOBLUDGEON) && I.force) - if(I.force <15) - for (var/mob/O in hearers(5, src.loc)) - O.show_message("[user.name] hits the [src.name] with the [I.name] with no visible effect.", SHOW_MESSAGE_VISIBLE) - playsound(src.loc, 'sound/effects/Glasshit.ogg', 25, 1) - else - src.hitstaken++ - if(src.hitstaken==3) - for (var/mob/O in hearers(5, src.loc)) - O.show_message("[user.name] smashes the [src.name]!", SHOW_MESSAGE_VISIBLE) - src.isbroken=1 - playsound(src.loc, 'sound/effects/Glassbr3.ogg', 50, 1) - else - for (var/mob/O in hearers(5, src.loc)) - O.show_message("[user.name] forcefully slams the [src.name] with the [I.name]!", SHOW_MESSAGE_VISIBLE) - playsound(src.loc, 'sound/effects/Glasshit.ogg', 25, 1) - else - to_chat(user, "This does nothing.") - src.update_icon() - -/obj/structure/machinery/newscaster/attack_remote(mob/user as mob) - return src.attack_hand(user) //or maybe it'll have some special functions? No idea. - -/obj/structure/machinery/newscaster/proc/AttachPhoto(mob/user as mob) - if(photo) - if(!isRemoteControlling(user)) - photo.forceMove(loc) - user.put_in_inactive_hand(photo) - photo = null - var/obj/item/photo/PH = user.get_active_hand() - if(istype(PH)) - if(user.drop_inv_item_to_loc(photo, src)) - photo = PH - - -//######################################################################################################################## -//###################################### NEWSPAPER! ###################################################################### -//######################################################################################################################## /obj/item/newspaper name = "newspaper" @@ -745,191 +15,3 @@ GLOBAL_LIST_INIT_TYPED(allCasters, /obj/structure/machinery/newscaster, list()) icon_state = "newspaper" w_class = SIZE_TINY //Let's make it fit in trashbags! attack_verb = list("bapped") - var/screen = 0 - var/pages = 0 - var/curr_page = 0 - var/list/datum/feed_channel/news_content = list() - var/datum/feed_message/important_message = null - var/scribble="" - var/scribble_page = null - -/obj/item/newspaper/attack_self(mob/user as mob) - ..() - if(!ishuman(user)) - to_chat(user, "The paper is full of intelligible symbols!") - return - - var/mob/living/carbon/human/human_user = user - var/dat - src.pages = 0 - switch(screen) - if(0) //Cover - dat+="
The Griffon
" - dat+="
Weyland-Yutani-standard newspaper, for use on Weyland-Yutani� Space Facilities

" - if(!length(src.news_content)) - if(src.important_message) - dat+="Contents:
" - else - dat+="Other than the title, the rest of the newspaper is unprinted..." - else - dat+="Contents:
" - if(scribble_page==curr_page) - dat+="
There is a small scribble near the end of this page... It reads: \"[src.scribble]\"" - dat+= "
Next Page
Done reading
" - if(1) // X channel pages inbetween. - for(var/datum/feed_channel/NP in src.news_content) - src.pages++ //Let's get it right again. - var/datum/feed_channel/C = src.news_content[src.curr_page] - dat+="[C.channel_name] \[created by: [C.author]\]

" - if(C.censored) - dat+="This channel was deemed dangerous to the general welfare of the station and therefore marked with a D-Notice. Its contents were not transferred to the newspaper at the time of printing." - else - if(!length(C.messages)) - dat+="No Feed stories stem from this channel..." - else - dat+="" - if(scribble_page==curr_page) - dat+="
There is a small scribble near the end of this page... It reads: \"[src.scribble]\"" - dat+= "

Previous Page
Next Page
" - if(2) //Last page - for(var/datum/feed_channel/NP in src.news_content) - src.pages++ - if(src.important_message!=null) - dat+="
Wanted Issue:


" - dat+="Criminal name: [important_message.author]
" - dat+="Description: [important_message.body]
" - dat+="Photo:: " - if(important_message.img) - user << browse_rsc(important_message.img, "tmp_photow.png") - dat+="
" - else - dat+="None" - else - dat+="Apart from some uninteresting Classified ads, there's nothing on this page..." - if(scribble_page==curr_page) - dat+="
There is a small scribble near the end of this page... It reads: \"[src.scribble]\"" - dat+= "
Previous Page
" - else - dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" - - dat+="

[src.curr_page+1]
" - human_user << browse(dat, "window=newspaper_main;size=300x400") - onclose(human_user, "newspaper_main") - - -/obj/item/newspaper/Topic(href, href_list) - var/mob/living/U = usr - ..() - if ((src in U.contents) || ( istype(loc, /turf) && in_range(src, U) )) - U.set_interaction(src) - if(href_list["next_page"]) - if(curr_page==src.pages+1) - return //Don't need that at all, but anyway. - if(src.curr_page == src.pages) //We're at the middle, get to the end - src.screen = 2 - else - if(curr_page == 0) //We're at the start, get to the middle - src.screen=1 - src.curr_page++ - playsound(src.loc, "pageturn", 15, 1) - - else if(href_list["prev_page"]) - if(curr_page == 0) - return - if(curr_page == 1) - src.screen = 0 - - else - if(curr_page == src.pages+1) //we're at the end, let's go back to the middle. - src.screen = 1 - src.curr_page-- - playsound(src.loc, "pageturn", 15, 1) - - if (istype(src.loc, /mob)) - src.attack_self(src.loc) - - -/obj/item/newspaper/attackby(obj/item/W as obj, mob/user as mob) - if(HAS_TRAIT(W, TRAIT_TOOL_PEN)) - if(src.scribble_page == src.curr_page) - to_chat(user, "There's already a scribble in this page... You wouldn't want to make things too cluttered, would you?") - else - var/s = strip_html( input(user, "Write something", "Newspaper", "") ) - s = strip_html(s) - if (!s) - return - if (!in_range(src, usr) && src.loc != usr) - return - src.scribble_page = src.curr_page - src.scribble = s - src.attack_self(user) - return - - -////////////////////////////////////helper procs - - -/obj/structure/machinery/newscaster/proc/scan_user(mob/living/user as mob) - if(istype(user,/mob/living/carbon/human)) //User is a human - var/mob/living/carbon/human/human_user = user - if(human_user.wear_id) //Newscaster scans you - if(istype(human_user.wear_id, /obj/item/card/id) ) - var/obj/item/card/id/ID = human_user.wear_id - src.scanned_user ="[ID.registered_name] ([ID.assignment])" - else - src.scanned_user ="Unknown" - else - src.scanned_user ="Unknown" - else - var/mob/living/silicon/ai_user = user - src.scanned_user = "[ai_user.name] ([ai_user.job])" - - -/obj/structure/machinery/newscaster/proc/print_paper() - var/obj/item/newspaper/NEWSPAPER = new /obj/item/newspaper - for(var/datum/feed_channel/FC in GLOB.news_network.network_channels) - NEWSPAPER.news_content += FC - if(GLOB.news_network.wanted_issue) - NEWSPAPER.important_message = GLOB.news_network.wanted_issue - NEWSPAPER.forceMove(get_turf(src)) - src.paper_remaining-- - return - -//Removed for now so these aren't even checked every tick. Left this here in-case Agouri needs it later. -///obj/structure/machinery/newscaster/process() //Was thinking of doing the icon update through process, but multiple iterations per second does not -// return //bode well with a newscaster network of 10+ machines. Let's just return it, as it's added in the machines list. - -/obj/structure/machinery/newscaster/proc/newsAlert(channel) //This isn't Agouri's work, for it is ugly and vile. - var/turf/T = get_turf(src) //Who the fuck uses spawn(600) anyway, jesus christ - if(channel) - for(var/mob/O in hearers(GLOB.world_view_size-1, T)) - O.show_message(SPAN_NEWSCASTER("[src.name] beeps, \"Breaking news from [channel]!\""), SHOW_MESSAGE_AUDIBLE) - src.alert = 1 - src.update_icon() - spawn(30 SECONDS) - src.alert = 0 - src.update_icon() - playsound(src.loc, 'sound/machines/twobeep.ogg', 25, 1) - else - for(var/mob/O in hearers(GLOB.world_view_size-1, T)) - O.show_message(SPAN_NEWSCASTER("[src.name] beeps, \"Attention! Wanted issue distributed!\""), SHOW_MESSAGE_AUDIBLE) - playsound(src.loc, 'sound/machines/warning-buzzer.ogg', 25, 1) - return diff --git a/code/game/machinery/vending/cm_vending.dm b/code/game/machinery/vending/cm_vending.dm index 2ba1ac089826..4f825030d627 100644 --- a/code/game/machinery/vending/cm_vending.dm +++ b/code/game/machinery/vending/cm_vending.dm @@ -900,7 +900,7 @@ GLOBAL_LIST_EMPTY(vending_products) for(var/list/vendspec in listed_products) var/multiplier = vendspec[2] if(multiplier > 0) - var/awarded = round(vendspec[2] * scale) // Starting amount + var/awarded = round(vendspec[2] * scale, 1) // Starting amount //Record the multiplier and how many have actually been given out dynamic_stock_multipliers[vendspec] = list(vendspec[2], awarded) vendspec[2] = awarded // Override starting amount @@ -920,8 +920,8 @@ GLOBAL_LIST_EMPTY(vending_products) var/list/metadata = dynamic_stock_multipliers[vendspec] var/multiplier = metadata[1] // How much do we multiply scales by var/previous_max_amount = metadata[2] // How many we already handed out at old scale - var/projected_max_amount = round(new_scale * multiplier) // How much we would have had total now in total - var/amount_to_add = round(projected_max_amount - previous_max_amount) // Rounding just in case + var/projected_max_amount = round(new_scale * multiplier, 1) // How much we would have had total now in total + var/amount_to_add = round(projected_max_amount - previous_max_amount, 1) // Rounding just in case if(amount_to_add > 0) metadata[2] += amount_to_add vendspec[2] += amount_to_add diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm index 6d015c203bd7..4053b9294c13 100644 --- a/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm +++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm @@ -36,13 +36,19 @@ GLOBAL_LIST_INIT(cm_vending_gear_engi, list( list("M20 Mine Box (x4 mines)", 18, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR), list("M40 MFHS Metal Foam Grenade", 5, /obj/item/explosive/grenade/metal_foam, null, VENDOR_ITEM_REGULAR), - list("AMMUNITION", 0, null, null, null), + list("PRIMARY AMMUNITION", 0, null, null, null), list("M4RA AP Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR), list("M39 AP Magazine (10x20mm)", 6, /obj/item/ammo_magazine/smg/m39/ap , null, VENDOR_ITEM_REGULAR), list("M39 Extended Magazine (10x20mm)", 6, /obj/item/ammo_magazine/smg/m39/extended , null, VENDOR_ITEM_REGULAR), list("M41A AP Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/ap , null, VENDOR_ITEM_REGULAR), list("M41A Extended Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/extended , null, VENDOR_ITEM_REGULAR), + list("SIDEARM AMMUNITION", 0, null, null, null), + list("M44 Heavy Speed Loader (.44)", 6, /obj/item/ammo_magazine/revolver/heavy, null, VENDOR_ITEM_REGULAR), + list("M4A3 HP Magazine", 3, /obj/item/ammo_magazine/pistol/hp, null, VENDOR_ITEM_REGULAR), + list("M4A3 AP Magazine", 3, /obj/item/ammo_magazine/pistol/ap, null, VENDOR_ITEM_REGULAR), + list("VP78 Magazine", 3, /obj/item/ammo_magazine/pistol/vp78, null, VENDOR_ITEM_REGULAR), + list("ARMORS", 0, null, null, null), list("M3 B12 Pattern Marine Armor", 24, /obj/item/clothing/suit/storage/marine/medium/leader, null, VENDOR_ITEM_REGULAR), list("M4 Pattern Armor", 30, /obj/item/clothing/suit/storage/marine/medium/rto, null, VENDOR_ITEM_REGULAR), diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_leader.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_leader.dm index 6c7cbf2db740..845d169a701a 100644 --- a/code/game/machinery/vending/vendor_types/squad_prep/squad_leader.dm +++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_leader.dm @@ -79,12 +79,20 @@ GLOBAL_LIST_INIT(cm_vending_gear_leader, list( list("Health Analyzer", 4, /obj/item/device/healthanalyzer, null, VENDOR_ITEM_REGULAR), list("Roller Bed", 2, /obj/item/roller, null, VENDOR_ITEM_REGULAR), - list("SPECIAL AMMUNITION", 0, null, null, null), + list("PRIMARY AMMUNITION", 0, null, null, null), list("M4RA AP Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR), list("M39 AP Magazine (10x20mm)", 6, /obj/item/ammo_magazine/smg/m39/ap , null, VENDOR_ITEM_REGULAR), list("M39 Extended Magazine (10x20mm)", 6, /obj/item/ammo_magazine/smg/m39/extended , null, VENDOR_ITEM_REGULAR), list("M41A AP Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/ap , null, VENDOR_ITEM_REGULAR), list("M41A Extended Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/extended , null, VENDOR_ITEM_REGULAR), + + list("SIDEARM AMMUNITION", 0, null, null, null), + list("M44 Heavy Speed Loader (.44)", 6, /obj/item/ammo_magazine/revolver/heavy, null, VENDOR_ITEM_REGULAR), + list("M4A3 HP Magazine", 3, /obj/item/ammo_magazine/pistol/hp, null, VENDOR_ITEM_REGULAR), + list("M4A3 AP Magazine", 3, /obj/item/ammo_magazine/pistol/ap, null, VENDOR_ITEM_REGULAR), + list("VP78 Magazine", 3, /obj/item/ammo_magazine/pistol/vp78, null, VENDOR_ITEM_REGULAR), + + list("SPECIAL AMMUNITION", 0, null, null, null), list("M240 Incinerator Tank (Napthal)", 3, /obj/item/ammo_magazine/flamer_tank, null, VENDOR_ITEM_REGULAR), list("M240 Incinerator Tank (B-Gel)", 3, /obj/item/ammo_magazine/flamer_tank/gellied, null, VENDOR_ITEM_REGULAR), diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm index 6b4954ee5e92..7d16d15af6fd 100644 --- a/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm +++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm @@ -58,13 +58,19 @@ GLOBAL_LIST_INIT(cm_vending_gear_medic, list( list("M74 AGM-Hornet Airburst Packet (x3 airburst grenades", 20, /obj/item/storage/box/packet/hornet, null, VENDOR_ITEM_REGULAR), list("M20 Mine Box (x4 mines)", 20, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR), - list("AMMUNITION", 0, null, null, null), + list("PRIMARY AMMUNITION", 0, null, null, null), list("M4RA AP Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR), list("M39 AP Magazine (10x20mm)", 6, /obj/item/ammo_magazine/smg/m39/ap , null, VENDOR_ITEM_REGULAR), list("M39 Extended Magazine (10x20mm)", 6, /obj/item/ammo_magazine/smg/m39/extended , null, VENDOR_ITEM_REGULAR), list("M41A AP Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/ap , null, VENDOR_ITEM_REGULAR), list("M41A Extended Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/extended , null, VENDOR_ITEM_REGULAR), + list("SIDEARM AMMUNITION", 0, null, null, null), + list("M44 Heavy Speed Loader (.44)", 6, /obj/item/ammo_magazine/revolver/heavy, null, VENDOR_ITEM_REGULAR), + list("M4A3 HP Magazine", 3, /obj/item/ammo_magazine/pistol/hp, null, VENDOR_ITEM_REGULAR), + list("M4A3 AP Magazine", 3, /obj/item/ammo_magazine/pistol/ap, null, VENDOR_ITEM_REGULAR), + list("VP78 Magazine", 3, /obj/item/ammo_magazine/pistol/vp78, null, VENDOR_ITEM_REGULAR), + list("ARMORS", 0, null, null, null), list("M3 B12 Pattern Marine Armor", 28, /obj/item/clothing/suit/storage/marine/medium/leader, null, VENDOR_ITEM_REGULAR), list("M4 Pattern Armor", 28, /obj/item/clothing/suit/storage/marine/medium/rto, null, VENDOR_ITEM_REGULAR), diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_rifleman.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_rifleman.dm index 15661cc4b661..b132d8d4f13d 100644 --- a/code/game/machinery/vending/vendor_types/squad_prep/squad_rifleman.dm +++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_rifleman.dm @@ -67,13 +67,18 @@ GLOBAL_LIST_INIT(cm_vending_clothing_marine, list( list("M74 AGM-Hornet Airburst Packet (x3 airburst grenades", 15, /obj/item/storage/box/packet/hornet, null, VENDOR_ITEM_REGULAR), list("M20 Mine Box (x4 mines)", 20, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR), - list("AMMUNITION", 0, null, null, null), + list("PRIMARY AMMUNITION", 0, null, null, null), list("M4RA AP Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR), list("M39 AP Magazine (10x20mm)", 10, /obj/item/ammo_magazine/smg/m39/ap , null, VENDOR_ITEM_REGULAR), list("M39 Extended Magazine (10x20mm)", 10, /obj/item/ammo_magazine/smg/m39/extended , null, VENDOR_ITEM_REGULAR), list("M41A AP Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/ap , null, VENDOR_ITEM_REGULAR), list("M41A Extended Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/extended , null, VENDOR_ITEM_REGULAR), + + list("SIDEARM AMMUNITION", 0, null, null, null), list("M44 Heavy Speed Loader (.44)", 10, /obj/item/ammo_magazine/revolver/heavy, null, VENDOR_ITEM_REGULAR), + list("M4A3 HP Magazine", 5, /obj/item/ammo_magazine/pistol/hp, null, VENDOR_ITEM_REGULAR), + list("M4A3 AP Magazine", 5, /obj/item/ammo_magazine/pistol/ap, null, VENDOR_ITEM_REGULAR), + list("VP78 Magazine", 5, /obj/item/ammo_magazine/pistol/vp78, null, VENDOR_ITEM_REGULAR), list("ARMORS", 0, null, null, null), list("M3 B12 Pattern Marine Armor", 30, /obj/item/clothing/suit/storage/marine/medium/leader, null, VENDOR_ITEM_REGULAR), diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_smartgunner.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_smartgunner.dm index 04061370168d..c022f87abc6d 100644 --- a/code/game/machinery/vending/vendor_types/squad_prep/squad_smartgunner.dm +++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_smartgunner.dm @@ -20,6 +20,12 @@ GLOBAL_LIST_INIT(cm_vending_gear_smartgun, list( list("M74 AGM-Hornet Airburst Packet (x3 airburst grenades", 20, /obj/item/storage/box/packet/hornet, null, VENDOR_ITEM_REGULAR), list("M20 Mine Box (x4 mines)", 20, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR), + list("SIDEARM AMMUNITION", 0, null, null, null), + list("M44 Heavy Speed Loader (.44)", 10, /obj/item/ammo_magazine/revolver/heavy, null, VENDOR_ITEM_REGULAR), + list("M4A3 HP Magazine", 5, /obj/item/ammo_magazine/pistol/hp, null, VENDOR_ITEM_REGULAR), + list("M4A3 AP Magazine", 5, /obj/item/ammo_magazine/pistol/ap, null, VENDOR_ITEM_REGULAR), + list("VP78 Magazine", 5, /obj/item/ammo_magazine/pistol/vp78, null, VENDOR_ITEM_REGULAR), + list("CLOTHING ITEMS", 0, null, null, null), list("Machete Scabbard (Full)", 6, /obj/item/storage/large_holster/machete/full, null, VENDOR_ITEM_REGULAR), list("Fuel Tank Strap Pouch", 5, /obj/item/storage/pouch/flamertank, null, VENDOR_ITEM_REGULAR), diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_tl.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_tl.dm index d9ba7ee97c26..a013ddb15212 100644 --- a/code/game/machinery/vending/vendor_types/squad_prep/squad_tl.dm +++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_tl.dm @@ -1,17 +1,6 @@ //------------GEAR VENDOR--------------- GLOBAL_LIST_INIT(cm_vending_gear_tl, list( - - list("AMMUNITION", 0, null, null, null), - list("M4RA AP Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR), - list("M39 AP Magazine (10x20mm)", 10, /obj/item/ammo_magazine/smg/m39/ap , null, VENDOR_ITEM_REGULAR), - list("M39 Extended Magazine (10x20mm)", 10, /obj/item/ammo_magazine/smg/m39/extended , null, VENDOR_ITEM_REGULAR), - list("M41A AP Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/ap , null, VENDOR_ITEM_REGULAR), - list("M41A Extended Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/extended , null, VENDOR_ITEM_REGULAR), - list("M44 Heavy Speed Loader (.44)", 10, /obj/item/ammo_magazine/revolver/heavy, null, VENDOR_ITEM_REGULAR), - list("M4A3 HP Magazine", 5, /obj/item/ammo_magazine/pistol/hp, null, VENDOR_ITEM_REGULAR), - list("M4A3 AP Magazine", 5, /obj/item/ammo_magazine/pistol/ap, null, VENDOR_ITEM_REGULAR), - list("EXPLOSIVES", 0, null, null, null), list("M40 HEDP High Explosive Packet (x3 grenades)", 18, /obj/item/storage/box/packet/high_explosive, null, VENDOR_ITEM_REGULAR), list("M40 HIDP Incendiary Packet (x3 grenades)", 18, /obj/item/storage/box/packet/incendiary, null, VENDOR_ITEM_REGULAR), @@ -24,6 +13,19 @@ GLOBAL_LIST_INIT(cm_vending_gear_tl, list( list("M20 Mine Box (x4 mines)", 20, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR), list("M40 MFHS Metal Foam Grenade", 5, /obj/item/explosive/grenade/metal_foam, null, VENDOR_ITEM_REGULAR), + list("PRIMARY AMMUNITION", 0, null, null, null), + list("M4RA AP Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR), + list("M39 AP Magazine (10x20mm)", 10, /obj/item/ammo_magazine/smg/m39/ap , null, VENDOR_ITEM_REGULAR), + list("M39 Extended Magazine (10x20mm)", 10, /obj/item/ammo_magazine/smg/m39/extended , null, VENDOR_ITEM_REGULAR), + list("M41A AP Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/ap , null, VENDOR_ITEM_REGULAR), + list("M41A Extended Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/extended , null, VENDOR_ITEM_REGULAR), + + list("SIDEARM AMMUNITION", 0, null, null, null), + list("M44 Heavy Speed Loader (.44)", 10, /obj/item/ammo_magazine/revolver/heavy, null, VENDOR_ITEM_REGULAR), + list("M4A3 HP Magazine", 5, /obj/item/ammo_magazine/pistol/hp, null, VENDOR_ITEM_REGULAR), + list("M4A3 AP Magazine", 5, /obj/item/ammo_magazine/pistol/ap, null, VENDOR_ITEM_REGULAR), + list("VP78 Magazine", 5, /obj/item/ammo_magazine/pistol/vp78, null, VENDOR_ITEM_REGULAR), + list("RESTRICTED FIREARMS", 0, null, null, null), list("VP78 Pistol", 10, /obj/item/storage/box/guncase/vp78, null, VENDOR_ITEM_REGULAR), list("SU-6 Smart Pistol", 15, /obj/item/storage/box/guncase/smartpistol, null, VENDOR_ITEM_REGULAR), diff --git a/code/game/objects/items/devices/motion_detector.dm b/code/game/objects/items/devices/motion_detector.dm index c26db692f082..7db2825deedf 100644 --- a/code/game/objects/items/devices/motion_detector.dm +++ b/code/game/objects/items/devices/motion_detector.dm @@ -191,6 +191,14 @@ if(ishuman(A.loc)) return A.loc +/obj/item/device/motiondetector/xm4 + +///Forces the blue blip to appear around the detected mob +/obj/item/device/motiondetector/xm4/get_user() + var/atom/holder = loc + if(ishuman(holder.loc)) + return holder.loc + /obj/item/device/motiondetector/proc/apply_debuff(mob/M) return diff --git a/code/game/objects/items/devices/portable_vendor.dm b/code/game/objects/items/devices/portable_vendor.dm index da399192b713..8e7c8df1d9a7 100644 --- a/code/game/objects/items/devices/portable_vendor.dm +++ b/code/game/objects/items/devices/portable_vendor.dm @@ -267,8 +267,11 @@ req_role = JOB_CORPORATE_LIAISON listed_products = list( list("INCENTIVES", 0, null, null, null), + list("Corporate Security Bodyguard", 50, /obj/item/handheld_distress_beacon/bodyguard, "white", "A beacon which sends the Corporate Security Division an encoded message informing them of your request for a Corporate Security Bodyguard."), + list("Corporate Lawyer Team", 50, /obj/item/handheld_distress_beacon/lawyer, "white", "A beacon which sends the Corporate Affairs Division an encoded message informing them of your request for a Corporate Lawyer, required when a contract signee breaks one of their clauses."), list("Neurostimulator Implant", 30, /obj/item/implanter/neurostim, "white", "Implant which regulates nociception and sensory function. Benefits include pain reduction, improved balance, and improved resistance to overstimulation and disorientation. To encourage compliance, negative stimulus is applied if the implant hears a (non-radio) spoken codephrase. Implant will be degraded by the body's immune system over time, and thus malfunction with gradually increasing frequency. Personal use not recommended."), list("Ultrazine Injector", 25, /obj/item/reagent_container/hypospray/autoinjector/ultrazine/liaison, "white", "Highly-addictive stimulant. Enhances short-term physical performance, particularly running speed. Effects last approximately 10 minutes per injection. More than two injections at a time will result in overdose. Withdrawal causes extreme discomfort and hallucinations. Long-term use results in halluciations and organ failure. Conditional distribution secures subject compliance. Not for personal use."), + list("Cyanide Pill", 20, /obj/item/reagent_container/pill/cyanide, "white", "A cyanide pill, also known as a suicide pill. For the easy way out."), list("Ceramic Plate", 10, /obj/item/trash/ceramic_plate, "white", "A ceramic plate, useful in a variety of situations."), list("Cash", 5, /obj/item/spacecash/c1000/counterfeit, "white", "$1000 USD, unmarked bills"), list("WY Encryption Key", 5, /obj/item/device/encryptionkey/WY, "white", "WY private comms encryption key, for conducting private business."), @@ -282,6 +285,7 @@ list("Sake", 5, /obj/item/reagent_container/food/drinks/bottle/sake, "white", "Weyland-Yutani Sake, for a proper business dinner."), list("Beer", 5, /obj/item/reagent_container/food/drinks/cans/aspen, "white", "Weyland-Yutani Aspen Beer, for a more casual night."), list("Drinking Glass", 1, /obj/item/reagent_container/food/drinks/drinkingglass, "white", "A Drinking Glass, because you have class."), + list("Weyland-Yutani Coffee Mug", 1, /obj/item/reagent_container/food/drinks/coffeecup/wy, "white", "A Weyland-Yutani coffee mug, for any Marines who want a Company souvenir."), list("STATIONARY", 0, null, null, null), list("WY pen, black", 1, /obj/item/tool/pen/clicky, "white", "A WY pen, for writing formally on the go."), diff --git a/code/game/objects/items/handheld_distress_beacon.dm b/code/game/objects/items/handheld_distress_beacon.dm index c11a7a57c350..91d6a6aa945f 100644 --- a/code/game/objects/items/handheld_distress_beacon.dm +++ b/code/game/objects/items/handheld_distress_beacon.dm @@ -64,3 +64,23 @@ recipient = "Anchorpoint Station" ert_full_name = list("CMB - Patrol Team - Marshals in Distress (Friendly)", "CMB - Anchorpoint Station Colonial Marine QRF (Friendly)") ert_short_name = list("SEND CMB", "SEND QRF") + +// Corporate Lawyer beacon available for 50 points at the CLs briefcase +/obj/item/handheld_distress_beacon/lawyer + name = "\improper Corporate Affairs Division handheld beacon" + desc = "An encoded beacon. This one is branded with the Weyland-Yutani slogan, 'Building Better Worlds since 2099'. Etched in stencil on the side is 'FOR CONTRACT BREAKERS ONLY'. This one is branded with the Corporate Affairs Division symbol and provided to most Executives situated in UA or TWE space." + + beacon_type = "Lawyer beacon" + recipient = "the Corporate Affairs Division" + ert_full_name = list("Lawyers - Corporate") + ert_short_name = list("SEND LAWYERS") + +// Corporate Security Bodyguard beacon available for 50 points at the CLs briefcase +/obj/item/handheld_distress_beacon/bodyguard + name = "\improper Corporate Security Division handheld beacon" + desc = "An standard Corporate Security beacon. This one is branded with the Weyland-Yutani slogan, 'Building Better Worlds since 2099'. This one is branded with the Corporate Security Division symbol and provided to Executives stationed in very dangerous sites across the entire Galaxy." + + beacon_type = "Bodyguard beacon" + recipient = "the Corporate Security Division" + ert_full_name = list("Weyland-Yutani Goon (Executive Bodyguard Detail)") + ert_short_name = list("SEND BODYGUARD") diff --git a/code/game/objects/items/toys/toys.dm b/code/game/objects/items/toys/toys.dm index 6fa420df35d5..91d8164dcf38 100644 --- a/code/game/objects/items/toys/toys.dm +++ b/code/game/objects/items/toys/toys.dm @@ -396,6 +396,14 @@ src.add_fingerprint(user) addtimer(VARSET_CALLBACK(src, spam_flag, FALSE), 2 SECONDS) +// rubber duck +/obj/item/toy/bikehorn/rubberducky + name = "rubber ducky" + desc = "Rubber ducky you're so fine, you make bathtime lots of fuuun. Rubber ducky I'm awfully fooooond of yooooouuuu~" //thanks doohl + icon = 'icons/obj/structures/props/watercloset.dmi' + icon_state = "rubberducky" + item_state = "rubberducky" + /obj/item/computer3_part name = "computer part" desc = "Holy jesus you donnit now" diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index cfb62812f0c6..d891119a8404 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -335,7 +335,7 @@ icon_closed = "closed_supply" /obj/structure/closet/crate/trashcart - name = "\improper trash cart" + name = "trash cart" desc = "A heavy, metal trashcart with wheels." icon_state = "closed_trashcart" icon_opened = "open_trashcart" @@ -437,4 +437,3 @@ density = TRUE icon_opened = "open_mcart_y" icon_closed = "closed_mcart_y" - diff --git a/code/game/objects/structures/shower.dm b/code/game/objects/structures/shower.dm new file mode 100644 index 000000000000..b731a2c0e242 --- /dev/null +++ b/code/game/objects/structures/shower.dm @@ -0,0 +1,222 @@ +/obj/structure/machinery/shower + name = "shower" + desc = "The HS-451. Installed in the 2050s by the Weyland Hygiene Division." + icon = 'icons/obj/structures/props/watercloset.dmi' + icon_state = "shower" + density = FALSE + anchored = TRUE + use_power = USE_POWER_NONE + var/on = 0 + var/obj/effect/mist/mymist = null + /// needs a var so we can make it linger~ + var/ismist = 0 + /// freezing, normal, or boiling + var/watertemp = "normal" + /// true if there is a mob on the shower's loc, this is to ease process() + var/mobpresent = 0 + var/is_washing = 0 + +/obj/structure/machinery/shower/Initialize() + . = ..() + create_reagents(2) + +//add heat controls? when emagged, you can freeze to death in it? + +/obj/effect/mist + name = "mist" + icon = 'icons/obj/structures/props/watercloset.dmi' + icon_state = "mist" + layer = FLY_LAYER + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + + +/obj/structure/machinery/shower/attack_hand(mob/M as mob) + on = !on + update_icon() + if(on) + start_processing() + if (M.loc == loc) + wash(M) + check_heat(M) + for (var/atom/movable/G in src.loc) + G.clean_blood() + else + stop_processing() + + +/obj/structure/machinery/shower/attackby(obj/item/I as obj, mob/user as mob) + if(I.type == /obj/item/device/analyzer) + to_chat(user, SPAN_NOTICE("The water temperature seems to be [watertemp].")) + if(HAS_TRAIT(I, TRAIT_TOOL_WRENCH)) + to_chat(user, SPAN_NOTICE("You begin to adjust the temperature valve with \the [I].")) + if(do_after(user, 50, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + switch(watertemp) + if("normal") + watertemp = "freezing" + if("freezing") + watertemp = "boiling" + if("boiling") + watertemp = "normal" + user.visible_message(SPAN_NOTICE("[user] adjusts the shower with \the [I]."), SPAN_NOTICE("You adjust the shower with \the [I].")) + add_fingerprint(user) + + +/obj/structure/machinery/shower/update_icon() //this is terribly unreadable, but basically it makes the shower mist up + overlays.Cut() //once it's been on for a while, in addition to handling the water overlay. + QDEL_NULL(mymist) + + if(on) + overlays += image('icons/obj/structures/props/watercloset.dmi', src, "water", MOB_LAYER + 1, dir) + if(watertemp == "freezing") + return + if(!ismist) + spawn(50) + if(src && on) + ismist = 1 + mymist = new /obj/effect/mist(loc) + else + ismist = 1 + mymist = new /obj/effect/mist(loc) + else if(ismist) + ismist = 1 + mymist = new /obj/effect/mist(loc) + spawn(250) + if(src && !on) + QDEL_NULL(mymist) + ismist = 0 + + +/obj/structure/machinery/shower/Crossed(atom/movable/O) + ..() + wash(O) + if(ismob(O)) + mobpresent++ + check_heat(O) + + +/obj/structure/machinery/shower/Uncrossed(atom/movable/O) + if(ismob(O)) + mobpresent-- + ..() + +//Yes, showers are super powerful as far as washing goes. +/obj/structure/machinery/shower/proc/wash(atom/movable/O as obj|mob) + if(!on) return + + + if(isliving(O)) + var/mob/living/L = O + L.ExtinguishMob() + L.fire_stacks = -20 //Douse ourselves with water to avoid fire more easily + to_chat(L, SPAN_WARNING("You've been drenched in water!")) + if(iscarbon(O)) + var/mob/living/carbon/M = O + if(M.r_hand) + M.r_hand.clean_blood() + if(M.l_hand) + M.l_hand.clean_blood() + if(M.back) + if(M.back.clean_blood()) + M.update_inv_back(0) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/washgloves = 1 + var/washshoes = 1 + var/washmask = 1 + var/washears = 1 + var/washglasses = 1 + + if(H.wear_suit) + washgloves = !(H.wear_suit.flags_inv_hide & HIDEGLOVES) + washshoes = !(H.wear_suit.flags_inv_hide & HIDESHOES) + + if(H.head) + washmask = !(H.head.flags_inv_hide & HIDEMASK) + washglasses = !(H.head.flags_inv_hide & HIDEEYES) + washears = !(H.head.flags_inv_hide & HIDEEARS) + + if(H.wear_mask) + if (washears) + washears = !(H.wear_mask.flags_inv_hide & HIDEEARS) + if (washglasses) + washglasses = !(H.wear_mask.flags_inv_hide & HIDEEYES) + + if(H.head) + if(H.head.clean_blood()) + H.update_inv_head() + if(H.wear_suit) + if(H.wear_suit.clean_blood()) + H.update_inv_wear_suit() + else if(H.w_uniform) + if(H.w_uniform.clean_blood()) + H.update_inv_w_uniform() + if(H.gloves && washgloves) + if(H.gloves.clean_blood()) + H.update_inv_gloves() + if(H.shoes && washshoes) + if(H.shoes.clean_blood()) + H.update_inv_shoes() + if(H.wear_mask && washmask) + if(H.wear_mask.clean_blood()) + H.update_inv_wear_mask() + if(H.glasses && washglasses) + if(H.glasses.clean_blood()) + H.update_inv_glasses() + if((H.wear_l_ear || H.wear_r_ear) && washears) + if((H.wear_l_ear && H.wear_l_ear.clean_blood()) ||(H.wear_r_ear && H.wear_r_ear.clean_blood())) + H.update_inv_ears() + if(H.belt) + if(H.belt.clean_blood()) + H.update_inv_belt() + H.clean_blood(washshoes) + else + if(M.wear_mask) //if the mob is not human, it cleans the mask without asking for bitflags + if(M.wear_mask.clean_blood()) + M.update_inv_wear_mask() + M.clean_blood() + else + O.clean_blood() + + if(isturf(loc)) + var/turf/tile = loc + tile.clean_blood() + for(var/obj/effect/E in tile) + if(istype(E,/obj/effect/decal/cleanable) || istype(E,/obj/effect/overlay)) + qdel(E) + + +/obj/structure/machinery/shower/process() + if(!on) return + wash_floor() + if(!mobpresent) return + for(var/mob/living/carbon/C in loc) + check_heat(C) + + +/obj/structure/machinery/shower/proc/wash_floor() + if(!ismist && is_washing) + return + is_washing = 1 + var/turf/T = get_turf(src) +// reagents.add_reagent("water", 2) + T.clean(src) + addtimer(VARSET_CALLBACK(src, is_washing, FALSE), 10 SECONDS) + + +/obj/structure/machinery/shower/proc/check_heat(mob/M as mob) + if(!on || watertemp == "normal") return + if(iscarbon(M)) + var/mob/living/carbon/C = M + + if(watertemp == "freezing") + C.bodytemperature = max(80, C.bodytemperature - 80) + C.recalculate_move_delay = TRUE + to_chat(C, SPAN_WARNING("The water is freezing!")) + return + if(watertemp == "boiling") + C.bodytemperature = min(500, C.bodytemperature + 35) + C.recalculate_move_delay = TRUE + C.apply_damage(5, BURN) + to_chat(C, SPAN_DANGER("The water is searing!")) + return diff --git a/code/game/objects/structures/sink.dm b/code/game/objects/structures/sink.dm new file mode 100644 index 000000000000..6bac40ea7da4 --- /dev/null +++ b/code/game/objects/structures/sink.dm @@ -0,0 +1,122 @@ +/obj/structure/sink + name = "sink" + icon = 'icons/obj/structures/props/watercloset.dmi' + icon_state = "sink_emptied_animation" + desc = "A sink used for washing one's hands and face." + anchored = TRUE + /// if something's being washed at the moment + var/busy = FALSE + + +/obj/structure/sink/Initialize() + . = ..() + if(prob(50)) + icon_state = "sink_emptied" + + +/obj/structure/sink/proc/stop_flow() //sets sink animation to normal sink (without running water) + + if(prob(50)) + icon_state = "sink_emptied_animation" + else + icon_state = "sink_emptied" + flick("sink_animation_empty", src) + + +/obj/structure/sink/attack_hand(mob/user) + if(isRemoteControlling(user)) + return + + if(!Adjacent(user)) + return + + if(busy) + to_chat(user, SPAN_DANGER("Someone's already washing here.")) + return + + to_chat(usr, SPAN_NOTICE(" You start washing your hands.")) + flick("sink_animation_fill", src) //<- play the filling animation then automatically switch back to the loop + icon_state = "sink_animation_fill_loop" //<- set it to the loop + addtimer(CALLBACK(src, PROC_REF(stop_flow)), 6 SECONDS) + playsound(loc, 'sound/effects/sinkrunning.ogg', 25, TRUE) + + busy = TRUE + sleep(40) + busy = FALSE + + if(!Adjacent(user)) return //Person has moved away from the sink + + user.clean_blood() + if(ishuman(user)) + user:update_inv_gloves() + for(var/mob/V in viewers(src, null)) + V.show_message(SPAN_NOTICE("[user] washes their hands using \the [src]."), SHOW_MESSAGE_VISIBLE) + + +/obj/structure/sink/attackby(obj/item/O as obj, mob/user as mob) + if(busy) + to_chat(user, SPAN_DANGER("Someone's already washing here.")) + return + + var/obj/item/reagent_container/RG = O + if (istype(RG) && RG.is_open_container()) + RG.reagents.add_reagent("water", min(RG.volume - RG.reagents.total_volume, RG.amount_per_transfer_from_this)) + user.visible_message(SPAN_NOTICE("[user] fills \the [RG] using \the [src]."),SPAN_NOTICE("You fill \the [RG] using \the [src].")) + return + + else if (istype(O, /obj/item/weapon/baton)) + var/obj/item/weapon/baton/B = O + if(B.bcell) + if(B.bcell.charge > 0 && B.status == 1) + flick("baton_active", src) + user.apply_effect(10, STUN) + user.stuttering = 10 + user.apply_effect(10, WEAKEN) + B.deductcharge(B.hitcost) + user.visible_message( \ + SPAN_DANGER("[user] was stunned by \his wet [O]!"), \ + SPAN_DANGER("You were stunned by your wet [O]!")) + return + + var/turf/location = user.loc + if(!isturf(location)) return + + var/obj/item/I = O + if(!I || !istype(I,/obj/item)) return + + to_chat(usr, SPAN_NOTICE(" You start washing \the [I].")) + + busy = TRUE + sleep(40) + busy = FALSE + + if(user.loc != location) return //User has moved + if(!I) return //Item's been destroyed while washing + if(user.get_active_hand() != I) return //Person has switched hands or the item in their hands + + O.clean_blood() + user.visible_message( \ + SPAN_NOTICE("[user] washes \a [I] using \the [src]."), \ + SPAN_NOTICE("You wash \a [I] using \the [src].")) + + +/obj/structure/sink/kitchen + name = "kitchen sink" + icon_state = "sink_alt" + + +/obj/structure/sink/puddle //splishy splashy ^_^ + name = "puddle" + icon_state = "puddle" + + +/obj/structure/sink/puddle/attack_hand(mob/M as mob) + icon_state = "puddle-splash" + ..() + icon_state = "puddle" + + +/obj/structure/sink/puddle/attackby(obj/item/O as obj, mob/user as mob) + icon_state = "puddle-splash" + ..() + icon_state = "puddle" diff --git a/code/game/objects/structures/urinal.dm b/code/game/objects/structures/urinal.dm new file mode 100644 index 000000000000..c6d14f46540a --- /dev/null +++ b/code/game/objects/structures/urinal.dm @@ -0,0 +1,22 @@ +/obj/structure/urinal + name = "urinal" + desc = "The HU-452, an experimental urinal." + icon = 'icons/obj/structures/props/watercloset.dmi' + icon_state = "urinal" + density = FALSE + anchored = TRUE + +/obj/structure/urinal/attackby(obj/item/I, mob/living/user) + if(istype(I, /obj/item/grab)) + if(isxeno(user)) return + var/obj/item/grab/G = I + if(isliving(G.grabbed_thing)) + var/mob/living/GM = G.grabbed_thing + if(user.grab_level > GRAB_PASSIVE) + if(!GM.loc == get_turf(src)) + to_chat(user, SPAN_NOTICE("[GM.name] needs to be on the urinal.")) + return + user.visible_message(SPAN_DANGER("[user] slams [GM.name] into [src]!"), SPAN_NOTICE("You slam [GM.name] into [src]!")) + GM.apply_damage(8, BRUTE) + else + to_chat(user, SPAN_NOTICE("You need a tighter grip.")) diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index ea93d868c5a1..1014e8ab7a96 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -1,5 +1,3 @@ -//todo: toothbrushes, and some sort of "toilet-filthinator" for the hos - /obj/structure/toilet name = "toilet" desc = "The HT-451, a torque rotation-based, waste disposal unit for small matter. This one seems remarkably clean." @@ -9,16 +7,19 @@ anchored = TRUE can_buckle = TRUE buckle_lying = 0 - var/open = 0 //if the lid is up - var/cistern = 0 //if the cistern bit is open - var/w_items = 0 //the combined w_class of all the items in the cistern - var/mob/living/swirlie = null //the mob being given a swirlie + /// if the lid is up + var/open = 0 + /// if the cistern bit is open + var/cistern = 0 + /// the combined w_class of all the items in the cistern + var/w_items = 0 + /// the mob being given a swirlie + var/mob/living/swirlie = null var/list/buckling_y = list("north" = 1, "south" = 4, "east" = 0, "west" = 0) var/list/buckling_x = list("north" = 0, "south" = 0, "east" = -5, "west" = 4) var/atom/movable/overlay/cistern_overlay - /obj/structure/toilet/Initialize() . = ..() open = round(rand(0, 1)) @@ -29,6 +30,7 @@ vis_contents += cistern_overlay update_icon() + /obj/structure/toilet/attack_hand(mob/living/user as mob) if(buckled_mob) manual_unbuckle(user) @@ -69,7 +71,6 @@ flick("cistern[cistern]_flush", cistern_overlay) - /obj/structure/toilet/send_buckling_message(mob/M, mob/user) if (M == user) to_chat(M, SPAN_NOTICE("You seat yourself onto the toilet")) @@ -77,10 +78,10 @@ to_chat(user, SPAN_NOTICE("[M] has been seated onto the toilet by [user].")) to_chat(M, SPAN_NOTICE("You have been seated onto the toilet by [user].")) + /obj/structure/toilet/afterbuckle(mob/M) . = ..() - if(. && buckled_mob == M) var/direction = dir2text(dir) M.pixel_y = buckling_y[direction] + pixel_y @@ -106,7 +107,6 @@ M.overlays -= image("toilet00") - /obj/structure/toilet/verb/flip_lid() set name = "Flip lid" set category = "Object" @@ -116,11 +116,11 @@ update_icon() - /obj/structure/toilet/update_icon() icon_state = "toilet[open][cistern]" cistern_overlay.icon_state = "cistern[cistern]" + /obj/structure/toilet/attackby(obj/item/I, mob/living/user) if(HAS_TRAIT(I, TRAIT_TOOL_CROWBAR)) to_chat(user, SPAN_NOTICE("You start to [cistern ? "replace the lid on the cistern" : "lift the lid off the cistern"].")) @@ -168,374 +168,3 @@ w_items += I.w_class to_chat(user, "You carefully place \the [I] into the cistern.") return - - - -/obj/structure/urinal - name = "urinal" - desc = "The HU-452, an experimental urinal." - icon = 'icons/obj/structures/props/watercloset.dmi' - icon_state = "urinal" - density = FALSE - anchored = TRUE - -/obj/structure/urinal/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/grab)) - if(isxeno(user)) return - var/obj/item/grab/G = I - if(isliving(G.grabbed_thing)) - var/mob/living/GM = G.grabbed_thing - if(user.grab_level > GRAB_PASSIVE) - if(!GM.loc == get_turf(src)) - to_chat(user, SPAN_NOTICE("[GM.name] needs to be on the urinal.")) - return - user.visible_message(SPAN_DANGER("[user] slams [GM.name] into [src]!"), SPAN_NOTICE("You slam [GM.name] into [src]!")) - GM.apply_damage(8, BRUTE) - else - to_chat(user, SPAN_NOTICE("You need a tighter grip.")) - - - -/obj/structure/machinery/shower - name = "shower" - desc = "The HS-451. Installed in the 2050s by the Weyland Hygiene Division." - icon = 'icons/obj/structures/props/watercloset.dmi' - icon_state = "shower" - density = FALSE - anchored = TRUE - use_power = USE_POWER_NONE - var/on = 0 - var/obj/effect/mist/mymist = null - var/ismist = 0 //needs a var so we can make it linger~ - var/watertemp = "normal" //freezing, normal, or boiling - var/mobpresent = 0 //true if there is a mob on the shower's loc, this is to ease process() - var/is_washing = 0 - -/obj/structure/machinery/shower/Initialize() - . = ..() - create_reagents(2) - -//add heat controls? when emagged, you can freeze to death in it? - -/obj/effect/mist - name = "mist" - icon = 'icons/obj/structures/props/watercloset.dmi' - icon_state = "mist" - layer = FLY_LAYER - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/structure/machinery/shower/attack_hand(mob/M as mob) - on = !on - update_icon() - if(on) - start_processing() - if (M.loc == loc) - wash(M) - check_heat(M) - for (var/atom/movable/G in src.loc) - G.clean_blood() - else - stop_processing() - -/obj/structure/machinery/shower/attackby(obj/item/I as obj, mob/user as mob) - if(I.type == /obj/item/device/analyzer) - to_chat(user, SPAN_NOTICE("The water temperature seems to be [watertemp].")) - if(HAS_TRAIT(I, TRAIT_TOOL_WRENCH)) - to_chat(user, SPAN_NOTICE("You begin to adjust the temperature valve with \the [I].")) - if(do_after(user, 50, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) - switch(watertemp) - if("normal") - watertemp = "freezing" - if("freezing") - watertemp = "boiling" - if("boiling") - watertemp = "normal" - user.visible_message(SPAN_NOTICE("[user] adjusts the shower with \the [I]."), SPAN_NOTICE("You adjust the shower with \the [I].")) - add_fingerprint(user) - -/obj/structure/machinery/shower/update_icon() //this is terribly unreadable, but basically it makes the shower mist up - overlays.Cut() //once it's been on for a while, in addition to handling the water overlay. - QDEL_NULL(mymist) - - if(on) - overlays += image('icons/obj/structures/props/watercloset.dmi', src, "water", MOB_LAYER + 1, dir) - if(watertemp == "freezing") - return - if(!ismist) - spawn(50) - if(src && on) - ismist = 1 - mymist = new /obj/effect/mist(loc) - else - ismist = 1 - mymist = new /obj/effect/mist(loc) - else if(ismist) - ismist = 1 - mymist = new /obj/effect/mist(loc) - spawn(250) - if(src && !on) - QDEL_NULL(mymist) - ismist = 0 - -/obj/structure/machinery/shower/Crossed(atom/movable/O) - ..() - wash(O) - if(ismob(O)) - mobpresent++ - check_heat(O) - -/obj/structure/machinery/shower/Uncrossed(atom/movable/O) - if(ismob(O)) - mobpresent-- - ..() - -//Yes, showers are super powerful as far as washing goes. -/obj/structure/machinery/shower/proc/wash(atom/movable/O as obj|mob) - if(!on) return - - - if(isliving(O)) - var/mob/living/L = O - L.ExtinguishMob() - L.fire_stacks = -20 //Douse ourselves with water to avoid fire more easily - to_chat(L, SPAN_WARNING("You've been drenched in water!")) - if(iscarbon(O)) - var/mob/living/carbon/M = O - if(M.r_hand) - M.r_hand.clean_blood() - if(M.l_hand) - M.l_hand.clean_blood() - if(M.back) - if(M.back.clean_blood()) - M.update_inv_back(0) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/washgloves = 1 - var/washshoes = 1 - var/washmask = 1 - var/washears = 1 - var/washglasses = 1 - - if(H.wear_suit) - washgloves = !(H.wear_suit.flags_inv_hide & HIDEGLOVES) - washshoes = !(H.wear_suit.flags_inv_hide & HIDESHOES) - - if(H.head) - washmask = !(H.head.flags_inv_hide & HIDEMASK) - washglasses = !(H.head.flags_inv_hide & HIDEEYES) - washears = !(H.head.flags_inv_hide & HIDEEARS) - - if(H.wear_mask) - if (washears) - washears = !(H.wear_mask.flags_inv_hide & HIDEEARS) - if (washglasses) - washglasses = !(H.wear_mask.flags_inv_hide & HIDEEYES) - - if(H.head) - if(H.head.clean_blood()) - H.update_inv_head() - if(H.wear_suit) - if(H.wear_suit.clean_blood()) - H.update_inv_wear_suit() - else if(H.w_uniform) - if(H.w_uniform.clean_blood()) - H.update_inv_w_uniform() - if(H.gloves && washgloves) - if(H.gloves.clean_blood()) - H.update_inv_gloves() - if(H.shoes && washshoes) - if(H.shoes.clean_blood()) - H.update_inv_shoes() - if(H.wear_mask && washmask) - if(H.wear_mask.clean_blood()) - H.update_inv_wear_mask() - if(H.glasses && washglasses) - if(H.glasses.clean_blood()) - H.update_inv_glasses() - if((H.wear_l_ear || H.wear_r_ear) && washears) - if((H.wear_l_ear && H.wear_l_ear.clean_blood()) ||(H.wear_r_ear && H.wear_r_ear.clean_blood())) - H.update_inv_ears() - if(H.belt) - if(H.belt.clean_blood()) - H.update_inv_belt() - H.clean_blood(washshoes) - else - if(M.wear_mask) //if the mob is not human, it cleans the mask without asking for bitflags - if(M.wear_mask.clean_blood()) - M.update_inv_wear_mask() - M.clean_blood() - else - O.clean_blood() - - if(isturf(loc)) - var/turf/tile = loc - tile.clean_blood() - for(var/obj/effect/E in tile) - if(istype(E,/obj/effect/decal/cleanable) || istype(E,/obj/effect/overlay)) - qdel(E) - -/obj/structure/machinery/shower/process() - if(!on) return - wash_floor() - if(!mobpresent) return - for(var/mob/living/carbon/C in loc) - check_heat(C) - -/obj/structure/machinery/shower/proc/wash_floor() - if(!ismist && is_washing) - return - is_washing = 1 - var/turf/T = get_turf(src) -// reagents.add_reagent("water", 2) - T.clean(src) - addtimer(VARSET_CALLBACK(src, is_washing, FALSE), 10 SECONDS) - -/obj/structure/machinery/shower/proc/check_heat(mob/M as mob) - if(!on || watertemp == "normal") return - if(iscarbon(M)) - var/mob/living/carbon/C = M - - if(watertemp == "freezing") - C.bodytemperature = max(80, C.bodytemperature - 80) - C.recalculate_move_delay = TRUE - to_chat(C, SPAN_WARNING("The water is freezing!")) - return - if(watertemp == "boiling") - C.bodytemperature = min(500, C.bodytemperature + 35) - C.recalculate_move_delay = TRUE - C.apply_damage(5, BURN) - to_chat(C, SPAN_DANGER("The water is searing!")) - return - - - -/obj/item/toy/bikehorn/rubberducky - name = "rubber ducky" - desc = "Rubber ducky you're so fine, you make bathtime lots of fuuun. Rubber ducky I'm awfully fooooond of yooooouuuu~" //thanks doohl - icon = 'icons/obj/structures/props/watercloset.dmi' - icon_state = "rubberducky" - item_state = "rubberducky" - - - -/obj/structure/sink - name = "sink" - icon = 'icons/obj/structures/props/watercloset.dmi' - icon_state = "sink_emptied_animation" - desc = "A sink used for washing one's hands and face." - anchored = TRUE - var/busy = FALSE //Something's being washed at the moment - -/obj/structure/sink/Initialize() - . = ..() - if(prob(50)) - icon_state = "sink_emptied" - - - -/obj/structure/sink/proc/stop_flow() //sets sink animation to normal sink (without running water) - - if(prob(50)) - icon_state = "sink_emptied_animation" - else - icon_state = "sink_emptied" - flick("sink_animation_empty", src) - - - -/obj/structure/sink/attack_hand(mob/user) - if(isRemoteControlling(user)) - return - - if(!Adjacent(user)) - return - - if(busy) - to_chat(user, SPAN_DANGER("Someone's already washing here.")) - return - - to_chat(usr, SPAN_NOTICE(" You start washing your hands.")) - flick("sink_animation_fill", src) //<- play the filling animation then automatically switch back to the loop - icon_state = "sink_animation_fill_loop" //<- set it to the loop - addtimer(CALLBACK(src, PROC_REF(stop_flow)), 6 SECONDS) - playsound(loc, 'sound/effects/sinkrunning.ogg', 25, TRUE) - - busy = TRUE - sleep(40) - busy = FALSE - - if(!Adjacent(user)) return //Person has moved away from the sink - - user.clean_blood() - if(ishuman(user)) - user:update_inv_gloves() - for(var/mob/V in viewers(src, null)) - V.show_message(SPAN_NOTICE("[user] washes their hands using \the [src]."), SHOW_MESSAGE_VISIBLE) - - -/obj/structure/sink/attackby(obj/item/O as obj, mob/user as mob) - if(busy) - to_chat(user, SPAN_DANGER("Someone's already washing here.")) - return - - var/obj/item/reagent_container/RG = O - if (istype(RG) && RG.is_open_container()) - RG.reagents.add_reagent("water", min(RG.volume - RG.reagents.total_volume, RG.amount_per_transfer_from_this)) - user.visible_message(SPAN_NOTICE("[user] fills \the [RG] using \the [src]."),SPAN_NOTICE("You fill \the [RG] using \the [src].")) - return - - else if (istype(O, /obj/item/weapon/baton)) - var/obj/item/weapon/baton/B = O - if(B.bcell) - if(B.bcell.charge > 0 && B.status == 1) - flick("baton_active", src) - user.apply_effect(10, STUN) - user.stuttering = 10 - user.apply_effect(10, WEAKEN) - B.deductcharge(B.hitcost) - user.visible_message( \ - SPAN_DANGER("[user] was stunned by \his wet [O]!"), \ - SPAN_DANGER("You were stunned by your wet [O]!")) - return - - var/turf/location = user.loc - if(!isturf(location)) return - - var/obj/item/I = O - if(!I || !istype(I,/obj/item)) return - - to_chat(usr, SPAN_NOTICE(" You start washing \the [I].")) - - busy = TRUE - sleep(40) - busy = FALSE - - if(user.loc != location) return //User has moved - if(!I) return //Item's been destroyed while washing - if(user.get_active_hand() != I) return //Person has switched hands or the item in their hands - - O.clean_blood() - user.visible_message( \ - SPAN_NOTICE("[user] washes \a [I] using \the [src]."), \ - SPAN_NOTICE("You wash \a [I] using \the [src].")) - - -/obj/structure/sink/kitchen - name = "kitchen sink" - icon_state = "sink_alt" - - -/obj/structure/sink/puddle //splishy splashy ^_^ - name = "puddle" - icon_state = "puddle" - -/obj/structure/sink/puddle/attack_hand(mob/M as mob) - icon_state = "puddle-splash" - ..() - icon_state = "puddle" - -/obj/structure/sink/puddle/attackby(obj/item/O as obj, mob/user as mob) - icon_state = "puddle-splash" - ..() - icon_state = "puddle" diff --git a/code/game/world.dm b/code/game/world.dm index f68263412715..2b7dacee373a 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -87,10 +87,6 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt")) GLOB.obfs_x = rand(-500, 500) //A number between -100 and 100 GLOB.obfs_y = rand(-500, 500) //A number between -100 and 100 - spawn(3000) //so we aren't adding to the round-start lag - if(CONFIG_GET(flag/ToRban)) - ToRban_autoupdate() - // If the server's configured for local testing, get everything set up ASAP. // Shamelessly stolen from the test manager's host_tests() proc if(testing_locally) @@ -129,7 +125,7 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt")) GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" GLOB.round_stats = "[GLOB.log_directory]/round_stats.log" GLOB.scheduler_stats = "[GLOB.log_directory]/round_scheduler_stats.log" - GLOB.mutator_logs = "[GLOB.log_directory]/mutator_logs.log" + GLOB.strain_logs = "[GLOB.log_directory]/strain_logs.log" start_log(GLOB.tgui_log) start_log(GLOB.world_href_log) @@ -138,7 +134,7 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt")) start_log(GLOB.world_runtime_log) start_log(GLOB.round_stats) start_log(GLOB.scheduler_stats) - start_log(GLOB.mutator_logs) + start_log(GLOB.strain_logs) if(fexists(GLOB.config_error_log)) fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 94f40629fc6a..85a1028c2296 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -1,6 +1,6 @@ #ifndef OVERRIDE_BAN_SYSTEM //Blocks an attempt to connect before even creating our client datum thing. -/world/IsBanned(key,address,computer_id, type, real_bans_only=FALSE) +/world/IsBanned(key,address,computer_id, type, real_bans_only=FALSE, is_telemetry = FALSE) var/ckey = ckey(key) // This is added siliently. Thanks to MSO for this fix. You will see it when/if we go OS @@ -17,6 +17,7 @@ message_admins("Failed Login: [key] - Guests not allowed") return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.") + // wait for database to be ready WAIT_DB_READY if(GLOB.admin_datums[ckey] && (GLOB.admin_datums[ckey].rights & R_MOD)) return ..() @@ -26,19 +27,8 @@ var/datum/entity/player/P = get_player_from_key(ckey) - //check if the IP address is a known TOR node - if(CONFIG_GET(flag/ToRban) && ToRban_isbanned(address)) - log_access("Failed Login: [src] - Banned: ToR") - message_admins("Failed Login: [src] - Banned: ToR") - return list("reason"="Using ToR", "desc"="\nReason: The network you are using to connect has been banned.\nIf you believe this is a mistake, please request help at [CONFIG_GET(string/banappeals)]") - - // wait for database to be ready - - . = P.check_ban(computer_id, address) - if(.) - return . - return ..() //default pager ban stuff + . = P.check_ban(computer_id, address, is_telemetry) #endif diff --git a/code/modules/admin/NewBan.dm b/code/modules/admin/NewBan.dm index 7dca354129ff..f4394738fb2d 100644 --- a/code/modules/admin/NewBan.dm +++ b/code/modules/admin/NewBan.dm @@ -180,15 +180,44 @@ GLOBAL_DATUM(Banlist, /savefile) expiry = "Removal Pending" else expiry = "Permaban" - var/unban_link = "(U)" + var/unban_link + if(ban.is_permabanned) + unban_link = "(UP)" + else + unban_link = "(UT)" - dat += "[unban_link] Key: [ban.ckey]ComputerID: [ban.last_known_cid]IP: [ban.last_known_ip] [expiry](By: [ban.admin])(Reason: [ban.reason])" + dat += "[unban_link] Key: [ban.ckey]ComputerID: [ban.last_known_cid]IP: [ban.last_known_ip] [expiry](By: [ban.admin ? ban.admin : "AdminBot"])(Reason: [ban.reason])" dat += "" - var/dat_header = "
Bans: (U) = Unban" + var/dat_header = "
Bans: (UP) = Unban Perma (UT) = Unban Timed" dat_header += " - Ban Listing
[dat]" show_browser(usr, dat_header, "Unban Panel", "unbanp", "size=875x400") +/datum/admins/proc/stickypanel() + var/add_sticky = "Add Sticky Ban" + var/find_sticky = "Find Sticky Ban" + + var/data = "
Sticky Bans: [add_sticky] [find_sticky]
" + + var/list/datum/view_record/stickyban/stickies = DB_VIEW(/datum/view_record/stickyban, + DB_COMP("active", DB_EQUALS, TRUE) + ) + + for(var/datum/view_record/stickyban/current_sticky in stickies) + var/whitelist_link = "(WHITELIST)" + var/remove_sticky_link = "(REMOVE)" + var/add_to_sticky_link = "(ADD)" + + var/impacted_ckey_link = "CKEYs" + var/impacted_ip_link = "IPs" + var/impacted_cid_link = "CIDs" + + data += "" + + data += "
[whitelist_link][remove_sticky_link][add_to_sticky_link]Identifier: [current_sticky.identifier]Reason: [current_sticky.reason]Message: [current_sticky.message] Admin: [current_sticky.admin] View: [impacted_ckey_link][impacted_ip_link][impacted_cid_link]
" + + show_browser(owner, data, "Stickyban Panel", "sticky", "size=875x400") + //////////////////////////////////// DEBUG //////////////////////////////////// /proc/CreateBans() @@ -251,3 +280,48 @@ GLOBAL_DATUM(Banlist, /savefile) if(P.is_time_banned && alert(usr, "Ban already exists. Proceed?", "Confirmation", "Yes", "No") != "Yes") return P.add_timed_ban(reason, mins) + +/client/proc/cmd_admin_do_stickyban(identifier, reason, message, list/impacted_ckeys, list/impacted_cids, list/impacted_ips) + if(!identifier) + identifier = tgui_input_text(src, "Name of the primary CKEY you are adding a stickyban to.", "BuildABan") + if(!identifier) + return + + if(!message) + message = tgui_input_text(src, "What message should be given to the impacted users?", "BuildABan", encode = FALSE) + if(!message) + return + + if(!reason) + reason = tgui_input_text(src, "What's the reason for the ban? This is shown internally, and not displayed in public notes and ban messages. Include as much detail as necessary.", "BuildABan", multiline = TRUE, encode = FALSE) + if(!reason) + return + + if(!length(impacted_ckeys)) + impacted_ckeys = splittext(tgui_input_text(src, "Which CKEYs should be impacted by this ban? Include the primary ckey, separated by semicolons.", "BuildABan", "player1;player2;player3"), ";") + + if(!length(impacted_cids)) + impacted_cids = splittext(tgui_input_text(src, "Which CIDs should be impacted by this ban? Separate with semicolons.", "BuildABan", "12345678;87654321"), ";") + + if(!length(impacted_ips)) + impacted_ips = splittext(tgui_input_text(src, "Which IPs should be impacted by this ban? Separate with semicolons.", "BuildABan", "1.1.1.1;8.8.8.8"), ";") + + var/datum/entity/stickyban/new_sticky = SSstickyban.add_stickyban(identifier, reason, message, player_data) + + if(!new_sticky) + to_chat(src, SPAN_ADMIN("Failed to apply stickyban.")) + return + + for(var/ckey in impacted_ckeys) + SSstickyban.add_matched_ckey(new_sticky.id, ckey) + + for(var/cid in impacted_cids) + SSstickyban.add_matched_cid(new_sticky.id, cid) + + for(var/ip in impacted_ips) + SSstickyban.add_matched_ip(new_sticky.id, ip) + + log_admin("STICKYBAN: Identifier: [identifier] Reason: [reason] Message: [message] CKEYs: [english_list(impacted_ckeys)] IPs: [english_list(impacted_ips)] CIDs: [english_list(impacted_cids)]") + message_admins("[key_name_admin(src)] has added a new stickyban with the identifier '[identifier]'.") + var/datum/tgs_chat_embed/field/reason_embed = new("Stickyban Reason", reason) + important_message_external("[src] has added a new stickyban with the identifier '[identifier]'.", "Stickyban Placed", list(reason_embed)) diff --git a/code/modules/admin/ToRban.dm b/code/modules/admin/ToRban.dm deleted file mode 100644 index 549353facfb8..000000000000 --- a/code/modules/admin/ToRban.dm +++ /dev/null @@ -1,88 +0,0 @@ -//By Carnwennan -//fetches an external list and processes it into a list of ip addresses. -//It then stores the processed list into a savefile for later use -#define TORFILE "data/ToR_ban.bdb" -#define TOR_UPDATE_INTERVAL 216000 //~6 hours - -/proc/ToRban_isbanned(ip_address) - var/savefile/F = new(TORFILE) - if(F) - if( ip_address in F.dir ) - return 1 - return 0 - -/proc/ToRban_autoupdate() - var/savefile/F = new(TORFILE) - if(F) - var/last_update - F["last_update"] >> last_update - if((last_update + TOR_UPDATE_INTERVAL) < world.realtime) //we haven't updated for a while - ToRban_update() - return - -/proc/ToRban_update() - spawn(0) - log_misc("Downloading updated ToR data...") - var/http[] = world.Export("https://check.torproject.org/exit-addresses") - - var/list/rawlist = file2list(http["CONTENT"]) - if(rawlist.len) - fdel(TORFILE) - var/savefile/F = new(TORFILE) - for( var/line in rawlist ) - if(!line) continue - if( copytext(line,1,12) == "ExitAddress" ) - var/cleaned = copytext(line,13,length(line)-19) - if(!cleaned) continue - F[cleaned] << 1 - F["last_update"] << world.realtime - log_misc("ToR data updated!") - if(usr) to_chat(usr, "ToRban updated.") - return - log_misc("ToR data update aborted: no data.") - return - -/client/proc/ToRban(task in list("update","toggle","show","remove","remove all","find")) - set name = "ToR Ban Settings" - set category = "Server" - if(!admin_holder) return - switch(task) - if("update") - ToRban_update() - if("toggle") - if(config) - if(CONFIG_GET(flag/ToRban)) - CONFIG_SET(flag/ToRban, FALSE) - message_admins("ToR banning disabled.") - else - CONFIG_SET(flag/ToRban, TRUE) - message_admins("ToR banning enabled.") - if("show") - var/savefile/F = new(TORFILE) - var/dat - if( length(F.dir) ) - for( var/i=1, i<=length(F.dir), i++ ) - dat += "#[i] [F.dir[i]]" - dat = "[dat]
" - else - dat = "No addresses in list." - src << browse(dat,"window=ToRban_show") - if("remove") - var/savefile/F = new(TORFILE) - var/choice = tgui_input_list(src,"Please select an IP address to remove from the ToR banlist:","Remove ToR ban", F.dir) - if(choice) - F.dir.Remove(choice) - to_chat(src, "Address removed") - if("remove all") - to_chat(src, "[TORFILE] was [fdel(TORFILE)?"":"not "]removed.") - if("find") - var/input = input(src,"Please input an IP address to search for:","Find ToR ban",null) as null|text - if(input) - if(ToRban_isbanned(input)) - to_chat(src, "Address is a known ToR address") - else - to_chat(src, "Address is not a known ToR address") - return - -#undef TORFILE -#undef TOR_UPDATE_INTERVAL diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 56002d139599..f3eae1447ba0 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -96,8 +96,9 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list( )) GLOBAL_LIST_INIT(admin_verbs_ban, list( - /client/proc/unban_panel - // /client/proc/jobbans // Disabled temporarily due to 15-30 second lag spikes. Don't forget the comma in the line above when uncommenting this! + /client/proc/unban_panel, + /client/proc/stickyban_panel, + // /client/proc/jobbans // Disabled temporarily due to 15-30 second lag spikes. )) GLOBAL_LIST_INIT(admin_verbs_sounds, list( @@ -242,7 +243,6 @@ GLOBAL_LIST_INIT(admin_verbs_possess, list( )) GLOBAL_LIST_INIT(admin_verbs_permissions, list( - /client/proc/ToRban, /client/proc/whitelist_panel, )) diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 82f25a6ebe3e..3a3ce661e231 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -10,11 +10,6 @@ GLOBAL_PROTECT(href_token) var/rights = 0 var/fakekey = null - var/admincaster_screen = 0 //See newscaster.dm under machinery for a full description - var/datum/feed_message/admincaster_feed_message = new /datum/feed_message //These two will act as admin_holders. - var/datum/feed_channel/admincaster_feed_channel = new /datum/feed_channel - var/admincaster_signature //What you'll sign the newsfeeds as - var/href_token var/datum/marked_datum @@ -31,7 +26,6 @@ GLOBAL_PROTECT(href_token) error("Admin datum created without a ckey argument. Datum has been deleted") qdel(src) return - admincaster_signature = "Weyland-Yutani Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" rank = initial_rank rights = initial_rights href_token = GenerateToken() diff --git a/code/modules/admin/player_panel/actions/punish.dm b/code/modules/admin/player_panel/actions/punish.dm index 576de30ae7ff..eb80632d8afe 100644 --- a/code/modules/admin/player_panel/actions/punish.dm +++ b/code/modules/admin/player_panel/actions/punish.dm @@ -45,6 +45,58 @@ return TRUE +/datum/player_action/permanent_ban + action_tag = "permanent_ban" + name = "Permanent Ban" + permissions_required = R_BAN + +/datum/player_action/permanent_ban/act(client/user, mob/target, list/params) + var/reason = tgui_input_text(user, "What message should be given to the permabanned user?", "Permanent Ban", encode = FALSE) + if(!reason) + return + + var/internal_reason = tgui_input_text(user, "What's the reason for the ban? This is shown internally, and not displayed in public notes and ban messages. Include as much detail as necessary.", "Permanent Ban", multiline = TRUE, encode = FALSE) + if(!internal_reason) + return + + var/datum/entity/player/target_entity = target.client?.player_data + if(!target_entity) + target_entity = get_player_from_key(target.ckey || target.persistent_ckey) + + if(!target_entity) + return + + if(!target_entity.add_perma_ban(reason, internal_reason, user.player_data)) + to_chat(user, SPAN_ADMIN("The user is already permabanned! If necessary, you can remove the permaban, and place a new one.")) + +/datum/player_action/sticky_ban + action_tag = "sticky_ban" + name = "Sticky Ban" + permissions_required = R_BAN + +/datum/player_action/sticky_ban/act(client/user, mob/target, list/params) + var/datum/entity/player/player = get_player_from_key(target.ckey || target.persistent_ckey) + if(!player) + return + + var/persistent_ip = target.client?.address || player.last_known_ip + var/persistent_cid = target.client?.computer_id || player.last_known_cid + + var/message = tgui_input_text(user, "What message should be given to the impacted users?", "BuildABan", encode = FALSE) + if(!message) + return + + var/reason = tgui_input_text(user, "What's the reason for the ban? This is shown internally, and not displayed in public notes and ban messages. Include as much detail as necessary.", "BuildABan", multiline = TRUE, encode = FALSE) + if(!reason) + return + + user.cmd_admin_do_stickyban(target.ckey, reason, message, impacted_ckeys = list(target.ckey), impacted_cids = list(persistent_cid), impacted_ips = list(persistent_ip)) + player.add_note("Stickybanned | [message]", FALSE, NOTE_ADMIN, TRUE) + player.add_note("Internal reason: [reason]", TRUE, NOTE_ADMIN) + + if(target.client) + qdel(target.client) + /datum/player_action/mute action_tag = "mob_mute" name = "Mute" diff --git a/code/modules/admin/stickyban.dm b/code/modules/admin/stickyban.dm deleted file mode 100644 index 69793a599596..000000000000 --- a/code/modules/admin/stickyban.dm +++ /dev/null @@ -1,66 +0,0 @@ -// BLOCKING PROC, RUN ASYNC - -/proc/stickyban_internal(ckey, address, cid, reason, linked_stickyban, datum/entity/player/banning_admin) - - if(!ckey || !address || !cid) - CRASH("Incorrect data passed to stickyban_internal ([ckey], [address], [cid])") - - var/datum/entity/player/P = get_player_from_key(ckey) - - if(!P) - message_admins("Tried stickybanning ckey \"[ckey]\", player entity was unable to be found. Please try again later.") - return - - var/datum/entity/player_sticky_ban/PSB = DB_ENTITY(/datum/entity/player_sticky_ban) - PSB.player_id = P.id - if(reason) - PSB.reason = reason - PSB.address = address - PSB.computer_id = cid - PSB.ckey = P.ckey - PSB.date = "[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]" - - if(linked_stickyban) - PSB.linked_stickyban = linked_stickyban - - if(!reason) - reason = "No reason given." - - if(banning_admin) - PSB.admin_id = banning_admin.id - if(banning_admin.owning_client) - message_admins("[banning_admin.owning_client.ckey] has stickybanned [ckey].") - - message_admins("[ckey] (IP: [address], CID: [cid]) has been stickybanned for: \"[reason]\".") - - if(P.owning_client) - to_chat_forced(P.owning_client, SPAN_WARNING("You have been sticky banned by [banning_admin? banning_admin.ckey : "Host"].\nReason: [sanitize(reason)].")) - to_chat_forced(P.owning_client, SPAN_WARNING("This is a permanent ban")) - QDEL_NULL(P.owning_client) - - PSB.save() - -/datum/entity/player/proc/process_stickyban(address, computer_id, source_id, reason, datum/entity/player/banning_admin, list/PSB) - if(length(PSB) > 0) // sticky ban with identical data already exists, no need for another copy - if(banning_admin) - to_chat(banning_admin, SPAN_WARNING("Failed to add stickyban to [ckey]. Reason: Stickyban already exists.")) - return - - stickyban_internal(ckey, address, computer_id, reason, source_id, banning_admin) - - -/datum/entity/player/proc/check_for_sticky_ban(address, computer_id) - var/list/datum/view_record/stickyban_list_view/SBLW = DB_VIEW(/datum/view_record/stickyban_list_view, - DB_OR( - DB_COMP("ckey", DB_EQUALS, ckey), - DB_COMP("address", DB_EQUALS, address), - DB_COMP("computer_id", DB_EQUALS, computer_id) - )) - - if(length(SBLW) == 0) - return - - if(stickyban_whitelisted) - return - - return SBLW diff --git a/code/modules/admin/tabs/admin_tab.dm b/code/modules/admin/tabs/admin_tab.dm index 8dce41ac8235..356762b5edd7 100644 --- a/code/modules/admin/tabs/admin_tab.dm +++ b/code/modules/admin/tabs/admin_tab.dm @@ -53,6 +53,12 @@ admin_holder.unbanpanel() return +/client/proc/stickyban_panel() + set name = "Stickyban Panel" + set category = "Admin.Panels" + + admin_holder?.stickypanel() + /client/proc/player_panel_new() set name = "Player Panel" set category = "Admin.Panels" diff --git a/code/modules/admin/topic/topic.dm b/code/modules/admin/topic/topic.dm index 825f7c3cdbf4..ecef2627ed3c 100644 --- a/code/modules/admin/topic/topic.dm +++ b/code/modules/admin/topic/topic.dm @@ -229,6 +229,200 @@ alert(usr, "This ban has already been lifted / does not exist.", "Error", "Ok") unbanpanel() + else if(href_list["unban_perma"]) + var/datum/entity/player/unban_player = get_player_from_key(href_list["unban_perma"]) + if(!(tgui_alert(owner, "Do you want to unban [unban_player.ckey]? They are currently permabanned for: [unban_player.permaban_reason], since [unban_player.permaban_date].", "Unban Player", list("Yes", "No")) == "Yes")) + return + + if(!unban_player.is_permabanned) + to_chat(owner, "The player is not currently permabanned.") + + unban_player.is_permabanned = FALSE + unban_player.permaban_admin_id = null + unban_player.permaban_date = null + unban_player.permaban_reason = null + + unban_player.save() + + message_admins("[key_name_admin(owner)] has removed the permanent ban on [unban_player.ckey].") + important_message_external("[owner] has removed the permanent ban on [unban_player.ckey].", "Permaban Removed") + + else if(href_list["sticky"]) + if(href_list["view_all_ckeys"]) + var/list/datum/view_record/stickyban_matched_ckey/all_ckeys = DB_VIEW(/datum/view_record/stickyban_matched_ckey, + DB_COMP("linked_stickyban", DB_EQUALS, href_list["sticky"]) + ) + + var/list/keys = list() + var/list/whitelisted = list() + for(var/datum/view_record/stickyban_matched_ckey/match as anything in all_ckeys) + if(match.whitelisted) + whitelisted += match.ckey + else + keys += match.ckey + + show_browser(owner, "Impacted: [english_list(keys)]

Whitelisted: [english_list(whitelisted)]", "Stickyban Keys", "stickykeys") + return + + if(href_list["view_all_cids"]) + var/list/datum/view_record/stickyban_matched_cid/all_cids = DB_VIEW(/datum/view_record/stickyban_matched_cid, + DB_COMP("linked_stickyban", DB_EQUALS, href_list["sticky"]) + ) + + var/list/cids = list() + for(var/datum/view_record/stickyban_matched_cid/match as anything in all_cids) + cids += match.cid + + show_browser(owner, english_list(cids), "Stickyban CIDs", "stickycids") + return + + if(href_list["view_all_ips"]) + var/list/datum/view_record/stickyban_matched_ip/all_ips = DB_VIEW(/datum/view_record/stickyban_matched_ip, + DB_COMP("linked_stickyban", DB_EQUALS, href_list["sticky"]) + ) + + var/list/ips = list() + for(var/datum/view_record/stickyban_matched_ip/match as anything in all_ips) + ips += match.ip + + show_browser(owner, english_list(ips), "Stickyban IPs", "stickycips") + return + + if(href_list["find_sticky"]) + var/ckey = ckey(tgui_input_text(owner, "Which CKEY should we attempt to find stickybans for?", "FindABan")) + if(!ckey) + return + + var/list/datum/view_record/stickyban/stickies = SSstickyban.check_for_sticky_ban(ckey) + if(!stickies) + to_chat(owner, SPAN_ADMIN("Could not locate any stickbans impacting [ckey].")) + return + + var/list/impacting_stickies = list() + + for(var/datum/view_record/stickyban/sticky as anything in stickies) + impacting_stickies += sticky.identifier + + to_chat(owner, SPAN_ADMIN("Found the following stickybans for [ckey]: [english_list(impacting_stickies)]")) + + if(!check_rights_for(owner, R_BAN)) + return + + if(href_list["new_sticky"]) + owner.cmd_admin_do_stickyban() + return + + var/datum/entity/stickyban/sticky = DB_ENTITY(/datum/entity/stickyban, href_list["sticky"]) + if(!sticky) + return + + sticky.sync() + + if(href_list["whitelist_ckey"]) + var/ckey_to_whitelist = ckey(tgui_input_text(owner, "What CKEY should be whitelisted? Editing stickyban: [sticky.identifier]")) + if(!ckey_to_whitelist) + return + + SSstickyban.whitelist_ckey(sticky.id, ckey_to_whitelist) + message_admins("[key_name_admin(owner)] has whitelisted [ckey_to_whitelist] against stickyban '[sticky.identifier]'.") + important_message_external("[owner] has whitelisted [ckey_to_whitelist] against stickyban '[sticky.identifier]'.", "CKEY Whitelisted") + + if(href_list["add"]) + var/option = tgui_input_list(owner, "What do you want to add?", "AddABan", list("CID", "CKEY", "IP")) + if(!option) + return + + var/to_add = tgui_input_text(owner, "Provide the [option] to add to the stickyban.", "AddABan") + if(!to_add) + return + + switch(option) + if("CID") + SSstickyban.add_matched_cid(sticky.id, to_add) + if("CKEY") + SSstickyban.add_matched_ckey(sticky.id, to_add) + if("IP") + SSstickyban.add_matched_ip(sticky.id, to_add) + + message_admins("[key_name_admin(owner)] has added a [option] ([to_add]) to stickyban '[sticky.identifier]'.") + important_message_external("[owner] has added a [option] ([to_add]) to stickyban '[sticky.identifier]'.", "[option] Added to Stickyban") + + if(href_list["remove"]) + var/option = tgui_input_list(owner, "What do you want to remove?", "DelABan", list("Entire Stickyban", "CID", "CKEY", "IP")) + switch(option) + if("Entire Stickyban") + if(!(tgui_alert(owner, "Are you sure you want to remove this stickyban? Identifier: [sticky.identifier] Reason: [sticky.reason]", "Confirm", list("Yes", "No")) == "Yes")) + return + + sticky.active = FALSE + sticky.save() + + message_admins("[key_name_admin(owner)] has deactivated stickyban '[sticky.identifier]'.") + important_message_external("[owner] has deactivated stickyban '[sticky.identifier]'.", "Stickyban Deactivated") + + if("CID") + var/list/datum/view_record/stickyban_matched_cid/all_cids = DB_VIEW(/datum/view_record/stickyban_matched_cid, + DB_COMP("linked_stickyban", DB_EQUALS, sticky.id) + ) + + var/list/cid_to_record_id = list() + for(var/datum/view_record/stickyban_matched_cid/match in all_cids) + cid_to_record_id["[match.cid]"] = match.id + + var/picked = tgui_input_list(owner, "Which CID to remove?", "DelABan", cid_to_record_id) + if(!picked) + return + + var/selected = cid_to_record_id[picked] + + var/datum/entity/stickyban_matched_cid/sticky_cid = DB_ENTITY(/datum/entity/stickyban_matched_cid, selected) + sticky_cid.delete() + + message_admins("[key_name_admin(owner)] has removed a CID ([picked]) from stickyban '[sticky.identifier]'.") + important_message_external("[owner] has removed a CID ([picked]) from stickyban '[sticky.identifier]'.", "CID Removed from Stickyban") + + if("CKEY") + var/list/datum/view_record/stickyban_matched_ckey/all_ckeys = DB_VIEW(/datum/view_record/stickyban_matched_ckey, + DB_COMP("linked_stickyban", DB_EQUALS, sticky.id) + ) + + var/list/ckey_to_record_id = list() + for(var/datum/view_record/stickyban_matched_ckey/match in all_ckeys) + ckey_to_record_id["[match.ckey]"] = match.id + + var/picked = tgui_input_list(owner, "Which CKEY to remove?", "DelABan", ckey_to_record_id) + if(!picked) + return + + var/selected = ckey_to_record_id[picked] + + var/datum/entity/stickyban_matched_ckey/sticky_ckey = DB_ENTITY(/datum/entity/stickyban_matched_ckey, selected) + sticky_ckey.delete() + + message_admins("[key_name_admin(owner)] has removed a CKEY ([picked]) from stickyban '[sticky.identifier]'.") + important_message_external("[owner] has removed a CKEY ([picked]) from stickyban '[sticky.identifier]'.", "CKEY Removed from Stickyban") + + if("IP") + var/list/datum/view_record/stickyban_matched_ip/all_ips = DB_VIEW(/datum/view_record/stickyban_matched_ip, + DB_COMP("linked_stickyban", DB_EQUALS, sticky.id) + ) + + var/list/ip_to_record_id = list() + for(var/datum/view_record/stickyban_matched_ip/match in all_ips) + ip_to_record_id["[match.ip]"] = match.id + + var/picked = tgui_input_list(owner, "Which IP to remove?", "DelABan", ip_to_record_id) + if(!picked) + return + + var/selected = ip_to_record_id[picked] + + var/datum/entity/stickyban_matched_ip/sticky_ip = DB_ENTITY(/datum/entity/stickyban_matched_ip, selected) + sticky_ip.delete() + + message_admins("[key_name_admin(owner)] has removed an IP ([picked]) from stickyban [sticky.identifier].") + important_message_external("[owner] has removed an IP ([picked]) from stickyban '[sticky.identifier].", "IP Removed from Stickyban") + else if(href_list["warn"]) usr.client.warn(href_list["warn"]) diff --git a/code/modules/admin/verbs/autoreplace.dm b/code/modules/admin/verbs/autoreplace.dm index b2fe04cfb4a3..a896c751f5ed 100644 --- a/code/modules/admin/verbs/autoreplace.dm +++ b/code/modules/admin/verbs/autoreplace.dm @@ -32,7 +32,7 @@ GLOBAL_LIST_INIT_TYPED(admin_runtime_decorators, /datum/decorator/manual/admin_r GLOB.admin_runtime_decorators.Add(SSdecorator.add_decorator(/datum/decorator/manual/admin_runtime, types, subtypes, field, value)) - log_and_message_admins("[src] activated new decorator id: [GLOB.admin_runtime_decorators.len] set for [hint_text] `[types]` for field `[field]` set value `[value]`") + message_admins("[src] activated new decorator id: [GLOB.admin_runtime_decorators.len] set for [hint_text] `[types]` for field `[field]` set value `[value]`") /client/proc/deactivate_autoreplacer() set category = "Admin.Events" @@ -49,7 +49,7 @@ GLOBAL_LIST_INIT_TYPED(admin_runtime_decorators, /datum/decorator/manual/admin_r GLOB.admin_runtime_decorators[num_value].enabled = FALSE - log_and_message_admins("[src] deactivated decorator id: [num_value]") + message_admins("[src] deactivated decorator id: [num_value]") /client/proc/rerun_decorators() set category = "Admin.Events" @@ -65,4 +65,4 @@ GLOBAL_LIST_INIT_TYPED(admin_runtime_decorators, /datum/decorator/manual/admin_r SSdecorator.force_update() - log_and_message_admins("[src] rerun all decorators.") + message_admins("[src] rerun all decorators.") diff --git a/code/modules/clans/client.dm b/code/modules/clans/client.dm index c4948b2a6923..3450b7553c51 100644 --- a/code/modules/clans/client.dm +++ b/code/modules/clans/client.dm @@ -234,7 +234,7 @@ return - log_and_message_admins("[key_name_admin(src)] has set the name of [target_clan.name] to [input].") + message_admins("[key_name_admin(src)] has set the name of [target_clan.name] to [input].") to_chat(src, SPAN_NOTICE("Set the name of [target_clan.name] to [input].")) target_clan.name = trim(input) @@ -247,7 +247,7 @@ if(!input || input == target_clan.description) return - log_and_message_admins("[key_name_admin(src)] has set the description of [target_clan.name].") + message_admins("[key_name_admin(src)] has set the description of [target_clan.name].") to_chat(src, SPAN_NOTICE("Set the description of [target_clan.name].")) target_clan.description = trim(input) @@ -261,7 +261,7 @@ return target_clan.color = color - log_and_message_admins("[key_name_admin(src)] has set the color of [target_clan.name] to [color].") + message_admins("[key_name_admin(src)] has set the color of [target_clan.name] to [color].") to_chat(src, SPAN_NOTICE("Set the name of [target_clan.name] to [color].")) if(CLAN_ACTION_CLAN_SETHONOR) if(!has_clan_permission(CLAN_PERMISSION_ADMIN_MANAGER)) @@ -272,7 +272,7 @@ if((!input && input != 0) || input == target_clan.honor) return - log_and_message_admins("[key_name_admin(src)] has set the honor of clan [target_clan.name] from [target_clan.honor] to [input].") + message_admins("[key_name_admin(src)] has set the honor of clan [target_clan.name] from [target_clan.honor] to [input].") to_chat(src, SPAN_NOTICE("Set the honor of [target_clan.name] from [target_clan.honor] to [input].")) target_clan.honor = input @@ -286,7 +286,7 @@ to_chat(src, "You have decided not to delete [target_clan.name].") return - log_and_message_admins("[key_name_admin(src)] has deleted the clan [target_clan.name].") + message_admins("[key_name_admin(src)] has deleted the clan [target_clan.name].") to_chat(src, SPAN_NOTICE("You have deleted [target_clan.name].")) var/list/datum/view_record/clan_playerbase_view/CPV = DB_VIEW(/datum/view_record/clan_playerbase_view, DB_COMP("clan_id", DB_EQUALS, target_clan.id)) @@ -339,7 +339,7 @@ return var/target_clan = target.clan_id - log_and_message_admins("[key_name_admin(src)] has purged [player_name]'s clan profile.") + message_admins("[key_name_admin(src)] has purged [player_name]'s clan profile.") to_chat(src, SPAN_NOTICE("You have purged [player_name]'s clan profile.")) target.delete() @@ -379,20 +379,20 @@ target.clan_id = null target.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_YOUNG] to_chat(src, SPAN_NOTICE("Removed [player_name] from their clan.")) - log_and_message_admins("[key_name_admin(src)] has removed [player_name] from their current clan.") + message_admins("[key_name_admin(src)] has removed [player_name] from their current clan.") else if(input == "Remove from Ancient") target.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_YOUNG] target.permissions = GLOB.clan_ranks[CLAN_RANK_YOUNG].permissions to_chat(src, SPAN_NOTICE("Removed [player_name] from ancient.")) - log_and_message_admins("[key_name_admin(src)] has removed [player_name] from ancient.") + message_admins("[key_name_admin(src)] has removed [player_name] from ancient.") else if(input == "Make Ancient" && is_clan_manager) target.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_ADMIN] target.permissions = CLAN_PERMISSION_ADMIN_ANCIENT to_chat(src, SPAN_NOTICE("Made [player_name] an ancient.")) - log_and_message_admins("[key_name_admin(src)] has made [player_name] an ancient.") + message_admins("[key_name_admin(src)] has made [player_name] an ancient.") else to_chat(src, SPAN_NOTICE("Moved [player_name] to [input].")) - log_and_message_admins("[key_name_admin(src)] has moved [player_name] to clan [input].") + message_admins("[key_name_admin(src)] has moved [player_name] to clan [input].") target.clan_id = clans[input] @@ -455,7 +455,7 @@ target.clan_rank = GLOB.clan_ranks_ordered[chosen_rank.name] target.permissions = chosen_rank.permissions - log_and_message_admins("[key_name_admin(src)] has set the rank of [player_name] to [chosen_rank.name] for their clan.") + message_admins("[key_name_admin(src)] has set the rank of [player_name] to [chosen_rank.name] for their clan.") to_chat(src, SPAN_NOTICE("Set [player_name]'s rank to [chosen_rank.name]")) target.save() diff --git a/code/modules/client/tgui_macro.dm b/code/modules/client/tgui_macro.dm index f245f1d657d4..5159a8b7414c 100644 --- a/code/modules/client/tgui_macro.dm +++ b/code/modules/client/tgui_macro.dm @@ -101,6 +101,7 @@ GLOBAL_LIST_EMPTY(ui_data_keybindings) prefs.save_preferences() INVOKE_ASYNC(owner, /client/proc/set_macros) return TRUE + if("clear_keybind") var/list/kbinds = prefs.key_bindings var/kb_name = params["keybinding"] @@ -111,13 +112,15 @@ GLOBAL_LIST_EMPTY(ui_data_keybindings) for(var/key in keys) if(kbinds[key]) kbinds[key] -= kb_name - kbinds["Unbound"] += kb_name if(!length(kbinds[key])) kbinds -= key + // Add the keybind name to the 'unbound' list if it's not already in there. + kbinds["Unbound"] |= kb_name prefs.save_preferences() INVOKE_ASYNC(owner, /client/proc/set_macros) return TRUE + if("clear_all_keybinds") var/choice = tgui_alert(owner, "Would you prefer 'hotkey' or 'classic' defaults?", "Setup keybindings", list("Hotkey", "Classic", "Cancel")) if(choice == "Cancel") diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index b5775652d8f6..2e551f65d674 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -380,7 +380,7 @@ GLOBAL_LIST_INIT(allowed_helmet_items, list( var/obj/item/storage/internal/headgear/pockets var/storage_slots = 2 // keep in mind, one slot is reserved for garb items - var/storage_slots_reserved_for_garb = 1 + var/storage_slots_reserved_for_garb = 2 var/storage_max_w_class = SIZE_TINY // can hold tiny items only, EXCEPT for glasses & metal flask. var/storage_max_storage_space = 4 diff --git a/code/modules/clothing/suits/marine_armor.dm b/code/modules/clothing/suits/marine_armor.dm index 1be37df150a8..bc29b0a33570 100644 --- a/code/modules/clothing/suits/marine_armor.dm +++ b/code/modules/clothing/suits/marine_armor.dm @@ -281,8 +281,122 @@ /obj/item/clothing/suit/storage/marine/medium/rto/intel name = "\improper XM4 pattern intelligence officer armor" - uniform_restricted = list(/obj/item/clothing/under/marine/officer, /obj/item/clothing/under/rank/qm_suit, /obj/item/clothing/under/marine/officer/intel) + uniform_restricted = list(/obj/item/clothing/under/marine/officer/intel) specialty = "XM4 pattern intel" + desc = "Tougher than steel, quieter than whispers, the XM4 Intel Armor provides capable protection combined with an experimental integrated motion tracker. It took an R&D team a weekend to develop and costs more than the Chinook Station... probably. When worn, uniform accessories such as webbing cannot be attached due to the motion sensors occupying the clips." + desc_lore = "ARMAT Perfection. The XM4 Soldier Awareness System mixes M4-style hard armor and a distributed series of motion sensors clipped onto the breastplate. When connected to any HUD optic, it replicates the effects of an M314 Motion Detector unit, increasing user situational awareness. It is currently undergoing field trials by intelligence operatives." + storage_slots = 5 + /// XM4 Integral Motion Detector Ability + actions_types = list(/datum/action/item_action/toggle, /datum/action/item_action/intel/toggle_motion_detector) + var/motion_detector = FALSE + var/obj/item/device/motiondetector/xm4/proximity + var/long_range_cooldown = 2 + var/recycletime = 120 + +/obj/item/clothing/suit/storage/marine/medium/rto/intel/Initialize(mapload, ...) + . = ..() + proximity = new(src) + update_icon() + +/datum/action/item_action/intel/action_activate() + if(!ishuman(owner)) + return + +/datum/action/item_action/intel/update_button_icon() + return + +/datum/action/item_action/intel/toggle_motion_detector/New(Target, obj/item/holder) + . = ..() + name = "Toggle Motion Detector" + action_icon_state = "motion_detector" + button.name = name + button.overlays.Cut() + button.overlays += image('icons/mob/hud/actions.dmi', button, action_icon_state) + +/datum/action/item_action/intel/toggle_motion_detector/action_activate() + . = ..() + var/obj/item/clothing/suit/storage/marine/medium/rto/intel/recon = holder_item + recon.toggle_motion_detector(owner) + +/datum/action/item_action/intel/toggle_motion_detector/proc/update_icon() + if(!holder_item) + return + var/obj/item/clothing/suit/storage/marine/medium/rto/intel/recon = holder_item + if(recon.motion_detector) + button.icon_state = "template_on" + else + button.icon_state = "template" + +/obj/item/clothing/suit/storage/marine/medium/rto/intel/process() + if(!motion_detector) + STOP_PROCESSING(SSobj, src) + if(motion_detector) + recycletime-- + if(!recycletime) + recycletime = initial(recycletime) + proximity.refresh_blip_pool() + long_range_cooldown-- + if(long_range_cooldown) + return + long_range_cooldown = initial(long_range_cooldown) + proximity.scan() + +/obj/item/clothing/suit/storage/marine/medium/rto/intel/proc/toggle_motion_detector(mob/user) + to_chat(user,SPAN_NOTICE("You [motion_detector? "disable" : "enable"] \the [src]'s motion detector.")) + if(!motion_detector) + playsound(loc,'sound/items/detector_turn_on.ogg', 25, 1) + else + playsound(loc,'sound/items/detector_turn_off.ogg', 25, 1) + motion_detector = !motion_detector + var/datum/action/item_action/intel/toggle_motion_detector/TMD = locate(/datum/action/item_action/intel/toggle_motion_detector) in actions + TMD.update_icon() + motion_detector() + +/obj/item/clothing/suit/storage/marine/medium/rto/intel/proc/motion_detector() + if(motion_detector) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + +/obj/item/clothing/suit/storage/marine/medium/rto/intel/mob_can_equip(mob/living/carbon/human/user, slot, disable_warning) //Thanks to Drathek for the help on this part! + if(!..()) + return FALSE + + // Only equip if uniform doesn't already have a utility accessory slot equipped + var/obj/item/clothing/under/uniform = user.w_uniform + var/accessory = locate(/obj/item/clothing/accessory/storage) in uniform.accessories + if(accessory) + to_chat(user, SPAN_WARNING("[src] can't be worn with [accessory].")) + return FALSE + // Only equip if user has expert intel skill level + if(!skillcheck(user, SKILL_INTEL, SKILL_INTEL_EXPERT)) + to_chat(user, SPAN_WARNING("You don't seem to know how to use [src]...")) + return FALSE + return TRUE + +/obj/item/clothing/suit/storage/marine/medium/rto/intel/equipped(mob/user, slot, silent) //When XM4 is equipped this removes ACCESSORY_SLOT_UTILITY as a valid accessory for the uniform + . = ..() + if(slot == WEAR_JACKET) + var/mob/living/carbon/human/human = user + var/obj/item/clothing/under/uniform = human.w_uniform + if(uniform?.valid_accessory_slots) + uniform?.valid_accessory_slots -= ACCESSORY_SLOT_UTILITY + +/obj/item/clothing/suit/storage/marine/medium/rto/intel/unequipped(mob/user, slot) //When unequipped this adds the ACCESSORY_SLOT_UTILITY back as a valid accessory + . = ..() + if(slot == WEAR_JACKET) + var/mob/living/carbon/human/human = user + var/obj/item/clothing/under/uniform = human.w_uniform + if(uniform) + // Figure out if the uniform originally allowed ACCESSORY_SLOT_UTILITY + var/obj/item/clothing/under/temp_uniform = new uniform.type + if(temp_uniform.valid_accessory_slots) + for(var/allowed in temp_uniform.valid_accessory_slots) + if(allowed == ACCESSORY_SLOT_UTILITY) + // It was allowed previously, now add it back + uniform.valid_accessory_slots += ACCESSORY_SLOT_UTILITY + break + qdel(temp_uniform) /obj/item/clothing/suit/storage/marine/MP name = "\improper M2 pattern MP armor" diff --git a/code/modules/cm_aliens/structures/fruit.dm b/code/modules/cm_aliens/structures/fruit.dm index 384da5da39a7..02fdb2da415b 100644 --- a/code/modules/cm_aliens/structures/fruit.dm +++ b/code/modules/cm_aliens/structures/fruit.dm @@ -93,22 +93,6 @@ qdel(src) . = ..() -/obj/effect/alien/resin/fruit/proc/delete_fruit() - //Notify and update the xeno count - if(!QDELETED(bound_xeno)) - if(!picked) - to_chat(bound_xeno, SPAN_XENOHIGHDANGER("We sense one of our fruit has been destroyed.")) - bound_xeno.current_fruits.Remove(src) - var/datum/action/xeno_action/onclick/plant_resin_fruit/prf = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/plant_resin_fruit) - prf.update_button_icon() - - if(picked) // No need to update the number, since the fruit still exists (just as a different item) - return - var/number_of_fruit = length(bound_xeno.current_fruits) - prf.button.set_maptext(SMALL_FONTS_COLOR(7, number_of_fruit, "#e69d00"), 19, 2) - prf.update_button_icon() - bound_xeno = null - /obj/effect/alien/resin/fruit/proc/reduce_timer(maturity_increase) if (mature || timer_id == TIMER_ID_NULL) return @@ -193,7 +177,19 @@ return FALSE /obj/effect/alien/resin/fruit/Destroy() - delete_fruit() + //Notify and update the xeno count + if(!QDELETED(bound_xeno)) + if(!picked) + to_chat(bound_xeno, SPAN_XENOHIGHDANGER("We sense one of our fruit has been destroyed.")) + bound_xeno.current_fruits.Remove(src) + + var/number_of_fruit = length(bound_xeno.current_fruits) + var/datum/action/xeno_action/onclick/plant_resin_fruit/plant_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/plant_resin_fruit) + plant_action.button.set_maptext(SMALL_FONTS_COLOR(7, number_of_fruit, "#e69d00"), 19, 2) + plant_action.update_button_icon() + + bound_xeno = null + return ..() //Greater @@ -281,9 +277,9 @@ ..() START_PROCESSING(SSobj, src) -/obj/effect/alien/resin/fruit/spore/delete_fruit() +/obj/effect/alien/resin/fruit/spore/Destroy() STOP_PROCESSING(SSobj, src) - ..() + return ..() /obj/effect/alien/resin/fruit/spore/process() if(mature) diff --git a/code/modules/cm_marines/dropship_equipment.dm b/code/modules/cm_marines/dropship_equipment.dm index 785283541eb8..b162fb7c6a87 100644 --- a/code/modules/cm_marines/dropship_equipment.dm +++ b/code/modules/cm_marines/dropship_equipment.dm @@ -1144,9 +1144,11 @@ var/list/possible_fultons = get_targets() - var/obj/item/stack/fulton/fult = possible_fultons[fulton_choice] if(!fulton_choice) return + // Strip any \proper or \improper in order to match the entry in possible_fultons. + fulton_choice = strip_improper(fulton_choice) + var/obj/item/stack/fulton/fult = possible_fultons[fulton_choice] if(!ship_base) //system was uninstalled midway return diff --git a/code/modules/dropships/attach_points/attach_point.dm b/code/modules/dropships/attach_points/attach_point.dm index 7230e67e0af1..6fef4d58e785 100644 --- a/code/modules/dropships/attach_points/attach_point.dm +++ b/code/modules/dropships/attach_points/attach_point.dm @@ -27,44 +27,45 @@ /obj/effect/attach_point/attackby(obj/item/I, mob/user) if(istype(I, /obj/item/powerloader_clamp)) - var/obj/item/powerloader_clamp/PC = I - install_equipment(PC, user) + var/obj/item/powerloader_clamp/clamp = I + install_equipment(clamp, user) return TRUE return ..() /// Called when a real user with a powerloader attempts to install an equipment on the attach point -/obj/effect/attach_point/proc/install_equipment(obj/item/powerloader_clamp/PC, mob/living/user) - if(!istype(PC.loaded, /obj/structure/dropship_equipment)) +/obj/effect/attach_point/proc/install_equipment(obj/item/powerloader_clamp/clamp, mob/living/user) + if(!istype(clamp.loaded, /obj/structure/dropship_equipment)) + return + var/obj/structure/dropship_equipment/ds_equipment = clamp.loaded + if(!(base_category in ds_equipment.equip_categories)) + to_chat(user, SPAN_WARNING("[ds_equipment] doesn't fit on [src].")) return - var/obj/structure/dropship_equipment/SE = PC.loaded - if(!(base_category in SE.equip_categories)) - to_chat(user, SPAN_WARNING("[SE] doesn't fit on [src].")) - return TRUE if(installed_equipment) - return TRUE - playsound(loc, 'sound/machines/hydraulics_1.ogg', 40, 1) + return + playsound(loc, 'sound/machines/hydraulics_1.ogg', 40, TRUE) var/point_loc = loc if(!user || !do_after(user, (7 SECONDS) * user.get_skill_duration_multiplier(SKILL_ENGINEER), INTERRUPT_NO_NEEDHAND|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) - return TRUE + return if(loc != point_loc)//dropship flew away - return TRUE - if(installed_equipment || PC.loaded != SE) - return TRUE - to_chat(user, SPAN_NOTICE("You install [SE] on [src].")) - SE.forceMove(loc) - PC.loaded = null - playsound(loc, 'sound/machines/hydraulics_2.ogg', 40, 1) - PC.update_icon() - installed_equipment = SE - SE.ship_base = src + return + if(installed_equipment || clamp.loaded != ds_equipment) + return + to_chat(user, SPAN_NOTICE("You install [ds_equipment] on [src].")) + ds_equipment.forceMove(loc) + clamp.loaded = null + playsound(loc, 'sound/machines/hydraulics_2.ogg', 40, TRUE) + clamp.update_icon() + installed_equipment = ds_equipment + ds_equipment.ship_base = src + ds_equipment.plane = plane for(var/obj/docking_port/mobile/marine_dropship/shuttle in SSshuttle.mobile) if(shuttle.id == ship_tag) - SE.linked_shuttle = shuttle - SEND_SIGNAL(shuttle, COMSIG_DROPSHIP_ADD_EQUIPMENT, SE) + ds_equipment.linked_shuttle = shuttle + SEND_SIGNAL(shuttle, COMSIG_DROPSHIP_ADD_EQUIPMENT, ds_equipment) break - SE.update_equipment() + ds_equipment.update_equipment() /// Weapon specific attachment point /obj/effect/attach_point/weapon diff --git a/code/modules/economy/economy_misc.dm b/code/modules/economy/economy_misc.dm index a5f061e1a727..6e22fd12d58a 100644 --- a/code/modules/economy/economy_misc.dm +++ b/code/modules/economy/economy_misc.dm @@ -75,27 +75,6 @@ GLOBAL_VAR_INIT(economy_init, FALSE) if(GLOB.economy_init) return 2 - var/datum/feed_channel/newChannel = new /datum/feed_channel - newChannel.channel_name = "Public Station Announcements" - newChannel.author = "Automated Announcement Listing" - newChannel.locked = 1 - newChannel.is_admin_channel = 1 - GLOB.news_network.network_channels += newChannel - - newChannel = new /datum/feed_channel - newChannel.channel_name = "Nyx Daily" - newChannel.author = "CentComm Minister of Information" - newChannel.locked = 1 - newChannel.is_admin_channel = 1 - GLOB.news_network.network_channels += newChannel - - newChannel = new /datum/feed_channel - newChannel.channel_name = "The Gibson Gazette" - newChannel.author = "Editor Mike Hammers" - newChannel.locked = 1 - newChannel.is_admin_channel = 1 - GLOB.news_network.network_channels += newChannel - for(var/loc_type in typesof(/datum/trade_destination) - /datum/trade_destination) var/datum/trade_destination/D = new loc_type GLOB.weighted_randomevent_locations[D] = D.viable_random_events.len diff --git a/code/modules/gear_presets/uscm.dm b/code/modules/gear_presets/uscm.dm index 8f5243edf84d..5402a5ed7305 100644 --- a/code/modules/gear_presets/uscm.dm +++ b/code/modules/gear_presets/uscm.dm @@ -251,8 +251,6 @@ /datum/equipment_preset/uscm/intel/full/load_gear(mob/living/carbon/human/new_human) var/obj/item/clothing/under/marine/officer/intel/U = new(new_human) - var/obj/item/clothing/accessory/storage/webbing/W = new() - U.attach_accessory(new_human, W) new_human.equip_to_slot_or_del(U, WEAR_BODY) new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/almayer/intel(new_human), WEAR_L_EAR) new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/knife(new_human), WEAR_FEET) diff --git a/code/modules/gear_presets/wy.dm b/code/modules/gear_presets/wy.dm index ac8aefb6238d..d492f9573b57 100644 --- a/code/modules/gear_presets/wy.dm +++ b/code/modules/gear_presets/wy.dm @@ -20,7 +20,7 @@ /datum/equipment_preset/wy/load_gear(mob/living/carbon/human/new_human) new_human.equip_to_slot_or_del(new headset_type(new_human), WEAR_L_EAR) new_human.equip_to_slot_or_del(new /obj/item/clothing/under/liaison_suit/ivy(new_human), WEAR_BODY) - new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/centcom(new_human), WEAR_FEET) + new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/laceup(new_human), WEAR_FEET) new_human.equip_to_slot_or_del(new /obj/item/storage/backpack/satchel(new_human), WEAR_BACK) . = ..() @@ -63,6 +63,20 @@ rank = JOB_EXECUTIVE_SPECIALIST paygrade = PAY_SHORT_WYC5 +/datum/equipment_preset/wy/exec_spec/lawyer + name = "Corporate - E - Lawyer" + +/datum/equipment_preset/wy/exec_spec/lawyer/load_gear(mob/living/carbon/human/new_human) + new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/WY(new_human), WEAR_L_EAR) + new_human.equip_to_slot_or_del(new /obj/item/clothing/under/liaison_suit/blue(new_human), WEAR_BODY) + new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/jacket/marine/corporate/blue(new_human), WEAR_JACKET) + new_human.equip_to_slot_or_del(new /obj/item/tool/pen/clicky(new_human), WEAR_IN_BACK) + new_human.equip_to_slot_or_del(new /obj/item/clipboard(new_human), WEAR_IN_BACK) + new_human.equip_to_slot_or_del(new /obj/item/notepad/blue(new_human), WEAR_R_STORE) + new_human.equip_to_slot_or_del(new /obj/item/device/taperecorder(new_human), WEAR_L_STORE) + + ..() + /datum/equipment_preset/wy/exec_supervisor name = "Corporate - F - Executive Supervisor" flags = EQUIPMENT_PRESET_EXTRA @@ -70,6 +84,21 @@ rank = JOB_EXECUTIVE_SUPERVISOR paygrade = PAY_SHORT_WYC6 +/datum/equipment_preset/wy/exec_supervisor/lawyer + name = "Corporate - F - Lawyer" + +/datum/equipment_preset/wy/exec_supervisor/lawyer/load_gear(mob/living/carbon/human/new_human) + new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/WY(new_human), WEAR_L_EAR) + new_human.equip_to_slot_or_del(new /obj/item/storage/secure/briefcase(new_human), WEAR_R_HAND) + new_human.equip_to_slot_or_del(new /obj/item/clothing/under/liaison_suit/black(new_human), WEAR_BODY) + new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/jacket/marine/corporate/black(new_human), WEAR_JACKET) + new_human.equip_to_slot_or_del(new /obj/item/tool/pen/clicky(new_human), WEAR_IN_BACK) + new_human.equip_to_slot_or_del(new /obj/item/clipboard(new_human), WEAR_IN_BACK) + new_human.equip_to_slot_or_del(new /obj/item/spacecash/c1000/counterfeit(new_human), WEAR_IN_BACK) + new_human.equip_to_slot_or_del(new /obj/item/notepad/black(new_human), WEAR_R_STORE) + new_human.equip_to_slot_or_del(new /obj/item/device/taperecorder(new_human), WEAR_L_STORE) + ..() + /datum/equipment_preset/wy/manager skills = /datum/skills/civilian/manager idtype = /obj/item/card/id/silver/clearance_badge/manager diff --git a/code/modules/gear_presets/wy_goons.dm b/code/modules/gear_presets/wy_goons.dm index 54b436c20854..a8f3a443311e 100644 --- a/code/modules/gear_presets/wy_goons.dm +++ b/code/modules/gear_presets/wy_goons.dm @@ -135,6 +135,7 @@ /datum/equipment_preset/goon/researcher/load_gear(mob/living/carbon/human/new_human) new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/WY, WEAR_L_EAR) + new_human.equip_to_slot_or_del(new /obj/item/clothing/glasses/science, WEAR_EYES) new_human.equip_to_slot_or_del(new /obj/item/clothing/under/liaison_suit/corporate_formal, WEAR_BODY) new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/labcoat, WEAR_JACKET) new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/corporate, WEAR_FEET) diff --git a/code/modules/logging/global_logs.dm b/code/modules/logging/global_logs.dm index ff384fe537c0..e0055907d67a 100644 --- a/code/modules/logging/global_logs.dm +++ b/code/modules/logging/global_logs.dm @@ -25,8 +25,8 @@ GLOBAL_PROTECT(world_runtime_log) GLOBAL_VAR(scheduler_stats) GLOBAL_PROTECT(scheduler_stats) -GLOBAL_VAR(mutator_logs) -GLOBAL_PROTECT(mutator_logs) +GLOBAL_VAR(strain_logs) +GLOBAL_PROTECT(strain_logs) GLOBAL_VAR(round_stats) GLOBAL_PROTECT(round_stats) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index cb16480fcfaa..ae86518a640f 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -316,7 +316,10 @@ hud_used.show_hud(hud_used.hud_version) /mob/dead/observer/Login() - ..() + ..() // This calls signals which might have resulted in our client getting deleted + + if(!client) + return if(client.check_whitelist_status(WHITELIST_PREDATOR)) RegisterSignal(SSdcs, COMSIG_GLOB_PREDATOR_ROUND_TOGGLED, PROC_REF(toggle_predator_action)) diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm index f1b600179450..fa7c7ec3176e 100644 --- a/code/modules/mob/emote.dm +++ b/code/modules/mob/emote.dm @@ -14,13 +14,15 @@ to_chat(src, SPAN_NOTICE("'[act]' emote does not exist. Say *help for a list.")) return FALSE var/silenced = FALSE - for(var/datum/emote/P in key_emotes) - if(!P.check_cooldown(src, intentional)) + for(var/datum/emote/current_emote in key_emotes) + if(!current_emote.check_cooldown(src, intentional)) silenced = TRUE continue - if(P.run_emote(src, param, m_type, intentional)) - SEND_SIGNAL(src, COMSIG_MOB_EMOTE, P, act, m_type, message, intentional) - SEND_SIGNAL(src, COMSIG_MOB_EMOTED(P.key)) + if(SEND_SIGNAL(src, COMSIG_MOB_TRY_EMOTE, current_emote, act, m_type, param, intentional) & COMPONENT_OVERRIDE_EMOTE) + silenced = TRUE + continue + if(current_emote.run_emote(src, param, m_type, intentional)) + SEND_SIGNAL(src, COMSIG_MOB_EMOTE, current_emote, act, m_type, message, intentional) return TRUE if(intentional && !silenced && !force_silence) to_chat(src, SPAN_NOTICE("Unusable emote '[act]'. Say *help for a list.")) diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 08ddd11da5b3..c38ddbcd552c 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -247,6 +247,13 @@ var/datum/action/item_action/smartgun/toggle_motion_detector/TMD = locate(/datum/action/item_action/smartgun/toggle_motion_detector) in sg.actions TMD.update_icon() sg.motion_detector() + if(istype(i, /obj/item/clothing/suit/storage/marine/medium/rto/intel)) + var/obj/item/clothing/suit/storage/marine/medium/rto/intel/xm4 = i + if(xm4.motion_detector) + xm4.motion_detector = FALSE + var/datum/action/item_action/intel/toggle_motion_detector/TMD = locate(/datum/action/item_action/intel/toggle_motion_detector) in xm4.actions + TMD.update_icon() + xm4.motion_detector() /mob/living/carbon/human/proc/disable_headsets() //Disable all radios to reduce radio spam for dead people diff --git a/code/modules/mob/living/carbon/xenomorph/Evolution.dm b/code/modules/mob/living/carbon/xenomorph/Evolution.dm index d6f963747e33..b6576b764b51 100644 --- a/code/modules/mob/living/carbon/xenomorph/Evolution.dm +++ b/code/modules/mob/living/carbon/xenomorph/Evolution.dm @@ -211,7 +211,10 @@ return FALSE if(lock_evolve) - to_chat(src, SPAN_WARNING("You are banished and cannot reach the hivemind.")) + if(banished) + to_chat(src, SPAN_WARNING("We are banished and cannot reach the hivemind.")) + else + to_chat(src, SPAN_WARNING("We can't evolve.")) return FALSE if(jobban_isbanned(src, JOB_XENOMORPH))//~who so genius to do this is? @@ -269,7 +272,10 @@ return if(lock_evolve) - to_chat(src, SPAN_WARNING("We are banished and cannot reach the hivemind.")) + if(banished) + to_chat(src, SPAN_WARNING("We are banished and cannot reach the hivemind.")) + else + to_chat(src, SPAN_WARNING("We can't deevolve.")) return FALSE diff --git a/code/modules/mob/living/carbon/xenomorph/XenoMutatorSets.dm b/code/modules/mob/living/carbon/xenomorph/XenoMutatorSets.dm deleted file mode 100644 index fc5532955ff9..000000000000 --- a/code/modules/mob/living/carbon/xenomorph/XenoMutatorSets.dm +++ /dev/null @@ -1,264 +0,0 @@ -#define MUTATOR_GAIN_PER_QUEEN_LEVEL 6 -#define MUTATOR_GAIN_PER_XENO_LEVEL 3 - -//A class that holds mutators for a given Xeno hive -//Each time a Queen matures, the hive gets more points -//Each time a Queen dies, the mutators are reset - -//The class contains a lot of variables that are applied to various xenos' stats and actions -/datum/mutator_set - var/remaining_points = 1 //How many points the xeno / hive still has to spend on mutators - var/list/purchased_mutators = list() //List of purchased mutators - var/user_level = 0 //Level of the Queen for Hive or the individual xeno. Starting at -1 so at tier 0 you'd get some mutators to play with - - var/tackle_strength_bonus = 0 - -//Functions to be overloaded to call for when something gets updated on the xenos -/datum/mutator_set/proc/recalculate_everything(description) -/datum/mutator_set/proc/recalculate_stats(description) -/datum/mutator_set/proc/recalculate_actions(description) -/datum/mutator_set/proc/recalculate_pheromones(description) -/datum/mutator_set/proc/give_feedback(description) - - -/datum/mutator_set/proc/purchase_mutator(name) - return FALSE - -/datum/mutator_set/proc/list_and_purchase_mutators() - var/list/mutators_for_purchase = available_mutators() - var/mob/living/carbon/xenomorph/Xeno = usr - if(mutators_for_purchase.len == 0) - to_chat(usr, "There are no available strains.") - var/pick = tgui_input_list(usr, "Which strain would you like to purchase?", "Purchase strain", mutators_for_purchase, theme="hive_status") - if(!pick) - return FALSE - if(alert(usr, "[GLOB.xeno_mutator_list[pick].description]\n\nConfirm mutation?", "Strain purchase", "Yes", "No") != "Yes") return - if(!Xeno.strain_checks()) - return - if(GLOB.xeno_mutator_list[pick].apply_mutator(src)) - to_chat(usr, "Mutation complete!") - return TRUE - else - to_chat(usr, "Mutation failed!") - return FALSE - -/datum/mutator_set/proc/can_purchase_mutator(mutator_name) - var/datum/xeno_mutator/XM = GLOB.xeno_mutator_list[mutator_name] - if(user_level < XM.required_level) - return FALSE //xeno doesn't meet the level requirements - if(remaining_points < XM.cost) - return FALSE //mutator is too expensive - if(XM.unique) - if(XM.name in purchased_mutators) - return FALSE //unique mutator already purchased - if(XM.keystone) - for(var/name in purchased_mutators) - if(GLOB.xeno_mutator_list[name].keystone) - return FALSE //We already have a keystone mutator - if(XM.flaw) - for(var/name in purchased_mutators) - if(GLOB.xeno_mutator_list[name].flaw) - return FALSE //We already have a flaw mutator - return TRUE - -//Lists mutators available for purchase -/datum/mutator_set/proc/available_mutators() - var/list/can_purchase = list() - - for(var/str in GLOB.xeno_mutator_list) - if (can_purchase_mutator(str)) - can_purchase += str //can purchase! - - return can_purchase - -//Mutators applying to the Hive as a whole -/datum/mutator_set/hive_mutators - var/datum/hive_status/hive //Which hive do these mutators apply to. Need this to affect variables there - var/leader_count_boost = 0 - var/maturation_multiplier = 1 - var/tier_slot_multiplier = 1 - var/larva_gestation_multiplier = 1 - var/bonus_larva_spawn_chance = 0 - -/datum/mutator_set/hive_mutators/list_and_purchase_mutators() - if(!hive || !hive.living_xeno_queen) - return //somehow Queen is not set but this function was called... - if(hive.living_xeno_queen.is_dead()) - return //Dead xenos can't mutate! - if(hive.living_xeno_queen.hardcore) - to_chat(usr, SPAN_WARNING("No time for that, must KILL!")) - return - if(!hive.living_xeno_queen.ovipositor) - to_chat(usr, "You must be in Ovipositor to purchase Hive Mutators.") - return - . = ..() - if (. == TRUE && purchased_mutators.len) - var/m = purchased_mutators[purchased_mutators.len] - log_mutator("[hive.living_xeno_queen.name] purchased Hive Mutator '[m]'") - -/datum/mutator_set/hive_mutators/can_purchase_mutator(mutator_name) - if (..() == FALSE) - return FALSE //Can't buy it regardless - var/datum/xeno_mutator/XM = GLOB.xeno_mutator_list[mutator_name] - if(XM.individual_only) - return FALSE //We can't buy individual mutators on a Hive level - return TRUE - -//Called when the Queen dies -// This isn't currently used, but if anyone wants to, expect it to be broken because -// I haven't made any effort to integrate it into the new system (Fourkhan, 5/11/19) -/datum/mutator_set/hive_mutators/proc/reset_mutators() - if(purchased_mutators.len == 0) - //No mutators purchased, nothing to reset! - return - - var/depowered = FALSE - for(var/name in purchased_mutators) - if(!GLOB.xeno_mutator_list[name].death_persistent) - purchased_mutators -= name - depowered = TRUE - - if(!depowered) - return //We haven't lost anything - - tackle_strength_bonus = 0 - - leader_count_boost = 0 - maturation_multiplier = 1 - tier_slot_multiplier = 1 - larva_gestation_multiplier = 1 - bonus_larva_spawn_chance = 0 - - for(var/mob/living/carbon/xenomorph/X in GLOB.living_xeno_list) - if(X.hivenumber == hive.hivenumber) - X.recalculate_everything() - to_chat(X, SPAN_XENOANNOUNCE("Queen's influence wanes. You feel weak!")) - playsound(X.loc, "alien_help", 25) - X.xeno_jitter(15) - -/datum/mutator_set/hive_mutators/recalculate_everything(description) - for(var/mob/living/carbon/xenomorph/X in GLOB.living_xeno_list) - if(X.hivenumber == hive.hivenumber) - X.recalculate_everything() - to_chat(X, SPAN_XENOANNOUNCE("Queen has granted the Hive a boon! [description]")) - X.xeno_jitter(15) -/datum/mutator_set/hive_mutators/recalculate_stats(description) - for(var/mob/living/carbon/xenomorph/X in GLOB.living_xeno_list) - if(X.hivenumber == hive.hivenumber) - X.recalculate_stats() - to_chat(X, SPAN_XENOANNOUNCE("Queen has granted the Hive a boon! [description]")) - X.xeno_jitter(15) -/datum/mutator_set/hive_mutators/recalculate_actions(description) - for(var/mob/living/carbon/xenomorph/X in GLOB.living_xeno_list) - if(X.hivenumber == hive.hivenumber) - X.recalculate_actions() - to_chat(X, SPAN_XENOANNOUNCE("Queen has granted the Hive a boon! [description]")) - X.xeno_jitter(15) -/datum/mutator_set/hive_mutators/recalculate_pheromones(description) - for(var/mob/living/carbon/xenomorph/X in GLOB.living_xeno_list) - if(X.hivenumber == hive.hivenumber) - X.recalculate_pheromones() - to_chat(X, SPAN_XENOANNOUNCE("Queen has granted the Hive a boon! [description]")) - X.xeno_jitter(15) -/datum/mutator_set/hive_mutators/proc/recalculate_maturation(description) - for(var/mob/living/carbon/xenomorph/X in GLOB.living_xeno_list) - if(X.hivenumber == hive.hivenumber) - X.recalculate_maturation() - to_chat(X, SPAN_XENOANNOUNCE("Queen has granted the Hive a boon! [description]")) - X.xeno_jitter(15) -/datum/mutator_set/hive_mutators/proc/recalculate_hive(description) - hive.recalculate_hive() - give_feedback(description) -/datum/mutator_set/hive_mutators/give_feedback(description) - for(var/mob/living/carbon/xenomorph/X in GLOB.living_xeno_list) - if(X.hivenumber == hive.hivenumber) - to_chat(X, SPAN_XENOANNOUNCE("Queen has granted the Hive a boon! [description]")) - X.xeno_jitter(15) - -//Mutators applying to an individual xeno -/datum/mutator_set/individual_mutators - var/mob/living/carbon/xenomorph/xeno - var/pull_multiplier = 1 - var/egg_laying_multiplier = 1 - var/need_weeds = TRUE - //Strains Below - remaining_points = 6 - - -/datum/mutator_set/individual_mutators/Destroy() - if(xeno) - xeno.mutators = null - xeno = null - . = ..() - -/datum/mutator_set/individual_mutators/list_and_purchase_mutators() - . = ..() - if (. == TRUE && purchased_mutators.len) - var/m = purchased_mutators[purchased_mutators.len] - log_mutator("[xeno.name] purchased Mutator '[m]'") - -/datum/mutator_set/individual_mutators/can_purchase_mutator(mutator_name) - if (..() == FALSE) - return FALSE //Can't buy it regardless - var/datum/xeno_mutator/XM = GLOB.xeno_mutator_list[mutator_name] - if(XM.hive_only) - return FALSE //We can't buy Hive mutators on an individual level - if(XM.caste_whitelist && (XM.caste_whitelist.len > 0) && !(xeno.caste_type in XM.caste_whitelist)) - return FALSE //We are not on the whitelist - return TRUE - -/datum/mutator_set/individual_mutators/recalculate_actions(description, flavor_description = null) - xeno.recalculate_actions() - to_chat(xeno, SPAN_XENOANNOUNCE("[description]")) - if (flavor_description != null) - to_chat(xeno, SPAN_XENOLEADER("[flavor_description]")) - xeno.xeno_jitter(15) - - -/mob/living/carbon/xenomorph/queen/verb/purchase_hive_mutators() - set name = "Purchase Hive Mutators" - set desc = "Purchase Mutators affecting the entire Hive." - set category = "Alien" - if(hardcore) - to_chat(usr, SPAN_WARNING("No time for that, must KILL!")) - return - if(!src.hive || !src.hive.mutators) - return //For some reason we don't have mutators - src.hive.mutators.list_and_purchase_mutators() - -/mob/living/carbon/xenomorph/verb/purchase_strains() - set name = "Purchase Strains" - set desc = "Purchase Strains for yourself." - set category = "Alien" - - if(!strain_checks()) - return - if(!src.mutators) - return //For some reason we don't have mutators - src.mutators.list_and_purchase_mutators() - -/mob/living/carbon/xenomorph/proc/strain_checks() - if(!check_state(TRUE)) - return FALSE - - if(is_ventcrawling) - to_chat(src, SPAN_WARNING("This place is too constraining to take a strain.")) - return FALSE - - if(!isturf(loc)) - to_chat(src, SPAN_WARNING("We can't take a strain here.")) - return FALSE - - if(handcuffed || legcuffed) - to_chat(src, SPAN_WARNING("The restraints are too restricting to allow us to take a strain.")) - return FALSE - - if(health < maxHealth) - to_chat(src, SPAN_WARNING("We must be at full health to take a strain.")) - return FALSE - - if(agility || fortify || crest_defense || stealth) - to_chat(src, SPAN_WARNING("We cannot take a strain while in this stance.")) - return FALSE - - return TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm index fc25c80e795f..c8d3df91c975 100644 --- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm +++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm @@ -130,10 +130,8 @@ var/weed_level = WEED_LEVEL_STANDARD var/acid_level = 0 - // Mutator-related and other important vars - var/mutation_icon_state = null - var/mutation_type = null - var/datum/mutator_set/individual_mutators/mutators = new + /// The xeno's strain, if they've taken one. + var/datum/xeno_strain/strain = null // Hive-related vars var/datum/hive_status/hive @@ -389,8 +387,6 @@ for(var/trait in hive.hive_inherant_traits) ADD_TRAIT(src, trait, TRAIT_SOURCE_HIVE) - mutators.xeno = src - //Set caste stuff if(caste_type && GLOB.xeno_datum_list[caste_type]) caste = GLOB.xeno_datum_list[caste_type] @@ -661,8 +657,8 @@ . += "It appears to belong to [hive?.name ? "the [hive.name]" : "a different hive"]." if(isxeno(user) || isobserver(user)) - if(mutation_type != "Normal") - . += "It has specialized into a [mutation_type]." + if(strain) + . += "It has specialized into a [strain.name]." if(iff_tag) . += SPAN_NOTICE("It has an IFF tag sticking out of its carapace.") @@ -693,7 +689,7 @@ selected_ability = null queued_action = null - QDEL_NULL(mutators) + QDEL_NULL(strain) QDEL_NULL(behavior_delegate) built_structures = null @@ -709,18 +705,11 @@ if(hardcore) attack_log?.Cut() // Completely clear out attack_log to limit mem usage if we fail to delete - . = ..() - - // Everything below fits the "we have to clear by principle it but i dont wanna break stuff" bill - mutators = null - - + return ..() /mob/living/carbon/xenomorph/slip(slip_source_name, stun_level, weaken_level, run_only, override_noslip, slide_steps) return FALSE - - /mob/living/carbon/xenomorph/start_pulling(atom/movable/AM, lunge, no_msg) if(SEND_SIGNAL(AM, COMSIG_MOVABLE_XENO_START_PULLING, src) & COMPONENT_ALLOW_PULL) return do_pull(AM, lunge, no_msg) @@ -823,10 +812,10 @@ //*********************************************************// -//********************Mutator functions********************// +// ******************** Strain Procs **********************// //*********************************************************// -//Call this function when major changes happen - evolutions, upgrades, mutators getting removed +//Call this proc when major changes happen - evolutions, upgrades, mutators getting removed /mob/living/carbon/xenomorph/proc/recalculate_everything() recalculate_stats() recalculate_actions() @@ -850,8 +839,6 @@ tackle_min = caste.tackle_min tackle_max = caste.tackle_max tackle_chance = caste.tackle_chance + tackle_chance_modifier - tacklestrength_min = caste.tacklestrength_min + mutators.tackle_strength_bonus + hive.mutators.tackle_strength_bonus - tacklestrength_max = caste.tacklestrength_max + mutators.tackle_strength_bonus + hive.mutators.tackle_strength_bonus /mob/living/carbon/xenomorph/proc/recalculate_health() var/new_max_health = nocrit ? health_modifier + maxHealth : health_modifier + caste.max_health @@ -907,18 +894,8 @@ /mob/living/carbon/xenomorph/proc/recalculate_actions() recalculate_acid() recalculate_weeds() - pull_multiplier = mutators.pull_multiplier - if(isrunner(src)) - //Xeno runners need a small nerf to dragging speed mutator - pull_multiplier = 1 - (1 - mutators.pull_multiplier) * 0.85 - if(is_zoomed) - zoom_out() - if(iscarrier(src)) - var/mob/living/carbon/xenomorph/carrier/carrier = src - carrier.huggers_max = caste.huggers_max - carrier.eggs_max = caste.eggs_max - need_weeds = mutators.need_weeds - + // Modified on subtypes + pull_multiplier = initial(pull_multiplier) /mob/living/carbon/xenomorph/proc/recalculate_acid() if(caste) @@ -1043,7 +1020,7 @@ handle_ghost_message() /mob/living/carbon/xenomorph/proc/handle_ghost_message() - notify_ghosts("[src] ([mutation_type] [caste_type]) has ghosted and their body is up for grabs!", source = src) + notify_ghosts("[src] ([get_strain_name()] [caste_type]) has ghosted and their body is up for grabs!", source = src) /mob/living/carbon/xenomorph/larva/handle_ghost_message() if(locate(/obj/effect/alien/resin/special/pylon/core) in range(2, get_turf(src))) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm index 4ac166c58c69..e1af5e36a40f 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm @@ -37,7 +37,7 @@ RegisterSignal(owner, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE, PROC_REF(check_directional_armor)) var/mob/living/carbon/xenomorph/xeno_owner = owner - if(!istype(xeno_owner) || xeno_owner.mutation_type != CRUSHER_NORMAL) + if(!istype(xeno_owner)) return var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate @@ -51,7 +51,7 @@ ..() UnregisterSignal(owner, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE) var/mob/living/carbon/xenomorph/xeno_owner = owner - if(!istype(xeno_owner) || xeno_owner.mutation_type != CRUSHER_NORMAL) + if(!istype(xeno_owner)) return var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate @@ -62,7 +62,7 @@ /datum/action/xeno_action/activable/pounce/crusher_charge/proc/undo_charging_icon() var/mob/living/carbon/xenomorph/xeno_owner = owner - if(!istype(xeno_owner) || xeno_owner.mutation_type != CRUSHER_NORMAL) + if(!istype(xeno_owner)) return var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_abilities.dm index 22d5f4b57aa2..d28a4bb67978 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_abilities.dm @@ -20,6 +20,13 @@ ability_primacy = XENO_PRIMARY_ACTION_2 xeno_cooldown = 4 SECONDS + var/base_damage = 30 + var/usable_while_fortified = FALSE + +/datum/action/xeno_action/activable/headbutt/steel_crest + base_damage = 37.5 + usable_while_fortified = TRUE + /datum/action/xeno_action/onclick/tail_sweep name = "Tail Sweep" action_icon_state = "tail_sweep" @@ -41,8 +48,9 @@ /// Extra armor when fortified and facing bullets. var/frontal_armor = 5 - /// Extra armor when steelcrest, fortified, and facing bullets. - var/steelcrest_frontal_armor = 15 + +/datum/action/xeno_action/activable/fortify/steel_crest + frontal_armor = 15 /datum/action/xeno_action/activable/tail_stab/slam name = "Tail Slam" diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm index 9326ae78bf9d..98c0c19f68f7 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm @@ -40,7 +40,7 @@ // Defender Headbutt /datum/action/xeno_action/activable/headbutt/use_ability(atom/target_atom) var/mob/living/carbon/xenomorph/fendy = owner - if (!istype(fendy)) + if(!istype(fendy)) return if(!isxeno_human(target_atom) || fendy.can_not_harm(target_atom)) @@ -49,13 +49,13 @@ if(!fendy.check_state()) return - if (!action_cooldown_check()) + if(!action_cooldown_check()) return if(!check_and_use_plasma_owner()) return - if(fendy.fortify && !(fendy.mutation_type == DEFENDER_STEELCREST)) + if(fendy.fortify && !usable_while_fortified) to_chat(fendy, SPAN_XENOWARNING("We cannot use headbutt while fortified.")) return @@ -81,11 +81,10 @@ fendy.visible_message(SPAN_XENOWARNING("[fendy] rams [carbone] with its armored crest!"), \ SPAN_XENOWARNING("We ram [carbone] with our armored crest!")) - if(carbone.stat != DEAD && (!(carbone.status_flags & XENO_HOST) || !HAS_TRAIT(carbone, TRAIT_NESTED)) ) - var/h_damage = 30 - (fendy.crest_defense * 10) - if(fendy.mutation_type == DEFENDER_STEELCREST) - h_damage += 7.5 - carbone.apply_armoured_damage(get_xeno_damage_slash(carbone, h_damage), ARMOR_MELEE, BRUTE, "chest", 5) + if(carbone.stat != DEAD && (!(carbone.status_flags & XENO_HOST) || !HAS_TRAIT(carbone, TRAIT_NESTED))) + // -10 damage if their crest is down. + var/damage = base_damage - (fendy.crest_defense * 10) + carbone.apply_armoured_damage(get_xeno_damage_slash(carbone, damage), ARMOR_MELEE, BRUTE, "chest", 5) var/facing = get_dir(fendy, carbone) var/headbutt_distance = 1 + (fendy.crest_defense * 2) + (fendy.fortify * 2) @@ -157,10 +156,6 @@ if (!istype(xeno)) return - if(xeno.crest_defense && xeno.mutation_type == DEFENDER_STEELCREST) - to_chat(src, SPAN_XENOWARNING("We cannot fortify while our crest is already down!")) - return - if(xeno.crest_defense) to_chat(src, SPAN_XENOWARNING("We cannot use fortify with our crest lowered.")) return @@ -201,55 +196,59 @@ if(xeno.fortify) button.icon_state = "template_active" -/datum/action/xeno_action/activable/fortify/proc/fortify_switch(mob/living/carbon/xenomorph/X, fortify_state) - if(X.fortify == fortify_state) +/datum/action/xeno_action/activable/fortify/proc/fortify_switch(mob/living/carbon/xenomorph/xeno, fortify_state) + if(xeno.fortify == fortify_state) return if(fortify_state) - to_chat(X, SPAN_XENOWARNING("We tuck ourself into a defensive stance.")) - if(X.mutation_type == DEFENDER_STEELCREST) - X.armor_deflection_buff += 10 - X.armor_explosive_buff += 60 - X.ability_speed_modifier += 3 - X.damage_modifier -= XENO_DAMAGE_MOD_SMALL - else - X.armor_deflection_buff += 30 - X.armor_explosive_buff += 60 - ADD_TRAIT(X, TRAIT_IMMOBILIZED, TRAIT_SOURCE_ABILITY("Fortify")) - X.anchored = TRUE - X.small_explosives_stun = FALSE + to_chat(xeno, SPAN_XENOWARNING("We tuck ourself into a defensive stance.")) RegisterSignal(owner, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE, PROC_REF(check_directional_armor)) - X.mob_size = MOB_SIZE_IMMOBILE //knockback immune - X.mob_flags &= ~SQUEEZE_UNDER_VEHICLES - X.update_icons() - X.fortify = TRUE + xeno.mob_size = MOB_SIZE_IMMOBILE //knockback immune + xeno.mob_flags &= ~SQUEEZE_UNDER_VEHICLES + xeno.fortify = TRUE else - to_chat(X, SPAN_XENOWARNING("We resume our normal stance.")) - REMOVE_TRAIT(X, TRAIT_IMMOBILIZED, TRAIT_SOURCE_ABILITY("Fortify")) - X.anchored = FALSE - if(X.mutation_type == DEFENDER_STEELCREST) - X.armor_deflection_buff -= 10 - X.armor_explosive_buff -= 60 - X.ability_speed_modifier -= 3 - X.damage_modifier += XENO_DAMAGE_MOD_SMALL - else - X.armor_deflection_buff -= 30 - X.armor_explosive_buff -= 60 - X.small_explosives_stun = TRUE + to_chat(xeno, SPAN_XENOWARNING("We resume our normal stance.")) + REMOVE_TRAIT(xeno, TRAIT_IMMOBILIZED, TRAIT_SOURCE_ABILITY("Fortify")) + xeno.anchored = FALSE UnregisterSignal(owner, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE) - X.mob_size = MOB_SIZE_XENO //no longer knockback immune - X.mob_flags |= SQUEEZE_UNDER_VEHICLES - X.update_icons() - X.fortify = FALSE + xeno.mob_size = MOB_SIZE_XENO //no longer knockback immune + xeno.mob_flags |= SQUEEZE_UNDER_VEHICLES + xeno.fortify = FALSE + + apply_modifiers(xeno, fortify_state) + xeno.update_icons() + +/datum/action/xeno_action/activable/fortify/proc/apply_modifiers(mob/living/carbon/xenomorph/xeno, fortify_state) + if(fortify_state) + xeno.armor_deflection_buff += 30 + xeno.armor_explosive_buff += 60 + ADD_TRAIT(xeno, TRAIT_IMMOBILIZED, TRAIT_SOURCE_ABILITY("Fortify")) + xeno.anchored = TRUE + xeno.small_explosives_stun = FALSE + else + xeno.armor_deflection_buff -= 30 + xeno.armor_explosive_buff -= 60 + xeno.small_explosives_stun = TRUE + +// Steel crest override +/datum/action/xeno_action/activable/fortify/steel_crest/apply_modifiers(mob/living/carbon/xenomorph/xeno, fortify_state) + if(fortify_state) + xeno.armor_deflection_buff += 10 + xeno.armor_explosive_buff += 60 + xeno.ability_speed_modifier += 3 + xeno.damage_modifier -= XENO_DAMAGE_MOD_SMALL + else + xeno.armor_deflection_buff -= 10 + xeno.armor_explosive_buff -= 60 + xeno.ability_speed_modifier -= 3 + xeno.damage_modifier += XENO_DAMAGE_MOD_SMALL /datum/action/xeno_action/activable/fortify/proc/check_directional_armor(mob/living/carbon/xenomorph/defendy, list/damagedata) SIGNAL_HANDLER var/projectile_direction = damagedata["direction"] + // If the defender is facing the projectile. if(defendy.dir & REVERSE_DIR(projectile_direction)) - if(defendy.mutation_type == DEFENDER_STEELCREST) - damagedata["armor"] += steelcrest_frontal_armor - else - damagedata["armor"] += frontal_armor + damagedata["armor"] += frontal_armor /datum/action/xeno_action/activable/fortify/proc/unconscious_check() SIGNAL_HANDLER diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm index fd525701b12d..8a829d8d6bc0 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm @@ -6,46 +6,11 @@ plasma_cost = 20 // Config options - distance = 6 knockdown = FALSE knockdown_duration = 2.5 - freeze_self = TRUE freeze_time = 15 can_be_shield_blocked = TRUE -/datum/action/xeno_action/activable/pounce/lurker/additional_effects_always() - var/mob/living/carbon/xenomorph/xeno = owner - if (!istype(xeno)) - return - if (xeno.mutation_type == LURKER_NORMAL) - var/found = FALSE - for (var/mob/living/carbon/human/human in get_turf(xeno)) - if(human.stat == DEAD) - continue - found = TRUE - break - - if (found) - var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis = get_xeno_action_by_type(xeno, /datum/action/xeno_action/onclick/lurker_invisibility) - if (istype(lurker_invis)) - lurker_invis.invisibility_off() - -/datum/action/xeno_action/activable/pounce/lurker/additional_effects(mob/living/living_mob) - var/mob/living/carbon/xenomorph/xeno = owner - if (!istype(xeno)) - return - - if (xeno.mutation_type == LURKER_NORMAL) - RegisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF, PROC_REF(remove_freeze), TRUE) // Suppresses runtime ever we pounce again before slashing - -/datum/action/xeno_action/activable/pounce/lurker/proc/remove_freeze(mob/living/carbon/xenomorph/xeno) - SIGNAL_HANDLER - - var/datum/behavior_delegate/lurker_base/behaviour_del = xeno.behavior_delegate - if (istype(behaviour_del)) - UnregisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF) - end_pounce_freeze() - /datum/action/xeno_action/onclick/lurker_invisibility name = "Turn Invisible" action_icon_state = "lurker_invisibility" diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm index 24aa94727ca9..373c2701ce37 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm @@ -1,3 +1,34 @@ +/datum/action/xeno_action/activable/pounce/lurker/additional_effects_always() + var/mob/living/carbon/xenomorph/xeno = owner + if(!istype(xeno)) + return + + var/found = FALSE + for(var/mob/living/carbon/human/human in get_turf(xeno)) + if(human.stat == DEAD) + continue + found = TRUE + break + + if(found) + var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis = get_xeno_action_by_type(xeno, /datum/action/xeno_action/onclick/lurker_invisibility) + lurker_invis.invisibility_off() + +/datum/action/xeno_action/activable/pounce/lurker/additional_effects(mob/living/living_mob) + var/mob/living/carbon/xenomorph/xeno = owner + if(!istype(xeno)) + return + + RegisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF, PROC_REF(remove_freeze), TRUE) // Suppresses runtime ever we pounce again before slashing + +/datum/action/xeno_action/activable/pounce/lurker/proc/remove_freeze(mob/living/carbon/xenomorph/xeno) + SIGNAL_HANDLER + + var/datum/behavior_delegate/lurker_base/behaviour_del = xeno.behavior_delegate + if(istype(behaviour_del)) + UnregisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF) + end_pounce_freeze() + /datum/action/xeno_action/onclick/lurker_invisibility/use_ability(atom/targeted_atom) var/mob/living/carbon/xenomorph/xeno = owner @@ -16,9 +47,8 @@ xeno.speed_modifier -= speed_buff xeno.recalculate_speed() - if (xeno.mutation_type == LURKER_NORMAL) - var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate - behavior.on_invisibility() + var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate + behavior.on_invisibility() // if we go off early, this also works fine. invis_timer_id = addtimer(CALLBACK(src, PROC_REF(invisibility_off)), duration, TIMER_STOPPABLE) @@ -45,10 +75,9 @@ xeno.speed_modifier += speed_buff xeno.recalculate_speed() - if (xeno.mutation_type == LURKER_NORMAL) - var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate - if (istype(behavior)) - behavior.on_invisibility_off() + var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate + if (istype(behavior)) + behavior.on_invisibility_off() /datum/action/xeno_action/onclick/lurker_invisibility/ability_cooldown_over() to_chat(owner, SPAN_XENOHIGHDANGER("We are ready to use our invisibility again!")) @@ -66,9 +95,6 @@ if (!check_and_use_plasma_owner()) return - if (xeno.mutation_type != LURKER_NORMAL) - return - var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate if (istype(behavior)) behavior.next_slash_buffed = TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/praetorian/praetorian_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/praetorian/praetorian_powers.dm index ee19a8c9de88..647bf7936195 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/praetorian/praetorian_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/praetorian/praetorian_powers.dm @@ -70,10 +70,9 @@ playsound(current_mob, 'sound/weapons/alien_tail_attack.ogg', 30, TRUE) if (target_mobs.len >= shield_regen_threshold) - if (source_xeno.mutation_type == PRAETORIAN_VANGUARD) - var/datum/behavior_delegate/praetorian_vanguard/BD = source_xeno.behavior_delegate - if (istype(BD)) - BD.regen_shield() + var/datum/behavior_delegate/praetorian_vanguard/behavior = source_xeno.behavior_delegate + if (istype(behavior)) + behavior.regen_shield() apply_cooldown() return ..() @@ -135,10 +134,9 @@ playsound(get_turf(H), "alien_claw_flesh", 30, 1) if (target_mobs.len >= shield_regen_threshold) - if (X.mutation_type == PRAETORIAN_VANGUARD) - var/datum/behavior_delegate/praetorian_vanguard/BD = X.behavior_delegate - if (istype(BD)) - BD.regen_shield() + var/datum/behavior_delegate/praetorian_vanguard/behavior = X.behavior_delegate + if (istype(behavior)) + behavior.regen_shield() /datum/action/xeno_action/activable/cleave/use_ability(atom/target_atom) var/mob/living/carbon/xenomorph/vanguard_user = owner @@ -545,16 +543,12 @@ if (!check_and_use_plasma_owner()) return - var/buffed = FALSE apply_cooldown() - if (dancer_user.mutation_type == PRAETORIAN_DANCER) - var/found = FALSE - for (var/datum/effects/dancer_tag/dancer_tag_effect in target_carbon.effects_list) - found = TRUE - qdel(dancer_tag_effect) - break - - buffed = found + var/buffed = FALSE + for (var/datum/effects/dancer_tag/dancer_tag_effect in target_carbon.effects_list) + buffed = TRUE + qdel(dancer_tag_effect) + break if(ishuman(target_carbon)) var/mob/living/carbon/human/Hu = target_carbon @@ -601,9 +595,6 @@ if (!check_and_use_plasma_owner()) return - if (xeno.mutation_type != PRAETORIAN_DANCER) - return - var/datum/behavior_delegate/praetorian_dancer/behavior = xeno.behavior_delegate if (!istype(behavior)) return @@ -626,9 +617,6 @@ if (!istype(xeno)) return - if (xeno.mutation_type != PRAETORIAN_DANCER) - return - var/datum/behavior_delegate/praetorian_dancer/behavior = xeno.behavior_delegate if (!istype(behavior)) return @@ -806,17 +794,16 @@ var/bonus_shield = 0 - if (X.mutation_type == PRAETORIAN_WARDEN) - var/datum/behavior_delegate/praetorian_warden/BD = X.behavior_delegate - if (!istype(BD)) - return + var/datum/behavior_delegate/praetorian_warden/behavior = X.behavior_delegate + if (!istype(behavior)) + return - if (!BD.use_internal_hp_ability(shield_cost)) - return + if (!behavior.use_internal_hp_ability(shield_cost)) + return - bonus_shield = BD.internal_hitpoints*0.5 - if (!BD.use_internal_hp_ability(bonus_shield)) - bonus_shield = 0 + bonus_shield = behavior.internal_hitpoints*0.5 + if (!behavior.use_internal_hp_ability(bonus_shield)) + bonus_shield = 0 var/total_shield_amount = shield_amount + bonus_shield @@ -841,7 +828,7 @@ if (!X.Adjacent(A)) to_chat(X, SPAN_XENODANGER("We must be within touching distance of [targetXeno]!")) return - if (targetXeno.mutation_type == PRAETORIAN_WARDEN) + if(istype(targetXeno.strain, /datum/xeno_strain/warden)) to_chat(X, SPAN_XENODANGER("We cannot heal a sister of the same strain!")) return if (SEND_SIGNAL(targetXeno, COMSIG_XENO_PRE_HEAL) & COMPONENT_CANCEL_XENO_HEAL) @@ -849,18 +836,16 @@ return var/bonus_heal = 0 + var/datum/behavior_delegate/praetorian_warden/behavior = X.behavior_delegate + if (!istype(behavior)) + return - if (X.mutation_type == PRAETORIAN_WARDEN) - var/datum/behavior_delegate/praetorian_warden/BD = X.behavior_delegate - if (!istype(BD)) - return - - if (!BD.use_internal_hp_ability(heal_cost)) - return + if (!behavior.use_internal_hp_ability(heal_cost)) + return - bonus_heal = BD.internal_hitpoints*0.5 - if (!BD.use_internal_hp_ability(bonus_heal)) - bonus_heal = 0 + bonus_heal = behavior.internal_hitpoints*0.5 + if (!behavior.use_internal_hp_ability(bonus_heal)) + bonus_heal = 0 to_chat(X, SPAN_XENODANGER("We heal [targetXeno]!")) to_chat(targetXeno, SPAN_XENOHIGHDANGER("We are healed by [X]!")) @@ -868,9 +853,7 @@ targetXeno.visible_message(SPAN_BOLDNOTICE("[X] places its claws on [targetXeno], and its wounds are quickly sealed!")) //marines probably should know if a xeno gets healed X.gain_health(heal_amount*0.5 + bonus_heal*0.5) X.flick_heal_overlay(3 SECONDS, "#00B800") - if(X.mutation_type == PRAETORIAN_WARDEN) - var/datum/behavior_delegate/praetorian_warden/warden_delegate = X.behavior_delegate - warden_delegate.transferred_healing += heal_amount + behavior.transferred_healing += heal_amount use_plasma = TRUE //it's already hard enough to gauge health without hp showing on the mob targetXeno.flick_heal_overlay(3 SECONDS, "#00B800")//so the visible_message and recovery overlay will warn marines and possibly predators that the xenomorph has been healed! @@ -879,13 +862,12 @@ to_chat(X, SPAN_XENOHIGHDANGER("We cannot rejuvenate targets through overwatch!")) return - if (X.mutation_type == PRAETORIAN_WARDEN) - var/datum/behavior_delegate/praetorian_warden/BD = X.behavior_delegate - if (!istype(BD)) - return + var/datum/behavior_delegate/praetorian_warden/behavior = X.behavior_delegate + if (!istype(behavior)) + return - if (!BD.use_internal_hp_ability(debuff_cost)) - return + if (!behavior.use_internal_hp_ability(debuff_cost)) + return to_chat(X, SPAN_XENODANGER("We rejuvenate [targetXeno]!")) to_chat(targetXeno, SPAN_XENOHIGHDANGER("We are rejuvenated by [X]!")) @@ -910,8 +892,8 @@ if(!istype(X)) return - var/datum/behavior_delegate/praetorian_warden/BD = X.behavior_delegate - if(!istype(BD)) + var/datum/behavior_delegate/praetorian_warden/behavior = X.behavior_delegate + if(!istype(behavior)) return if(X.observed_xeno != null) @@ -926,16 +908,16 @@ to_chat(X, SPAN_XENODANGER("We cannot retrieve ourself!")) return - if(X.anchored) - to_chat(X, SPAN_XENODANGER("That sister cannot move!")) - return - if(!(A in view(7, X))) to_chat(X, SPAN_XENODANGER("That sister is too far away!")) return var/mob/living/carbon/xenomorph/targetXeno = A + if(targetXeno.anchored) + to_chat(X, SPAN_XENODANGER("That sister cannot move!")) + return + if(!(targetXeno.resting || targetXeno.stat == UNCONSCIOUS)) if(targetXeno.mob_size > MOB_SIZE_BIG) to_chat(X, SPAN_WARNING("[targetXeno] is too big to retrieve while standing up!")) @@ -954,7 +936,7 @@ if(!check_plasma_owner()) return - if(!BD.use_internal_hp_ability(retrieve_cost)) + if(!behavior.use_internal_hp_ability(retrieve_cost)) return if(!check_and_use_plasma_owner()) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_abilities.dm index d245449fa2cf..9ec61b4c95ca 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_abilities.dm @@ -62,6 +62,10 @@ . = ..() SSticker.OnRoundstart(CALLBACK(src, PROC_REF(apply_queen_build_boost))) +// queenos don't need weeds under them to build on ovi +/datum/action/xeno_action/activable/secrete_resin/remote/queen/can_remote_build() + return TRUE + /datum/action/xeno_action/activable/secrete_resin/remote/queen/proc/apply_queen_build_boost() var/boost_duration = 30 MINUTES // In the event secrete_resin is given after round start diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/ravager/ravager_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/ravager/ravager_powers.dm index daad0362e91e..8fe101a08dfa 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/ravager/ravager_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/ravager/ravager_powers.dm @@ -6,9 +6,6 @@ if(!xeno.check_state()) return - if(xeno.mutation_type != RAVAGER_NORMAL) - return - if(!action_cooldown_check()) return @@ -122,8 +119,6 @@ var/mob/living/carbon/human/human = living var/mob/living/carbon/xenomorph/xeno = owner - if(xeno.mutation_type != RAVAGER_NORMAL) - return var/datum/behavior_delegate/ravager_base/behavior = xeno.behavior_delegate if(behavior.empower_targets < behavior.super_empower_threshold) return @@ -146,8 +141,6 @@ // Determine whether or not we should daze here var/should_sslow = FALSE - if(ravager_user.mutation_type != RAVAGER_NORMAL) - return var/datum/behavior_delegate/ravager_base/ravager_delegate = ravager_user.behavior_delegate if(ravager_delegate.empower_targets >= ravager_delegate.super_empower_threshold) should_sslow = TRUE @@ -302,17 +295,14 @@ if (carbon.stat == DEAD) return - // All strain-specific behavior - if (xeno.mutation_type == RAVAGER_BERSERKER) - var/datum/behavior_delegate/ravager_berserker/behavior = xeno.behavior_delegate - - if (behavior.rage >= 2) - behavior.decrement_rage() - heal_amount += additional_healing_enraged - else - to_chat(xeno, SPAN_XENOWARNING("Our rejuvenation was weaker without rage!")) - debilitate = FALSE - fling_distance-- + var/datum/behavior_delegate/ravager_berserker/behavior = xeno.behavior_delegate + if (behavior.rage >= 2) + behavior.decrement_rage() + heal_amount += additional_healing_enraged + else + to_chat(xeno, SPAN_XENOWARNING("Our rejuvenation was weaker without rage!")) + debilitate = FALSE + fling_distance-- // Damage var/obj/limb/head/head = carbon.get_limb("head") @@ -361,17 +351,16 @@ var/max_lifesteal = 250 var/lifesteal_range = 1 - if (xeno.mutation_type == RAVAGER_BERSERKER) - var/datum/behavior_delegate/ravager_berserker/behavior = xeno.behavior_delegate - if (behavior.rage == 0) - to_chat(xeno, SPAN_XENODANGER("We cannot eviscerate when we have 0 rage!")) - return - damage = damage_at_rage_levels[clamp(behavior.rage, 1, behavior.max_rage)] - range = range_at_rage_levels[clamp(behavior.rage, 1, behavior.max_rage)] - windup_reduction = windup_reduction_at_rage_levels[clamp(behavior.rage, 1, behavior.max_rage)] - behavior.decrement_rage(behavior.rage) + var/datum/behavior_delegate/ravager_berserker/behavior = xeno.behavior_delegate + if (behavior.rage == 0) + to_chat(xeno, SPAN_XENODANGER("We cannot eviscerate when we have 0 rage!")) + return + damage = damage_at_rage_levels[clamp(behavior.rage, 1, behavior.max_rage)] + range = range_at_rage_levels[clamp(behavior.rage, 1, behavior.max_rage)] + windup_reduction = windup_reduction_at_rage_levels[clamp(behavior.rage, 1, behavior.max_rage)] + behavior.decrement_rage(behavior.rage) - apply_cooldown() + apply_cooldown() if (range > 1) xeno.visible_message(SPAN_XENOHIGHDANGER("[xeno] begins digging in for a massive strike!"), SPAN_XENOHIGHDANGER("We begin digging in for a massive strike!")) @@ -439,12 +428,11 @@ if (!xeno.check_state()) return - if (xeno.mutation_type == RAVAGER_HEDGEHOG) - var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate - if (!behavior.check_shards(shard_cost)) - to_chat(xeno, SPAN_DANGER("Not enough shards! We need [shard_cost - behavior.shards] more!")) - return - behavior.use_shards(shard_cost) + var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate + if (!behavior.check_shards(shard_cost)) + to_chat(xeno, SPAN_DANGER("Not enough shards! We need [shard_cost - behavior.shards] more!")) + return + behavior.use_shards(shard_cost) xeno.visible_message(SPAN_XENODANGER("[xeno] ruffles its bone-shard quills, forming a defensive shell!"), SPAN_XENODANGER("We ruffle our bone-shard quills, forming a defensive shell!")) @@ -468,10 +456,8 @@ return FALSE else if (cooldown_timer_id == TIMER_ID_NULL) var/mob/living/carbon/xenomorph/xeno = owner - if (xeno.mutation_type == RAVAGER_HEDGEHOG) - var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate - return behavior.check_shards(shard_cost) - return TRUE + var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate + return behavior.check_shards(shard_cost) return FALSE /datum/action/xeno_action/onclick/spike_shield/proc/remove_shield() @@ -502,12 +488,11 @@ if(!affected_atom || affected_atom.layer >= FLY_LAYER || !isturf(xeno.loc) || !xeno.check_state()) return - if (xeno.mutation_type == RAVAGER_HEDGEHOG) - var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate - if (!behavior.check_shards(shard_cost)) - to_chat(xeno, SPAN_DANGER("Not enough shards! We need [shard_cost - behavior.shards] more!")) - return - behavior.use_shards(shard_cost) + var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate + if (!behavior.check_shards(shard_cost)) + to_chat(xeno, SPAN_DANGER("Not enough shards! We need [shard_cost - behavior.shards] more!")) + return + behavior.use_shards(shard_cost) xeno.visible_message(SPAN_XENOWARNING("[xeno] fires their spikes at [affected_atom]!"), SPAN_XENOWARNING("We fire our spikes at [affected_atom]!")) @@ -531,11 +516,8 @@ var/mob/living/carbon/xenomorph/xeno = owner if(!istype(xeno)) return FALSE - if (xeno.mutation_type == RAVAGER_HEDGEHOG) - var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate - return behavior.check_shards(shard_cost) - - return TRUE + var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate + return behavior.check_shards(shard_cost) else return FALSE @@ -548,13 +530,12 @@ if (!xeno.check_state()) return - if (xeno.mutation_type == RAVAGER_HEDGEHOG) - var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate - if (!behavior.check_shards(shard_cost)) - to_chat(xeno, SPAN_DANGER("Not enough shards! We need [shard_cost - behavior.shards] more!")) - return - behavior.use_shards(shard_cost) - behavior.lock_shards() + var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate + if (!behavior.check_shards(shard_cost)) + to_chat(xeno, SPAN_DANGER("Not enough shards! We need [shard_cost - behavior.shards] more!")) + return + behavior.use_shards(shard_cost) + behavior.lock_shards() xeno.visible_message(SPAN_XENOWARNING("[xeno] sheds their spikes, firing them in all directions!"), SPAN_XENOWARNING("We shed our spikes, firing them in all directions!!")) xeno.spin_circle() @@ -567,10 +548,7 @@ /datum/action/xeno_action/onclick/spike_shed/action_cooldown_check() if (cooldown_timer_id == TIMER_ID_NULL) var/mob/living/carbon/xenomorph/xeno = owner - if (xeno.mutation_type == RAVAGER_HEDGEHOG) - var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate - return behavior.check_shards(shard_cost) - - return TRUE + var/datum/behavior_delegate/ravager_hedgehog/behavior = xeno.behavior_delegate + return behavior.check_shards(shard_cost) else return FALSE diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/runner/runner_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/runner/runner_powers.dm index b907a382dda1..f5e090bc7e4b 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/runner/runner_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/runner/runner_powers.dm @@ -45,7 +45,7 @@ return ..() /mob/living/carbon/xenomorph/runner/corrosive_acid(atom/affected_atom, acid_type, plasma_cost) - if (mutation_type != RUNNER_ACIDER) + if(!istype(strain, /datum/xeno_strain/acider)) return ..() if(!affected_atom.Adjacent(src)) if(istype(affected_atom,/obj/item/explosive/plastic)) @@ -159,9 +159,6 @@ if(!action_cooldown_check()) return - if(xeno.mutation_type != RUNNER_ACIDER) - return - var/datum/behavior_delegate/runner_acider/behavior_delegate = xeno.behavior_delegate if(!istype(behavior_delegate)) return diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/sentinel/sentinel_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/sentinel/sentinel_powers.dm index 1ddec771d075..1ed8863c231a 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/sentinel/sentinel_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/sentinel/sentinel_powers.dm @@ -35,9 +35,6 @@ if(!xeno.check_state()) return - if(xeno.mutation_type != SENTINEL_NORMAL) - return - if(!action_cooldown_check()) to_chat(src, SPAN_WARNING("We must wait for your spit glands to refill.")) return @@ -77,9 +74,6 @@ if (!check_and_use_plasma_owner()) return - if (xeno.mutation_type != SENTINEL_NORMAL) - return - var/datum/behavior_delegate/sentinel_base/behavior = xeno.behavior_delegate if (istype(behavior)) behavior.next_slash_buffed = TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm b/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm index 074af92d69fa..f7e906a82b28 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm @@ -13,6 +13,7 @@ evasion = XENO_EVASION_NONE speed = XENO_SPEED_TIER_3 + available_strains = list(/datum/xeno_strain/trapper) behavior_delegate_type = /datum/behavior_delegate/boiler_base evolution_allowed = FALSE @@ -50,7 +51,6 @@ tier = 3 gib_chance = 100 drag_delay = 6 //pulling a big dead xeno is hard - mutation_type = BOILER_NORMAL spit_delay = 30 SECONDS tileoffset = 3 viewsize = 7 @@ -98,4 +98,3 @@ // No special behavior for boilers /datum/behavior_delegate/boiler_base name = "Base Boiler Behavior Delegate" - diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm b/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm index 886ffcfe1ef2..bec41a87521e 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm @@ -74,8 +74,6 @@ /mob/living/carbon/xenomorph/proc/set_hugger_reserve_for_morpher, ) - mutation_type = BURROWER_NORMAL - icon_xeno = 'icons/mob/xenos/burrower.dmi' icon_xenonid = 'icons/mob/xenonids/burrower.dmi' @@ -109,11 +107,3 @@ /datum/behavior_delegate/burrower_base name = "Base Burrower Behavior Delegate" - -/datum/behavior_delegate/burrower_base/on_update_icons() - if(bound_xeno.stat == DEAD) - return - - if(HAS_TRAIT(bound_xeno, TRAIT_ABILITY_BURROWED)) - bound_xeno.icon_state = "[bound_xeno.mutation_icon_state] Burrower Burrowed" - return TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm index 2e106743514a..d290b917e945 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm @@ -14,6 +14,9 @@ evasion = XENO_EVASION_NONE speed = XENO_SPEED_TIER_4 + available_strains = list(/datum/xeno_strain/eggsac) + behavior_delegate_type = /datum/behavior_delegate/carrier_base + evolution_allowed = FALSE deevolves_to = list(XENO_CASTE_DRONE) throwspeed = SPEED_AVERAGE @@ -75,7 +78,6 @@ /mob/living/carbon/xenomorph/proc/rename_tunnel, /mob/living/carbon/xenomorph/proc/set_hugger_reserve_for_morpher, ) - mutation_type = CARRIER_NORMAL icon_xenonid = 'icons/mob/xenonids/carrier.dmi' @@ -95,18 +97,9 @@ var/eggs_max = 0 var/laid_egg = 0 -/mob/living/carbon/xenomorph/carrier/update_icons() - . = ..() - if (mutation_type == CARRIER_NORMAL) - update_hugger_overlays() - if (mutation_type == CARRIER_EGGSAC) - update_eggsac_overlays() - /mob/living/carbon/xenomorph/carrier/proc/update_hugger_overlays() if(!hugger_overlays_icon) return - if(mutation_type != CARRIER_NORMAL) - return overlays -= hugger_overlays_icon hugger_overlays_icon.overlays.Cut() @@ -146,8 +139,6 @@ /mob/living/carbon/xenomorph/carrier/proc/update_eggsac_overlays() if(!eggsac_overlays_icon) return - if(mutation_type != CARRIER_EGGSAC) - return overlays -= eggsac_overlays_icon eggsac_overlays_icon.overlays.Cut() @@ -184,9 +175,6 @@ . = ..(cause, gibbed) if(.) var/chance = 75 //75% to drop an egg or hugger. - if(mutation_type == CARRIER_EGGSAC) - visible_message(SPAN_XENOWARNING("[src] throes as its eggsac bursts into a mess of acid!")) - playsound(src.loc, 'sound/effects/alien_egg_burst.ogg', 25, 1) if(huggers_cur) //Hugger explosion, like an egg morpher @@ -207,6 +195,11 @@ if(eggs_dropped) //Checks whether or not to announce egg drop. xeno_message(SPAN_XENOANNOUNCE("[src] has dropped some precious eggs!"), 2, hive.hivenumber) +/mob/living/carbon/xenomorph/carrier/recalculate_actions() + . = ..() + huggers_max = caste.huggers_max + eggs_max = caste.eggs_max + /mob/living/carbon/xenomorph/carrier/get_status_tab_items() . = ..() if(huggers_max > 0) @@ -397,3 +390,10 @@ return GLOB.hive_datum[hivenumber].spawn_as_hugger(user, src) huggers_cur-- + +/datum/behavior_delegate/carrier_base + name = "Base Carrier Behavior Delegate" + +/datum/behavior_delegate/carrier_base/on_update_icons() + var/mob/living/carbon/xenomorph/carrier/bound_carrier = bound_xeno + bound_carrier.update_hugger_overlays() diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm index d2c0b0e40e59..38b1e5816ffe 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm @@ -14,6 +14,7 @@ speed = XENO_SPEED_TIER_2 heal_standing = 0.66 + available_strains = list(/datum/xeno_strain/charger) behavior_delegate_type = /datum/behavior_delegate/crusher_base minimum_evolve_time = 15 MINUTES @@ -63,8 +64,6 @@ ) claw_type = CLAW_TYPE_VERY_SHARP - mutation_icon_state = CRUSHER_NORMAL - mutation_type = CRUSHER_NORMAL icon_xeno = 'icons/mob/xenos/crusher.dmi' icon_xenonid = 'icons/mob/xenonids/crusher.dmi' @@ -281,5 +280,5 @@ /datum/behavior_delegate/crusher_base/on_update_icons() if(bound_xeno.throwing || is_charging) //Let it build up a bit so we're not changing icons every single turf - bound_xeno.icon_state = "[bound_xeno.mutation_icon_state || bound_xeno.mutation_type] Crusher Charging" + bound_xeno.icon_state = "[bound_xeno.get_strain_icon()] Crusher Charging" return TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm b/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm index 8ec53d51046e..a63bafb5d2b0 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm @@ -18,6 +18,7 @@ deevolves_to = list("Larva") can_vent_crawl = 0 + available_strains = list(/datum/xeno_strain/steel_crest) behavior_delegate_type = /datum/behavior_delegate/defender_base tackle_min = 2 @@ -51,9 +52,6 @@ /datum/action/xeno_action/onclick/tacmap, ) - mutation_icon_state = DEFENDER_NORMAL - mutation_type = DEFENDER_NORMAL - icon_xeno = 'icons/mob/xenos/defender.dmi' icon_xenonid = 'icons/mob/xenonids/defender.dmi' @@ -90,8 +88,8 @@ return if(bound_xeno.fortify && bound_xeno.health > 0) - bound_xeno.icon_state = "[bound_xeno.mutation_icon_state || bound_xeno.mutation_type] Defender Fortify" + bound_xeno.icon_state = "[bound_xeno.get_strain_icon()] Defender Fortify" return TRUE if(bound_xeno.crest_defense && bound_xeno.health > 0) - bound_xeno.icon_state = "[bound_xeno.mutation_icon_state || bound_xeno.mutation_type] Defender Crest" + bound_xeno.icon_state = "[bound_xeno.get_strain_icon()] Defender Crest" return TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm b/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm index c4c9b11b37e4..a0ce70316eb8 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm @@ -12,6 +12,11 @@ evasion = XENO_EVASION_MEDIUM speed = XENO_SPEED_TIER_7 + available_strains = list( + /datum/xeno_strain/gardener, + /datum/xeno_strain/healer, + ) + build_time_mult = BUILD_TIME_MULT_BUILDER caste_desc = "A builder of hives. Only drones may evolve into Queens." @@ -69,7 +74,6 @@ /mob/living/carbon/xenomorph/proc/rename_tunnel, /mob/living/carbon/xenomorph/proc/set_hugger_reserve_for_morpher, ) - mutation_type = DRONE_NORMAL icon_xeno = 'icons/mob/xenos/drone.dmi' icon_xenonid = 'icons/mob/xenonids/drone.dmi' diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm index 7ce3a8750568..d7702b9d8ee1 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm @@ -9,6 +9,9 @@ caste_desc = "Ewwww, that's disgusting!" speed = XENO_SPEED_TIER_8 + available_strains = list(/datum/xeno_strain/watcher) + behavior_delegate_type = /datum/behavior_delegate/facehugger_base + evolution_allowed = FALSE can_be_revived = FALSE @@ -55,7 +58,6 @@ inherent_verbs = list( /mob/living/carbon/xenomorph/proc/vent_crawl, ) - mutation_type = "Normal" icon_xeno = 'icons/mob/xenos/facehugger.dmi' icon_xenonid = 'icons/mob/xenonids/facehugger.dmi' @@ -64,6 +66,20 @@ weed_food_states = list("Facehugger_1","Facehugger_2","Facehugger_3") weed_food_states_flipped = list("Facehugger_1","Facehugger_2","Facehugger_3") +/mob/living/carbon/xenomorph/facehugger/Login() + var/last_ckey_inhabited = persistent_ckey + . = ..() + if(ckey == last_ckey_inhabited) + return + + AddComponent(\ + /datum/component/temporary_mute,\ + "We aren't old enough to vocalize anything yet.",\ + "We aren't old enough to communicate like this yet.",\ + "We feel old enough to be able to vocalize and speak to the hivemind.",\ + 3 MINUTES,\ + ) + /mob/living/carbon/xenomorph/facehugger/initialize_pass_flags(datum/pass_flags_container/PF) ..() if (PF) @@ -74,34 +90,16 @@ if(stat == DEAD) return ..() - if(body_position == STANDING_UP && !(mutation_type == FACEHUGGER_WATCHER) && !(locate(/obj/effect/alien/weeds) in get_turf(src))) - adjustBruteLoss(1) - return ..() - if(!client && !aghosted && away_timer > XENO_FACEHUGGER_LEAVE_TIMER) // Become a npc once again new /obj/item/clothing/mask/facehugger(loc, hivenumber) qdel(src) return ..() -/mob/living/carbon/xenomorph/facehugger/update_icons(is_pouncing) - if(!caste) - return - - if(stat == DEAD) - icon_state = "[mutation_type] [caste.caste_type] Dead" - else if(body_position == LYING_DOWN) - if(!HAS_TRAIT(src, TRAIT_INCAPACITATED) && !HAS_TRAIT(src, TRAIT_FLOORED)) - icon_state = "[mutation_type] [caste.caste_type] Sleeping" - else - icon_state = "[mutation_type] [caste.caste_type] Knocked Down" - else if(is_pouncing) - icon_state = "[mutation_type] [caste.caste_type] Thrown" - else - icon_state = "[mutation_type] [caste.caste_type] Running" - - update_fire() //the fire overlay depends on the xeno's stance, so we must update it. - update_wounds() +/mob/living/carbon/xenomorph/facehugger/update_icons() + . = ..() + if(throwing) + icon_state = "[get_strain_icon()] [caste.caste_type] Thrown" /mob/living/carbon/xenomorph/facehugger/start_pulling(atom/movable/AM) return @@ -235,34 +233,9 @@ else . += "Lifetime Hugs: [total_facehugs]" +/datum/behavior_delegate/facehugger_base + name = "Base Facehugger Behavior Delegate" -/datum/xeno_mutator/watcher - name = "STRAIN: Facehugger - Watcher" - description = "You lose your ability to hide in exchange to see further and the ability to no longer take damage outside of weeds. This enables you to stalk your host from a distance and wait for the perfect oppertunity to strike." - flavor_description = "No need to hide when you can see the danger." - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_FACEHUGGER) - mutator_actions_to_remove = list( - /datum/action/xeno_action/onclick/xenohide, - ) - mutator_actions_to_add = list( - /datum/action/xeno_action/onclick/toggle_long_range/runner, - ) - - cost = 1 - - keystone = TRUE - -/datum/xeno_mutator/watcher/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if(!.) - return - - var/mob/living/carbon/xenomorph/facehugger/facehugger = mutator_set.xeno - - facehugger.viewsize = 10 - facehugger.layer = MOB_LAYER - - facehugger.mutation_type = FACEHUGGER_WATCHER - mutator_update_actions(facehugger) - mutator_set.recalculate_actions(description, flavor_description) +/datum/behavior_delegate/facehugger_base/on_life() + if(bound_xeno.body_position == STANDING_UP && !(locate(/obj/effect/alien/weeds) in get_turf(bound_xeno))) + bound_xeno.adjustBruteLoss(1) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm b/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm index 6868fd5ac589..93d40820bf7b 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm @@ -64,7 +64,6 @@ inherent_verbs = list( /mob/living/carbon/xenomorph/proc/vent_crawl, ) - mutation_type = HELLHOUND_NORMAL icon_xeno = 'icons/mob/xenos/hellhound.dmi' icon_xenonid = 'icons/mob/xenos/hellhound.dmi' diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm b/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm index b00ec2a9c1eb..ee4157a67d84 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm @@ -13,6 +13,8 @@ evasion = XENO_EVASION_NONE speed = XENO_SPEED_TIER_2 + available_strains = list(/datum/xeno_strain/resin_whisperer) + evolution_allowed = FALSE caste_desc = "A builder of really big hives." deevolves_to = list(XENO_CASTE_DRONE) @@ -76,8 +78,6 @@ /mob/living/carbon/xenomorph/proc/set_hugger_reserve_for_morpher, ) - mutation_type = HIVELORD_NORMAL - icon_xeno = 'icons/mob/xenos/hivelord.dmi' icon_xenonid = 'icons/mob/xenonids/hivelord.dmi' @@ -121,10 +121,3 @@ toggle_resin_walker() to_chat(bound_xeno, SPAN_WARNING("You feel dizzy as the world slows down.")) bound_xeno.recalculate_move_delay = TRUE - -/// This check mainly exists because of the new resin node ability for resin whisperer. -/mob/living/carbon/xenomorph/hivelord/proc/on_weeds() - var/turf/turf = get_turf(src) - if(locate(/obj/effect/alien/weeds) in turf) - return TRUE - return FALSE diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm b/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm index f1c77e7fb757..c06b8c43839d 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm @@ -46,7 +46,6 @@ inherent_verbs = list( /mob/living/carbon/xenomorph/proc/vent_crawl, ) - mutation_type = "Normal" var/burrowable = TRUE //Can it be safely burrowed if it has no player? var/state_override diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm index 469b9a78d063..b9bde4c78992 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm @@ -15,6 +15,7 @@ attack_delay = 2 // VERY high slash damage, but attacks relatively slowly + available_strains = list(/datum/xeno_strain/vampire) behavior_delegate_type = /datum/behavior_delegate/lurker_base deevolves_to = list(XENO_CASTE_RUNNER) @@ -50,7 +51,6 @@ inherent_verbs = list( /mob/living/carbon/xenomorph/proc/vent_crawl, ) - mutation_type = LURKER_NORMAL claw_type = CLAW_TYPE_SHARP tackle_min = 2 diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm b/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm index 9536ef1553e9..69b679573352 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm @@ -26,6 +26,12 @@ tackle_max = 5 tackle_chance = 45 + available_strains = list( + /datum/xeno_strain/dancer, + /datum/xeno_strain/oppressor, + /datum/xeno_strain/vanguard, + /datum/xeno_strain/warden, + ) behavior_delegate_type = /datum/behavior_delegate/praetorian_base minimum_evolve_time = 15 MINUTES @@ -46,7 +52,6 @@ mob_size = MOB_SIZE_BIG drag_delay = 6 //pulling a big dead xeno is hard tier = 3 - mutation_type = PRAETORIAN_NORMAL base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm b/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm index bcf47386fefc..ebae6f56830f 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm @@ -63,8 +63,6 @@ /datum/action/xeno_action/onclick/tacmap, ) - mutation_type = "Normal" - weed_food_icon = 'icons/mob/xenos/weeds_64x64.dmi' weed_food_states = list("Predalien_1","Predalien_2","Predalien_3") weed_food_states_flipped = list("Predalien_1","Predalien_2","Predalien_3") diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm index 2e5c6a9112a3..f5207e6abf30 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm @@ -350,8 +350,6 @@ /datum/action/xeno_action/activable/xeno_spit/queen_macro, //third macro /datum/action/xeno_action/onclick/shift_spits, //second macro ) - mutation_icon_state = QUEEN_NORMAL - mutation_type = QUEEN_NORMAL claw_type = CLAW_TYPE_VERY_SHARP var/queen_aged = FALSE @@ -519,7 +517,7 @@ overwatch(observed_xeno, TRUE) if(ovipositor && !is_mob_incapacitated(TRUE)) - egg_amount += 0.07 * mutators.egg_laying_multiplier //one egg approximately every 30 seconds + egg_amount += 0.07 //one egg approximately every 30 seconds if(egg_amount >= 1) if(isturf(loc)) var/turf/T = loc @@ -595,7 +593,7 @@ xeno_announcement(input, hivenumber, "The words of the [name] reverberate in our head...") - log_and_message_admins("[key_name_admin(src)] has created a Word of the Queen report:") + message_admins("[key_name_admin(src)] has created a Word of the Queen report:") log_admin("[key_name_admin(src)] Word of the Queen: [input]") return TRUE @@ -936,7 +934,7 @@ var/mob/living/carbon/xenomorph/queen/Queen = bound_xeno if(Queen.ovipositor) Queen.icon = Queen.queen_ovipositor_icon - Queen.icon_state = "[Queen.mutation_icon_state || Queen.mutation_type] Queen Ovipositor" + Queen.icon_state = "[Queen.get_strain_name()] Queen Ovipositor" return TRUE // Switch icon back and then let normal icon behavior happen diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm b/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm index 6d19a1b8feec..e50a0f026d65 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm @@ -26,6 +26,10 @@ fire_immunity = FIRE_IMMUNITY_NO_DAMAGE|FIRE_IMMUNITY_XENO_FRENZY attack_delay = -1 + available_strains = list( + /datum/xeno_strain/berserker, + /datum/xeno_strain/hedgehog, + ) behavior_delegate_type = /datum/behavior_delegate/ravager_base minimum_evolve_time = 15 MINUTES @@ -45,7 +49,6 @@ tier = 3 pixel_x = -16 old_x = -16 - mutation_type = RAVAGER_NORMAL claw_type = CLAW_TYPE_VERY_SHARP base_actions = list( diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm index 3a23afc145d1..400195f21de0 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm @@ -13,6 +13,8 @@ evasion = XENO_EVASION_NONE speed = XENO_SPEED_RUNNER attack_delay = -4 + + available_strains = list(/datum/xeno_strain/acider) behavior_delegate_type = /datum/behavior_delegate/runner_base evolves_to = list(XENO_CASTE_LURKER) deevolves_to = list("Larva") @@ -62,7 +64,6 @@ inherent_verbs = list( /mob/living/carbon/xenomorph/proc/vent_crawl, ) - mutation_type = RUNNER_NORMAL icon_xeno = 'icons/mob/xenos/runner.dmi' icon_xenonid = 'icons/mob/xenonids/runner.dmi' @@ -77,6 +78,12 @@ if (pass_flags_container) pass_flags_container.flags_pass |= PASS_FLAGS_CRAWLER +/mob/living/carbon/xenomorph/runner/recalculate_actions() + . = ..() + pull_multiplier *= 0.85 + if(is_zoomed) + zoom_out() + /datum/behavior_delegate/runner_base name = "Base Runner Behavior Delegate" diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm b/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm index a568a093b3a4..2e53f97e297b 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm @@ -53,7 +53,6 @@ inherent_verbs = list( /mob/living/carbon/xenomorph/proc/vent_crawl, ) - mutation_type = SENTINEL_NORMAL icon_xeno = 'icons/mob/xenos/sentinel.dmi' icon_xenonid = 'icons/mob/xenonids/sentinel.dmi' diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm b/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm index 9ad2f4909fb8..984a2d08bb75 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm @@ -56,7 +56,6 @@ inherent_verbs = list( /mob/living/carbon/xenomorph/proc/vent_crawl, ) - mutation_type = SPITTER_NORMAL icon_xeno = 'icons/mob/xenos/spitter.dmi' icon_xenonid = 'icons/mob/xenonids/spitter.dmi' diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm index b19978a33766..1c329c8b9e82 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm @@ -55,7 +55,6 @@ /datum/action/xeno_action/onclick/tacmap, ) - mutation_type = WARRIOR_NORMAL claw_type = CLAW_TYPE_SHARP icon_xeno = 'icons/mob/xenos/warrior.dmi' diff --git a/code/modules/mob/living/carbon/xenomorph/castes/caste_datum.dm b/code/modules/mob/living/carbon/xenomorph/castes/caste_datum.dm index cfaedf013a44..feee2edecb67 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/caste_datum.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/caste_datum.dm @@ -66,6 +66,9 @@ var/agility_speed_increase = 0 // this opens up possibilities for balancing + /// A list of strain typepaths that are able to be chosen by this caste. + var/list/available_strains = list() + // The type of mutator delegate to instantiate on the base caste. Will // be replaced when the Xeno chooses a strain. var/behavior_delegate_type = /datum/behavior_delegate diff --git a/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm b/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm index f050a0dcfe8a..8b268ebfce62 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm @@ -74,8 +74,6 @@ /mob/living/carbon/xenomorph/proc/set_hugger_reserve_for_morpher, ) - mutation_type = DRONE_NORMAL - icon_xeno = 'icons/mob/xenos/lesser_drone.dmi' icon_xenonid = 'icons/mob/xenonids/lesser_drone.dmi' @@ -83,6 +81,20 @@ weed_food_states = list("Lesser_Drone_1","Lesser_Drone_2","Lesser_Drone_3") weed_food_states_flipped = list("Lesser_Drone_1","Lesser_Drone_2","Lesser_Drone_3") +/mob/living/carbon/xenomorph/lesser_drone/Login() + var/last_ckey_inhabited = persistent_ckey + . = ..() + if(ckey == last_ckey_inhabited) + return + + AddComponent(\ + /datum/component/temporary_mute,\ + "We aren't old enough to vocalize anything yet.",\ + "We aren't old enough to communicate like this yet.",\ + "We feel old enough to be able to vocalize and speak to the hivemind.",\ + 3 MINUTES,\ + ) + /mob/living/carbon/xenomorph/lesser_drone/age_xeno() if(stat == DEAD || !caste || QDELETED(src) || !client) return diff --git a/code/modules/mob/living/carbon/xenomorph/egg_item.dm b/code/modules/mob/living/carbon/xenomorph/egg_item.dm index 1bc41b881129..f349b8acfb8d 100644 --- a/code/modules/mob/living/carbon/xenomorph/egg_item.dm +++ b/code/modules/mob/living/carbon/xenomorph/egg_item.dm @@ -104,12 +104,15 @@ if(weed.weed_strength >= WEED_LEVEL_WEAK && weed.linked_hive.hivenumber == hivenumber) //check for ANY weeds any_weeds = weed + // If the user isn't an eggsac carrier, then they can only plant eggs on hive weeds. + var/needs_hive_weeds = !istype(user.strain, /datum/xeno_strain/eggsac) + var/datum/hive_status/hive = GLOB.hive_datum[hivenumber] if(!any_weeds && !hive_weeds) //you need at least some weeds to plant on. to_chat(user, SPAN_XENOWARNING("[src] must be planted on [lowertext(hive.prefix)]weeds.")) return - if(!hive_weeds && user.mutation_type != CARRIER_EGGSAC) + if(!hive_weeds && needs_hive_weeds) to_chat(user, SPAN_XENOWARNING("[src] can only be planted on [lowertext(hive.prefix)]hive weeds.")) return @@ -138,7 +141,7 @@ return for(var/obj/effect/alien/weeds/weed in T) - if(weed.weed_strength >= WEED_LEVEL_HIVE || user.mutation_type == CARRIER_EGGSAC) + if(weed.weed_strength >= WEED_LEVEL_HIVE || !needs_hive_weeds) user.use_plasma(30) var/obj/effect/alien/egg/newegg if(weed.weed_strength >= WEED_LEVEL_HIVE) diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm index bb67eaa055a8..4b6c0f4b279e 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm @@ -46,7 +46,6 @@ var/allowed_nest_distance = 15 //How far away do we allow nests from an ovied Queen. Default 15 tiles. var/obj/effect/alien/resin/special/pylon/core/hive_location = null //Set to ref every time a core is built, for defining the hive location - var/datum/mutator_set/hive_mutators/mutators = new var/tier_slot_multiplier = 1 var/larva_gestation_multiplier = 1 var/bonus_larva_spawn_chance = 1 @@ -143,7 +142,6 @@ var/static/list/evolution_menu_images /datum/hive_status/New() - mutators.hive = src hive_ui = new(src) mark_ui = new(src) faction_ui = new(src) @@ -279,7 +277,6 @@ /datum/hive_status/proc/set_living_xeno_queen(mob/living/carbon/xenomorph/queen/queen) if(!queen) - mutators.reset_mutators() SStracking.delete_leader("hive_[hivenumber]") SStracking.stop_tracking("hive_[hivenumber]", living_xeno_queen) SShive_status.wait = 10 SECONDS @@ -293,10 +290,8 @@ recalculate_hive() /datum/hive_status/proc/recalculate_hive() - if (!living_xeno_queen) - queen_leader_limit = 0 //No leaders for a Hive without a Queen! - else - queen_leader_limit = 4 + mutators.leader_count_boost + //No leaders for a Hive without a Queen! + queen_leader_limit = living_xeno_queen ? 4 : 0 if (xeno_leader_list.len > queen_leader_limit) var/diff = 0 @@ -310,11 +305,6 @@ open_xeno_leader_positions += i xeno_leader_list.len++ - - tier_slot_multiplier = mutators.tier_slot_multiplier - larva_gestation_multiplier = mutators.larva_gestation_multiplier - bonus_larva_spawn_chance = mutators.bonus_larva_spawn_chance - hive_ui.update_all_data() /datum/hive_status/proc/add_hive_leader(mob/living/carbon/xenomorph/xeno) @@ -524,7 +514,7 @@ xeno_name = "Larva ([X.nicknumber])" xenos["[X.nicknumber]"] = list( "name" = xeno_name, - "strain" = X.mutation_type, + "strain" = X.get_strain_name(), "ref" = "\ref[X]" ) diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/mutator.dm b/code/modules/mob/living/carbon/xenomorph/mutators/mutator.dm deleted file mode 100644 index 6736bdb0419b..000000000000 --- a/code/modules/mob/living/carbon/xenomorph/mutators/mutator.dm +++ /dev/null @@ -1,103 +0,0 @@ -#define MUTATOR_COST_CHEAP 2 -#define MUTATOR_COST_MODERATE 3 -#define MUTATOR_COST_EXPENSIVE 1 - -//Individual mutator -/datum/xeno_mutator - var/name = "Mutator name" //Name of the mutator, should be short but informative - var/description = "Mutator description" //Description to be displayed on purchase - var/flavor_description = null // Optional flavor text to be shown. Semi-OOC - var/cost = MUTATOR_COST_CHEAP //How expensive the mutator is - var/required_level = 0 //Level of xeno upgrade required to unlock - var/unique = TRUE //True if you can only buy it once - var/death_persistent = FALSE //True if the mutators persists after Queen death (aka, mostly for "once ever" mutators) - var/hive_only = FALSE //Hive-only mutators - var/individual_only = FALSE //Individual-only mutators - var/keystone = FALSE //Xeno can only take one Keystone mutator - var/flaw = FALSE //Flaws give you points back, but you can only take one of them - var/list/caste_whitelist = list() //List of the only castes that can buy this mutator - - // Rework by Fourkhan - 4/26/19, redone again c. 2/2020 - // HOW TO ADD A NEW MUTATOR - // Step 0: Write an action(s) - // the "handler" procs go in the appropriate caste's subfolder under the "ABILITIES" file. - // the actual ACTION procs go in the appropriate caste's subfolder under the "POWERS" file. - // Any constants you need to access for your strain should be in the behavior holder and - // accessed using a cast to it using the mutator_type variable as defined below. (Or using an istype of the behavior holder) - // vars that absolutely must be held on the xenos themselves can be added to the Xenomorph class itself. - // Be sure to follow the spec in xeno_action.dm as far as setting up xeno_cooldown is concerned. - // - // Step 1: Write the Behavior Delegate datum IF NECESSARY - // the "behavior holder" datum defines all unique behavior and state for each xeno/strain. It works by embedding a number of 'hooks' - // for example, if you want to store bonus damage and apply it on slashes, behavior delegates are the way to do it. - // in common procs that call back to Xeno features. See other behavior delegates for examples. Afterward, set the behavior_delegate_type - // var on the strain datum to indicate which behavior holder to apply to your strain. - // - // Step 1: Copy/paste another datum definiton and edit it for your strain - // make sure to populate each of the variables listed above (at least as much as other strains) - // - // Step 2: Write the apply_mutator proc. - // FIRST: populate mutator_actions_to_add and mutator_actions_to_remove according to that documentation. - // THEN: write the body of the apply_mutator method according to your speficiations - // THEN: call mutator_update_actions on your xeno - // call recalculate actions on your mutator set (this should be auto populated) - // You should probably also call recalculate_everything() on the host Xeno to make sure you don't end up with any - // strange transient values. - // THEN: Set the mutation_type var on the host xeno to "name" the strain. - // FINALLY: Call apply_behavior_holder() to add the behavior datum to the new Xeno. - // - // You're done! - - // Both should be set to null when their use is not necessary. - /// A list of PATHS of actions that need to be removed when a xeno takes the mutator. - var/list/mutator_actions_to_remove //Actions to remove when the mutator is added - /// A list of PATHS of actions to be ADDED when the Xeno takes the mutator. - var/list/mutator_actions_to_add //Actions to add when the mutator is added - - // Type of the behavior datum to add - var/behavior_delegate_type = null // Specify this on subtypes - -/datum/xeno_mutator/New() - . = ..() - name = "[name]" - - -/datum/xeno_mutator/proc/apply_mutator(datum/mutator_set/MS) - if(!MS.can_purchase_mutator(name)) - return FALSE - if(MS.remaining_points < cost) - return FALSE - MS.remaining_points -= cost - MS.purchased_mutators += name - - if(istype(MS, /datum/mutator_set/individual_mutators)) - var/datum/mutator_set/individual_mutators/IS = MS - if(IS.xeno) - IS.xeno.hive.hive_ui.update_xeno_info() - - return TRUE - -// Sets up actions for when a mutator is taken -// Must be called at the end of any mutator that changes available actions -// (read: Strains) apply_mutator proc for the mutator to work correctly. -/datum/xeno_mutator/proc/mutator_update_actions(mob/living/carbon/xenomorph/X) - if(mutator_actions_to_remove) - for(var/action_path in mutator_actions_to_remove) - remove_action(X, action_path) - if(mutator_actions_to_add) - for(var/action_path in mutator_actions_to_add) - give_action(X, action_path) - -// Substitutes the existing behavior delegate for the strain-defined one. -/datum/xeno_mutator/proc/apply_behavior_holder(mob/living/carbon/xenomorph/X) - if (!istype(X)) - log_debug("Null mob handed to apply_behavior_holder. Tell the devs.") - log_admin("Null mob handed to apply_behavior_holder. Tell the devs.") - message_admins("Null mob handed to apply_behavior_holder. Tell the devs.") - - if (behavior_delegate_type) - if(X.behavior_delegate) - qdel(X.behavior_delegate) - X.behavior_delegate = new behavior_delegate_type() - X.behavior_delegate.bound_xeno = X - X.behavior_delegate.add_to_xeno() diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/defender/steel_crest.dm b/code/modules/mob/living/carbon/xenomorph/mutators/strains/defender/steel_crest.dm deleted file mode 100644 index 957e7f1b8926..000000000000 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/defender/steel_crest.dm +++ /dev/null @@ -1,44 +0,0 @@ -/datum/xeno_mutator/steel_crest - name = "STRAIN: Defender - Steel Crest" - description = "You trade your tail sweep and a small amount of your slash damage for slightly increased headbutt knockback and damage and the ability to slowly move and headbutt while fortified. Along with this, you gain a unique ability to accumulate damage, and use it to recover a slight amount of health and refresh your tail slam." - flavor_description = "To handle yourself, use your head. To handle others, use your head." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_DEFENDER) - mutator_actions_to_remove = list( - /datum/action/xeno_action/onclick/tail_sweep, - ) - mutator_actions_to_add = list( - /datum/action/xeno_action/onclick/soak, - ) - behavior_delegate_type = /datum/behavior_delegate/defender_steel_crest - keystone = TRUE - -/datum/xeno_mutator/steel_crest/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return - - var/mob/living/carbon/xenomorph/defender/defender = mutator_set.xeno - defender.mutation_type = DEFENDER_STEELCREST - defender.mutation_icon_state = DEFENDER_STEELCREST - defender.damage_modifier -= XENO_DAMAGE_MOD_VERY_SMALL - if(defender.fortify) - defender.ability_speed_modifier += 2.5 - mutator_update_actions(defender) - mutator_set.recalculate_actions(description, flavor_description) - defender.recalculate_stats() - -/datum/behavior_delegate/defender_steel_crest - name = "Steel Crest Defender Behavior Delegate" - -/datum/behavior_delegate/defender_steel_crest/on_update_icons() - if(bound_xeno.stat == DEAD) - return - - if(bound_xeno.fortify) - bound_xeno.icon_state = "[bound_xeno.mutation_icon_state || bound_xeno.mutation_type] Steelcrest Defender Fortify" - return TRUE - if(bound_xeno.crest_defense) - bound_xeno.icon_state = "[bound_xeno.mutation_icon_state || bound_xeno.mutation_type] Steelcrest Defender Crest" - return TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/say.dm b/code/modules/mob/living/carbon/xenomorph/say.dm index 5b8ce1ecd292..c40a50ce7523 100644 --- a/code/modules/mob/living/carbon/xenomorph/say.dm +++ b/code/modules/mob/living/carbon/xenomorph/say.dm @@ -98,6 +98,9 @@ to_chat(src, SPAN_WARNING("A headhunter temporarily cut off your psychic connection!")) return + if(SEND_SIGNAL(src, COMSIG_XENO_TRY_HIVEMIND_TALK, message) & COMPONENT_OVERRIDE_HIVEMIND_TALK) + return + hivemind_broadcast(message, hive) /mob/living/carbon/proc/hivemind_broadcast(message, datum/hive_status/hive) diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/behavior_delegate.dm b/code/modules/mob/living/carbon/xenomorph/strains/behavior_delegate.dm similarity index 100% rename from code/modules/mob/living/carbon/xenomorph/mutators/behavior_delegate.dm rename to code/modules/mob/living/carbon/xenomorph/strains/behavior_delegate.dm diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/boiler/trapper.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm similarity index 87% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/boiler/trapper.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm index c14d2c6773cf..e6c8061bd0fe 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/boiler/trapper.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm @@ -1,40 +1,33 @@ - -/datum/xeno_mutator/trapper - name = "STRAIN: Boiler - Trapper" +/datum/xeno_strain/trapper + name = BOILER_TRAPPER description = "You trade your ability to bombard, lance, and dump your acid in order to gain some speed and the ability to create acid explosions and restrain talls within them. With your longer-range vision, set up traps that immobilize your opponents and place acid mines which deal damage to talls and barricades and reduce the cooldown of your trap deployment for every tall hit. Finally, hit talls with your Acid Shotgun ability which adds a stack of insight to empower the next trap you place once you reach a maximum of ten insight. A point-blank shot or a shot on a stunned target will instantly apply ten stacks." flavor_description = "Hsss, I love the smell of burnin' tallhost flesh in the mornin'." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_BOILER) //Only boiler. - mutator_actions_to_remove = list( + + actions_to_remove = list( /datum/action/xeno_action/activable/xeno_spit/bombard, /datum/action/xeno_action/onclick/shift_spits/boiler, /datum/action/xeno_action/activable/spray_acid/boiler, /datum/action/xeno_action/onclick/toggle_long_range/boiler, /datum/action/xeno_action/onclick/acid_shroud, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/activable/boiler_trap, /datum/action/xeno_action/activable/acid_mine, /datum/action/xeno_action/activable/acid_shotgun, /datum/action/xeno_action/onclick/toggle_long_range/trapper, ) - keystone = TRUE behavior_delegate_type = /datum/behavior_delegate/boiler_trapper -/datum/xeno_mutator/trapper/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if(. == 0) - return +/datum/xeno_strain/trapper/apply_strain(mob/living/carbon/xenomorph/boiler/boiler) + if(!istype(boiler)) + return FALSE - var/mob/living/carbon/xenomorph/boiler/boiler = mutator_set.xeno if(boiler.is_zoomed) boiler.zoom_out() boiler.tileoffset = 0 boiler.viewsize = TRAPPER_VIEWRANGE - boiler.mutation_type = BOILER_TRAPPER boiler.plasma_types -= PLASMA_NEUROTOXIN boiler.armor_modifier -= XENO_ARMOR_MOD_LARGE // no armor boiler.health_modifier -= XENO_HEALTH_MOD_MED @@ -42,12 +35,6 @@ boiler.speed_modifier += XENO_SPEED_SLOWMOD_TIER_5 // compensating for base buffs boiler.recalculate_everything() - apply_behavior_holder(boiler) - - mutator_update_actions(boiler) - mutator_set.recalculate_actions(description, flavor_description) - - /datum/behavior_delegate/boiler_trapper name = "Boiler Trapper Behavior Delegate" diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/carrier/eggsac.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/carrier/eggsac.dm similarity index 86% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/carrier/eggsac.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/carrier/eggsac.dm index 10cbc29f51b0..a73a7082fbdb 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/carrier/eggsac.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/carrier/eggsac.dm @@ -1,48 +1,36 @@ -/datum/xeno_mutator/eggsac - name = "STRAIN: Carrier - Eggsac" +/datum/xeno_strain/eggsac + name = CARRIER_EGGSAC description = "In exchange for your ability to store huggers and place traps, you gain larger plasma stores, strong pheromones, and the ability to lay eggs by using your plasma stores. In addition, you can now carry twelve eggs at once and can place eggs one pace further than normal. \n\nYou can also place a small number of fragile eggs on normal weeds. These eggs have a lifetime of five minutes while you remain within 14 tiles. Or one minute if you leave this range." flavor_description = "An egg is always an adventure; the next one may be different." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_CARRIER) - mutator_actions_to_remove = list( + icon_state_prefix = "Eggsac" + + actions_to_remove = list( /datum/action/xeno_action/activable/throw_hugger, /datum/action/xeno_action/onclick/place_trap, /datum/action/xeno_action/activable/retrieve_egg, // readding it so it gets at the end of the ability list /datum/action/xeno_action/onclick/set_hugger_reserve, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/active_toggle/generate_egg, /datum/action/xeno_action/activable/retrieve_egg, // readding it so it gets at the end of the ability list ) + behavior_delegate_type = /datum/behavior_delegate/carrier_eggsac - keystone = TRUE -/datum/xeno_mutator/eggsac/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (!.) - return - var/mob/living/carbon/xenomorph/carrier/carrier = mutator_set.xeno - if(!istype(carrier)) - return FALSE +/datum/xeno_strain/eggsac/apply_strain(mob/living/carbon/xenomorph/carrier/carrier) carrier.plasma_types = list(PLASMA_EGG) carrier.phero_modifier += XENO_PHERO_MOD_LARGE // praetorian level pheremones - carrier.plasmapool_modifier = 1.2 - mutator_update_actions(carrier) - mutator_set.recalculate_actions(description, flavor_description) - carrier.recalculate_pheromones() carrier.recalculate_plasma() - if(carrier.huggers_cur > 0) - playsound(carrier.loc, 'sound/voice/alien_facehugger_dies.ogg', 25, 1) + carrier.recalculate_pheromones() + + if(carrier.huggers_cur) + playsound(carrier.loc, 'sound/voice/alien_facehugger_dies.ogg', 25, TRUE) carrier.huggers_cur = 0 carrier.huggers_max = 0 carrier.update_hugger_overlays() - carrier.mutation_type = CARRIER_EGGSAC - carrier.update_eggsac_overlays() carrier.eggs_max = 12 carrier.egg_planting_range = 2 - apply_behavior_holder(carrier) - return TRUE + carrier.update_eggsac_overlays() #define EGGSAC_OFF_WEED_EGGCAP 4 #define EGGSAC_EGG_SUSTAIN_DISTANCE 14 @@ -60,6 +48,10 @@ . = list() . += "Eggs sustained: [length(eggs_sustained)] / [egg_sustain_cap]" +/datum/behavior_delegate/carrier_eggsac/on_update_icons() + var/mob/living/carbon/xenomorph/carrier/bound_carrier = bound_xeno + bound_carrier.update_eggsac_overlays() + /datum/behavior_delegate/carrier_eggsac/on_life() if(length(eggs_sustained) > egg_sustain_cap) var/obj/effect/alien/egg/carrier_egg/my_egg = eggs_sustained[1] @@ -87,6 +79,9 @@ remove_egg_owner(my_egg) my_egg.start_unstoppable_decay() + M.visible_message(SPAN_XENOWARNING("[M] throes as its eggsac bursts into a mess of acid!")) + playsound(M.loc, 'sound/effects/alien_egg_burst.ogg', 25, TRUE) + ///Remove all references to src in eggs_sustained /datum/behavior_delegate/carrier_eggsac/Destroy() for(var/obj/effect/alien/egg/carrier_egg/my_egg as anything in eggs_sustained) diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/crusher/charger.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm similarity index 96% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/crusher/charger.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm index 17b5cf62052c..84877b43571e 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/crusher/charger.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm @@ -1,10 +1,8 @@ -// // Specific momentum based damage defines #define CHARGER_DESTROY charger_ability.momentum * 40 #define CHARGER_DAMAGE_CADE charger_ability.momentum * 22 #define CHARGER_DAMAGE_SENTRY charger_ability.momentum * 9 -#define CHARGER_DAMAGE_MG charger_ability.momentum * 15 // Momentum loss defines. 8 is maximum momentum @@ -14,34 +12,26 @@ #define CCA_MOMENTUM_LOSS_MIN 1 -/datum/xeno_mutator/charger - name = "STRAIN: Crusher - Charger" +/datum/xeno_strain/charger + name = CRUSHER_CHARGER description = "In exchange for your shield, a little bit of your armor and damage, your slowdown resist from autospitters, your influence under frenzy pheromones, your stomp no longer knocking down talls, and your ability to lock your direction, you gain a considerable amount of health, some speed, your stomp does extra damage when stomping over a grounded tall, and your charge is now manually-controlled and momentum-based; the further you go, the more damage and speed you will gain until you achieve maximum momentum, indicated by your roar. In addition, your armor is now directional, being the toughest on the front, weaker on the sides, and weakest from the back. In return, you gain an ability to tumble to pass through talls and avoid enemy fire, and an ability to forcefully move enemies via ramming into them." flavor_description = "We're just getting started. Nothing stops this train. Nothing." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_CRUSHER) - mutator_actions_to_remove = list ( + + actions_to_remove = list( /datum/action/xeno_action/activable/pounce/crusher_charge, /datum/action/xeno_action/onclick/crusher_stomp, /datum/action/xeno_action/onclick/crusher_shield, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/onclick/charger_charge, /datum/action/xeno_action/activable/tumble, /datum/action/xeno_action/onclick/crusher_stomp/charger, /datum/action/xeno_action/activable/fling/charger, ) - keystone = TRUE - behavior_delegate_type = /datum/behavior_delegate/crusher_charger -/datum/xeno_mutator/charger/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return + behavior_delegate_type = /datum/behavior_delegate/crusher_charger - var/mob/living/carbon/xenomorph/crusher/crusher = mutator_set.xeno - crusher.mutation_type = CRUSHER_CHARGER +/datum/xeno_strain/charger/apply_strain(mob/living/carbon/xenomorph/crusher/crusher) crusher.small_explosives_stun = FALSE crusher.health_modifier += XENO_HEALTH_MOD_LARGE crusher.speed_modifier += XENO_SPEED_FASTMOD_TIER_3 @@ -49,10 +39,6 @@ crusher.damage_modifier -= XENO_DAMAGE_MOD_SMALL crusher.ignore_aura = "frenzy" // no funny crushers going 7 morbillion kilometers per second crusher.phero_modifier = -crusher.caste.aura_strength - crusher.recalculate_pheromones() - mutator_update_actions(crusher) - mutator_set.recalculate_actions(description, flavor_description) - apply_behavior_holder(crusher) crusher.recalculate_everything() /datum/behavior_delegate/crusher_charger @@ -83,7 +69,7 @@ /datum/behavior_delegate/crusher_charger/on_update_icons() if(HAS_TRAIT(bound_xeno, TRAIT_CHARGING) && bound_xeno.body_position == STANDING_UP) - bound_xeno.icon_state = "[bound_xeno.mutation_icon_state || bound_xeno.mutation_type] Crusher Charging" + bound_xeno.icon_state = "[bound_xeno.get_strain_icon()] Crusher Charging" return TRUE // Fallback proc for shit that doesn't have a collision def @@ -643,3 +629,12 @@ return XENO_CHARGE_TRY_MOVE charger_ability.stop_momentum() + + +#undef CHARGER_DESTROY +#undef CHARGER_DAMAGE_CADE +#undef CHARGER_DAMAGE_SENTRY +#undef CCA_MOMENTUM_LOSS_HALF +#undef CCA_MOMENTUM_LOSS_THIRD +#undef CCA_MOMENTUM_LOSS_QUARTER +#undef CCA_MOMENTUM_LOSS_MIN diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/defender/steel_crest.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/defender/steel_crest.dm new file mode 100644 index 000000000000..f84566e9c841 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/defender/steel_crest.dm @@ -0,0 +1,22 @@ +/datum/xeno_strain/steel_crest + name = DEFENDER_STEELCREST + description = "You trade your tail sweep and a small amount of your slash damage for slightly increased headbutt knockback and damage and the ability to slowly move and headbutt while fortified. Along with this, you gain a unique ability to accumulate damage, and use it to recover a slight amount of health and refresh your tail slam." + flavor_description = "To handle yourself, use your head. To handle others, use your head." + icon_state_prefix = "Steelcrest" + + actions_to_remove = list( + /datum/action/xeno_action/activable/headbutt, + /datum/action/xeno_action/activable/fortify, + /datum/action/xeno_action/onclick/tail_sweep, + ) + actions_to_add = list( + /datum/action/xeno_action/activable/headbutt/steel_crest, + /datum/action/xeno_action/activable/fortify/steel_crest, + /datum/action/xeno_action/onclick/soak, + ) + +/datum/xeno_strain/steel_crest/apply_strain(mob/living/carbon/xenomorph/defender/defender) + defender.damage_modifier -= XENO_DAMAGE_MOD_VERY_SMALL + if(defender.fortify) + defender.ability_speed_modifier += 2.5 + defender.recalculate_stats() diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/gardener.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/gardener.dm similarity index 93% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/gardener.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/drone/gardener.dm index da354b465331..d54d268f12d9 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/gardener.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/gardener.dm @@ -1,45 +1,41 @@ -/datum/xeno_mutator/gardener - name = "STRAIN: Drone - Gardener" +/datum/xeno_strain/gardener + name = DRONE_GARDENER description = "You trade your choice of resin secretions, your corrosive acid, and your ability to transfer plasma for a tiny bit of extra health regeneration on weeds and several new abilities, including the ability to plant hardier weeds, temporarily reinforce structures with your plasma, and to plant up to six potent resin fruits for your sisters by secreting your vital fluids at the cost of a bit of your health for each fruit you shape. You can use Resin Surge to speed up the growth of your fruits." flavor_description = "The glory of gardening: hands in the weeds, head in the dark, heart with resin." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_DRONE) //Only drone. - mutator_actions_to_remove = list( + + actions_to_remove = list( /datum/action/xeno_action/activable/secrete_resin, /datum/action/xeno_action/onclick/choose_resin, /datum/action/xeno_action/activable/corrosive_acid/weak, /datum/action/xeno_action/activable/transfer_plasma, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/onclick/plant_weeds/gardener, // second macro /datum/action/xeno_action/activable/resin_surge, // third macro /datum/action/xeno_action/onclick/plant_resin_fruit/greater, // fourth macro /datum/action/xeno_action/onclick/change_fruit, /datum/action/xeno_action/activable/transfer_plasma, ) - keystone = TRUE - behavior_delegate_type = /datum/behavior_delegate/drone_gardener -/datum/xeno_mutator/gardener/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return + behavior_delegate_type = /datum/behavior_delegate/drone_gardener - var/mob/living/carbon/xenomorph/drone/drone = mutator_set.xeno - drone.mutation_type = DRONE_GARDENER - drone.available_fruits = list(/obj/effect/alien/resin/fruit/greater, /obj/effect/alien/resin/fruit/unstable, /obj/effect/alien/resin/fruit/spore, /obj/effect/alien/resin/fruit/speed, /obj/effect/alien/resin/fruit/plasma) +/datum/xeno_strain/gardener/apply_strain(mob/living/carbon/xenomorph/drone/drone) + drone.available_fruits = list( + /obj/effect/alien/resin/fruit/greater, + /obj/effect/alien/resin/fruit/unstable, + /obj/effect/alien/resin/fruit/spore, + /obj/effect/alien/resin/fruit/speed, + /obj/effect/alien/resin/fruit/plasma + ) drone.selected_fruit = /obj/effect/alien/resin/fruit/greater drone.max_placeable = 6 drone.regeneration_multiplier = XENO_REGEN_MULTIPLIER_TIER_1 - mutator_update_actions(drone) - apply_behavior_holder(drone) + // Also change the primacy value for our place construction ability (because we want it in the same place but have another primacy ability) for(var/datum/action/xeno_action/action in drone.actions) if(istype(action, /datum/action/xeno_action/activable/place_construction)) action.ability_primacy = XENO_NOT_PRIMARY_ACTION break // Don't need to keep looking - mutator_set.recalculate_actions(description, flavor_description) /datum/action/xeno_action/onclick/plant_resin_fruit name = "Plant Resin Fruit (50)" @@ -368,9 +364,6 @@ var/mutable_appearance/fruit_sac_overlay_icon -/datum/behavior_delegate/drone_gardener/add_to_xeno() - on_update_icons() - /datum/behavior_delegate/drone_gardener/on_update_icons() if(!fruit_sac_overlay_icon) fruit_sac_overlay_icon = mutable_appearance('icons/mob/xenos/drone_strain_overlays.dmi', "Gardener Drone Walking") diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/healer.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/healer.dm similarity index 90% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/healer.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/drone/healer.dm index 86f50b47e080..0fcbb2ecf09a 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/healer.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/healer.dm @@ -1,18 +1,17 @@ -/datum/xeno_mutator/healer - name = "STRAIN: Drone - Healer" +/datum/xeno_strain/healer + name = DRONE_HEALER description = "You lose your choice of resin secretions, a chunk of your slash damage, and you will experience a slighty-increased difficulty in tackling tallhosts in exchange for strong pheromones, the ability to use a bit of your health to plant a maximum of three lesser resin fruits, and the ability to heal your sisters' wounds by secreting a regenerative resin salve by using your vital fluids and a fifth of your plasma. Be wary, this is a dangerous process; overexert yourself and you may exhaust yourself to unconsciousness, or die..." flavor_description = "To the very last drop, your blood belongs to The Hive; share it with your sisters to keep them fighting." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_DRONE) //Only drone. - mutator_actions_to_remove = list( + icon_state_prefix = "Healer" + + actions_to_remove = list( /datum/action/xeno_action/activable/secrete_resin, /datum/action/xeno_action/onclick/choose_resin, /datum/action/xeno_action/activable/transfer_plasma, /datum/action/xeno_action/activable/place_construction, // so it doesn't use fifth macro /datum/action/xeno_action/onclick/plant_weeds, // so it doesn't break order ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/activable/place_construction/not_primary, // so it doesn't use fifth macro /datum/action/xeno_action/onclick/plant_weeds, // so it doesn't break order /datum/action/xeno_action/onclick/plant_resin_fruit, // Second macro. Resin fruits belong to Gardener, but Healer has a minor variant. @@ -20,32 +19,20 @@ /datum/action/xeno_action/activable/transfer_plasma/healer, //Fourth macro, an improved plasma transfer. /datum/action/xeno_action/activable/healer_sacrifice, //Fifth macro, the ultimate ability to sacrifice yourself ) - keystone = TRUE - behavior_delegate_type = /datum/behavior_delegate/drone_healer - -/datum/xeno_mutator/healer/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return + behavior_delegate_type = /datum/behavior_delegate/drone_healer - var/mob/living/carbon/xenomorph/drone/drone = mutator_set.xeno - drone.mutation_type = DRONE_HEALER +/datum/xeno_strain/healer/apply_strain(mob/living/carbon/xenomorph/drone/drone) drone.phero_modifier += XENO_PHERO_MOD_LARGE drone.plasma_types += PLASMA_PHEROMONE drone.damage_modifier -= XENO_DAMAGE_MOD_VERY_SMALL + drone.tackle_chance_modifier -= 5 drone.max_placeable = 3 drone.available_fruits = list(/obj/effect/alien/resin/fruit) drone.selected_fruit = /obj/effect/alien/resin/fruit - drone.tackle_chance_modifier -= 5 - mutator_update_actions(drone) - apply_behavior_holder(drone) - mutator_set.recalculate_actions(description, flavor_description) - drone.recalculate_health() - drone.recalculate_damage() - drone.recalculate_pheromones() - drone.recalculate_tackle() + + drone.recalculate_everything() /* Improved Plasma Transfer @@ -123,13 +110,14 @@ to_chat(src, SPAN_XENOWARNING("[target_xeno] is already at max health!")) return -///Tiny xenos (Larva and Facehuggers), don't need as much health so don't cost as much. - if(target_xeno.mob_size == 0) + //Tiny xenos (Larva and Facehuggers), don't need as much health so don't cost as much. + if(target_xeno.mob_size == MOB_SIZE_SMALL) amount = amount * 0.15 damage_taken_mod = 1 -//Forces an equivalent exchange of health between healers so they do not spam heal each other to full health. - if(target_xeno.mutation_type == DRONE_HEALER) + //Forces an equivalent exchange of health between healers so they do not spam heal each other to full health. + var/target_is_healer = istype(target_xeno.strain, /datum/xeno_strain/healer) + if(target_is_healer) damage_taken_mod = 1 face_atom(target_xeno) @@ -144,7 +132,7 @@ playsound(src, "alien_drool", 25) var/datum/behavior_delegate/drone_healer/healer_delegate = behavior_delegate healer_delegate.salve_applied_recently = TRUE - if(target_xeno.mutation_type != DRONE_HEALER && !isfacehugger(target_xeno)) // no cheap grinding + if(!target_is_healer && !isfacehugger(target_xeno)) // no cheap grinding healer_delegate.modify_transferred(amount * damage_taken_mod) update_icons() addtimer(CALLBACK(healer_delegate, /datum/behavior_delegate/drone_healer/proc/un_salve), 10 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE) @@ -301,4 +289,3 @@ to_chat(xeno, SPAN_HIGHDANGER("Warning: [name] is a last measure skill. Using it will kill us.")) else to_chat(xeno, SPAN_HIGHDANGER("Warning: [name] is a last measure skill. Using it will kill us, but new life will be granted for our hard work for the hive.")) - diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm new file mode 100644 index 000000000000..7fba30b6f352 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm @@ -0,0 +1,21 @@ +/datum/xeno_strain/watcher + name = FACEHUGGER_WATCHER + description = "You lose your ability to hide in exchange to see further and the ability to no longer take damage outside of weeds. This enables you to stalk your host from a distance and wait for the perfect oppertunity to strike." + flavor_description = "No need to hide when you can see the danger." + + actions_to_remove = list( + /datum/action/xeno_action/onclick/xenohide, + ) + actions_to_add = list( + /datum/action/xeno_action/onclick/toggle_long_range/runner, + ) + + behavior_delegate_type = /datum/behavior_delegate/facehugger_watcher + +/datum/xeno_strain/watcher/apply_strain(mob/living/carbon/xenomorph/facehugger/huggy) + huggy.viewsize = 10 + huggy.layer = initial(huggy.layer) + +// This has no special effects, it's just here to skip `/datum/behavior_delegate/facehugger_base/on_life()`. +/datum/behavior_delegate/facehugger_watcher + name = "Watcher Facehugger Behavior Delegate" diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/hivelord/resin_whisperer.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/hivelord/resin_whisperer.dm similarity index 80% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/hivelord/resin_whisperer.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/hivelord/resin_whisperer.dm index 3653209b78f2..8bdcd1311f94 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/hivelord/resin_whisperer.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/hivelord/resin_whisperer.dm @@ -1,41 +1,30 @@ -/datum/xeno_mutator/resinwhisperer - name = "STRAIN: Hivelord - Resin Whisperer" +/datum/xeno_strain/resin_whisperer + name = HIVELORD_RESIN_WHISPERER description = "You lose your corrosive acid, your ability to secrete thick resin, your ability to reinforce resin secretions, sacrifice your ability to plant resin nodes outside of weeds, and you sacrifice a fifth of your plasma reserves to enhance your vision and gain a stronger connection to the resin. You can now remotely place resin secretions including resin nodes up to a distance of twelve paces!" flavor_description = "Let the resin guide you. It whispers, so listen closely." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_HIVELORD) - mutator_actions_to_remove = list( + icon_state_prefix = "Resin Whisperer" + + actions_to_remove = list( /datum/action/xeno_action/onclick/plant_weeds, /datum/action/xeno_action/activable/secrete_resin/hivelord, /datum/action/xeno_action/activable/corrosive_acid, /datum/action/xeno_action/activable/transfer_plasma/hivelord, /datum/action/xeno_action/active_toggle/toggle_speed, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/activable/secrete_resin/remote, //third macro /datum/action/xeno_action/activable/transfer_plasma/hivelord, // readding it so it gets at the end of the ability list /datum/action/xeno_action/active_toggle/toggle_speed, // readding it so it gets at the end of the ability list ) - keystone = TRUE - -/datum/xeno_mutator/resinwhisperer/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if(!.) - return - var/mob/living/carbon/xenomorph/hivelord/hivelord = mutator_set.xeno +/datum/xeno_strain/resin_whisperer/apply_strain(mob/living/carbon/xenomorph/hivelord/hivelord) hivelord.plasmapool_modifier = 0.8 // -20% plasma pool hivelord.extra_build_dist = 12 // 1 + 12 = 13 tile build range hivelord.can_stack_builds = TRUE + hivelord.recalculate_plasma() hivelord.client.change_view(10, src) - hivelord.mutation_type = HIVELORD_RESIN_WHISPERER - mutator_update_actions(hivelord) - mutator_set.recalculate_actions(description, flavor_description) - hivelord.recalculate_plasma() - hivelord.set_resin_build_order(GLOB.resin_build_order_hivelord_whisperer) for(var/datum/action/xeno_action/action in hivelord.actions) // Also update the choose_resin icon since it resets @@ -68,12 +57,9 @@ action_type = XENO_ACTION_CLICK /datum/action/xeno_action/activable/secrete_resin/remote/use_ability(atom/target_atom, mods) - var/mob/living/carbon/xenomorph/xeno_owner = owner - if(xeno_owner.mutation_type == HIVELORD_RESIN_WHISPERER) - var/mob/living/carbon/xenomorph/hivelord/hivelord_mob = owner - if(!hivelord_mob.on_weeds()) // There is a chance that queen can't place down buildings in ovi build view so we place the rein whisperer check here. - to_chat(owner, SPAN_XENONOTICE("We must be standing on weeds to establish a connection to the resin.")) - return + if(!can_remote_build()) + to_chat(owner, SPAN_XENONOTICE("We must be standing on weeds to establish a connection to the resin.")) + return if(!action_cooldown_check()) return @@ -115,6 +101,12 @@ playsound(target_turf, "alien_resin_build", 25) return TRUE +// By default, the xeno must be on a weed tile in order to build from a distance. +/datum/action/xeno_action/activable/secrete_resin/remote/proc/can_remote_build() + if(!locate(/obj/effect/alien/weeds) in get_turf(owner)) + return FALSE + return TRUE + /datum/action/xeno_action/verb/verb_coerce_resin() set category = "Alien" set name = "Coerce Resin" diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/lurker/vampire.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/lurker/vampire.dm similarity index 71% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/lurker/vampire.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/lurker/vampire.dm index 72214d06332e..1df49092e783 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/lurker/vampire.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/lurker/vampire.dm @@ -1,31 +1,22 @@ - -/datum/xeno_mutator/Vampire - name = "STRAIN: Lurker - Vampire" +/datum/xeno_strain/vampire + name = LURKER_VAMPIRE description = "You lose all of your abilities and you forefeit a chunk of your health and damage in exchange for a large amount of armor, a little bit of movement speed, increased attack speed, and brand new abilities that make you an assassin. Rush on your opponent to disorient them and Flurry to unleash a forward cleave that can hit and slow three talls and heal you for every tall you hit. Use your special AoE Tail Jab to knock talls away, doing more damage with direct hits and even more damage and a stun if they smack into walls. Finally, execute unconscious talls with a headbite that bypasses armor and heals you for a grand amount of health." flavor_description = "Your thirst for tallhost blood surpasses even mine, child. Show no mercy! Slaughter them all!" - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_LURKER) - mutator_actions_to_remove = list( + icon_state_prefix = "Vampire" + + actions_to_remove = list( /datum/action/xeno_action/onclick/lurker_invisibility, /datum/action/xeno_action/onclick/lurker_assassinate, /datum/action/xeno_action/activable/pounce/lurker, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/activable/pounce/rush, /datum/action/xeno_action/activable/flurry, /datum/action/xeno_action/activable/tail_jab, /datum/action/xeno_action/activable/headbite, ) - keystone = TRUE - -/datum/xeno_mutator/Vampire/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == FALSE) - return - - var/mob/living/carbon/xenomorph/lurker/lurker = mutator_set.xeno +/datum/xeno_strain/vampire/apply_strain(mob/living/carbon/xenomorph/lurker/lurker) lurker.plasmapool_modifier = 0 lurker.health_modifier -= XENO_HEALTH_MOD_MED lurker.speed_modifier += XENO_SPEED_FASTMOD_TIER_1 @@ -34,7 +25,4 @@ lurker.melee_damage_upper = XENO_DAMAGE_TIER_3 lurker.attack_speed_modifier -= 2 - mutator_update_actions(lurker) - mutator_set.recalculate_actions(description, flavor_description) lurker.recalculate_everything() - lurker.mutation_type = LURKER_VAMPIRE diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/dancer.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/dancer.dm similarity index 68% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/dancer.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/dancer.dm index a21f549ea8cd..f9a5dbedb614 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/dancer.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/dancer.dm @@ -1,44 +1,31 @@ -/datum/xeno_mutator/praetorian_dancer +/datum/xeno_strain/dancer // My name is Cuban Pete, I'm the King of the Rumba Beat - name = "STRAIN: Praetorian - Dancer" + name = PRAETORIAN_DANCER description = "You lose all of your acid-based abilities and a small amount of your armor in exchange for increased movement speed, evasion, and unparalleled agility that gives you an ability to move even more quickly, dodge bullets, and phase through tallhosts. By slashing tallhosts, you temporarily increase your movement speed and you also you apply a tag that changes how your two new tail abilities function. By tagging hosts, you will make Impale hit twice instead of once and make Tail Trip knock hosts down instead of stunning them." flavor_description = "Demonstrate to the talls what 'there is beauty in death' truly symbolizes, then dance upon their graves!" - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_PRAETORIAN) // Only bae - mutator_actions_to_remove = list( + icon_state_prefix = "Dancer" + + actions_to_remove = list( /datum/action/xeno_action/activable/xeno_spit, /datum/action/xeno_action/activable/pounce/base_prae_dash, /datum/action/xeno_action/activable/prae_acid_ball, /datum/action/xeno_action/activable/spray_acid/base_prae_spray_acid, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/activable/prae_impale, /datum/action/xeno_action/onclick/prae_dodge, /datum/action/xeno_action/activable/prae_tail_trip, ) - behavior_delegate_type = /datum/behavior_delegate/praetorian_dancer - keystone = TRUE - -/datum/xeno_mutator/praetorian_dancer/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return - - var/mob/living/carbon/xenomorph/praetorian/praetorian = mutator_set.xeno - praetorian.armor_modifier -= XENO_ARMOR_MOD_VERY_SMALL - praetorian.speed_modifier += XENO_SPEED_FASTMOD_TIER_5 - praetorian.plasma_types = list(PLASMA_CATECHOLAMINE) - praetorian.claw_type = CLAW_TYPE_SHARP - mutator_update_actions(praetorian) - mutator_set.recalculate_actions(description, flavor_description) + behavior_delegate_type = /datum/behavior_delegate/praetorian_dancer - praetorian.recalculate_everything() +/datum/xeno_strain/dancer/apply_strain(mob/living/carbon/xenomorph/praetorian/prae) + prae.armor_modifier -= XENO_ARMOR_MOD_VERY_SMALL + prae.speed_modifier += XENO_SPEED_FASTMOD_TIER_5 + prae.plasma_types = list(PLASMA_CATECHOLAMINE) + prae.claw_type = CLAW_TYPE_SHARP - apply_behavior_holder(praetorian) - praetorian.mutation_icon_state = PRAETORIAN_DANCER - praetorian.mutation_type = PRAETORIAN_DANCER + prae.recalculate_everything() /datum/behavior_delegate/praetorian_dancer name = "Praetorian Dancer Behavior Delegate" diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/oppressor.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/oppressor.dm similarity index 67% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/oppressor.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/oppressor.dm index 4beaedf8d6a8..91ea59661462 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/oppressor.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/oppressor.dm @@ -1,12 +1,11 @@ -/datum/xeno_mutator/praetorian_oppressor +/datum/xeno_strain/oppressor // Dread it, run from it, destiny still arrives... or should I say, I do - name = "STRAIN: Praetorian - Oppressor" + name = PRAETORIAN_OPPRESSOR description = "You abandon all of your acid-based abilities, your dash, some speed, and a bit of your slash damage for some resistance against small explosives, slashes that deal extra damage to prone targets, and a powerful hook ability that pulls up to three talls towards you, slows them, and has varying effects depending on how many talls you pull. You also gain a powerful punch that reduces your other abilities' cooldowns, pierces through armor, and does double damage in addition to rooting slowed targets. You can also knock talls back and slow them with your new Tail Lash and quickly grab a tall, slow it, and pull it towards you with your unique Tail Stab." flavor_description = "Dread it. Run from it. The Hive arrives all the same, or, more accurately, you do." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_PRAETORIAN) - mutator_actions_to_remove = list( + icon_state_prefix = "Oppressor" + + actions_to_remove = list( /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/xeno_spit, /datum/action/xeno_action/activable/pounce/base_prae_dash, @@ -14,38 +13,24 @@ /datum/action/xeno_action/activable/spray_acid/base_prae_spray_acid, /datum/action/xeno_action/activable/corrosive_acid, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/activable/tail_stab/tail_seize, /datum/action/xeno_action/activable/prae_abduct, /datum/action/xeno_action/activable/oppressor_punch, /datum/action/xeno_action/activable/tail_lash, ) - behavior_delegate_type = /datum/behavior_delegate/oppressor_praetorian - keystone = TRUE - -/datum/xeno_mutator/praetorian_oppressor/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return - - var/mob/living/carbon/xenomorph/praetorian/praetorian = mutator_set.xeno - praetorian.damage_modifier -= XENO_DAMAGE_MOD_SMALL - praetorian.explosivearmor_modifier += XENO_EXPOSIVEARMOR_MOD_SMALL - praetorian.small_explosives_stun = FALSE - praetorian.speed_modifier += XENO_SPEED_SLOWMOD_TIER_5 - praetorian.plasma_types = list(PLASMA_NEUROTOXIN, PLASMA_CHITIN) - praetorian.claw_type = CLAW_TYPE_SHARP - - mutator_update_actions(praetorian) - - mutator_set.recalculate_actions(description, flavor_description) + behavior_delegate_type = /datum/behavior_delegate/oppressor_praetorian - apply_behavior_holder(praetorian) +/datum/xeno_strain/oppressor/apply_strain(mob/living/carbon/xenomorph/praetorian/prae) + prae.damage_modifier -= XENO_DAMAGE_MOD_SMALL + prae.explosivearmor_modifier += XENO_EXPOSIVEARMOR_MOD_SMALL + prae.small_explosives_stun = FALSE + prae.speed_modifier += XENO_SPEED_SLOWMOD_TIER_5 + prae.plasma_types = list(PLASMA_NEUROTOXIN, PLASMA_CHITIN) + prae.claw_type = CLAW_TYPE_SHARP - praetorian.recalculate_everything() - praetorian.mutation_icon_state = PRAETORIAN_OPPRESSOR - praetorian.mutation_type = PRAETORIAN_OPPRESSOR + prae.recalculate_everything() /datum/behavior_delegate/oppressor_praetorian name = "Oppressor Praetorian Behavior Delegate" @@ -60,4 +45,3 @@ target_carbon.apply_armoured_damage(get_xeno_damage_slash(target_carbon, tearing_damage), ARMOR_MELEE, BRUTE, bound_xeno.zone_selected ? bound_xeno.zone_selected : "chest") target_carbon.visible_message(SPAN_DANGER("[bound_xeno] tears into [target_carbon]!")) playsound(bound_xeno, 'sound/weapons/alien_tail_attack.ogg', 25, TRUE) - diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/vanguard.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/vanguard.dm similarity index 80% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/vanguard.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/vanguard.dm index d5ca8c4d6aad..2a344523e974 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/vanguard.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/vanguard.dm @@ -1,43 +1,31 @@ -/datum/xeno_mutator/vanguard - name = "STRAIN: Praetorian - Vanguard" +/datum/xeno_strain/vanguard + name = PRAETORIAN_VANGUARD description = "You forfeit all of your acid-based abilities and some health for some extra speed and a rechargable shield that can block one attack. Use your Pierce from up to three paces away to stab through talls, while stabbing through several will completely recharge your shield. Use your charge to plow through enemies and use it again to unleash a powerful AoE slash that reaches up to three paces. You also have a Cleave ability, amplified by your shield, which you can toggle to either immobilize or fling a target away." flavor_description = "They are my bulwark against the tallhosts. They are my Vanguard and they shall know no fear." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_PRAETORIAN) //Only praetorian. - mutator_actions_to_remove = list( + icon_state_prefix = "Vanguard" + + actions_to_remove = list( /datum/action/xeno_action/activable/xeno_spit, /datum/action/xeno_action/activable/pounce/base_prae_dash, /datum/action/xeno_action/activable/prae_acid_ball, /datum/action/xeno_action/activable/spray_acid/base_prae_spray_acid, /datum/action/xeno_action/activable/corrosive_acid, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/activable/pierce, /datum/action/xeno_action/activable/pounce/prae_dash, /datum/action/xeno_action/activable/cleave, /datum/action/xeno_action/onclick/toggle_cleave, ) - behavior_delegate_type = /datum/behavior_delegate/praetorian_vanguard - keystone = TRUE - -/datum/xeno_mutator/vanguard/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return - var/mob/living/carbon/xenomorph/praetorian/praetorian = mutator_set.xeno - praetorian.speed_modifier += XENO_SPEED_FASTMOD_TIER_3 - praetorian.health_modifier -= XENO_HEALTH_MOD_MED - praetorian.claw_type = CLAW_TYPE_SHARP - mutator_update_actions(praetorian) - mutator_set.recalculate_actions(description, flavor_description) - praetorian.recalculate_everything() + behavior_delegate_type = /datum/behavior_delegate/praetorian_vanguard - praetorian.mutation_icon_state = PRAETORIAN_VANGUARD - praetorian.mutation_type = PRAETORIAN_VANGUARD +/datum/xeno_strain/vanguard/apply_strain(mob/living/carbon/xenomorph/praetorian/prae) + prae.speed_modifier += XENO_SPEED_FASTMOD_TIER_3 + prae.health_modifier -= XENO_HEALTH_MOD_MED + prae.claw_type = CLAW_TYPE_SHARP - apply_behavior_holder(praetorian) + prae.recalculate_everything() /datum/behavior_delegate/praetorian_vanguard name = "Praetorian Vanguard Behavior Delegate" @@ -103,8 +91,3 @@ var/datum/action/xeno_action/activable/cleave/caction = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/activable/cleave) if (istype(caction)) caction.buffed = TRUE - - - - - diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/warden.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/warden.dm similarity index 80% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/warden.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/warden.dm index d9946fb9ec64..313778baf038 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/praetorian/warden.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/warden.dm @@ -1,19 +1,18 @@ -/datum/xeno_mutator/praetorian_warden +/datum/xeno_strain/warden // i mean so basically im braum - name = "STRAIN: Praetorian - Warden" + name = PRAETORIAN_WARDEN description = "You trade your acid ball, acid spray, dash, and a small bit of your slash damage and speed to become an effective medic. You gain the ability to emit strong pheromones, an ability that retrieves endangered, knocked-down or sitting allies and pulls them to your location, and you gain an internal hitpoint pool that fills with every slash against your enemies, which can be spent to aid your allies and yourself by healing them or curing their ailments." flavor_description = "Only in death does your sisters' service to the Queen end. They will be untouched by plague or disease; no sickness will blight them." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_PRAETORIAN) // Only bae - mutator_actions_to_remove = list( + icon_state_prefix = "Warden" + + actions_to_remove = list( /datum/action/xeno_action/activable/xeno_spit, /datum/action/xeno_action/activable/pounce/base_prae_dash, /datum/action/xeno_action/activable/prae_acid_ball, /datum/action/xeno_action/activable/spray_acid/base_prae_spray_acid, /datum/action/xeno_action/onclick/tacmap, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/onclick/emit_pheromones, /datum/action/xeno_action/activable/xeno_spit, /datum/action/xeno_action/activable/spray_acid/prae_warden, @@ -22,28 +21,15 @@ /datum/action/xeno_action/onclick/prae_switch_heal_type, /datum/action/xeno_action/onclick/tacmap, ) - behavior_delegate_type = /datum/behavior_delegate/praetorian_warden - keystone = TRUE - -/datum/xeno_mutator/praetorian_warden/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return - var/mob/living/carbon/xenomorph/praetorian/praetorian = mutator_set.xeno + behavior_delegate_type = /datum/behavior_delegate/praetorian_warden +/datum/xeno_strain/warden/apply_strain(mob/living/carbon/xenomorph/praetorian/prae) // Make a 'halftank' - praetorian.speed_modifier += XENO_SPEED_SLOWMOD_TIER_5 - praetorian.damage_modifier -= XENO_DAMAGE_MOD_SMALL - - mutator_update_actions(praetorian) - mutator_set.recalculate_actions(description, flavor_description) + prae.speed_modifier += XENO_SPEED_SLOWMOD_TIER_5 + prae.damage_modifier -= XENO_DAMAGE_MOD_SMALL - praetorian.recalculate_everything() - - apply_behavior_holder(praetorian) - praetorian.mutation_icon_state = PRAETORIAN_WARDEN - praetorian.mutation_type = PRAETORIAN_WARDEN + prae.recalculate_everything() /datum/behavior_delegate/praetorian_warden name = "Praetorian Warden Behavior Delegate" @@ -58,7 +44,6 @@ var/internal_hitpoints = 0 var/transferred_healing = 0 - /datum/behavior_delegate/praetorian_warden/append_to_stat() . = list() . += "Energy Reserves: [internal_hitpoints]/[internal_hitpoints_max]" diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/ravager/berserker.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm similarity index 90% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/ravager/berserker.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm index 7881c9aa75f1..c12324aa5b2a 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/ravager/berserker.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm @@ -1,39 +1,28 @@ -/datum/xeno_mutator/berserker - name = "STRAIN: Ravager - Berserker" +/datum/xeno_strain/berserker + name = RAVAGER_BERSERKER description = "You lose your empower, charge, and scissor cut, decrease your health, and sacrifice a bit of your influence under frenzy pheromones to increase your movement speed, slightly increase your armor, and gain a new set of abilities that make you a terrifying melee monster. By slashing, you heal yourself and gain a stack of rage that increases your armor, movement speed, attack speed, and your heals per slash, to a maximum of six rage. Use your new Appehend ability to increase your movement speed and apply a slow on the next target you slash and use your Clothesline ability to fling your target to heal yourself, even more-so if you have a rage stack that will be used up. Finally, use your Eviscerate to unleash a devastating windmill attack that heals you for every host you hit after an immobilizing wind-up." flavor_description = "They shall be my finest warriors. They will rend and tear, crush and butcher, and maim and rage until every tallhost falls." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_RAVAGER) - mutator_actions_to_remove = list( + icon_state_prefix = "Berserker" + + actions_to_remove = list( /datum/action/xeno_action/onclick/empower, /datum/action/xeno_action/activable/pounce/charge, /datum/action/xeno_action/activable/scissor_cut, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/onclick/apprehend, /datum/action/xeno_action/activable/clothesline, /datum/action/xeno_action/activable/eviscerate, ) - keystone = TRUE - behavior_delegate_type = /datum/behavior_delegate/ravager_berserker -/datum/xeno_mutator/berserker/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return + behavior_delegate_type = /datum/behavior_delegate/ravager_berserker - var/mob/living/carbon/xenomorph/ravager/ravager = mutator_set.xeno - ravager.mutation_type = RAVAGER_BERSERKER +/datum/xeno_strain/berserker/apply_strain(mob/living/carbon/xenomorph/ravager/ravager) ravager.plasma_max = 0 ravager.health_modifier -= XENO_HEALTH_MOD_MED ravager.armor_modifier += XENO_ARMOR_MOD_VERY_SMALL ravager.speed_modifier += XENO_SPEED_FASTMOD_TIER_3 ravager.received_phero_caps["frenzy"] = 2.9 // Moderate - mutator_update_actions(ravager) - mutator_set.recalculate_actions(description, flavor_description) - - apply_behavior_holder(ravager) ravager.recalculate_everything() diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/ravager/hedgehog.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm similarity index 87% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/ravager/hedgehog.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm index 913883549fba..e1d6dc583416 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/ravager/hedgehog.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm @@ -1,41 +1,28 @@ -/datum/xeno_mutator/hedgehog - name = "STRAIN: Ravager - Hedgehog" +/datum/xeno_strain/hedgehog + name = RAVAGER_HEDGEHOG description = "You lose your empower, charge, scissor cut and some slash damage, for a bit more explosive resistance, immunity to small explosions, and you gain several new abilities that allow you to become a spiky tank. You build up shards internally over time and also when taking damage that increase your armor's resilience. You can use these shards to power three new abilities: Spike Shield, which gives you a temporary shield that spits bone shards around you when damaged, Fire Spikes, which launches spikes at your target that slows them and does extra damage if they move, and finally, Spike Shed, which launches spikes all around yourself and gives you a temporary speed boost as an escape plan at the cost of all your stored shards and being unable to gain shards for thirty seconds." flavor_description = "They will be of iron will and steely muscle. In great armor shall they be clad, and with the mightiest spikes will they be armed." - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_RAVAGER) // Only Ravager. - mutator_actions_to_remove = list( + icon_state_prefix = "Hedgehog" + + actions_to_remove = list( /datum/action/xeno_action/onclick/empower, /datum/action/xeno_action/activable/pounce/charge, /datum/action/xeno_action/activable/scissor_cut, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/onclick/spike_shield, /datum/action/xeno_action/activable/rav_spikes, /datum/action/xeno_action/onclick/spike_shed, ) - behavior_delegate_type = /datum/behavior_delegate/ravager_hedgehog - keystone = TRUE - -/datum/xeno_mutator/hedgehog/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return - var/mob/living/carbon/xenomorph/ravager/ravager = mutator_set.xeno + behavior_delegate_type = /datum/behavior_delegate/ravager_hedgehog - ravager.mutation_type = RAVAGER_HEDGEHOG +/datum/xeno_strain/hedgehog/apply_strain(mob/living/carbon/xenomorph/ravager/ravager) ravager.plasma_max = 0 ravager.small_explosives_stun = FALSE ravager.explosivearmor_modifier += XENO_EXPOSIVEARMOR_MOD_SMALL ravager.damage_modifier -= XENO_DAMAGE_MOD_SMALL - apply_behavior_holder(ravager) - - mutator_update_actions(ravager) - mutator_set.recalculate_actions(description, flavor_description) - ravager.recalculate_everything() /datum/behavior_delegate/ravager_hedgehog diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/runner/acid.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm similarity index 90% rename from code/modules/mob/living/carbon/xenomorph/mutators/strains/runner/acid.dm rename to code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm index 490e5ca36cba..7b9bafadeb7b 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/runner/acid.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm @@ -1,37 +1,27 @@ -/datum/xeno_mutator/acider - name = "STRAIN: Runner - Acider" +/datum/xeno_strain/acider + name = RUNNER_ACIDER description = "At the cost of a little bit of your speed and all of your current abilities, you gain a considerable amount of health, some armor, and a new organ that fills with volatile acid over time. Your Tail Stab and slashes apply acid to living lifeforms that slowly burns them, and slashes against targets with acid stacks fill your acid glands. You also gain Corrosive Acid equivalent to that of a boiler that you can deploy more quickly than any other caste, at the cost of a chunk of your acid reserves with each use. Finally, after a twenty second windup, you can force your body to explode, covering everything near you with acid. The more acid you have stored, the more devastating the explosion will be, but during those twenty seconds before detonation you are slowed and give off several warning signals which give talls an opportunity to end you before you can detonate. If you successfully explode, you will reincarnate as a larva again!" flavor_description = "Burn their walls, maim their faces! Your life, for The Hive!" - cost = MUTATOR_COST_EXPENSIVE - individual_only = TRUE - caste_whitelist = list(XENO_CASTE_RUNNER) - keystone = TRUE - behavior_delegate_type = /datum/behavior_delegate/runner_acider - mutator_actions_to_remove = list( + icon_state_prefix = "Acider" + + actions_to_remove = list( /datum/action/xeno_action/activable/pounce/runner, /datum/action/xeno_action/activable/runner_skillshot, /datum/action/xeno_action/onclick/toggle_long_range/runner, ) - mutator_actions_to_add = list( + actions_to_add = list( /datum/action/xeno_action/activable/acider_acid, /datum/action/xeno_action/activable/acider_for_the_hive, ) -/datum/xeno_mutator/acider/apply_mutator(datum/mutator_set/individual_mutators/mutator_set) - . = ..() - if (. == 0) - return + behavior_delegate_type = /datum/behavior_delegate/runner_acider - var/mob/living/carbon/xenomorph/runner/runner = mutator_set.xeno - runner.mutation_icon_state = RUNNER_ACIDER - runner.mutation_type = RUNNER_ACIDER +/datum/xeno_strain/acider/apply_strain(mob/living/carbon/xenomorph/runner/runner) runner.speed_modifier += XENO_SPEED_SLOWMOD_TIER_5 runner.armor_modifier += XENO_ARMOR_MOD_MED runner.health_modifier += XENO_HEALTH_MOD_ACIDER - apply_behavior_holder(runner) - mutator_update_actions(runner) + runner.recalculate_everything() - mutator_set.recalculate_actions(description, flavor_description) /datum/behavior_delegate/runner_acider var/acid_amount = 0 diff --git a/code/modules/mob/living/carbon/xenomorph/strains/xeno_strain.dm b/code/modules/mob/living/carbon/xenomorph/strains/xeno_strain.dm new file mode 100644 index 000000000000..18f1f892ddfa --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/strains/xeno_strain.dm @@ -0,0 +1,131 @@ +/datum/xeno_strain + /// The name of the strain. Should be short but informative. + var/name + /// Description to be displayed on purchase. + var/description + /// (OPTIONAL) Flavor text to be shown on purchase. Semi-OOC + var/flavor_description + /// (OPTIONAL) A custom icon state prefix for xenos who have taken the strain. + var/icon_state_prefix + + /// A list of action typepaths which should be removed when a xeno takes the strain. + var/list/actions_to_remove + /// A list of action typepaths which should be added when a xeno takes the strain. + var/list/actions_to_add + + /// Typepath of the [/datum/behavior_delegate] to add. + var/behavior_delegate_type + +/** + * Add this strain to `xeno`, replacing their actions and behavior holder. + * + * Returns a bool indicating if the strain was successfully applied. + * **Override [/datum/xeno_strain/proc/apply_strain], not this! (Unless you know what you're doing.)** + */ +/datum/xeno_strain/proc/_add_to_xeno(mob/living/carbon/xenomorph/xeno) + SHOULD_NOT_OVERRIDE(TRUE) + + xeno.strain = src + + // Update the xeno's actions. + for(var/action_path in actions_to_remove) + remove_action(xeno, action_path) + for(var/action_path in actions_to_add) + give_action(xeno, action_path) + + // Update the xeno's behavior delegate. + if(behavior_delegate_type) + if(xeno.behavior_delegate) + qdel(xeno.behavior_delegate) + xeno.behavior_delegate = new behavior_delegate_type() + xeno.behavior_delegate.bound_xeno = xeno + xeno.behavior_delegate.add_to_xeno() + + apply_strain(xeno) + + xeno.update_icons() + xeno.hive.hive_ui.update_xeno_info() + + // Give them all of the info about the strain. + to_chat(xeno, SPAN_XENOANNOUNCE(description)) + if(flavor_description) + to_chat(xeno, SPAN_XENOLEADER(flavor_description)) + return TRUE + +/** + * Adds any special modifiers/changes from this strain to `xeno`. + * + * Called when the strain is first added to the player. + */ +/datum/xeno_strain/proc/apply_strain(mob/living/carbon/xenomorph/xeno) + // Override with custom behaviour. + return + + +/mob/living/carbon/xenomorph/verb/purchase_strain() + set name = "Purchase Strain" + set desc = "Purchase a strain for yourself" + set category = "Alien" + + // Firstly, make sure the xeno is actually able to take a strain. + if(!can_take_strain()) + return + + // Make an assoc list of {name: typepath} from the strains available to the xeno's caste. + var/list/strain_list = list() + for(var/datum/xeno_strain/strain_type as anything in caste.available_strains) + strain_list[initial(strain_type.name)] = strain_type + + // Ask the user which strain they want. + var/strain_choice = tgui_input_list(usr, "Which strain would you like to take?", "Choose Strain", strain_list, theme = "hive_status") + if(!strain_choice) + return + var/datum/xeno_strain/chosen_strain = strain_list[strain_choice] + + // Check again after the user picks one, in case anything changed. + if(!can_take_strain()) + return + // Show the user the strain's description, and double check that they want it. + if(alert(usr, "[initial(chosen_strain.description)]\n\nConfirm mutation?", "Choose Strain", "Yes", "No") != "Yes") + return + // One more time after they confirm. + if(!can_take_strain()) + return + + // Create the strain datum and apply it to the xeno. + var/datum/xeno_strain/strain_instance = new chosen_strain() + if(strain_instance._add_to_xeno(src)) + xeno_jitter(1.5 SECONDS) + // If it applied successfully, add it to the logs. + log_strain("[name] purchased strain '[strain_instance.type]'") + +/// Is this xeno currently able to take a strain? +/mob/living/carbon/xenomorph/proc/can_take_strain() + if(!length(caste.available_strains) || !check_state(TRUE)) + return FALSE + + if(strain) + to_chat(src, SPAN_WARNING("We have already chosen a strain.")) + return FALSE + + if(is_ventcrawling) + to_chat(src, SPAN_WARNING("This place is too constraining to take a strain.")) + return FALSE + + if(!isturf(loc)) + to_chat(src, SPAN_WARNING("We can't take a strain here.")) + return FALSE + + if(handcuffed || legcuffed) + to_chat(src, SPAN_WARNING("The restraints are too restricting to allow us to take a strain.")) + return FALSE + + if(health < maxHealth) + to_chat(src, SPAN_WARNING("We must be at full health to take a strain.")) + return FALSE + + if(agility || fortify || crest_defense || stealth) + to_chat(src, SPAN_WARNING("We cannot take a strain while in this stance.")) + return FALSE + + return TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/update_icons.dm b/code/modules/mob/living/carbon/xenomorph/update_icons.dm index 55995ec0b264..571f261ab981 100644 --- a/code/modules/mob/living/carbon/xenomorph/update_icons.dm +++ b/code/modules/mob/living/carbon/xenomorph/update_icons.dm @@ -41,7 +41,7 @@ Q.queen_standing_icon = icon_xeno Q.queen_ovipositor_icon = 'icons/mob/xenos/ovipositor.dmi' - var/mutation_caste_state = "[mutation_type] [caste.caste_type]" + var/mutation_caste_state = "[get_strain_icon()] [caste.caste_type]" if(!walking_state_cache[mutation_caste_state]) var/cache_walking_state = FALSE for(var/state in icon_states(icon)) @@ -64,7 +64,7 @@ if(behavior_delegate?.on_update_icons()) return - var/mutation_caste_state = "[mutation_icon_state || mutation_type] [caste.caste_type]" + var/mutation_caste_state = "[get_strain_icon()] [caste.caste_type]" if(stat == DEAD) icon_state = "[mutation_caste_state] Dead" if(!(icon_state in icon_states(icon_xeno))) diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_helpers.dm b/code/modules/mob/living/carbon/xenomorph/xeno_helpers.dm index 7324a3af6892..2e4b968d5a59 100644 --- a/code/modules/mob/living/carbon/xenomorph/xeno_helpers.dm +++ b/code/modules/mob/living/carbon/xenomorph/xeno_helpers.dm @@ -34,6 +34,23 @@ return 100 return round(armor_integrity * 100 / armor_integrity_max) +/** + * Returns the name of the xeno's strain, if it has one. + * + * If that can't be found, returns "Normal". + */ +/mob/living/carbon/xenomorph/proc/get_strain_name() + return strain?.name || "Normal" + +/** + * Returns the custom icon state from the xeno's strain, if it has one. + * + * If that can't be found, returns "Normal" + */ +/mob/living/carbon/xenomorph/proc/get_strain_icon() + return strain?.icon_state_prefix || "Normal" + // TODO: Go through xeno/xenoid sprites and remove "Normal", so that this isn't needed. + //These don't do much currently. Or anything? Only around for legacy code. /mob/living/carbon/xenomorph/is_mob_restrained() return 0 @@ -58,4 +75,4 @@ return caste.fire_intensity_resistance /mob/living/carbon/xenomorph/alter_ghost(mob/dead/observer/ghost) - ghost.icon_state = "[mutation_type] [caste.caste_type] Running" + ghost.icon_state = "[get_strain_icon()] [caste.caste_type] Running" diff --git a/code/modules/mob/mob_verbs.dm b/code/modules/mob/mob_verbs.dm index 1ba8985d56bd..f12d00cc0988 100644 --- a/code/modules/mob/mob_verbs.dm +++ b/code/modules/mob/mob_verbs.dm @@ -50,34 +50,33 @@ to_chat(usr, SPAN_DANGER("This mob type cannot throw items.")) return -/mob/proc/point_to(atom/A in view()) +/mob/proc/point_to(atom/target in view()) //set name = "Point To" //set category = "Object" - if(!isturf(src.loc) || !(A in view(src)))//target is no longer visible to us - return 0 + if(!isturf(src.loc) || !(target in view(src)))//target is no longer visible to us + return FALSE - if(!A.mouse_opacity)//can't click it? can't point at it. - return 0 + if(!target.mouse_opacity)//can't click it? can't point at it. + return FALSE if(is_mob_incapacitated() || (status_flags & FAKEDEATH)) //incapacitated, can't point - return 0 + return FALSE - var/tile = get_turf(A) - if (!tile) - return 0 + var/tile = get_turf(target) + if(!tile) + return FALSE if(recently_pointed_to > world.time) - return 0 - - next_move = world.time + 2 - - point_to_atom(A, tile) - return 1 - + return FALSE + if(SEND_SIGNAL(src, COMSIG_MOB_TRY_POINT, target) & COMPONENT_OVERRIDE_POINT) + return FALSE + next_move = world.time + 2 + point_to_atom(target, tile) + return TRUE /mob/verb/memory() set name = "Notes" diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 497b95061d6c..3f7533f26620 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1344,7 +1344,7 @@ and you're good to go. user.next_move = world.time //No click delay on PBs. //Point blanking doesn't actually fire the projectile. Instead, it simulates firing the bullet proper. - if(!able_to_fire(user)) //If it's a valid PB aside from that you can't fire the gun, do nothing. + if(flags_gun_features & GUN_BURST_FIRING || !able_to_fire(user)) //If it's a valid PB aside from that you can't fire the gun, do nothing. return TRUE //The following relating to bursts was borrowed from Fire code. diff --git a/code/modules/projectiles/gun_attachables.dm b/code/modules/projectiles/gun_attachables.dm index 1807213f6226..37094bb12775 100644 --- a/code/modules/projectiles/gun_attachables.dm +++ b/code/modules/projectiles/gun_attachables.dm @@ -3301,6 +3301,10 @@ Defined in conflicts.dm of the #defines folder. var/bipod_deployed = FALSE /// If this should anchor the user while in use var/heavy_bipod = FALSE + // Are switching to full auto when deploying the bipod + var/full_auto_switch = FALSE + // Store our old firemode so we can switch to it when undeploying the bipod + var/old_firemode = null /obj/item/attachable/bipod/New() ..() @@ -3311,16 +3315,33 @@ Defined in conflicts.dm of the #defines folder. scatter_mod = SCATTER_AMOUNT_TIER_9 recoil_mod = RECOIL_AMOUNT_TIER_5 -/obj/item/attachable/bipod/Attach(obj/item/weapon/gun/G) +/obj/item/attachable/bipod/Attach(obj/item/weapon/gun/gun, mob/user) ..() - RegisterSignal(G, COMSIG_ITEM_DROPPED, PROC_REF(handle_drop)) + if((GUN_FIREMODE_AUTOMATIC in gun.gun_firemode_list) || (gun.flags_gun_features & GUN_SUPPORT_PLATFORM)) + var/given_action = FALSE + if(user && (gun == user.l_hand || gun == user.r_hand)) + give_action(user, /datum/action/item_action/bipod/toggle_full_auto_switch, src, gun) + given_action = TRUE + if(!given_action) + new /datum/action/item_action/bipod/toggle_full_auto_switch(src, gun) + + RegisterSignal(gun, COMSIG_ITEM_DROPPED, PROC_REF(handle_drop)) /obj/item/attachable/bipod/Detach(mob/user, obj/item/weapon/gun/detaching_gub) UnregisterSignal(detaching_gub, COMSIG_ITEM_DROPPED) + //clear out anything related to full auto switching + full_auto_switch = FALSE + old_firemode = null + for(var/item_action in detaching_gub.actions) + var/datum/action/item_action/bipod/toggle_full_auto_switch/target_action = item_action + if(target_action.target == src) + qdel(item_action) + break + if(bipod_deployed) - undeploy_bipod(detaching_gub) + undeploy_bipod(detaching_gub, user) ..() /obj/item/attachable/bipod/update_icon() @@ -3334,60 +3355,62 @@ Defined in conflicts.dm of the #defines folder. if(istype(loc, /obj/item/weapon/gun)) var/obj/item/weapon/gun/gun = loc gun.update_attachable(slot) - for(var/datum/action/A as anything in gun.actions) - A.update_button_icon() + for(var/datum/action/item_action as anything in gun.actions) + if(!istype(item_action, /datum/action/item_action/bipod/toggle_full_auto_switch)) + item_action.update_button_icon() -/obj/item/attachable/bipod/proc/handle_drop(obj/item/weapon/gun/G, mob/living/carbon/human/user) +/obj/item/attachable/bipod/proc/handle_drop(obj/item/weapon/gun/gun, mob/living/carbon/human/user) SIGNAL_HANDLER UnregisterSignal(user, COMSIG_MOB_MOVE_OR_LOOK) if(bipod_deployed) - undeploy_bipod(G) + undeploy_bipod(gun, user) user.apply_effect(1, SUPERSLOW) user.apply_effect(2, SLOW) -/obj/item/attachable/bipod/proc/undeploy_bipod(obj/item/weapon/gun/G) - REMOVE_TRAIT(G, TRAIT_GUN_BIPODDED, "attached_bipod") +/obj/item/attachable/bipod/proc/undeploy_bipod(obj/item/weapon/gun/gun, mob/user) + REMOVE_TRAIT(gun, TRAIT_GUN_BIPODDED, "attached_bipod") bipod_deployed = FALSE accuracy_mod = -HIT_ACCURACY_MULT_TIER_5 scatter_mod = SCATTER_AMOUNT_TIER_9 recoil_mod = RECOIL_AMOUNT_TIER_5 burst_scatter_mod = 0 delay_mod = FIRE_DELAY_TIER_12 - G.recalculate_attachment_bonuses() - G.stop_fire() - var/mob/living/user - if(isliving(G.loc)) - user = G.loc - SEND_SIGNAL(user, COMSIG_MOB_UNDEPLOYED_BIPOD) - UnregisterSignal(user, COMSIG_MOB_MOVE_OR_LOOK) + //if we are no longer on full auto, don't bother switching back to the old firemode + if(full_auto_switch && gun.gun_firemode == GUN_FIREMODE_AUTOMATIC && gun.gun_firemode != old_firemode) + gun.do_toggle_firemode(user, null, old_firemode) - if(G.flags_gun_features & GUN_SUPPORT_PLATFORM) - G.remove_firemode(GUN_FIREMODE_AUTOMATIC) + gun.recalculate_attachment_bonuses() + gun.stop_fire() + SEND_SIGNAL(user, COMSIG_MOB_UNDEPLOYED_BIPOD) + UnregisterSignal(user, COMSIG_MOB_MOVE_OR_LOOK) + + if(gun.flags_gun_features & GUN_SUPPORT_PLATFORM) + gun.remove_firemode(GUN_FIREMODE_AUTOMATIC) if(heavy_bipod) user.anchored = FALSE - if(!QDELETED(G)) + if(!QDELETED(gun)) playsound(user,'sound/items/m56dauto_rotate.ogg', 55, 1) update_icon() -/obj/item/attachable/bipod/activate_attachment(obj/item/weapon/gun/G,mob/living/user, turn_off) +/obj/item/attachable/bipod/activate_attachment(obj/item/weapon/gun/gun, mob/living/user, turn_off) if(turn_off) if(bipod_deployed) - undeploy_bipod(G) + undeploy_bipod(gun, user) else - var/obj/support = check_bipod_support(G, user) + var/obj/support = check_bipod_support(gun, user) if(!support&&!bipod_deployed) to_chat(user, SPAN_NOTICE("You start deploying [src] on the ground.")) - if(!do_after(user, 15, INTERRUPT_ALL, BUSY_ICON_HOSTILE, G,INTERRUPT_DIFF_LOC)) + if(!do_after(user, 15, INTERRUPT_ALL, BUSY_ICON_HOSTILE, gun, INTERRUPT_DIFF_LOC)) return FALSE bipod_deployed = !bipod_deployed if(user) if(bipod_deployed) - ADD_TRAIT(G, TRAIT_GUN_BIPODDED, "attached_bipod") + ADD_TRAIT(gun, TRAIT_GUN_BIPODDED, "attached_bipod") to_chat(user, SPAN_NOTICE("You deploy [src] [support ? "on [support]" : "on the ground"].")) SEND_SIGNAL(user, COMSIG_MOB_DEPLOYED_BIPOD) playsound(user,'sound/items/m56dauto_rotate.ogg', 55, 1) @@ -3395,25 +3418,29 @@ Defined in conflicts.dm of the #defines folder. scatter_mod = -SCATTER_AMOUNT_TIER_10 recoil_mod = -RECOIL_AMOUNT_TIER_4 burst_scatter_mod = -SCATTER_AMOUNT_TIER_8 - if(istype(G,/obj/item/weapon/gun/rifle/sniper/M42A)) + if(istype(gun, /obj/item/weapon/gun/rifle/sniper/M42A)) delay_mod = -FIRE_DELAY_TIER_7 else delay_mod = -FIRE_DELAY_TIER_12 - G.recalculate_attachment_bonuses() - G.stop_fire() + gun.recalculate_attachment_bonuses() + gun.stop_fire() initial_mob_dir = user.dir RegisterSignal(user, COMSIG_MOB_MOVE_OR_LOOK, PROC_REF(handle_mob_move_or_look)) - if(G.flags_gun_features & GUN_SUPPORT_PLATFORM) - G.add_firemode(GUN_FIREMODE_AUTOMATIC) + if(gun.flags_gun_features & GUN_SUPPORT_PLATFORM) + gun.add_firemode(GUN_FIREMODE_AUTOMATIC) if(heavy_bipod) user.anchored = TRUE + old_firemode = gun.gun_firemode + if(full_auto_switch && gun.gun_firemode != GUN_FIREMODE_AUTOMATIC) + gun.do_toggle_firemode(user, null, GUN_FIREMODE_AUTOMATIC) + else to_chat(user, SPAN_NOTICE("You retract [src].")) - undeploy_bipod(G) + undeploy_bipod(gun, user) update_icon() @@ -3430,10 +3457,10 @@ Defined in conflicts.dm of the #defines folder. //when user fires the gun, we check if they have something to support the gun's bipod. -/obj/item/attachable/proc/check_bipod_support(obj/item/weapon/gun/G, mob/living/user) +/obj/item/attachable/proc/check_bipod_support(obj/item/weapon/gun/gun, mob/living/user) return 0 -/obj/item/attachable/bipod/check_bipod_support(obj/item/weapon/gun/G, mob/living/user) +/obj/item/attachable/bipod/check_bipod_support(obj/item/weapon/gun/gun, mob/living/user) var/turf/T = get_turf(user) for(var/obj/O in T) if(O.throwpass && O.density && O.dir == user.dir && O.flags_atom & ON_BORDER) @@ -3445,6 +3472,31 @@ Defined in conflicts.dm of the #defines folder. return O2 return 0 +//item actions for handling deployment to full auto. +/datum/action/item_action/bipod/toggle_full_auto_switch/New(Target, obj/item/holder) + . = ..() + name = "Toggle Full Auto Switch" + action_icon_state = "full_auto_switch" + button.name = name + button.overlays.Cut() + button.overlays += image('icons/mob/hud/actions.dmi', button, action_icon_state) + +/datum/action/item_action/bipod/toggle_full_auto_switch/action_activate() + var/obj/item/weapon/gun/holder_gun = holder_item + var/obj/item/attachable/bipod/attached_bipod = holder_gun.attachments["under"] + + attached_bipod.full_auto_switch = !attached_bipod.full_auto_switch + to_chat(owner, SPAN_NOTICE("[icon2html(holder_gun, owner)] You will [attached_bipod.full_auto_switch? "start" : "stop"] switching to full auto when deploying the bipod.")) + playsound(owner, 'sound/weapons/handling/gun_burst_toggle.ogg', 15, 1) + + if(attached_bipod.full_auto_switch) + button.icon_state = "template_on" + else + button.icon_state = "template" + + button.overlays.Cut() + button.overlays += image('icons/mob/hud/actions.dmi', button, action_icon_state) + /obj/item/attachable/bipod/m60 name = "bipod" diff --git a/code/modules/projectiles/gun_helpers.dm b/code/modules/projectiles/gun_helpers.dm index 66e3a65f2f77..781db8fc1222 100644 --- a/code/modules/projectiles/gun_helpers.dm +++ b/code/modules/projectiles/gun_helpers.dm @@ -390,7 +390,7 @@ DEFINES in setup.dm, referenced here. user.visible_message(SPAN_NOTICE("[user] attaches [attachment] to [src]."), SPAN_NOTICE("You attach [attachment] to [src]."), null, 4) user.temp_drop_inv_item(attachment) - attachment.Attach(src) + attachment.Attach(src, user) update_attachable(attachment.slot) playsound(user, 'sound/handling/attachment_add.ogg', 15, 1, 4) return TRUE diff --git a/code/modules/projectiles/guns/rifles.dm b/code/modules/projectiles/guns/rifles.dm index 29b2c55b7ec0..b631f093bafe 100644 --- a/code/modules/projectiles/guns/rifles.dm +++ b/code/modules/projectiles/guns/rifles.dm @@ -1763,9 +1763,9 @@ /obj/item/weapon/gun/rifle/rmc_f90/set_gun_config_values() ..() - fire_delay = FIRE_DELAY_TIER_8 + fire_delay = FIRE_DELAY_TIER_11 burst_amount = BURST_AMOUNT_TIER_3 - burst_delay = FIRE_DELAY_TIER_8 + burst_delay = FIRE_DELAY_TIER_11 accuracy_mult = BASE_ACCURACY_MULT + HIT_ACCURACY_MULT_TIER_4 + 2*HIT_ACCURACY_MULT_TIER_1 accuracy_mult_unwielded = BASE_ACCURACY_MULT - HIT_ACCURACY_MULT_TIER_7 scatter = SCATTER_AMOUNT_TIER_8 @@ -1804,7 +1804,7 @@ /obj/item/weapon/gun/rifle/rmc_f90/scope/set_gun_config_values() ..() - fire_delay = FIRE_DELAY_TIER_7 + fire_delay = FIRE_DELAY_TIER_11 burst_amount = 0 accuracy_mult = BASE_ACCURACY_MULT + HIT_ACCURACY_MULT_TIER_4 + 2*HIT_ACCURACY_MULT_TIER_1 accuracy_mult_unwielded = BASE_ACCURACY_MULT - HIT_ACCURACY_MULT_TIER_7 @@ -1844,9 +1844,9 @@ /obj/item/weapon/gun/rifle/rmc_f90/shotgun/set_gun_config_values() ..() - fire_delay = FIRE_DELAY_TIER_8 + fire_delay = FIRE_DELAY_TIER_11 burst_amount = BURST_AMOUNT_TIER_3 - burst_delay = FIRE_DELAY_TIER_8 + burst_delay = FIRE_DELAY_TIER_11 accuracy_mult = BASE_ACCURACY_MULT + HIT_ACCURACY_MULT_TIER_4 + 2*HIT_ACCURACY_MULT_TIER_1 accuracy_mult_unwielded = BASE_ACCURACY_MULT - HIT_ACCURACY_MULT_TIER_2 scatter = SCATTER_AMOUNT_TIER_8 diff --git a/code/modules/tgui/tgui_number_input.dm b/code/modules/tgui/tgui_number_input.dm index 4c06b5333b6d..356448853db3 100644 --- a/code/modules/tgui/tgui_number_input.dm +++ b/code/modules/tgui/tgui_number_input.dm @@ -145,6 +145,7 @@ "min_value" = min_value, "preferences" = list(), "title" = title, + "integer_only" = integer_only ) /datum/tgui_input_number/ui_data(mob/user) diff --git a/code/modules/tgui_panel/telemetry.dm b/code/modules/tgui_panel/telemetry.dm index eb5c7d96a4d9..4ef1f06bfac0 100644 --- a/code/modules/tgui_panel/telemetry.dm +++ b/code/modules/tgui_panel/telemetry.dm @@ -86,7 +86,7 @@ // Check for a malformed history object if (!row || row.len < 3 || (!row["ckey"] || !row["address"] || !row["computer_id"])) - return + continue /* TODO - Reintroduce this when we get a proper round ID tracking, and we want to log it to database @@ -103,7 +103,7 @@ continue */ - if (world.IsBanned(row["ckey"], row["address"], row["computer_id"], real_bans_only = TRUE)) + if (world.IsBanned(row["ckey"], row["address"], row["computer_id"], real_bans_only = TRUE, is_telemetry = TRUE)) found = row break diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 90040ec9acf9..3967ee70e391 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -74,19 +74,23 @@ /// A trait source when adding traits through unit tests #define TRAIT_SOURCE_UNIT_TESTS "unit_tests" +// Unit tests #include "autowiki.dm" +#include "check_runtimes.dm" #include "create_and_destroy.dm" -#include "focus_only_tests.dm" +#include "emote_panels.dm" #include "missing_icons.dm" #include "resist.dm" +#include "spawn_humans.dm" #include "spritesheets.dm" #include "subsystem_init.dm" #include "tgui_create_message.dm" #include "timer_sanity.dm" +#include "tutorials.dm" + +// Unit tests backend +#include "focus_only_tests.dm" #include "unit_test.dm" -#include "spawn_humans.dm" -#include "check_runtimes.dm" -#include "emote_panels.dm" #undef TEST_ASSERT #undef TEST_ASSERT_EQUAL diff --git a/code/modules/unit_tests/tutorials.dm b/code/modules/unit_tests/tutorials.dm new file mode 100644 index 000000000000..d835a4f272cb --- /dev/null +++ b/code/modules/unit_tests/tutorials.dm @@ -0,0 +1,19 @@ +/datum/unit_test/tutorials + +/datum/unit_test/tutorials/Run() + var/datum/tutorial/base_path = /datum/tutorial + for(var/datum/tutorial/tutorial_path as anything in subtypesof(base_path)) + if(initial(tutorial_path.parent_path) == tutorial_path) + continue + + // Make sure these variables are overridden on any subtypes. + TEST_ASSERT_NOTEQUAL(initial(tutorial_path.name), initial(base_path.name), + "[tutorial_path] does not have a name set.") + TEST_ASSERT_NOTEQUAL(initial(tutorial_path.tutorial_id), initial(base_path.tutorial_id), + "[tutorial_path] does not have a tutorial_id set.") + TEST_ASSERT_NOTEQUAL(initial(tutorial_path.desc), initial(base_path.desc), + "[tutorial_path] does not have a desc set.") + TEST_ASSERT_NOTEQUAL(initial(tutorial_path.icon_state), initial(base_path.icon_state), + "[tutorial_path] does not have an icon_state set.") + +// TODO: Add a test verifying that a basic tutorial can be started and completed. (Requires unit test client handling) diff --git a/colonialmarines.dme b/colonialmarines.dme index 0b39e89042d2..f3dc6f1f81d9 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -298,6 +298,7 @@ #include "code\controllers\subsystem\sound_loops.dm" #include "code\controllers\subsystem\soundscape.dm" #include "code\controllers\subsystem\statpanel.dm" +#include "code\controllers\subsystem\stickyban.dm" #include "code\controllers\subsystem\techtree.dm" #include "code\controllers\subsystem\tgui.dm" #include "code\controllers\subsystem\ticker.dm" @@ -406,6 +407,7 @@ #include "code\datums\components\overlay_lighting.dm" #include "code\datums\components\rename.dm" #include "code\datums\components\speed_modifier.dm" +#include "code\datums\components\temporary_mute.dm" #include "code\datums\components\toxin_buildup.dm" #include "code\datums\components\tutorial_status.dm" #include "code\datums\components\weed_damage_reduction.dm" @@ -1284,10 +1286,13 @@ #include "code\game\objects\structures\props.dm" #include "code\game\objects\structures\reagent_dispensers.dm" #include "code\game\objects\structures\safe.dm" +#include "code\game\objects\structures\shower.dm" #include "code\game\objects\structures\signs.dm" +#include "code\game\objects\structures\sink.dm" #include "code\game\objects\structures\surface.dm" #include "code\game\objects\structures\tables_racks.dm" #include "code\game\objects\structures\tank_dispenser.dm" +#include "code\game\objects\structures\urinal.dm" #include "code\game\objects\structures\vulture_spotter.dm" #include "code\game\objects\structures\watercloset.dm" #include "code\game\objects\structures\windoor_assembly.dm" @@ -1394,10 +1399,8 @@ #include "code\modules\admin\NewBan.dm" #include "code\modules\admin\player_notes.dm" #include "code\modules\admin\server_verbs.dm" -#include "code\modules\admin\stickyban.dm" #include "code\modules\admin\STUI.dm" #include "code\modules\admin\tag.dm" -#include "code\modules\admin\ToRban.dm" #include "code\modules\admin\medal_panel\medals_panel.dm" #include "code\modules\admin\medal_panel\medals_panel_tgui.dm" #include "code\modules\admin\player_panel\player_action.dm" @@ -1965,7 +1968,6 @@ #include "code\modules\mob\living\carbon\xenomorph\xeno_verbs.dm" #include "code\modules\mob\living\carbon\xenomorph\XenoAttacks.dm" #include "code\modules\mob\living\carbon\xenomorph\Xenomorph.dm" -#include "code\modules\mob\living\carbon\xenomorph\XenoMutatorSets.dm" #include "code\modules\mob\living\carbon\xenomorph\XenoOverwatch.dm" #include "code\modules\mob\living\carbon\xenomorph\XenoProcs.dm" #include "code\modules\mob\living\carbon\xenomorph\XenoUpgrade.dm" @@ -2045,23 +2047,24 @@ #include "code\modules\mob\living\carbon\xenomorph\castes\Spitter.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Warrior.dm" #include "code\modules\mob\living\carbon\xenomorph\items\iff_tag.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\behavior_delegate.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\mutator.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\boiler\trapper.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\carrier\eggsac.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\crusher\charger.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\defender\steel_crest.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\drone\gardener.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\drone\healer.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\hivelord\resin_whisperer.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\lurker\vampire.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\praetorian\dancer.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\praetorian\oppressor.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\praetorian\vanguard.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\praetorian\warden.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\ravager\berserker.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\ravager\hedgehog.dm" -#include "code\modules\mob\living\carbon\xenomorph\mutators\strains\runner\acid.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\behavior_delegate.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\xeno_strain.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\boiler\trapper.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\carrier\eggsac.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\crusher\charger.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\defender\steel_crest.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\drone\gardener.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\drone\healer.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\facehugger\watcher.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\hivelord\resin_whisperer.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\lurker\vampire.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\praetorian\dancer.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\praetorian\oppressor.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\praetorian\vanguard.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\praetorian\warden.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\ravager\berserker.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\ravager\hedgehog.dm" +#include "code\modules\mob\living\carbon\xenomorph\strains\castes\runner\acid.dm" #include "code\modules\mob\living\silicon\death.dm" #include "code\modules\mob\living\silicon\login.dm" #include "code\modules\mob\living\silicon\say.dm" diff --git a/config/example/config.txt b/config/example/config.txt index 8a976e02a580..f9e0956593a9 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -151,9 +151,6 @@ BANAPPEALS https://cm-ss13.com/viewforum.php?f=76 ##Defines the ticklag for the world. 0.9 is the normal one, 0.5 is smoother. TICKLAG 0.5 -## Uncomment this to ban use of ToR -#TOR_BAN - ## Uncomment this to have country flags pop up in OOC alongside names if the user has the pref turned on (uses IP-API) #OOC_COUNTRY_FLAGS diff --git a/html/changelogs/AutoChangeLog-pr-5620.yml b/html/changelogs/AutoChangeLog-pr-5620.yml deleted file mode 100644 index 0d24f1e7121b..000000000000 --- a/html/changelogs/AutoChangeLog-pr-5620.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Steelpoint" -delete-after: True -changes: - - rscadd: "USCM Chestrig has been added to squad prep vendors, alternative sprite of a satchel, similar in appearance to the welder-chestrig." \ No newline at end of file diff --git a/html/changelogs/archive/2024-02.yml b/html/changelogs/archive/2024-02.yml index 35518fa281c9..16fd2fd70475 100644 --- a/html/changelogs/archive/2024-02.yml +++ b/html/changelogs/archive/2024-02.yml @@ -108,3 +108,107 @@ Vicacrov: - bugfix: Fixes Defenders staying Fortified in critical state - Fortify now automatically toggles itself off upon the Defender getting critted. +2024-02-07: + InsaneRed: + - balance: Scout specialist incin/high impact mag crates now only has 3 instead + of 5 + Steelpoint: + - rscadd: USCM Chestrig has been added to squad prep vendors, alternative sprite + of a satchel, similar in appearance to the welder-chestrig. +2024-02-08: + Megaddd: + - imageadd: added 4 bone-gel fill level sprite states + SpartanBobby: + - maptweak: changes to LV624 Engineering and T-comms, mostly detailing but changes + some doors and object placement +2024-02-09: + Huffie56: + - refactor: turning newscasters into "props". + - qol: added two new section to replace ammunition section primary ammunition and + sidearm ammunition and rearranged it for team leader. + - balance: added the ability for comtech, medic, and squad leader to buy M4A3 (HP + and AP) and VP78 magazines for 3 points and M44 Heavy for 6 points. + - balance: added the ability for SG to buy M4A3 (HP and AP) and VP78 magazines for + 5 points and M44 Heavy for 10 points. + - balance: added the ability for rifleman to buy M4A3 (HP and AP) and VP78 magazines + for 5 points. +2024-02-10: + Drathek: + - bugfix: Fixed an oversight with burst point blank firing + SabreML: + - bugfix: Fixed trash carts not being pick-uppable by the dropship's fulton recovery + system. + blackdragonTOW: + - admin: added logging for 3rd party victory events + private-tristan: + - rscadd: Pizza ERT is back, although incredibly unlikely to occur + - bugfix: UPP ERT no longer rolls 3 times (1 for random, 1 for friendly, and 1 for + hostile), due to this, UPP comms will always be scrambled at the beginning, + no way to tell intentions before meeting. + - rscadd: Chem Goon Research Consultant now spawns with reagent scanner goggles. +2024-02-11: + Drathek: + - bugfix: Fix gardener fruit count not always updating + Vicacrov: + - bugfix: Fixed Warden Praetorians being able to move around the ovipositored Queen + with their Retrieve ability. +2024-02-12: + Stakeyng: + - qol: Lets you put an extra accessory item in your helmet. + harryob: + - server: tor banning functionality is removed +2024-02-13: + Huffie56: + - refactor: refactored watercloset.dm file. + SabreML: + - ui: Made the Tutorial Menu automatically close when a tutorial is started. + - code_imp: Made the tutorial Xenomorph not inherit the user's 'age' prefix. + - code_imp: Added a basic unit test for tutorials. +2024-02-14: + SabreML: + - bugfix: Fixed crates and similar objects layering below the dropship's fulton + recovery system. + Zonespace Drathek: + - balance: Lesser drones and Facehuggers cannot speak, custom emote, or point for + 3 minutes after spawning +2024-02-15: + InsaneRed: + - balance: Drop pouch now scales the same as normal webbing + SabreML: + - bugfix: Fixed some changelog icons being smaller than others. + Vicacrov: + - maptweak: LV-624 tfort's floodlights are now colony floodlights. + VileBeggar: + - qol: Attaching the bipod to weapons that have full auto (HPR included) will give + you a button toggle for immediately switching to full auto when deploying the + bipod. +2024-02-16: + BadAtThisGame302: + - rscadd: Added two new beacons for the CL, Lawyer and Bodyguard. + - rscadd: Added an Executive Specialist and Executive Supervisor Lawyer preset. + - rscadd: Added a cyanide pill in the CLs briefcase for an easy way out. + - rscadd: Added a Wey-Yu Coffee Mug in the CLs briefcase as a souvenir sale. + Drathek: + - bugfix: Fixed xeno tutorial allowing evolution/de-evolution + - bugfix: Fixed some runtimes with tutorials + SabreML: + - bugfix: Fixed the 'Clear' button not resetting the number of keybind assignment + slots. (Keybinds menu) + - refactor: Refactored xeno strains. + - rscadd: Added a keybind setting to open the 'Purchase Strain' window. (Also renamed + the macro) + cuberound: + - balance: m4ra ammo looses about 1/5 of initial effect duration per 10 tiles traveled + harryob: + - admin: permanent and sticky bans are now handled in game, instead of being managed + externally +2024-02-17: + MobiusWon: + - rscadd: Adds integral Motion Detector to XM4 IO Armor + - balance: Uniform utility accessories (I.E. Webbing) no longer can be equipped + while the XM4 is worn. Armor slots increased to 5 to compensate. + Steelpoint: + - balance: Royal Marine Commando's firearms now have a higher rate of fire, comparable + to the M41A. + mullenpaul: + - code_imp: rework logic of restrictedinput component to reduce checks diff --git a/icons/mob/hud/actions.dmi b/icons/mob/hud/actions.dmi index 820da22ea5b1..3c3e6dd9de3d 100644 Binary files a/icons/mob/hud/actions.dmi and b/icons/mob/hud/actions.dmi differ diff --git a/icons/mob/xenos/burrower.dmi b/icons/mob/xenos/burrower.dmi index f546776e6b27..c54684e61bc9 100644 Binary files a/icons/mob/xenos/burrower.dmi and b/icons/mob/xenos/burrower.dmi differ diff --git a/maps/map_files/LV624/LV624.dmm b/maps/map_files/LV624/LV624.dmm index 5a0bb9dfc88d..0e1948deadf3 100644 --- a/maps/map_files/LV624/LV624.dmm +++ b/maps/map_files/LV624/LV624.dmm @@ -1126,9 +1126,7 @@ /area/lv624/ground/barrens/containers) "afu" = ( /obj/item/ammo_casing, -/obj/structure/machinery/floodlight/landing{ - name = "bolted floodlight" - }, +/obj/structure/machinery/colony_floodlight, /turf/open/floor/plating{ dir = 9; icon_state = "warnplate" @@ -1162,9 +1160,7 @@ }, /area/lv624/ground/barrens/central_barrens) "afy" = ( -/obj/structure/machinery/floodlight/landing{ - name = "bolted floodlight" - }, +/obj/structure/machinery/colony_floodlight, /turf/open/floor/plating{ dir = 5; icon_state = "warnplate" @@ -1409,9 +1405,7 @@ /area/lv624/ground/barrens/central_barrens) "agF" = ( /obj/item/ammo_casing, -/obj/structure/machinery/floodlight/landing{ - name = "bolted floodlight" - }, +/obj/structure/machinery/colony_floodlight, /turf/open/floor/plating, /area/lv624/ground/barrens/central_barrens) "agG" = ( @@ -1588,9 +1582,7 @@ }, /area/lv624/ground/barrens/west_barrens/ceiling) "ahM" = ( -/obj/structure/machinery/floodlight/landing{ - name = "bolted floodlight" - }, +/obj/structure/machinery/colony_floodlight, /turf/open/floor/plating{ dir = 10; icon_state = "warnplate" @@ -1630,9 +1622,7 @@ }, /area/lv624/ground/barrens/central_barrens) "ahT" = ( -/obj/structure/machinery/floodlight/landing{ - name = "bolted floodlight" - }, +/obj/structure/machinery/colony_floodlight, /turf/open/floor/plating{ dir = 6; icon_state = "warnplate" @@ -6359,8 +6349,9 @@ }, /area/lv624/lazarus/sleep_male) "aCV" = ( +/obj/structure/flora/bush/ausbushes/lavendergrass, /turf/open/gm/grass/grass1, -/area/lv624/lazarus/sleep_male) +/area/lv624/ground/jungle/central_jungle) "aCX" = ( /turf/open/floor/grass, /area/lv624/lazarus/main_hall) @@ -7934,11 +7925,11 @@ "aIE" = ( /obj/structure/flora/bush/ausbushes/var3/stalkybush, /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 11; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 11; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_west_caves) @@ -9548,6 +9539,7 @@ /area/lv624/ground/caves/north_east_caves) "aPN" = ( /obj/structure/window_frame/colony/reinforced, +/obj/item/stack/rods, /turf/open/floor/plating, /area/lv624/lazarus/comms) "aPO" = ( @@ -9558,8 +9550,7 @@ req_one_access = null }, /turf/open/floor{ - dir = 9; - icon_state = "brown" + icon_state = "delivery" }, /area/lv624/lazarus/comms) "aPR" = ( @@ -9845,38 +9836,28 @@ "aRd" = ( /obj/structure/surface/table, /obj/item/device/mmi/radio_enabled, -/turf/open/floor{ - dir = 9; - icon_state = "brown" - }, -/area/lv624/lazarus/comms) -"aRe" = ( -/obj/structure/surface/table, -/obj/item/device/flashlight, /obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, /turf/open/floor{ dir = 9; icon_state = "brown" }, /area/lv624/lazarus/comms) -"aRf" = ( -/obj/structure/surface/table, -/obj/item/device/radio/headset{ - frequency = 1469; - pixel_x = 4; - pixel_y = 4 - }, -/obj/item/device/radio/headset{ - frequency = 1469 - }, -/obj/structure/machinery/light{ - dir = 1 +"aRe" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/machinery/power/monitor, +/obj/item/ashtray/glass{ + pixel_x = 3; + pixel_y = 15 }, /turf/open/floor{ dir = 9; icon_state = "brown" }, /area/lv624/lazarus/comms) +"aRf" = ( +/obj/structure/flora/bush/ausbushes/lavendergrass, +/turf/open/gm/grass/grass1, +/area/lv624/ground/jungle/west_central_jungle) "aRg" = ( /turf/open/floor/plating, /area/lv624/lazarus/landing_zones/lz2) @@ -9888,10 +9869,11 @@ }, /area/lv624/lazarus/chapel) "aRi" = ( -/obj/structure/surface/table, -/obj/item/ashtray/glass, -/obj/item/tool/crowbar, -/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, +/obj/structure/machinery/light{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt, +/obj/structure/machinery/cm_vending/sorted/tech/electronics_storage, /turf/open/floor{ dir = 9; icon_state = "brown" @@ -10120,8 +10102,14 @@ /turf/open/floor/plating, /area/lv624/lazarus/landing_zones/lz1) "aSd" = ( -/obj/structure/bed/chair/office/light{ - dir = 1 +/obj/structure/machinery/blackbox_recorder, +/obj/item/prop/almayer/flight_recorder/colony{ + pixel_x = -6; + pixel_y = 10 + }, +/obj/effect/decal/warning_stripes{ + icon_state = "W"; + pixel_x = -1 }, /turf/open/floor{ dir = 9; @@ -10129,8 +10117,12 @@ }, /area/lv624/lazarus/comms) "aSe" = ( -/obj/structure/surface/table, -/obj/item/device/multitool, +/obj/structure/machinery/cm_vending/sorted/tech/comp_storage, +/obj/effect/decal/warning_stripes{ + icon_state = "E"; + pixel_x = 1 + }, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ dir = 9; icon_state = "brown" @@ -10270,30 +10262,24 @@ }, /area/lv624/lazarus/canteen) "aSI" = ( -/obj/structure/machinery/computer/telecomms/server, +/obj/structure/machinery/light{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ dir = 9; icon_state = "brown" }, /area/lv624/lazarus/comms) "aSJ" = ( -/obj/item/device/radio/intercom{ - freerange = 1; - frequency = 1469; - name = "General Listening Channel"; - pixel_x = -30 - }, /turf/open/floor{ - dir = 9; - icon_state = "brown" + icon_state = "podhatchfloor" }, /area/lv624/lazarus/comms) "aSK" = ( -/obj/structure/machinery/computer/telecomms/traffic, -/turf/open/floor{ - dir = 9; - icon_state = "brown" - }, +/obj/structure/window_frame/colony/reinforced, +/obj/item/shard, +/turf/open/floor/plating, /area/lv624/lazarus/comms) "aSL" = ( /turf/closed/wall/r_wall, @@ -10382,25 +10368,25 @@ "aTg" = ( /turf/closed/wall, /area/lv624/lazarus/comms) -"aTh" = ( -/obj/structure/reagent_dispensers/watertank, -/turf/open/floor{ - dir = 9; - icon_state = "brown" - }, -/area/lv624/lazarus/comms) "aTi" = ( -/obj/structure/reagent_dispensers/fueltank, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ dir = 9; icon_state = "brown" }, /area/lv624/lazarus/comms) "aTj" = ( -/obj/structure/machinery/blackbox_recorder, -/obj/item/prop/almayer/flight_recorder/colony{ - pixel_x = -6; - pixel_y = 10 +/obj/effect/decal/warning_stripes{ + icon_state = "W"; + pixel_x = -1 + }, +/obj/structure/machinery/computer/telecomms/traffic{ + layer = 3.1; + pixel_y = 16 + }, +/obj/structure/bed/chair/office/light{ + dir = 1; + layer = 3.2 }, /turf/open/floor{ dir = 9; @@ -10410,8 +10396,7 @@ "aTk" = ( /obj/effect/landmark/static_comms/net_one, /turf/open/floor{ - dir = 9; - icon_state = "brown" + icon_state = "podhatchfloor" }, /area/lv624/lazarus/comms) "aTo" = ( @@ -10446,6 +10431,11 @@ /obj/structure/machinery/colony_floodlight_switch{ pixel_y = 30 }, +/obj/item/device/assembly/voice, +/obj/effect/decal/warning_stripes{ + icon_state = "W"; + pixel_x = -1 + }, /turf/open/floor{ icon_state = "dark" }, @@ -10537,7 +10527,6 @@ /obj/structure/machinery/power/apc{ dir = 1; name = "Telecomms APC"; - pixel_y = 30; start_charge = 15 }, /turf/open/floor{ @@ -10545,7 +10534,12 @@ }, /area/lv624/lazarus/comms) "aTK" = ( -/turf/open/gm/dirt, +/obj/structure/surface/rack, +/obj/item/device/multitool, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, /area/lv624/lazarus/comms) "aTM" = ( /turf/open/floor{ @@ -10553,6 +10547,10 @@ }, /area/lv624/lazarus/engineering) "aTN" = ( +/obj/structure/stairs/perspective{ + dir = 1; + icon_state = "p_stair_full" + }, /turf/open/floor/plating{ icon_state = "warnplate" }, @@ -10679,22 +10677,18 @@ req_one_access = null }, /turf/open/floor{ - dir = 9; - icon_state = "brown" + icon_state = "delivery" }, /area/lv624/lazarus/comms) "aUl" = ( -/obj/item/device/multitool, -/obj/structure/surface/rack, -/turf/open/floor{ - dir = 9; - icon_state = "brown" - }, -/area/lv624/lazarus/comms) +/turf/open/gm/dirtgrassborder/south, +/area/lv624/ground/colony/north_tcomms_road) "aUo" = ( /obj/effect/spawner/random/toolbox, /obj/structure/surface/table/reinforced/prison, /obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, +/obj/effect/decal/cleanable/dirt, +/obj/structure/foamed_metal, /turf/open/floor{ icon_state = "dark" }, @@ -10711,17 +10705,18 @@ }, /area/lv624/lazarus/engineering) "aUt" = ( -/obj/structure/surface/table/reinforced/prison, -/obj/item/device/assembly/timer, -/obj/item/device/assembly/voice, -/obj/item/tool/crowbar, -/turf/open/floor{ - icon_state = "dark" - }, -/area/lv624/lazarus/engineering) +/obj/structure/flora/jungle/vines/heavy, +/obj/structure/window_frame/colony, +/obj/item/shard, +/turf/open/floor/plating, +/area/lv624/lazarus/yggdrasil) "aUu" = ( /obj/structure/machinery/light, /obj/structure/bed/stool, +/obj/effect/decal/warning_stripes{ + icon_state = "N"; + pixel_y = 2 + }, /turf/open/floor{ icon_state = "dark" }, @@ -10887,6 +10882,7 @@ /area/lv624/ground/jungle/south_west_jungle) "aVb" = ( /obj/structure/machinery/light, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ dir = 9; icon_state = "brown" @@ -10899,6 +10895,7 @@ /obj/structure/machinery/power/geothermal{ fail_rate = 5 }, +/obj/structure/platform, /turf/open/floor/plating{ dir = 6; icon_state = "warnplate" @@ -10917,6 +10914,10 @@ /obj/structure/machinery/power/geothermal{ fail_rate = 5 }, +/obj/item/clothing/head/hardhat/orange{ + pixel_x = -7; + pixel_y = 13 + }, /turf/open/floor/plating{ dir = 8; icon_state = "warnplate" @@ -10935,6 +10936,9 @@ }, /area/lv624/lazarus/engineering) "aVj" = ( +/obj/structure/platform_decoration{ + dir = 1 + }, /obj/structure/machinery/door/airlock/almayer/engineering/colony{ dir = 1; locked = 1; @@ -10943,7 +10947,7 @@ req_one_access = null }, /turf/open/floor{ - icon_state = "dark" + icon_state = "delivery" }, /area/lv624/lazarus/engineering) "aVk" = ( @@ -11044,11 +11048,12 @@ /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/south_west_jungle) "aVF" = ( -/obj/structure/machinery/power/apc{ - dir = 1; - name = "Geothermal APC"; - pixel_y = 30; - start_charge = 0 +/obj/structure/extinguisher_cabinet{ + pixel_y = 30 + }, +/obj/effect/decal/warning_stripes{ + icon_state = "W"; + pixel_x = -1 }, /turf/open/floor{ icon_state = "dark" @@ -11061,22 +11066,28 @@ name = "General Listening Channel"; pixel_y = 30 }, +/obj/structure/stairs/perspective{ + dir = 4; + icon_state = "p_stair_full" + }, +/obj/structure/platform/stair_cut, +/obj/item/clothing/head/hardhat/orange, /turf/open/floor{ icon_state = "dark" }, /area/lv624/lazarus/engineering) "aVH" = ( -/obj/item/clothing/head/hardhat/orange, /obj/structure/machinery/light{ dir = 1 }, +/obj/structure/foamed_metal, /turf/open/floor{ icon_state = "dark" }, /area/lv624/lazarus/engineering) "aVI" = ( -/obj/structure/foamed_metal, /obj/structure/machinery/vending/coffee, +/obj/structure/foamed_metal, /turf/open/floor{ icon_state = "dark" }, @@ -11178,17 +11189,17 @@ /turf/open/floor/plating, /area/lv624/lazarus/secure_storage) "aWd" = ( -/obj/structure/machinery/cm_vending/sorted/tech/comp_storage, +/obj/item/shard, /turf/open/floor{ - dir = 9; - icon_state = "brown" + icon_state = "podhatchfloor" }, /area/lv624/lazarus/comms) "aWe" = ( -/obj/structure/machinery/computer/telecomms/monitor, +/obj/structure/machinery/computer/telecomms/monitor{ + pixel_y = 16 + }, /turf/open/floor{ - dir = 9; - icon_state = "brown" + icon_state = "dark" }, /area/lv624/lazarus/comms) "aWf" = ( @@ -11197,9 +11208,8 @@ req_access_txt = "100"; req_one_access = null }, -/obj/structure/foamed_metal, /turf/open/floor{ - icon_state = "dark" + icon_state = "delivery" }, /area/lv624/lazarus/engineering) "aWg" = ( @@ -11207,6 +11217,7 @@ /obj/structure/lattice{ layer = 2.9 }, +/obj/structure/platform, /turf/open/floor/plating{ dir = 10; icon_state = "warnplate" @@ -11302,28 +11313,38 @@ /obj/effect/decal/remains/xeno, /turf/open/gm/dirt, /area/lv624/ground/colony/south_medbay_road) -"aWx" = ( -/obj/structure/bed/chair/office/light, +"aWy" = ( +/obj/structure/surface/table, +/obj/item/device/radio/headset{ + frequency = 1469; + pixel_x = 4; + pixel_y = 4 + }, +/obj/item/device/radio/headset{ + frequency = 1469 + }, +/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, /turf/open/floor{ dir = 9; icon_state = "brown" }, /area/lv624/lazarus/comms) -"aWy" = ( -/obj/structure/foamed_metal, -/turf/open/gm/dirt, -/area/lv624/lazarus/comms) "aWz" = ( /obj/structure/window_frame/colony/reinforced, /turf/open/floor/plating, /area/lv624/lazarus/engineering) "aWA" = ( -/obj/structure/foamed_metal, -/obj/structure/machinery/cm_vending/sorted/tech/electronics_storage, +/obj/structure/machinery/computer/telecomms/server{ + pixel_y = 16 + }, +/obj/structure/bed/chair/office/light{ + dir = 1 + }, /turf/open/floor{ - icon_state = "dark" + dir = 9; + icon_state = "brown" }, -/area/lv624/lazarus/engineering) +/area/lv624/lazarus/comms) "aWC" = ( /obj/structure/machinery/colony_floodlight, /turf/open/gm/grass/grass1, @@ -11335,31 +11356,35 @@ }, /area/lv624/lazarus/engineering) "aWE" = ( -/obj/structure/sign/safety/high_voltage{ - pixel_x = 7; - pixel_y = -32 +/obj/effect/decal/warning_stripes{ + icon_state = "E"; + pixel_x = 1 }, -/obj/structure/surface/table/reinforced/prison, -/obj/item/tool/extinguisher, -/obj/effect/spawner/random/powercell, +/obj/item/prop/alien/hugger, /turf/open/floor{ - icon_state = "dark" + dir = 9; + icon_state = "brown" }, -/area/lv624/lazarus/engineering) +/area/lv624/lazarus/comms) "aWF" = ( +/obj/effect/spawner/random/toolbox, +/obj/structure/surface/table/reinforced/prison, +/obj/item/device/radio, +/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, /obj/structure/machinery/light, -/obj/structure/machinery/power/monitor{ - name = "Main Power Grid Monitoring" - }, /turf/open/floor{ icon_state = "dark" }, /area/lv624/lazarus/engineering) "aWG" = ( -/obj/effect/spawner/random/toolbox, -/obj/structure/surface/table/reinforced/prison, -/obj/item/device/radio, -/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, +/obj/structure/stairs/perspective{ + dir = 4; + icon_state = "p_stair_full" + }, +/obj/structure/platform{ + layer = 3.1 + }, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -11368,6 +11393,10 @@ /obj/structure/surface/table/reinforced/prison, /obj/item/folder, /obj/item/device/assembly/signaller, +/obj/structure/platform_decoration{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -11377,6 +11406,7 @@ /obj/item/device/flashlight, /obj/effect/spawner/random/tool, /obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -11456,46 +11486,35 @@ /turf/open/floor/greengrid, /area/lv624/lazarus/secure_storage) "aWX" = ( -/obj/structure/surface/table, -/obj/item/tool/weldingtool, -/obj/item/tool/wrench, -/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, -/turf/open/floor{ - dir = 9; - icon_state = "brown" +/obj/effect/decal/warning_stripes{ + icon_state = "W"; + pixel_x = -1 }, -/area/lv624/lazarus/comms) -"aWY" = ( -/obj/structure/surface/table, -/obj/item/device/radio/off{ - frequency = 1469 +/obj/structure/machinery/light{ + dir = 4 }, +/obj/effect/decal/cleanable/dirt, +/obj/item/stack/rods, /turf/open/floor{ dir = 9; icon_state = "brown" }, /area/lv624/lazarus/comms) +"aWY" = ( +/turf/closed/wall, +/area/lv624/lazarus/yggdrasil) "aWZ" = ( -/obj/structure/surface/table, -/obj/item/clothing/head/soft/blue, -/obj/structure/machinery/light, -/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, -/turf/open/floor{ - dir = 9; - icon_state = "brown" - }, -/area/lv624/lazarus/comms) +/obj/structure/flora/bush/ausbushes/lavendergrass, +/turf/open/gm/grass/grass1, +/area/lv624/ground/colony/west_tcomms_road) "aXa" = ( -/obj/structure/surface/table, -/obj/item/device/radio/off{ - frequency = 1469 - }, -/obj/item/tool/crowbar, /obj/structure/machinery/light, -/obj/item/tool/hatchet{ - pixel_x = 6; - pixel_y = 4 +/obj/structure/stairs/perspective{ + dir = 8; + icon_state = "p_stair_full" }, +/obj/structure/platform, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ dir = 9; icon_state = "brown" @@ -11508,11 +11527,24 @@ }, /area/lv624/lazarus/sleep_male) "aXd" = ( -/turf/open/gm/dirtgrassborder/south, +/obj/structure/bed/chair/office/light{ + dir = 1 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, /area/lv624/lazarus/comms) "aXe" = ( -/obj/structure/foamed_metal, -/turf/open/gm/dirtgrassborder/south, +/obj/effect/decal/warning_stripes{ + icon_state = "N"; + pixel_y = 1 + }, +/obj/item/tool/crowbar, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, /area/lv624/lazarus/comms) "aXf" = ( /obj/structure/machinery/door/airlock/almayer/engineering/colony{ @@ -11523,7 +11555,7 @@ req_one_access = null }, /turf/open/floor{ - icon_state = "dark" + icon_state = "delivery" }, /area/lv624/lazarus/engineering) "aXg" = ( @@ -11534,7 +11566,7 @@ req_one_access = null }, /turf/open/floor{ - icon_state = "dark" + icon_state = "delivery" }, /area/lv624/lazarus/engineering) "aXh" = ( @@ -11559,19 +11591,29 @@ /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/south_west_jungle) "aXn" = ( -/obj/structure/fence, -/turf/open/gm/grass/grass1, -/area/lv624/ground/jungle/south_central_jungle) +/obj/effect/decal/warning_stripes{ + icon_state = "NE-out"; + pixel_x = 1; + pixel_y = 1 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "aXo" = ( /obj/effect/landmark/crap_item, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/east_central_jungle) "aXs" = ( -/obj/structure/extinguisher_cabinet{ - pixel_y = 30 +/obj/effect/decal/cleanable/dirt, +/obj/structure/machinery/power/apc{ + dir = 1; + name = "Geothermal APC"; + start_charge = 0 }, /turf/open/floor{ - icon_state = "dark" + icon_state = "delivery" }, /area/lv624/lazarus/engineering) "aXt" = ( @@ -11592,6 +11634,7 @@ dir = 1 }, /obj/structure/reagent_dispensers/fueltank, +/obj/effect/spawner/random/tool, /turf/open/floor{ icon_state = "dark" }, @@ -11684,6 +11727,7 @@ /obj/structure/machinery/light{ dir = 4 }, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -11698,6 +11742,7 @@ /area/lv624/lazarus/security) "aXN" = ( /obj/effect/spawner/random/powercell, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -11744,7 +11789,14 @@ /area/lv624/lazarus/landing_zones/lz2) "aXZ" = ( /obj/structure/surface/rack, -/obj/effect/spawner/random/tool, +/obj/item/weapon/gun/smg/nailgun, +/obj/item/weapon/gun/smg/nailgun, +/obj/item/ammo_magazine/smg/nailgun, +/obj/item/ammo_magazine/smg/nailgun, +/obj/item/ammo_magazine/smg/nailgun, +/obj/item/ammo_magazine/smg/nailgun, +/obj/item/ammo_magazine/smg/nailgun, +/obj/item/ammo_magazine/smg/nailgun, /turf/open/floor{ icon_state = "dark" }, @@ -11773,19 +11825,13 @@ /turf/open/floor/greengrid, /area/lv624/lazarus/secure_storage) "aYh" = ( -/obj/structure/surface/rack, -/obj/item/weapon/gun/smg/nailgun, -/obj/item/weapon/gun/smg/nailgun, -/obj/item/ammo_magazine/smg/nailgun, -/obj/item/ammo_magazine/smg/nailgun, -/obj/item/ammo_magazine/smg/nailgun, -/obj/item/ammo_magazine/smg/nailgun, -/obj/item/ammo_magazine/smg/nailgun, -/obj/item/ammo_magazine/smg/nailgun, +/obj/effect/decal/cleanable/dirt, +/obj/item/stack/rods, /turf/open/floor{ - icon_state = "dark" + dir = 9; + icon_state = "brown" }, -/area/lv624/lazarus/engineering) +/area/lv624/lazarus/comms) "aYj" = ( /obj/effect/landmark/lv624/xeno_tunnel, /obj/structure/flora/jungle/vines/light_3, @@ -11982,15 +12028,6 @@ /obj/effect/landmark/lv624/fog_blocker, /turf/open/gm/dirt, /area/lv624/ground/river/west_river) -"aZc" = ( -/obj/structure/surface/table, -/obj/item/weapon/twohanded/fireaxe, -/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, -/turf/open/floor{ - dir = 9; - icon_state = "brown" - }, -/area/lv624/lazarus/comms) "aZd" = ( /obj/structure/closet/radiation, /obj/item/device/radio/intercom{ @@ -11999,6 +12036,7 @@ name = "General Listening Channel"; pixel_x = -30 }, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -12009,6 +12047,7 @@ /obj/item/book/manual/engineering_guide, /obj/item/book/manual/engineering_hacking, /obj/item/book/manual/atmospipes, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -12019,6 +12058,7 @@ /obj/item/device/analyzer, /obj/item/device/multitool, /obj/item/device/assembly/prox_sensor, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -12036,9 +12076,11 @@ "aZh" = ( /obj/structure/surface/table, /obj/effect/landmark/objective_landmark/close, +/obj/item/tool/crowbar, +/obj/effect/decal/cleanable/dirt, +/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, /turf/open/floor{ - dir = 9; - icon_state = "brown" + icon_state = "dark" }, /area/lv624/lazarus/comms) "aZi" = ( @@ -12112,6 +12154,7 @@ /obj/structure/machinery/light{ dir = 8 }, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -12119,6 +12162,7 @@ "aZy" = ( /obj/structure/closet/secure_closet/engineering_personal, /obj/effect/landmark/objective_landmark/close, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -12191,14 +12235,28 @@ }, /area/lv624/lazarus/hop) "aZG" = ( -/obj/structure/filingcabinet, -/obj/effect/landmark/objective_landmark/close, +/obj/effect/decal/warning_stripes{ + icon_state = "N"; + pixel_y = 1 + }, /turf/open/floor{ - icon_state = "dark" + dir = 9; + icon_state = "brown" }, -/area/lv624/lazarus/engineering) +/area/lv624/lazarus/comms) "aZI" = ( -/obj/structure/filingcabinet/chestdrawer, +/obj/structure/filingcabinet/chestdrawer{ + density = 0; + pixel_x = -8; + pixel_y = 16 + }, +/obj/structure/filingcabinet{ + density = 0; + pixel_x = 7; + pixel_y = 16 + }, +/obj/effect/landmark/objective_landmark/close, +/obj/effect/decal/cleanable/dirt, /turf/open/floor{ icon_state = "dark" }, @@ -12212,8 +12270,11 @@ }, /area/lv624/lazarus/engineering) "aZK" = ( -/obj/structure/surface/rack, -/obj/item/clothing/mask/gas, +/obj/effect/decal/warning_stripes{ + icon_state = "NW-out"; + pixel_x = -1; + pixel_y = 1 + }, /turf/open/floor{ dir = 9; icon_state = "brown" @@ -12347,11 +12408,11 @@ /area/lv624/ground/caves/west_caves) "bcU" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -10; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -10; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_east_caves) @@ -12741,6 +12802,13 @@ }, /turf/open/gm/coast/beachcorner/north_east, /area/lv624/ground/caves/sand_temple) +"bJe" = ( +/obj/structure/reagent_dispensers/watertank, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "bJz" = ( /obj/effect/decal/cleanable/blood, /obj/effect/landmark/corpsespawner/wygoon, @@ -12760,6 +12828,18 @@ "bLE" = ( /turf/open/gm/dirtgrassborder/south, /area/lv624/ground/caves/sand_temple) +"bLH" = ( +/obj/structure/machinery/power/monitor{ + name = "Main Power Grid Monitoring" + }, +/obj/effect/decal/warning_stripes{ + icon_state = "N"; + pixel_y = 2 + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "bMu" = ( /turf/open/gm/dirtgrassborder/west, /area/lv624/ground/jungle/east_jungle) @@ -12790,10 +12870,10 @@ /area/lv624/ground/barrens/south_eastern_barrens) "bOm" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 4; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 4 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/barrens/north_east_barrens) @@ -12815,6 +12895,13 @@ /obj/structure/flora/bush/ausbushes/pointybush, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/north_west_caves) +"bQf" = ( +/obj/structure/machinery/power/port_gen/pacman/super, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "bQz" = ( /obj/effect/landmark/structure_spawner/setup/distress/xeno_weed_node, /obj/effect/decal/grass_overlay/grass1{ @@ -12855,6 +12942,13 @@ }, /turf/open/floor/vault, /area/lv624/lazarus/quartstorage) +"bUc" = ( +/obj/effect/decal/cleanable/dirt, +/obj/item/shard, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "bUs" = ( /obj/structure/surface/table/woodentable/poor, /obj/item/ammo_magazine/shotgun/buckshot, @@ -13001,11 +13095,11 @@ /area/lv624/ground/caves/west_caves) "chi" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -10; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -10; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_west_caves) @@ -13137,9 +13231,12 @@ /obj/effect/landmark/structure_spawner/setup/distress/xeno_weed_node, /turf/open/gm/dirt, /area/lv624/ground/barrens/central_barrens) +"ctS" = ( +/obj/structure/fence, +/turf/open/gm/dirt, +/area/lv624/ground/colony/west_tcomms_road) "cvk" = ( /obj/structure/fence, -/obj/structure/flora/bush/ausbushes/lavendergrass, /turf/open/gm/dirtgrassborder/east, /area/lv624/ground/jungle/west_central_jungle) "cwv" = ( @@ -13164,6 +13261,18 @@ /obj/structure/foamed_metal, /turf/open/gm/dirtgrassborder/south, /area/lv624/ground/jungle/east_central_jungle) +"cyP" = ( +/obj/structure/stairs/perspective{ + dir = 8; + icon_state = "p_stair_full" + }, +/obj/structure/platform/stair_cut/alt, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "czq" = ( /obj/effect/landmark/objective_landmark/science, /turf/open/auto_turf/strata_grass/layer1, @@ -13323,6 +13432,17 @@ }, /turf/open/gm/dirt, /area/lv624/ground/caves/north_west_caves) +"cNE" = ( +/obj/structure/machinery/light{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt, +/obj/item/shard, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "cNH" = ( /obj/structure/surface/rack, /obj/item/storage/box/beakers, @@ -13356,6 +13476,14 @@ /obj/structure/machinery/colony_floodlight, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/south_west_jungle) +"cQU" = ( +/obj/effect/decal/cleanable/dirt, +/obj/item/device/multitool, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "cQX" = ( /obj/structure/stairs/perspective{ color = "#b29082"; @@ -13587,6 +13715,15 @@ icon_state = "dark" }, /area/lv624/ground/barrens/north_east_barrens/ceiling) +"drX" = ( +/obj/effect/decal/warning_stripes{ + icon_state = "N"; + pixel_y = 1 + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "dsi" = ( /obj/effect/landmark/monkey_spawn, /turf/open/gm/dirt, @@ -13680,10 +13817,10 @@ /area/lv624/ground/caves/south_central_caves) "dBS" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -10; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -10 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/north_west_caves) @@ -13761,6 +13898,13 @@ /obj/effect/landmark/lv624/fog_blocker, /turf/open/gm/dirtgrassborder/grassdirt_corner/south_east, /area/lv624/ground/jungle/north_jungle) +"dHr" = ( +/obj/item/weapon/twohanded/fireaxe, +/obj/effect/decal/cleanable/blood, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "dId" = ( /obj/structure/machinery/cm_vending/sorted/medical/no_access, /turf/open/floor{ @@ -14137,6 +14281,16 @@ }, /turf/open/gm/dirt, /area/lv624/ground/caves/east_caves) +"elO" = ( +/obj/structure/platform_decoration{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "enn" = ( /obj/effect/landmark/structure_spawner/setup/distress/xeno_weed_node, /obj/effect/decal/grass_overlay/grass1{ @@ -14223,6 +14377,16 @@ /obj/effect/landmark/objective_landmark/medium, /turf/open/gm/dirt, /area/lv624/ground/caves/south_west_caves) +"euU" = ( +/obj/structure/machinery/light{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "euW" = ( /obj/structure/surface/table/woodentable/poor, /obj/item/tool/candle, @@ -14245,10 +14409,18 @@ /turf/open/gm/dirtgrassborder/north, /area/lv624/ground/jungle/south_west_jungle) "eyb" = ( -/obj/structure/fence, -/obj/structure/flora/bush/ausbushes/lavendergrass, -/turf/open/gm/dirtgrassborder/west, -/area/lv624/ground/colony/west_tcomms_road) +/obj/effect/decal/warning_stripes{ + icon_state = "NW-out"; + pixel_x = -1; + pixel_y = 1 + }, +/obj/effect/decal/cleanable/dirt, +/obj/item/shard, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "eyn" = ( /obj/structure/flora/jungle/vines/light_3, /turf/closed/wall/strata_ice/jungle, @@ -14497,11 +14669,11 @@ /area/lv624/ground/jungle/east_jungle) "fcQ" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -5; - pixel_y = -5; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -5; + pixel_y = -5 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_west_caves) @@ -14561,6 +14733,13 @@ /obj/structure/flora/jungle/vines/light_2, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/south_east_jungle) +"fij" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/bed/chair/office/light, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "fio" = ( /obj/structure/flora/grass/tallgrass/jungle/corner{ dir = 9 @@ -14725,6 +14904,12 @@ icon_state = "squareswood" }, /area/lv624/ground/caves/sand_temple) +"fAs" = ( +/obj/item/tool/crowbar, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "fAz" = ( /obj/structure/machinery/cm_vending/sorted/medical/no_access, /turf/open/floor{ @@ -14858,10 +15043,26 @@ /obj/structure/flora/bush/ausbushes/ausbush, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_west_caves) +"fLf" = ( +/obj/effect/landmark/survivor_spawner, +/obj/effect/decal/warning_stripes{ + icon_state = "W"; + pixel_x = -1 + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "fLh" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/angel, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/west_caves) +"fMa" = ( +/obj/item/prop/alien/hugger, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "fMl" = ( /obj/structure/machinery/door/airlock/multi_tile/almayer/generic{ dir = 1; @@ -14884,6 +15085,16 @@ "fPH" = ( /turf/open/gm/dirtgrassborder/south, /area/lv624/ground/jungle/west_central_jungle) +"fQx" = ( +/obj/effect/decal/warning_stripes{ + icon_state = "NW-out"; + pixel_x = -1; + pixel_y = 2 + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "fQL" = ( /turf/open/gm/coast/beachcorner2/south_east, /area/lv624/ground/river/west_river) @@ -14974,11 +15185,11 @@ /area/lv624/ground/jungle/south_central_jungle) "gcB" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 6; - pixel_y = -8; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 6; + pixel_y = -8 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/central_caves) @@ -15069,11 +15280,11 @@ /area/lv624/ground/barrens/south_eastern_jungle_barrens) "gnt" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -5; - pixel_y = -5; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -5; + pixel_y = -5 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/north_west_caves) @@ -15096,10 +15307,15 @@ /turf/open/gm/dirtgrassborder/grassdirt_corner/south_west, /area/lv624/ground/jungle/west_central_jungle) "gqG" = ( -/obj/item/device/assembly/infra, +/obj/structure/sign/safety/high_voltage{ + pixel_x = 7; + pixel_y = -32 + }, /obj/structure/surface/table/reinforced/prison, +/obj/item/tool/extinguisher, +/obj/effect/spawner/random/powercell, +/obj/item/device/assembly/infra, /obj/effect/spawner/random/powercell, -/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, /turf/open/floor{ icon_state = "dark" }, @@ -15269,12 +15485,20 @@ /turf/open/gm/dirt, /area/lv624/ground/caves/south_central_caves) "gKg" = ( -/obj/item/clothing/head/hardhat/orange, /turf/open/floor/plating{ dir = 5; icon_state = "warnplate" }, /area/lv624/lazarus/engineering) +"gKN" = ( +/obj/structure/platform_decoration{ + dir = 8 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "gMe" = ( /obj/effect/landmark/nightmare{ insert_tag = "lv-rightsidepass" @@ -15469,11 +15693,11 @@ /area/lv624/lazarus/landing_zones/lz2) "hez" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/central_caves) @@ -15580,11 +15804,11 @@ /area/lv624/ground/caves/north_central_caves) "hpK" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -10; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -10; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_central_caves) @@ -15625,10 +15849,10 @@ /area/lv624/ground/river/central_river) "htV" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 4; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 4 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/west_caves) @@ -15689,6 +15913,13 @@ /obj/effect/landmark/structure_spawner/setup/distress/xeno_membrane, /turf/open/gm/dirt, /area/lv624/ground/caves/central_caves) +"hDe" = ( +/obj/structure/bed/stool, +/obj/item/prop/alien/hugger, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "hDX" = ( /obj/effect/decal/remains/xeno, /turf/open/gm/dirt, @@ -15748,6 +15979,20 @@ /obj/effect/landmark/objective_landmark/far, /turf/open/floor/vault, /area/lv624/lazarus/quartstorage) +"hJh" = ( +/obj/structure/coatrack{ + pixel_x = 11; + pixel_y = 14 + }, +/obj/item/clothing/head/soft/blue{ + pixel_x = 7; + pixel_y = 28 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "hJn" = ( /obj/structure/flora/jungle/vines/light_2, /turf/open/gm/grass/grass2, @@ -15806,6 +16051,18 @@ /obj/item/reagent_container/food/snacks/grown/mushroom/plumphelmet, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/central_caves) +"hOo" = ( +/obj/structure/stairs/perspective{ + dir = 1; + icon_state = "p_stair_full" + }, +/obj/effect/decal/cleanable/dirt, +/obj/item/stack/rods, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "hPV" = ( /obj/effect/decal/remains/xeno{ pixel_x = 31 @@ -15974,11 +16231,11 @@ /area/lv624/lazarus/landing_zones/lz1) "ieN" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/west_caves) @@ -16021,6 +16278,16 @@ /obj/effect/landmark/queen_spawn, /turf/open/gm/dirt, /area/lv624/ground/caves/east_caves) +"iiO" = ( +/obj/effect/decal/warning_stripes{ + icon_state = "NW-out"; + pixel_x = -1; + pixel_y = 1 + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "ikA" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/chanterelle, /turf/open/auto_turf/strata_grass/layer1, @@ -16076,8 +16343,11 @@ /turf/open/gm/dirt, /area/lv624/ground/caves/north_east_caves) "isR" = ( -/obj/effect/landmark/objective_landmark/medium, -/obj/structure/largecrate/random, +/obj/structure/surface/table, +/obj/item/device/flashlight, +/obj/item/device/radio/off{ + frequency = 1469 + }, /turf/open/floor{ dir = 9; icon_state = "brown" @@ -16348,6 +16618,19 @@ /obj/structure/flora/bush/ausbushes/var3/fullgrass, /turf/open/gm/grass/grass1, /area/lv624/ground/colony/west_tcomms_road) +"iYL" = ( +/obj/item/device/radio/intercom{ + freerange = 1; + frequency = 1469; + name = "General Listening Channel"; + pixel_y = 26 + }, +/obj/item/prop/alien/hugger, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "iZG" = ( /obj/effect/landmark/crap_item, /turf/open/gm/dirt, @@ -16388,9 +16671,25 @@ /obj/structure/flora/bush/ausbushes/lavendergrass, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/south_west_jungle) +"jdL" = ( +/obj/structure/foamed_metal, +/obj/structure/flora/jungle/vines/light_2, +/turf/open/floor/plating{ + icon_state = "warnplate" + }, +/area/lv624/lazarus/engineering) "jeL" = ( /turf/closed/wall/r_wall, /area/lv624/ground/caves/north_central_caves) +"jfN" = ( +/obj/structure/platform{ + dir = 4 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "jga" = ( /turf/open/gm/river, /area/lv624/ground/jungle/west_jungle) @@ -16516,6 +16815,11 @@ /turf/open/gm/dirt, /area/lv624/ground/caves/west_caves) "jwW" = ( +/obj/structure/platform_decoration, +/obj/structure/stairs/perspective{ + dir = 1; + icon_state = "p_stair_full" + }, /turf/open/floor/plating{ dir = 6; icon_state = "warnplate" @@ -16807,11 +17111,11 @@ /area/lv624/ground/jungle/north_west_jungle) "jRL" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/east_caves) @@ -17054,6 +17358,11 @@ "kvo" = ( /turf/open/gm/dirtgrassborder/grassdirt_corner2/south_west, /area/lv624/ground/jungle/east_central_jungle) +"kvv" = ( +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "kvE" = ( /obj/structure/machinery/landinglight/ds2/delaythree, /turf/open/floor/plating, @@ -17086,6 +17395,16 @@ }, /turf/open/gm/dirt, /area/lv624/ground/caves/south_west_caves) +"kyz" = ( +/obj/effect/decal/warning_stripes{ + icon_state = "NE-out"; + pixel_x = 1; + pixel_y = 1 + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "kyN" = ( /obj/structure/flora/bush/ausbushes/var3/ywflowers, /turf/open/gm/grass/grass1, @@ -17113,6 +17432,13 @@ }, /turf/open/gm/dirt, /area/lv624/ground/caves/north_west_caves) +"kzp" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/foamed_metal, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "kzu" = ( /obj/structure/flora/bush/ausbushes/grassybush, /turf/open/gm/grass/grass1, @@ -17208,6 +17534,20 @@ }, /turf/open/gm/dirt, /area/lv624/ground/caves/north_west_caves) +"kKL" = ( +/obj/structure/machinery/light{ + dir = 1 + }, +/obj/effect/decal/warning_stripes{ + icon_state = "E"; + pixel_x = 1 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "kLl" = ( /obj/effect/decal/grass_overlay/grass1/inner{ dir = 8 @@ -17318,11 +17658,13 @@ /turf/open/gm/dirt, /area/lv624/ground/caves/west_caves) "kWX" = ( +/obj/effect/landmark/objective_landmark/medium, +/obj/structure/largecrate/random, /turf/open/floor{ - dir = 8; - icon_state = "asteroidwarning" + dir = 9; + icon_state = "brown" }, -/area/lv624/ground/colony/telecomm/sw_lz2) +/area/lv624/lazarus/comms) "kXE" = ( /obj/structure/flora/bush/ausbushes/ausbush, /turf/open/gm/dirtgrassborder/north, @@ -17517,10 +17859,12 @@ /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/central_jungle) "lud" = ( -/obj/structure/fence, -/obj/structure/flora/bush/ausbushes/lavendergrass, -/turf/open/gm/dirtgrassborder/west, -/area/lv624/ground/colony/north_tcomms_road) +/obj/structure/foamed_metal, +/obj/structure/flora/jungle/vines/heavy, +/turf/open/floor/plating{ + icon_state = "warnplate" + }, +/area/lv624/lazarus/engineering) "lxr" = ( /obj/structure/flora/jungle/vines/light_2, /turf/open/gm/dirtgrassborder/south, @@ -17561,6 +17905,16 @@ icon_state = "desert0" }, /area/lv624/ground/barrens/west_barrens) +"lzW" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/warning_stripes{ + icon_state = "E"; + pixel_x = 1 + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "lAX" = ( /obj/structure/flora/bush/ausbushes/reedbush, /turf/open/gm/grass/grass1, @@ -17701,11 +18055,11 @@ /area/lv624/lazarus/landing_zones/lz2) "lKl" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 6; - pixel_y = -8; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 6; + pixel_y = -8 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/west_caves) @@ -17742,11 +18096,11 @@ /area/lv624/lazarus/corporate_dome) "lNG" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_east_caves) @@ -17758,6 +18112,11 @@ "lQC" = ( /turf/open/gm/dirt, /area/lv624/ground/barrens/east_barrens) +"lQP" = ( +/obj/structure/window_frame/colony, +/obj/item/shard, +/turf/open/floor/plating, +/area/lv624/lazarus/comms) "lRd" = ( /obj/item/stack/sheet/wood{ amount = 2 @@ -17911,11 +18270,11 @@ /area/lv624/ground/jungle/east_jungle) "mca" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_central_caves) @@ -17928,11 +18287,11 @@ /area/lv624/ground/jungle/south_east_jungle) "mfn" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/east_caves) @@ -18018,7 +18377,9 @@ "mkU" = ( /obj/structure/foamed_metal, /obj/structure/flora/jungle/vines/light_2, -/turf/open/floor/plating, +/turf/open/floor{ + icon_state = "delivery" + }, /area/lv624/lazarus/engineering) "mkW" = ( /obj/structure/flora/grass/tallgrass/jungle/corner{ @@ -18026,6 +18387,15 @@ }, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/north_east_jungle) +"mkZ" = ( +/obj/structure/platform_decoration{ + dir = 4 + }, +/obj/structure/foamed_metal, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "mmu" = ( /obj/structure/flora/jungle/plantbot1, /turf/open/gm/grass/grass1, @@ -18145,6 +18515,15 @@ /obj/structure/flora/jungle/vines/light_2, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/south_west_jungle) +"mBH" = ( +/obj/structure/foamed_metal{ + layer = 3.1 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "mBL" = ( /turf/open/floor{ dir = 1; @@ -18523,6 +18902,17 @@ icon_state = "desert3" }, /area/lv624/ground/barrens/west_barrens) +"nrK" = ( +/obj/structure/stairs/perspective{ + dir = 1; + icon_state = "p_stair_full" + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "nrP" = ( /obj/structure/transmitter/colony_net{ phone_category = "Lazarus Landing"; @@ -18533,10 +18923,10 @@ /area/lv624/lazarus/quartstorage) "nrR" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -10; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -10 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_east_caves) @@ -18615,6 +19005,13 @@ /turf/open/gm/dirt, /area/lv624/lazarus/landing_zones/lz2) "nwR" = ( +/obj/structure/platform_decoration{ + dir = 1 + }, +/obj/structure/stairs/perspective{ + dir = 1; + icon_state = "p_stair_full" + }, /turf/open/floor/plating{ dir = 10; icon_state = "warnplate" @@ -18828,11 +19225,11 @@ /area/lv624/lazarus/corporate_dome) "nNu" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 6; - pixel_y = -8; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 6; + pixel_y = -8 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/barrens/north_east_barrens) @@ -18869,11 +19266,11 @@ /area/lv624/ground/caves/sand_temple) "nQH" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 6; - pixel_y = -8; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 6; + pixel_y = -8 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_west_caves) @@ -19011,6 +19408,11 @@ /obj/structure/flora/bush/ausbushes/lavendergrass, /turf/open/gm/grass/grass2, /area/lv624/ground/barrens/south_eastern_jungle_barrens) +"oas" = ( +/turf/open/floor{ + icon_state = "delivery" + }, +/area/lv624/lazarus/engineering) "oaL" = ( /obj/structure/flora/bush/ausbushes/ausbush, /turf/open/gm/grass/grass1, @@ -19097,6 +19499,7 @@ /turf/open/gm/dirt, /area/lv624/ground/river/east_river) "ogR" = ( +/obj/item/prop/alien/hugger, /turf/open/floor/plating{ dir = 9; icon_state = "warnplate" @@ -19137,6 +19540,16 @@ /obj/effect/landmark/objective_landmark/close, /turf/open/floor/plating, /area/lv624/ground/barrens/central_barrens) +"oiR" = ( +/obj/effect/decal/warning_stripes{ + icon_state = "W"; + pixel_x = -1 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "omu" = ( /obj/structure/flora/bush/ausbushes/var3/sparsegrass, /turf/open/gm/dirt, @@ -19186,11 +19599,11 @@ /area/lv624/ground/colony/telecomm/cargo) "osf" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 11; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 11; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/central_caves) @@ -19384,6 +19797,16 @@ icon_state = "asteroidplating" }, /area/lv624/ground/caves/north_central_caves) +"oIO" = ( +/obj/structure/surface/table, +/obj/effect/decal/cleanable/dirt, +/obj/item/device/radio/off{ + frequency = 1469 + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "oJL" = ( /obj/effect/landmark/crap_item, /turf/open/gm/grass/grass1, @@ -19455,6 +19878,15 @@ "oRH" = ( /turf/open/gm/dirtgrassborder/south, /area/lv624/ground/jungle/north_east_jungle) +"oRY" = ( +/obj/structure/surface/rack, +/obj/item/clothing/mask/gas, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "oSh" = ( /obj/item/stack/sheet/wood{ amount = 2 @@ -19825,6 +20257,12 @@ icon_state = "white" }, /area/lv624/lazarus/medbay) +"pzP" = ( +/obj/effect/decal/cleanable/blood/oil, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "pAE" = ( /obj/structure/flora/bush/ausbushes/var3/leafybush, /turf/open/auto_turf/strata_grass/layer1, @@ -19955,8 +20393,8 @@ /area/lv624/lazarus/quartstorage/outdoors) "pKm" = ( /turf/open/floor{ - icon_state = "asteroidwarning"; - dir = 8 + dir = 8; + icon_state = "asteroidwarning" }, /area/lv624/ground/colony/telecomm/sw_lz2) "pKp" = ( @@ -19976,10 +20414,10 @@ /area/lv624/ground/colony/west_nexus_road) "pLv" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 4; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 4 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/north_west_caves) @@ -20110,10 +20548,10 @@ /area/lv624/ground/caves/east_caves) "pVZ" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -10; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -10 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/central_caves) @@ -20143,6 +20581,18 @@ /obj/item/clothing/mask/gas, /turf/open/floor/vault, /area/lv624/lazarus/quartstorage) +"qap" = ( +/obj/structure/machinery/light, +/obj/structure/stairs/perspective{ + dir = 9; + icon_state = "p_stair_full" + }, +/obj/structure/platform, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "qaE" = ( /obj/effect/landmark/hunter_primary, /obj/effect/landmark/queen_spawn, @@ -20176,6 +20626,13 @@ icon_state = "white" }, /area/lv624/lazarus/corporate_dome) +"qez" = ( +/obj/effect/landmark/good_item, +/obj/effect/decal/cleanable/blood/xeno, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "qeW" = ( /turf/open/gm/coast/beachcorner2/north_west, /area/lv624/ground/caves/sand_temple) @@ -20210,6 +20667,21 @@ }, /turf/open/gm/dirt, /area/lv624/ground/caves/west_caves) +"qit" = ( +/obj/item/prop/alien/hugger{ + pixel_x = -20; + pixel_y = 8 + }, +/obj/item/tool/hatchet{ + pixel_x = -14; + pixel_y = 6 + }, +/obj/effect/decal/cleanable/blood/xeno, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "qiL" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/amanita, /turf/open/auto_turf/strata_grass/layer1, @@ -20418,7 +20890,9 @@ "qGR" = ( /obj/structure/foamed_metal, /obj/structure/flora/jungle/vines/heavy, -/turf/open/floor/plating, +/turf/open/floor{ + icon_state = "delivery" + }, /area/lv624/lazarus/engineering) "qHC" = ( /obj/structure/largecrate/random/barrel/red, @@ -20475,6 +20949,24 @@ /obj/structure/flora/jungle/alienplant1, /turf/open/gm/river, /area/lv624/ground/jungle/west_jungle) +"qNl" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/reagent_dispensers/fueltank, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) +"qNz" = ( +/obj/structure/stairs/perspective{ + dir = 1; + icon_state = "p_stair_full" + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "qNQ" = ( /obj/structure/machinery/landinglight/ds2/delayone{ dir = 4 @@ -20556,11 +21048,11 @@ /area/lv624/ground/caves/north_west_caves) "qVi" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -10; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -10; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/north_east_caves) @@ -20584,6 +21076,12 @@ icon_state = "vault" }, /area/lv624/lazarus/quartstorage) +"qXt" = ( +/obj/item/stack/rods, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "qYF" = ( /obj/structure/flora/bush/ausbushes/var3/sunnybush, /turf/open/gm/dirt, @@ -20697,10 +21195,10 @@ /area/lv624/ground/jungle/north_east_jungle) "rhi" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 4; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 4 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/east_caves) @@ -20714,32 +21212,40 @@ icon_state = "green" }, /area/lv624/lazarus/hydroponics) +"rkZ" = ( +/obj/item/shard, +/obj/item/stack/rods, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "rmg" = ( -/obj/structure/machinery/power/monitor, -/obj/structure/foamed_metal, /obj/structure/machinery/light{ dir = 1 }, +/obj/effect/decal/cleanable/dirt, +/obj/structure/foamed_metal, /turf/open/floor{ icon_state = "dark" }, /area/lv624/lazarus/engineering) "rmt" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 4; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 4 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_central_caves) "rmW" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 11; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 11; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/west_caves) @@ -20875,11 +21381,11 @@ /area/lv624/lazarus/corporate_dome) "rAU" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 6; - pixel_y = -8; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 6; + pixel_y = -8 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/east_caves) @@ -20928,11 +21434,11 @@ /area/lv624/lazarus/quartstorage) "rGE" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 6; - pixel_y = -8; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 6; + pixel_y = -8 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_east_caves) @@ -21166,6 +21672,9 @@ icon_state = "vault" }, /area/lv624/lazarus/quartstorage) +"sfg" = ( +/turf/open/gm/dirtgrassborder/grassdirt_corner2/south_east, +/area/lv624/ground/jungle/south_central_jungle) "sfH" = ( /obj/structure/inflatable, /turf/open/gm/dirt, @@ -21221,10 +21730,10 @@ /area/lv624/ground/caves/sand_temple) "snc" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 4; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 4 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_west_caves) @@ -21324,6 +21833,15 @@ /obj/structure/flora/bush/ausbushes/grassybush, /turf/open/gm/grass/grass1, /area/lv624/lazarus/landing_zones/lz2) +"svv" = ( +/obj/structure/extinguisher_cabinet{ + pixel_y = 30 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "swR" = ( /obj/structure/flora/jungle/vines/light_3, /obj/structure/flora/jungle/vines/heavy, @@ -21638,11 +22156,11 @@ /area/lv624/ground/caves/sand_temple) "sXg" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -10; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -10; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/west_caves) @@ -21666,8 +22184,6 @@ }, /area/lv624/lazarus/landing_zones/lz1) "taK" = ( -/obj/structure/fence, -/obj/structure/flora/bush/ausbushes/lavendergrass, /turf/open/gm/dirtgrassborder/grassdirt_corner2/south_east, /area/lv624/ground/colony/north_tcomms_road) "tbV" = ( @@ -21739,11 +22255,11 @@ /area/lv624/ground/river/west_river) "thk" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -5; - pixel_y = -5; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -5; + pixel_y = -5 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/central_caves) @@ -21912,11 +22428,11 @@ /area/lv624/ground/barrens/south_eastern_barrens) "tuJ" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -5; - pixel_y = -5; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -5; + pixel_y = -5 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/north_east_caves) @@ -21976,6 +22492,16 @@ /obj/effect/landmark/crap_item, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/south_central_jungle) +"tzP" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/bed/chair/office/light{ + dir = 1 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "tBB" = ( /obj/structure/machinery/colony_floodlight, /turf/open/gm/grass/grass1, @@ -22102,6 +22628,10 @@ icon_state = "whiteyellowfull" }, /area/lv624/ground/barrens/south_eastern_barrens) +"tMP" = ( +/obj/structure/window_frame/colony, +/turf/open/floor/plating, +/area/lv624/lazarus/comms) "tMQ" = ( /obj/effect/decal/grass_overlay/grass1/inner{ dir = 9 @@ -22126,11 +22656,11 @@ /area/lv624/ground/caves/south_central_caves) "tQU" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_west_caves) @@ -22222,6 +22752,16 @@ "tZD" = ( /turf/closed/wall/r_wall, /area/lv624/lazarus/landing_zones/lz2) +"uaz" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/bed/chair/office/light{ + dir = 8 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "uaL" = ( /obj/structure/flora/bush/ausbushes/lavendergrass, /turf/open/auto_turf/strata_grass/layer1, @@ -22350,6 +22890,16 @@ icon_state = "desert1" }, /area/lv624/ground/barrens/south_eastern_barrens) +"ukS" = ( +/obj/structure/reagent_dispensers/fueltank, +/obj/item/device/flashlight{ + pixel_y = 5 + }, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "ukY" = ( /obj/effect/landmark/structure_spawner/xvx_hive/xeno_door, /obj/effect/landmark/structure_spawner/setup/distress/xeno_door, @@ -22403,6 +22953,13 @@ /obj/structure/flora/bush/ausbushes/reedbush, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/east_caves) +"upp" = ( +/obj/structure/platform_decoration, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "upM" = ( /obj/effect/landmark/crap_item, /turf/open/gm/grass/grass1, @@ -22415,8 +22972,8 @@ /area/lv624/ground/caves/east_caves) "upV" = ( /obj/item/stack/cable_coil/random{ - pixel_y = 9; - pixel_x = 7 + pixel_x = 7; + pixel_y = 9 }, /turf/open/gm/dirt, /area/lv624/ground/jungle/east_jungle) @@ -22471,11 +23028,11 @@ /area/lv624/ground/caves/north_central_caves) "uxL" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 11; - pixel_y = -2; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 11; + pixel_y = -2 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_central_caves) @@ -22719,6 +23276,12 @@ "uWJ" = ( /turf/closed/wall/strata_ice/jungle, /area/lv624/ground/caves/south_west_caves) +"uXT" = ( +/obj/item/device/assembly/timer, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "uXV" = ( /obj/structure/flora/bush/ausbushes/lavendergrass, /turf/open/gm/dirt, @@ -22789,6 +23352,15 @@ /obj/structure/machinery/colony_floodlight, /turf/open/gm/grass/grass1, /area/lv624/lazarus/landing_zones/lz2) +"vdl" = ( +/obj/structure/stairs/perspective{ + dir = 4; + icon_state = "p_stair_full" + }, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "vdt" = ( /obj/effect/landmark/structure_spawner/xvx_hive/xeno_door, /obj/effect/landmark/structure_spawner/setup/distress/xeno_door, @@ -22897,11 +23469,11 @@ dir = 10 }, /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/gm/dirt, /area/lv624/ground/caves/south_east_caves) @@ -23119,6 +23691,12 @@ /obj/item/bananapeel, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/west_jungle) +"vJM" = ( +/obj/item/shard, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/comms) "vKc" = ( /obj/effect/landmark/crap_item, /turf/open/gm/grass/grass1, @@ -23130,6 +23708,16 @@ "vLO" = ( /turf/open/gm/dirtgrassborder/south, /area/lv624/lazarus/quartstorage/outdoors) +"vMC" = ( +/obj/structure/extinguisher_cabinet{ + pixel_y = 30 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "vMV" = ( /obj/effect/landmark/structure_spawner/xvx_hive/xeno_door, /obj/structure/blocker/forcefield/multitile_vehicles, @@ -23183,6 +23771,17 @@ /obj/structure/flora/bush/ausbushes/var3/sunnybush, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_east_caves) +"vRe" = ( +/obj/structure/surface/table, +/obj/structure/machinery/light, +/obj/item/tool/wrench, +/obj/item/tool/weldingtool, +/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "vSG" = ( /obj/structure/flora/jungle/vines/light_1, /turf/open/gm/grass/grass1, @@ -23242,6 +23841,12 @@ /obj/structure/flora/jungle/vines/heavy, /turf/open/gm/grass/grass1, /area/lv624/ground/jungle/north_west_jungle) +"vYL" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + icon_state = "dark" + }, +/area/lv624/lazarus/engineering) "vZT" = ( /obj/effect/landmark/objective_landmark/close, /turf/open/gm/dirt, @@ -23315,11 +23920,11 @@ /area/lv624/ground/caves/sand_temple) "whk" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/north_west_caves) @@ -23563,11 +24168,11 @@ /area/lv624/lazarus/landing_zones/lz2) "wHE" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = -1; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = -1; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/south_west_caves) @@ -23615,11 +24220,11 @@ /area/lv624/ground/jungle/west_central_jungle) "wMr" = ( /obj/item/reagent_container/food/snacks/grown/mushroom/glowshroom{ - pixel_x = 2; - pixel_y = 7; light_on = 1; light_range = 1; - light_system = 1 + light_system = 1; + pixel_x = 2; + pixel_y = 7 }, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/north_east_caves) @@ -23886,6 +24491,14 @@ /obj/structure/flora/jungle/vines/light_2, /turf/open/gm/grass/grass2, /area/lv624/ground/jungle/south_west_jungle) +"xfa" = ( +/obj/structure/reagent_dispensers/watertank, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "xfP" = ( /obj/item/stack/rods, /obj/item/shard, @@ -23970,6 +24583,14 @@ /obj/effect/landmark/lv624/fog_blocker, /turf/open/gm/dirt, /area/lv624/ground/river/west_river) +"xvz" = ( +/obj/structure/platform_decoration, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor{ + dir = 9; + icon_state = "brown" + }, +/area/lv624/lazarus/comms) "xvN" = ( /obj/structure/barricade/handrail/strata{ dir = 1 @@ -24274,6 +24895,16 @@ /obj/structure/blocker/forcefield/multitile_vehicles, /turf/open/auto_turf/strata_grass/layer1, /area/lv624/ground/caves/central_caves) +"ydX" = ( +/obj/structure/machinery/door/airlock/multi_tile/almayer/engidoor/glass/colony{ + name = "\improper Communications Dome"; + req_access_txt = "100"; + req_one_access = null + }, +/turf/open/floor{ + icon_state = "delivery" + }, +/area/lv624/lazarus/engineering) "yfe" = ( /obj/effect/decal/grass_overlay/grass1{ dir = 8 @@ -31164,7 +31795,7 @@ uiW sqs oTJ qKC -kWX +pKm hdh oKP aAp @@ -35989,7 +36620,7 @@ rAo fkJ fau xgE -efp +aWZ efp efp efp @@ -36218,7 +36849,7 @@ xgE sFY xgE xgE -eyb +bgL bgL bgL bgL @@ -37585,7 +38216,7 @@ xgE sxY sxY xgE -cPV +aRf cPV uSq qIO @@ -38959,8 +39590,8 @@ uSq qIO qIO qIO -qIO -qIO +ctS +ctS ntL kxI kxI @@ -39187,9 +39818,9 @@ uSq njC qIO qIO -qIO -qIO -ntL +ctS +sfg +vVe kxI jxG qZv @@ -39415,9 +40046,9 @@ uSq qIO qIO qIO -qIO -qIO -ntL +ctS +hLu +kxI kxI oAJ qZv @@ -39644,8 +40275,8 @@ aPT aUk aPT aPt -qIO -ntL +hLu +kxI kxI gzd eqs @@ -39868,9 +40499,9 @@ aPT aPT aPT aPt -aTG -aQn +vMC aQn +qNl aPt aPT aPT @@ -40092,17 +40723,17 @@ bwR cPV bCH aPT +aWy +aXd aQn -aON -aQn -aQn -aQn -aQn +aTi +aTi aQn aQn +aTi aQn isR -aSg +vRe aPT ooM nuU @@ -40325,12 +40956,12 @@ aQn aQn aQn aTH -aUl aQn aQn aQn aQn -aWX +aQn +uaz aPT kxI kxI @@ -40546,19 +41177,19 @@ byY fTf nhi cPV -cPV +aRf aPT -aRe -aSd -aSI -aTg +aWA aQn +aON +aTg +iYL aQn aQn aTg -aWd -aSd -aWY +hJh +aQn +aTi aPT kxI kxI @@ -40773,10 +41404,10 @@ ylL byY mun oGs -lud +oGs aPt aPt -aRf +euU aQn aTg aTg @@ -40785,8 +41416,8 @@ aQn aVb aTg aTg +aTG aQn -aWZ aPt aPt kxI @@ -41003,19 +41634,19 @@ byY byY byY aPN +cNE +aTi aQn aQn aQn -aSJ -aTh -aQn -aQn aQn -aTh aQn aQn +cQU +aTi aQn aQn +xfa aPT kxI lAX @@ -41233,9 +41864,9 @@ byY aPO aQn aQn +rkZ aQn -aQn -aQn +aTi vEj aTg rVH @@ -41458,20 +42089,20 @@ byY byY byY byY -aPN -aQn -aQn +aSK +aSI +aYh aQn aQn aTi -aQn -aQn -aQn aTi -aQn -aQn -aQn -aQn +qit +upp +jfN +jfN +gKN +xvz +elO aPT kxI lIU @@ -41688,16 +42319,16 @@ byY taK aPt aPt -aTI +aQn aQn aTg aTg -aTI -aQn -aVb +kKL +aXn +qap aTg aTg -aTG +cyP aXa aPt aPt @@ -41913,20 +42544,20 @@ uiN byY byY byY -fTf +aUl aPT aRi +aTi aQn aQn -aSK aTg aTJ -aQn -aQn +aZG +qNz aTg aWe -aWx -aZc +kvv +kvv aPT kxI kxI @@ -42141,19 +42772,19 @@ uiN byY byY byY -fTf +aUl aPT aSe +aWE +aXn aQn aQn -aQn -aQn -aQn +oiR aZK -aQn -aQn -aQn -aQn +qNz +kvv +kvv +kvv aZh aPT kxI @@ -42369,20 +43000,20 @@ uiN byY byY byY -fTf +aUl aPT -aQn +aSJ aTk +aXe aQn aQn -aTj -aQn -aQn -aQn -aTH -aQn aQn aQn +qNz +qez +kvv +fij +oIO aPT sBJ kxI @@ -42592,25 +43223,25 @@ aLv aJr iSg aJr -aJr +aCV aIO aMN aNA aMN aIO aPt -aTI -aQn -aQn +aWd +aSJ +aZG aQn -aPt aQn aQn aQn +qNz +kvv +dHr +bUc aPt -aPT -aPT -aPN aPt tzK kxI @@ -42826,20 +43457,20 @@ aMO aKB aMP aIO -aSg +aSd +aTj +aWX +eyb aQn aQn aQn aQn -aPt -aPT -aUk -aPT -aPt -aTK -aTK -aXd -aXn +nrK +kvv +kvv +qXt +tMP +kxI kxI tsa kxI @@ -43054,20 +43685,20 @@ aMP aNB aNJ aIO +aUt aIL -aIL -aIO +aWY aRe -aQn -aPT -aTK -aTK -aTK -aTK -aTK -aWy -aXe -aXn +tzP +bJe +ukS +aTi +hOo +vJM +kvv +pzP +lQP +kxI kxI kxI kxI @@ -43287,15 +43918,15 @@ aQo aJz aIO aSg -aPT -aTK +aPt aTK +oRY aSL +aWf aVB aWf -fFN aSL -aXn +sBJ kxI kxI lUc @@ -43514,14 +44145,14 @@ aEt aQp aRo aIO -aSg +kWX aSL aSL aSL aSL rmg aTq -aWA +aTq aSL aSL aVB @@ -43747,9 +44378,9 @@ aSL aVg tem aVd -aTM +kzp +aTq aTq -aZG aSX aZI aXJ @@ -43976,12 +44607,12 @@ ogR aUs nwR aTM -aTM -aTM +aTq +kzp aXf aTM aTM -aXW +mBH aVB kxI kxI @@ -44199,12 +44830,12 @@ aQs aQo aSi mkU -qGR +lud aWD aUq aTN aTM -aTM +fMa aUo aSX aZJ @@ -44427,13 +45058,13 @@ aQt aQo aSj qGR -mkU +jdL aWD aUq aTN udM aTM -aWE +aUx aSX aSX aSX @@ -44666,7 +45297,7 @@ aSX aZd aZx aXZ -aYh +vYL aVB kxI kxI @@ -44887,14 +45518,14 @@ aSL aVf jMk aWg -aTM -aTM +lzW +kyz aWF aSX -aXs +svv aTM aZM -aTM +vYL aXg kyN kxI @@ -45116,9 +45747,9 @@ aSX aSX aSX aXs +drX aTM -aTM -aXg +ydX aTM aVC aTM @@ -45341,12 +45972,12 @@ aJz aSL aTs aTP -aUt +bLH aSX aVF -aTM -aTM -aXg +iiO +uXT +oas aTM aTM aZf @@ -45572,12 +46203,12 @@ aTR aUu aSX aVG -aTM +vdl aWG aSX aXt aWi -aYc +bQf aVB dEp kxI @@ -45796,11 +46427,11 @@ xuk oUy aSL aTu -aZM -aTM +fLf +fQx aVj -aTM -aWi +mkZ +hDe aWH aSX aXu @@ -46025,7 +46656,7 @@ mNO fFN aTM aTM -aUx +fAs aSX aVH aTq @@ -46483,7 +47114,7 @@ aSm aSm oUy aSL -hHc +aVB aWf aVB aSL @@ -47594,7 +48225,7 @@ oUy aMD bbJ bbO -aCV +oUy aDS aDS aFh diff --git a/tgui/packages/tgfont/config.cjs b/tgui/packages/tgfont/config.cjs index 4f6b58f1061e..73d96ac6ce90 100644 --- a/tgui/packages/tgfont/config.cjs +++ b/tgui/packages/tgfont/config.cjs @@ -7,8 +7,12 @@ module.exports = { name: 'tgfont', inputDir: './icons', + normalize: true, outputDir: './dist', fontTypes: ['woff2', 'eot'], assetTypes: ['css'], prefix: 'tg', + formatOptions: { + preserveAspectRatio: true, + }, }; diff --git a/tgui/packages/tgfont/icons/image-minus.svg b/tgui/packages/tgfont/icons/image-minus.svg new file mode 100644 index 000000000000..8c3231917ff9 --- /dev/null +++ b/tgui/packages/tgfont/icons/image-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tgui/packages/tgfont/icons/image-plus.svg b/tgui/packages/tgfont/icons/image-plus.svg new file mode 100644 index 000000000000..1658509429e3 --- /dev/null +++ b/tgui/packages/tgfont/icons/image-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tgui/packages/tgfont/icons/sound-minus.svg b/tgui/packages/tgfont/icons/sound-minus.svg new file mode 100644 index 000000000000..df51179d4b53 --- /dev/null +++ b/tgui/packages/tgfont/icons/sound-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tgui/packages/tgfont/icons/sound-plus.svg b/tgui/packages/tgfont/icons/sound-plus.svg new file mode 100644 index 000000000000..c5f40d53b560 --- /dev/null +++ b/tgui/packages/tgfont/icons/sound-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tgui/packages/tgfont/static/tgfont.css b/tgui/packages/tgfont/static/tgfont.css index 992f088913c4..49e59cdd6959 100644 --- a/tgui/packages/tgfont/static/tgfont.css +++ b/tgui/packages/tgfont/static/tgfont.css @@ -1,7 +1,7 @@ @font-face { font-family: "tgfont"; - src: url("./tgfont.woff2?b809fa4bdd4aff7084540f130e4e524b") format("woff2"), -url("./tgfont.eot?b809fa4bdd4aff7084540f130e4e524b#iefix") format("embedded-opentype"); + src: url("./tgfont.woff2?0843e2c3dacddd319720859dcaeda508") format("woff2"), +url("./tgfont.eot?0843e2c3dacddd319720859dcaeda508#iefix") format("embedded-opentype"); } i[class^="tg-"]:before, i[class*=" tg-"]:before { @@ -15,39 +15,15 @@ i[class^="tg-"]:before, i[class*=" tg-"]:before { -moz-osx-font-smoothing: grayscale; } -.tg-air-tank-slash:before { - content: "\f101"; -} -.tg-air-tank:before { - content: "\f102"; -} -.tg-bad-touch:before { - content: "\f103"; -} .tg-image-minus:before { - content: "\f104"; + content: "\f101"; } .tg-image-plus:before { - content: "\f105"; -} -.tg-nanotrasen-logo:before { - content: "\f106"; -} -.tg-non-binary:before { - content: "\f107"; -} -.tg-prosthetic-full:before { - content: "\f108"; -} -.tg-prosthetic-leg:before { - content: "\f109"; + content: "\f102"; } .tg-sound-minus:before { - content: "\f10a"; + content: "\f103"; } .tg-sound-plus:before { - content: "\f10b"; -} -.tg-syndicate-logo:before { - content: "\f10c"; + content: "\f104"; } diff --git a/tgui/packages/tgfont/static/tgfont.eot b/tgui/packages/tgfont/static/tgfont.eot index e879c327d3f1..17e3790d026c 100644 Binary files a/tgui/packages/tgfont/static/tgfont.eot and b/tgui/packages/tgfont/static/tgfont.eot differ diff --git a/tgui/packages/tgfont/static/tgfont.woff2 b/tgui/packages/tgfont/static/tgfont.woff2 index 6a3be3d2cb40..a4cc01a984a1 100644 Binary files a/tgui/packages/tgfont/static/tgfont.woff2 and b/tgui/packages/tgfont/static/tgfont.woff2 differ diff --git a/tgui/packages/tgui/components/RestrictedInput.jsx b/tgui/packages/tgui/components/RestrictedInput.jsx index 082fc606d998..f61025039f34 100644 --- a/tgui/packages/tgui/components/RestrictedInput.jsx +++ b/tgui/packages/tgui/components/RestrictedInput.jsx @@ -1,48 +1,56 @@ -import { classes } from 'common/react'; +import { KEY_ENTER, KEY_ESCAPE } from 'common/keycodes'; import { clamp } from 'common/math'; +import { classes } from 'common/react'; import { Component, createRef } from 'react'; + import { Box } from './Box'; -import { KEY_ESCAPE, KEY_ENTER } from 'common/keycodes'; -const DEFAULT_MIN = -16777216; -const DEFAULT_MAX = 16777216; +const DEFAULT_MIN = 0; +const DEFAULT_MAX = 10000; /** - * Takes a string input and parses integers from it. + * Takes a string input and parses integers or floats from it. * If none: Minimum is set. * Else: Clamps it to the given range. */ -const getClampedNumber = (value, minValue, maxValue) => { +const getClampedNumber = (value, minValue, maxValue, allowFloats) => { const minimum = minValue || DEFAULT_MIN; const maximum = maxValue || maxValue === 0 ? maxValue : DEFAULT_MAX; - const defaultValue = maximum < 0 ? minimum : minimum > 0 ? minimum : 0; if (!value || !value.length) { - return String(defaultValue); + return String(minimum); } - let parsedValue = parseFloat(value.replace(/[^\-.\d]/g, ''), 10); + let parsedValue = allowFloats + ? parseFloat(value.replace(/[^\-\d.]/g, '')) + : parseInt(value.replace(/[^\-\d]/g, ''), 10); if (isNaN(parsedValue)) { - return String(defaultValue); + return String(minimum); } else { return String(clamp(parsedValue, minimum, maximum)); } }; export class RestrictedInput extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); this.state = { editing: false, }; this.handleBlur = (e) => { + const { maxValue, minValue, allowFloats } = this.props; const { editing } = this.state; if (editing) { + e.target.value = getClampedNumber( + e.target.value, + minValue, + maxValue, + allowFloats + ); this.setEditing(false); } }; this.handleChange = (e) => { - const { maxValue, minValue, onChange } = this.props; - e.target.value = getClampedNumber(e.target.value, minValue, maxValue); + const { onChange } = this.props; if (onChange) { onChange(e, +e.target.value); } @@ -64,9 +72,14 @@ export class RestrictedInput extends Component { } }; this.handleKeyDown = (e) => { - const { maxValue, minValue, onChange, onEnter } = this.props; + const { maxValue, minValue, onChange, onEnter, allowFloats } = this.props; if (e.keyCode === KEY_ENTER) { - const safeNum = getClampedNumber(e.target.value, minValue, maxValue); + const safeNum = getClampedNumber( + e.target.value, + minValue, + maxValue, + allowFloats + ); this.setEditing(false); if (onChange) { onChange(e, +safeNum); @@ -91,11 +104,16 @@ export class RestrictedInput extends Component { } componentDidMount() { - const { maxValue, minValue } = this.props; + const { maxValue, minValue, allowFloats } = this.props; const nextValue = this.props.value?.toString(); const input = this.inputRef.current; if (input) { - input.value = getClampedNumber(nextValue, minValue, maxValue); + input.value = getClampedNumber( + nextValue, + minValue, + maxValue, + allowFloats + ); } if (this.props.autoFocus || this.props.autoSelect) { setTimeout(() => { @@ -109,14 +127,19 @@ export class RestrictedInput extends Component { } componentDidUpdate(prevProps, _) { - const { maxValue, minValue } = this.props; + const { maxValue, minValue, allowFloats } = this.props; const { editing } = this.state; const prevValue = prevProps.value?.toString(); const nextValue = this.props.value?.toString(); const input = this.inputRef.current; if (input && !editing) { if (nextValue !== prevValue && nextValue !== input.value) { - input.value = getClampedNumber(nextValue, minValue, maxValue); + input.value = getClampedNumber( + nextValue, + minValue, + maxValue, + allowFloats + ); } } } diff --git a/tgui/packages/tgui/interfaces/NumberInputModal.tsx b/tgui/packages/tgui/interfaces/NumberInputModal.tsx index eff7acb62a1b..32d7161d8faf 100644 --- a/tgui/packages/tgui/interfaces/NumberInputModal.tsx +++ b/tgui/packages/tgui/interfaces/NumberInputModal.tsx @@ -13,11 +13,19 @@ type NumberInputData = { min_value: number | null; timeout: number; title: string; + integer_only: 0 | 1; }; -export const NumberInputModal = (props) => { +export const NumberInputModal = () => { const { act, data } = useBackend(); - const { init_value, large_buttons, message = '', timeout, title } = data; + const { + init_value, + large_buttons, + message = '', + timeout, + title, + integer_only, + } = data; const [input, setInput] = useLocalState('input', init_value); const onChange = (value: number) => { if (value === input) { @@ -56,7 +64,12 @@ export const NumberInputModal = (props) => { {message} - + @@ -72,7 +85,7 @@ export const NumberInputModal = (props) => { const InputArea = (props) => { const { act, data } = useBackend(); const { min_value, max_value, init_value } = data; - const { input, onClick, onChange } = props; + const { input, onClick, onChange, integer_only } = props; return ( @@ -94,6 +107,7 @@ const InputArea = (props) => { onChange={(_, value) => onChange(value)} onEnter={(_, value) => act('submit', { entry: value })} value={input} + allowFloats={integer_only === 1 ? false : true} /> diff --git a/tgui/packages/tgui/interfaces/PlayerPanel.jsx b/tgui/packages/tgui/interfaces/PlayerPanel.jsx index 85f73b581c67..88b30aa51802 100644 --- a/tgui/packages/tgui/interfaces/PlayerPanel.jsx +++ b/tgui/packages/tgui/interfaces/PlayerPanel.jsx @@ -236,7 +236,6 @@ const GeneralActions = (props) => { /> { />