diff --git a/code/__defines/species_languages.dm b/code/__defines/species_languages.dm
index c1aaed9e42..44273c80ff 100644
--- a/code/__defines/species_languages.dm
+++ b/code/__defines/species_languages.dm
@@ -5,12 +5,15 @@
#define NO_PAIN 0x8 // Cannot suffer halloss/recieves deceptive health indicator.
#define NO_SLIP 0x10 // Cannot fall over.
#define NO_POISON 0x20 // Cannot not suffer toxloss.
+#define NO_EMBED 0x40 // Can step on broken glass with no ill-effects and cannot have shrapnel embedded in it.
// unused: 0x8000 - higher than this will overflow
// Species spawn flags
#define SPECIES_IS_WHITELISTED 0x1 // Must be whitelisted to play.
#define SPECIES_IS_RESTRICTED 0x2 // Is not a core/normally playable species. (castes, mutantraces)
#define SPECIES_CAN_JOIN 0x4 // Species is selectable in chargen.
+#define SPECIES_NO_FBP_CONSTRUCTION 0x8 // FBP of this species can't be made in-game.
+#define SPECIES_NO_FBP_CHARGEN 0x10 // FBP of this species can't be selected at chargen.
// Species appearance flags
#define HAS_SKIN_TONE 0x1 // Skin tone selectable in chargen. (0-255)
@@ -22,15 +25,19 @@
#define RADIATION_GLOWS 0x40 // Radiation causes this character to glow.
// Languages.
+#define LANGUAGE_GALCOM "Galactic Common"
+#define LANGUAGE_EAL "Encoded Audio Language"
#define LANGUAGE_SOL_COMMON "Sol Common"
#define LANGUAGE_UNATHI "Sinta'unathi"
#define LANGUAGE_SIIK "Siik"
#define LANGUAGE_SKRELLIAN "Skrellian"
-#define LANGUAGE_ROOTSPEAK "Rootspeak"
#define LANGUAGE_TRADEBAND "Tradeband"
#define LANGUAGE_GUTTER "Gutter"
#define LANGUAGE_SCHECHI "Schechi"
+#define LANGUAGE_ROOTLOCAL "Local Rootspeak"
+#define LANGUAGE_ROOTGLOBAL "Global Rootspeak"
#define LANGUAGE_CULT "Cult"
+#define LANGUAGE_SIGN "Sign Language"
// Language flags.
#define WHITELISTED 1 // Language is available if the speaker is whitelisted.
@@ -42,3 +49,4 @@
#define INNATE 64 // All mobs can be assumed to speak and understand this language. (audible emotes)
#define NO_TALK_MSG 128 // Do not show the "\The [speaker] talks into \the [radio]" message
#define NO_STUTTER 256 // No stuttering, slurring, or other speech problems
+#define ALT_TRANSMIT 512 // Language is not based on vision or sound (Todo: add this into the say code and use it for the rootspeak languages)
\ No newline at end of file
diff --git a/code/__defines/unit_tests.dm b/code/__defines/unit_tests.dm
index 478c75fdb2..840673221c 100644
--- a/code/__defines/unit_tests.dm
+++ b/code/__defines/unit_tests.dm
@@ -1,4 +1,5 @@
#define ASCII_ESC ascii2text(27)
#define ASCII_RED "[ASCII_ESC]\[31m"
#define ASCII_GREEN "[ASCII_ESC]\[32m"
+#define ASCII_YELLOW "[ASCII_ESC]\[33m"
#define ASCII_RESET "[ASCII_ESC]\[0m"
\ No newline at end of file
diff --git a/code/_helpers/turfs.dm b/code/_helpers/turfs.dm
index 8583628114..1926246856 100644
--- a/code/_helpers/turfs.dm
+++ b/code/_helpers/turfs.dm
@@ -32,3 +32,10 @@
if(!available_turfs.len)
available_turfs = start_turfs
return pick(available_turfs)
+
+/proc/is_below_sound_pressure(var/turf/T)
+ var/datum/gas_mixture/environment = T ? T.return_air() : null
+ var/pressure = environment ? environment.return_pressure() : 0
+ if(pressure < SOUND_MINIMUM_PRESSURE)
+ return TRUE
+ return FALSE
\ No newline at end of file
diff --git a/code/_helpers/type2type.dm b/code/_helpers/type2type.dm
index 0bcf546123..8dc9cb020b 100644
--- a/code/_helpers/type2type.dm
+++ b/code/_helpers/type2type.dm
@@ -73,14 +73,14 @@
// Turns a direction into text
/proc/dir2text(direction)
switch (direction)
- if (1.0) return "north"
- if (2.0) return "south"
- if (4.0) return "east"
- if (8.0) return "west"
- if (5.0) return "northeast"
- if (6.0) return "southeast"
- if (9.0) return "northwest"
- if (10.0) return "southwest"
+ if (NORTH) return "north"
+ if (SOUTH) return "south"
+ if (EAST) return "east"
+ if (WEST) return "west"
+ if (NORTHEAST) return "northeast"
+ if (SOUTHEAST) return "southeast"
+ if (NORTHWEST) return "northwest"
+ if (SOUTHWEST) return "southwest"
// Turns text into proper directions
/proc/text2dir(direction)
diff --git a/code/controllers/communications.dm b/code/controllers/communications.dm
index f9e7b67c74..97d193bc5b 100644
--- a/code/controllers/communications.dm
+++ b/code/controllers/communications.dm
@@ -108,6 +108,7 @@ var/const/ERT_FREQ = 1345
var/const/AI_FREQ = 1343
var/const/DTH_FREQ = 1341
var/const/SYND_FREQ = 1213
+var/const/ENT_FREQ = 1461 //entertainment frequency. This is not a diona exclusive frequency.
// department channels
var/const/PUB_FREQ = 1459
@@ -135,6 +136,7 @@ var/list/radiochannels = list(
"Supply" = SUP_FREQ,
"Service" = SRV_FREQ,
"AI Private" = AI_FREQ,
+ "Entertainment" = ENT_FREQ,
"Medical(I)" = MED_I_FREQ,
"Security(I)" = SEC_I_FREQ
)
@@ -146,7 +148,7 @@ var/list/CENT_FREQS = list(ERT_FREQ, DTH_FREQ)
var/list/ANTAG_FREQS = list(SYND_FREQ)
//Department channels, arranged lexically
-var/list/DEPT_FREQS = list(AI_FREQ, COMM_FREQ, ENG_FREQ, MED_FREQ, SEC_FREQ, SCI_FREQ, SRV_FREQ, SUP_FREQ)
+var/list/DEPT_FREQS = list(AI_FREQ, COMM_FREQ, ENG_FREQ, ENT_FREQ, MED_FREQ, SEC_FREQ, SCI_FREQ, SRV_FREQ, SUP_FREQ)
#define TRANSMISSION_WIRE 0
#define TRANSMISSION_RADIO 1
@@ -177,6 +179,8 @@ var/list/DEPT_FREQS = list(AI_FREQ, COMM_FREQ, ENG_FREQ, MED_FREQ, SEC_FREQ, SCI
return "supradio"
if(frequency == SRV_FREQ) // service
return "srvradio"
+ if(frequency == ENT_FREQ) // entertainment
+ return "entradio"
if(frequency in DEPT_FREQS)
return "deptradio"
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index ae0d5a2544..43424f97df 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -160,6 +160,7 @@ var/list/gamemode_cache = list()
var/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt
var/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt
var/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database
+ var/use_age_restriction_for_antags = 0 //Do antags use account age restrictions? --requires database
var/simultaneous_pm_warning_timeout = 100
@@ -278,6 +279,9 @@ var/list/gamemode_cache = list()
if ("use_age_restriction_for_jobs")
config.use_age_restriction_for_jobs = 1
+ if ("use_age_restriction_for_antags")
+ config.use_age_restriction_for_antags = 1
+
if ("jobs_have_minimal_access")
config.jobs_have_minimal_access = 1
diff --git a/code/datums/EPv2.dm b/code/datums/EPv2.dm
index a4e57d346d..37a1a0ba1f 100644
--- a/code/datums/EPv2.dm
+++ b/code/datums/EPv2.dm
@@ -112,23 +112,27 @@ var/global/list/all_exonet_connections = list()
return null
// Proc: send_message()
-// Parameters: 3 (target_address - the desired address to send the message to, message - the message to send, text - the message text if message is of type "text")
-// Description: Sends the message to target_address, by calling receive_message() on the desired datum.
-/datum/exonet_protocol/proc/send_message(var/target_address, var/message, var/text)
+// Parameters: 3 (target_address - the desired address to send the message to, data_type - text stating what the content is meant to be used for,
+// content - the actual 'message' being sent to the address)
+// Description: Sends the message to target_address, by calling receive_message() on the desired datum. Returns true if the message is recieved.
+/datum/exonet_protocol/proc/send_message(var/target_address, var/data_type, var/content)
if(!address)
- return 0
+ return FALSE
+ var/obj/machinery/exonet_node/node = get_exonet_node()
+ if(!node) // Telecomms went boom, ion storm, etc.
+ return FALSE
for(var/datum/exonet_protocol/exonet in all_exonet_connections)
if(exonet.address == target_address)
- exonet.receive_message(holder, address, message, text)
- break
+ node.write_log(src.address, target_address, data_type, content)
+ return exonet.receive_message(holder, address, data_type, content)
// Proc: receive_message()
-// Parameters: 4 (origin_atom - the origin datum's holder, origin_address - the address the message originated from, message - the message that was sent,
-// text - the message text if message is of type "text")
+// Parameters: 4 (origin_atom - the origin datum's holder, origin_address - the address the message originated from,
+// data_type - text stating what the content is meant to be used for, content - the actual 'message' being sent from origin_atom)
// Description: Called when send_message() successfully reaches the intended datum. By default, calls receive_exonet_message() on the holder atom.
-/datum/exonet_protocol/proc/receive_message(var/atom/origin_atom, var/origin_address, var/message, var/text)
- holder.receive_exonet_message(origin_atom, origin_address, message, text)
- return
+/datum/exonet_protocol/proc/receive_message(var/atom/origin_atom, var/origin_address, var/data_type, var/content)
+ holder.receive_exonet_message(origin_atom, origin_address, data_type, content)
+ return TRUE // for send_message()
// Proc: receive_exonet_message()
// Parameters: 3 (origin_atom - the origin datum's holder, origin_address - the address the message originated from, message - the message that was sent)
diff --git a/code/datums/underwear/bottom.dm b/code/datums/underwear/bottom.dm
index 379bb1aca4..944d373889 100644
--- a/code/datums/underwear/bottom.dm
+++ b/code/datums/underwear/bottom.dm
@@ -62,4 +62,9 @@
/datum/category_item/underwear/bottom/striped_panties
name = "Striped Panties"
icon_state = "striped_panties"
+ has_color = TRUE
+
+/datum/category_item/underwear/bottom/longjon
+ name = "Long John Bottoms"
+ icon_state = "ljonb"
has_color = TRUE
\ No newline at end of file
diff --git a/code/datums/underwear/undershirts.dm b/code/datums/underwear/undershirts.dm
index 273c5cba9e..9a4ab9600b 100644
--- a/code/datums/underwear/undershirts.dm
+++ b/code/datums/underwear/undershirts.dm
@@ -51,6 +51,18 @@
name = "Tank top, fire, feminine"
icon_state = "tank_fire_fem_s"
+/datum/category_item/underwear/undershirt/tank_top_rainbow
+ name = "Tank top, rainbow"
+ icon_state = "tank_rainbow_s"
+
+/datum/category_item/underwear/undershirt/tank_top_stripes
+ name = "Tank top, striped"
+ icon_state = "tank_stripes_s"
+
+/datum/category_item/underwear/undershirt/tank_top_sun
+ name = "Tank top, sun"
+ icon_state = "tank_sun_s"
+
/datum/category_item/underwear/undershirt/shirt_heart
name = "Shirt, heart"
icon_state = "lover_s"
@@ -125,4 +137,17 @@
/datum/category_item/underwear/undershirt/bowlingw
name = "Bowling Shirt, White"
- icon_state = "bowlingw"
\ No newline at end of file
+ icon_state = "bowlingw"
+
+/datum/category_item/underwear/undershirt/longjon
+ name = "Long John Shirt"
+ icon_state = "ljont"
+ has_color = TRUE
+
+/datum/category_item/underwear/undershirt/longstripe_black
+ name = "Longsleeve Striped Shirt, Black"
+ icon_state = "longstripe"
+
+/datum/category_item/underwear/undershirt/longstripe_blue
+ name = "Longsleeve Striped Shirt, Blue"
+ icon_state = "longstripe_blue"
\ No newline at end of file
diff --git a/code/game/antagonist/antagonist.dm b/code/game/antagonist/antagonist.dm
index 348bf6244d..47b82b82ac 100644
--- a/code/game/antagonist/antagonist.dm
+++ b/code/game/antagonist/antagonist.dm
@@ -47,6 +47,7 @@
var/mob_path = /mob/living/carbon/human // Mobtype this antag will use if none is provided.
var/feedback_tag = "traitor_objective" // End of round
var/bantype = "Syndicate" // Ban to check when spawning this antag.
+ var/minimum_player_age = 7 // Players need to be at least minimum_player_age days old before they are eligable for auto-spawning
var/suspicion_chance = 50 // Prob of being on the initial Command report
var/flags = 0 // Various runtime options.
@@ -105,6 +106,8 @@
if(ghosts_only && !istype(player.current, /mob/observer/dead))
candidates -= player
log_debug("[key_name(player)] is not eligible to become a [role_text]: Only ghosts may join as this role! They have been removed from the draft.")
+ else if(config.use_age_restriction_for_antags && player.current.client.player_age < minimum_player_age)
+ log_debug("[key_name(player)] is not eligible to become a [role_text]: Is only [player.current.client.player_age] day\s old, has to be [minimum_player_age] day\s!")
else if(istype(player.current, /mob/living/voice))
candidates -= player
log_debug("[key_name(player)] is not eligible to become a [role_text]: They are only a communicator voice. They have been removed from the draft.")
diff --git a/code/game/antagonist/station/changeling.dm b/code/game/antagonist/station/changeling.dm
index 7ace822b1a..4f60792810 100644
--- a/code/game/antagonist/station/changeling.dm
+++ b/code/game/antagonist/station/changeling.dm
@@ -56,23 +56,24 @@
return
/datum/antagonist/changeling/can_become_antag(var/datum/mind/player, var/ignore_role)
- if(..())
- if(player.current)
- if(ishuman(player.current))
- var/mob/living/carbon/human/H = player.current
- if(H.isSynthetic())
+ if(!..())
+ return 0
+ if(player.current)
+ if(ishuman(player.current))
+ var/mob/living/carbon/human/H = player.current
+ if(H.isSynthetic())
+ return 0
+ if(H.species.flags & NO_SCAN)
+ return 0
+ return 1
+ else if(isnewplayer(player.current))
+ if(player.current.client && player.current.client.prefs)
+ var/datum/species/S = all_species[player.current.client.prefs.species]
+ if(S && (S.flags & NO_SCAN))
return 0
- if(H.species.flags & NO_SCAN)
+ if(player.current.client.prefs.organ_data["torso"] == "cyborg") // Full synthetic.
return 0
return 1
- else if(isnewplayer(player.current))
- if(player.current.client && player.current.client.prefs)
- var/datum/species/S = all_species[player.current.client.prefs.species]
- if(S && (S.flags & NO_SCAN))
- return 0
- if(player.current.client.prefs.organ_data["torso"] == "cyborg") // Full synthetic.
- return 0
- return 1
return 0
/datum/antagonist/changeling/print_player_full(var/datum/mind/ply)
diff --git a/code/game/gamemodes/game_mode_latespawn.dm b/code/game/gamemodes/game_mode_latespawn.dm
index 4fc1bc6f77..ecafc0dbc5 100644
--- a/code/game/gamemodes/game_mode_latespawn.dm
+++ b/code/game/gamemodes/game_mode_latespawn.dm
@@ -31,6 +31,16 @@
if(emergency_shuttle.departed || !round_autoantag)
return
+ if(emergency_shuttle.shuttle && (emergency_shuttle.shuttle.moving_status == SHUTTLE_WARMUP || emergency_shuttle.shuttle.moving_status == SHUTTLE_INTRANSIT))
+ return // Don't do anything if the shuttle's coming.
+
+ var/mills = round_duration_in_ticks
+ var/mins = round((mills % 36000) / 600)
+ var/hours = round(mills / 36000)
+
+ if(hours >= 2 && mins >= 40) // Don't do anything in the last twenty minutes of the round, as well.
+ return
+
if(world.time < next_spawn)
return
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 7205d0e850..393d3aee74 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -5,11 +5,11 @@
/var/list/meteors_normal = list(/obj/effect/meteor/dust=3, /obj/effect/meteor/medium=8, /obj/effect/meteor/big=3, \
/obj/effect/meteor/flaming=1, /obj/effect/meteor/irradiated=3) //for normal meteor event
-/var/list/meteors_threatening = list(/obj/effect/meteor/medium=4, /obj/effect/meteor/big=8, \
- /obj/effect/meteor/flaming=3, /obj/effect/meteor/irradiated=3) //for threatening meteor event
+/var/list/meteors_threatening = list(/obj/effect/meteor/medium=5, /obj/effect/meteor/big=10, \
+ /obj/effect/meteor/flaming=3, /obj/effect/meteor/irradiated=3, /obj/effect/meteor/emp=3) //for threatening meteor event
/var/list/meteors_catastrophic = list(/obj/effect/meteor/medium=5, /obj/effect/meteor/big=75, \
- /obj/effect/meteor/flaming=10, /obj/effect/meteor/irradiated=10, /obj/effect/meteor/tunguska = 1) //for catastrophic meteor event
+ /obj/effect/meteor/flaming=10, /obj/effect/meteor/irradiated=10, /obj/effect/meteor/emp=10, /obj/effect/meteor/tunguska = 1) //for catastrophic meteor event
/var/list/meteors_dust = list(/obj/effect/meteor/dust) //for space dust event
@@ -38,7 +38,6 @@
var/Me = pickweight(meteortypes)
var/obj/effect/meteor/M = new Me(pickedstart)
M.dest = pickedgoal
- M.z_original = startLevel
spawn(0)
walk_towards(M, M.dest, 1)
return
@@ -97,11 +96,16 @@
var/dest
pass_flags = PASSTABLE
var/heavy = 0
- var/z_original = 1
+ var/z_original
var/meteordrop = /obj/item/weapon/ore/iron
var/dropamt = 2
+/obj/effect/meteor/New()
+ ..()
+ z_original = z
+
+
/obj/effect/meteor/Move()
if(z != z_original || loc == dest)
qdel(src)
@@ -151,7 +155,7 @@
hits--
if(hits <= 0)
make_debris()
- meteor_effect(heavy)
+ meteor_effect()
qdel(src)
/obj/effect/meteor/ex_act()
@@ -168,8 +172,8 @@
var/obj/item/O = new meteordrop(get_turf(src))
O.throw_at(dest, 5, 10)
-/obj/effect/meteor/proc/meteor_effect(var/effect=1)
- if(effect)
+/obj/effect/meteor/proc/meteor_effect()
+ if(heavy)
for(var/mob/M in player_list)
var/turf/T = get_turf(M)
if(!T || T.z != src.z)
@@ -197,7 +201,7 @@
dropamt = 3
/obj/effect/meteor/medium/meteor_effect()
- ..(heavy)
+ ..()
explosion(src.loc, 0, 1, 2, 3, 0)
//Large-sized
@@ -209,7 +213,7 @@
dropamt = 4
/obj/effect/meteor/big/meteor_effect()
- ..(heavy)
+ ..()
explosion(src.loc, 1, 2, 3, 4, 0)
//Flaming meteor
@@ -221,7 +225,7 @@
meteordrop = /obj/item/weapon/ore/phoron
/obj/effect/meteor/flaming/meteor_effect()
- ..(heavy)
+ ..()
explosion(src.loc, 1, 2, 3, 4, 0, 0, 5)
//Radiation meteor
@@ -233,12 +237,25 @@
/obj/effect/meteor/irradiated/meteor_effect()
- ..(heavy)
+ ..()
explosion(src.loc, 0, 0, 4, 3, 0)
new /obj/effect/decal/cleanable/greenglow(get_turf(src))
for(var/mob/living/L in view(5, src))
L.apply_effect(40, IRRADIATE)
+/obj/effect/meteor/emp
+ name = "conducting meteor"
+ icon_state = "glowing_blue"
+ desc = "Hide your floppies!"
+ meteordrop = /obj/item/weapon/ore/osmium
+ dropamt = 3
+
+/obj/effect/meteor/emp/meteor_effect()
+ ..()
+ // Best case scenario: Comparable to a low-yield EMP grenade.
+ // Worst case scenario: Comparable to a standard yield EMP grenade.
+ empulse(src, rand(2, 4), rand(4, 10))
+
//Station buster Tunguska
/obj/effect/meteor/tunguska
name = "tunguska meteor"
@@ -250,7 +267,7 @@
meteordrop = /obj/item/weapon/ore/phoron
/obj/effect/meteor/tunguska/meteor_effect()
- ..(heavy)
+ ..()
explosion(src.loc, 5, 10, 15, 20, 0)
/obj/effect/meteor/tunguska/Bump()
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 1f0b5f454f..03d3c1170f 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -219,6 +219,25 @@
light_range_on = 2
network = list(NETWORK_THUNDER)
circuit = /obj/item/weapon/circuitboard/security/telescreen/entertainment
+ var/obj/item/device/radio/radio = null
+
+/obj/machinery/computer/security/telescreen/entertainment/initialize()
+ ..()
+ radio = new(src)
+ radio.listening = TRUE
+ radio.broadcasting = FALSE
+ radio.set_frequency(ENT_FREQ)
+ radio.canhear_range = 7 // Same as default sight range.
+ power_change()
+
+/obj/machinery/computer/security/telescreen/entertainment/power_change()
+ ..()
+ if(radio)
+ if(stat & NOPOWER)
+ radio.on = FALSE
+ else
+ radio.on = TRUE
+
/obj/machinery/computer/security/wooden_tv
name = "security camera monitor"
desc = "An old TV hooked into the stations camera network."
@@ -228,6 +247,7 @@
circuit = null
light_color = "#3848B3"
light_power_on = 0.5
+
/obj/machinery/computer/security/mining
name = "outpost camera monitor"
desc = "Used to access the various cameras on the outpost."
@@ -236,6 +256,7 @@
network = list("MINE")
circuit = /obj/item/weapon/circuitboard/security/mining
light_color = "#F9BBFC"
+
/obj/machinery/computer/security/engineering
name = "engineering camera monitor"
desc = "Used to monitor fires and breaches."
@@ -243,16 +264,19 @@
icon_screen = "engie_cams"
circuit = /obj/item/weapon/circuitboard/security/engineering
light_color = "#FAC54B"
+
/obj/machinery/computer/security/engineering/New()
if(!network)
network = engineering_networks.Copy()
..()
+
/obj/machinery/computer/security/nuclear
name = "head mounted camera monitor"
desc = "Used to access the built-in cameras in helmets."
icon_state = "syndicam"
network = list(NETWORK_MERCENARY)
circuit = null
+
/obj/machinery/computer/security/nuclear/New()
..()
req_access = list(150)
\ No newline at end of file
diff --git a/code/game/machinery/exonet_node.dm b/code/game/machinery/exonet_node.dm
index aa36e34c9e..d871a3ec38 100644
--- a/code/game/machinery/exonet_node.dm
+++ b/code/game/machinery/exonet_node.dm
@@ -15,6 +15,8 @@
var/opened = 0
+ var/list/logs = list() // Gets written to by exonet's send_message() function.
+
// Proc: New()
// Parameters: None
// Description: Adds components to the machine for deconstruction.
@@ -60,6 +62,7 @@
else
on = 0
idle_power_usage = 0
+ update_icon()
// Proc: emp_act()
// Parameters: 1 (severity - how strong the EMP is, with lower numbers being stronger)
@@ -114,6 +117,7 @@
data["allowPDAs"] = allow_external_PDAs
data["allowCommunicators"] = allow_external_communicators
data["allowNewscasters"] = allow_external_newscasters
+ data["logs"] = logs
// update the ui if it exists, returns null if no ui is passed/found
@@ -171,3 +175,14 @@
for(var/obj/machinery/exonet_node/E in machines)
if(E.on)
return E
+
+// Proc: write_log()
+// Parameters: 4 (origin_address - Where the message is from, target_address - Where the message is going, data_type - Instructions on how to interpet content,
+// content - The actual message.
+// Description: This writes to the logs list, so that people can see what people are doing on the Exonet ingame. Note that this is not an admin logging function.
+// Communicators are already logged seperately.
+/obj/machinery/exonet_node/proc/write_log(var/origin_address, var/target_address, var/data_type, var/content)
+ //var/timestamp = time2text(station_time_in_ticks, "hh:mm:ss")
+ var/timestamp = "[stationdate2text()] [stationtime2text()]"
+ var/msg = "[timestamp] | FROM [origin_address] TO [target_address] | TYPE: [data_type] | CONTENT: [content]"
+ logs.Add(msg)
diff --git a/code/game/machinery/telecomms/presets.dm b/code/game/machinery/telecomms/presets.dm
index 4a6aded43b..93d6743b8e 100644
--- a/code/game/machinery/telecomms/presets.dm
+++ b/code/game/machinery/telecomms/presets.dm
@@ -56,7 +56,7 @@
id = "Receiver A"
network = "tcommsat"
autolinkers = list("receiverA") // link to relay
- freq_listening = list(AI_FREQ, SCI_FREQ, MED_FREQ, SUP_FREQ, SRV_FREQ, COMM_FREQ, ENG_FREQ, SEC_FREQ)
+ freq_listening = list(AI_FREQ, SCI_FREQ, MED_FREQ, SUP_FREQ, SRV_FREQ, COMM_FREQ, ENG_FREQ, SEC_FREQ, ENT_FREQ)
//Common and other radio frequencies for people to freely use
New()
@@ -102,7 +102,7 @@
/obj/machinery/telecomms/bus/preset_four
id = "Bus 4"
network = "tcommsat"
- freq_listening = list(ENG_FREQ, AI_FREQ, PUB_FREQ)
+ freq_listening = list(ENG_FREQ, AI_FREQ, PUB_FREQ, ENT_FREQ)
autolinkers = list("processor4", "engineering", "common")
/obj/machinery/telecomms/bus/preset_cent
@@ -168,7 +168,7 @@
/obj/machinery/telecomms/server/presets/common
id = "Common Server"
- freq_listening = list(PUB_FREQ, AI_FREQ) // AI Private and Common
+ freq_listening = list(PUB_FREQ, AI_FREQ, ENT_FREQ) // AI Private and Common
autolinkers = list("common")
// "Unused" channels, AKA all others.
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 40e05b695c..0559b626e1 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -206,37 +206,16 @@
R.activate_module(src)
R.hud_used.update_robot_modules_display()
-// Due to storage type consolidation this should get used more now.
-// I have cleaned it up a little, but it could probably use more. -Sayu
/obj/item/attackby(obj/item/weapon/W as obj, mob/user as mob)
- if(istype(W,/obj/item/weapon/storage))
+ if(istype(W, /obj/item/weapon/storage))
var/obj/item/weapon/storage/S = W
if(S.use_to_pickup)
- if(S.collection_mode) //Mode is set to collect all items on a tile and we clicked on a valid one.
+ if(S.collection_mode) //Mode is set to collect all items
if(isturf(src.loc))
- var/list/rejections = list()
- var/success = 0
- var/failure = 0
-
- for(var/obj/item/I in src.loc)
- if(I.type in rejections) // To limit bag spamming: any given type only complains once
- continue
- if(!S.can_be_inserted(I)) // Note can_be_inserted still makes noise when the answer is no
- rejections += I.type // therefore full bags are still a little spammy
- failure = 1
- continue
- success = 1
- S.handle_item_insertion(I, 1) //The 1 stops the "You put the [src] into [S]" insertion message from being displayed.
- if(success && !failure)
- user << "You put everything in [S]."
- else if(success)
- user << "You put some things in [S]."
- else
- user << "You fail to pick anything up with \the [S]."
+ S.gather_all(src.loc, user)
else if(S.can_be_inserted(src))
S.handle_item_insertion(src)
-
return
/obj/item/proc/talk_into(mob/M as mob, text)
@@ -461,12 +440,13 @@ var/list/global/slot_flags_enumeration = list(
user << "You cannot locate any eyes on [M]!"
return
- var/hit_zone = get_zone_with_miss_chance(U.zone_sel.selecting, M, U.get_accuracy_penalty(U))
- if(!hit_zone)
- U.do_attack_animation(M)
- playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
- visible_message("\red [U] attempts to stab [M] in the eyes, but misses!")
- return
+ if(U.get_accuracy_penalty(U)) //Should only trigger if they're not aiming well
+ var/hit_zone = get_zone_with_miss_chance(U.zone_sel.selecting, M, U.get_accuracy_penalty(U))
+ if(!hit_zone)
+ U.do_attack_animation(M)
+ playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
+ visible_message("\red [U] attempts to stab [M] in the eyes, but misses!")
+ return
user.attack_log += "\[[time_stamp()]\] Attacked [M.name] ([M.ckey]) with [src.name] (INTENT: [uppertext(user.a_intent)])"
M.attack_log += "\[[time_stamp()]\] Attacked by [user.name] ([user.ckey]) with [src.name] (INTENT: [uppertext(user.a_intent)])"
diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm
index 8562ac7f28..b553f4416c 100644
--- a/code/game/objects/items/devices/flash.dm
+++ b/code/game/objects/items/devices/flash.dm
@@ -13,6 +13,8 @@
var/times_used = 0 //Number of times it's been used.
var/broken = 0 //Is the flash burnt out?
var/last_used = 0 //last world.time it was used.
+ var/max_flashes = 10 // How many times the flash can be used before needing to self recharge.
+ var/halloss_per_flash = 30
/obj/item/device/flash/proc/clown_check(var/mob/user)
if(user && (CLUMSY in user.mutations) && prob(50))
@@ -22,15 +24,36 @@
return 1
/obj/item/device/flash/proc/flash_recharge()
- //capacitor recharges over time
- for(var/i=0, i<3, i++)
- if(last_used+600 > world.time)
+ //Every ten seconds the flash doesn't get used, the times_used variable goes down by one, making the flash less likely to burn out,
+ // as well as being able to flash more before reaching max_flashes cap.
+ for(var/i=0, i < max_flashes, i++)
+ if(last_used + 10 SECONDS > world.time)
break
- last_used += 600
- times_used -= 2
+ last_used += 10 SECONDS
+ times_used--
last_used = world.time
times_used = max(0,round(times_used)) //sanity
+// Returns true if the device can flash.
+/obj/item/device/flash/proc/check_capacitor(var/mob/user)
+ //spamming the flash before it's fully charged (60 seconds) increases the chance of it breaking
+ //It will never break on the first use.
+ if(times_used <= max_flashes)
+ last_used = world.time
+ if(prob( round(times_used / 2) )) //if you use it 10 times in a minute it has a 5% chance to break.
+ broken = 1
+ if(user)
+ user << "The bulb has burnt out!"
+ icon_state = "flashburnt"
+ return FALSE
+ else
+ times_used++
+ return TRUE
+ else //can only use it 10 times a minute
+ if(user)
+ user << "*click* *click*"
+ return FALSE
+
//attack_as_weapon
/obj/item/device/flash/attack(mob/living/M, mob/living/user, var/target_zone)
if(!user || !M) return //sanity
@@ -49,20 +72,8 @@
flash_recharge()
- //spamming the flash before it's fully charged (60seconds) increases the chance of it breaking
- //It will never break on the first use.
- switch(times_used)
- if(0 to 5)
- last_used = world.time
- if(prob(times_used)) //if you use it 5 times in a minute it has a 10% chance to break!
- broken = 1
- user << "The bulb has burnt out!"
- icon_state = "flashburnt"
- return
- times_used++
- else //can only use it 5 times a minute
- user << "*click* *click*"
- return
+ if(!check_capacitor(user))
+ return
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
user.do_attack_animation(M)
@@ -71,12 +82,13 @@
var/flashfail = 0
if(iscarbon(M))
- if(M.stat!=DEAD)
- var/safety = M:eyecheck()
+ var/mob/living/carbon/C = M
+ if(C.stat != DEAD)
+ var/safety = C.eyecheck()
if(safety <= 0)
var/flash_strength = 5
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
flash_strength *= H.species.flash_mod
if(flash_strength > 0)
@@ -84,6 +96,7 @@
H.eye_blind = max(H.eye_blind, flash_strength)
H.eye_blurry = max(H.eye_blurry, flash_strength + 5)
H.flash_eyes()
+ H.adjustHalLoss(halloss_per_flash * (flash_strength / 5)) // Should take four flashes to stun.
else
flashfail = 1
@@ -132,19 +145,9 @@
flash_recharge()
- //spamming the flash before it's fully charged (60seconds) increases the chance of it breaking
- //It will never break on the first use.
- switch(times_used)
- if(0 to 5)
- if(prob(2*times_used)) //if you use it 5 times in a minute it has a 10% chance to break!
- broken = 1
- user << "The bulb has burnt out!"
- icon_state = "flashburnt"
- return
- times_used++
- else //can only use it 5 times a minute
- user.show_message("*click* *click*", 2)
- return
+ if(!check_capacitor(user))
+ return
+
playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1)
flick("flash2", src)
if(user && isrobot(user))
@@ -158,32 +161,29 @@
sleep(5)
qdel(animation)
- for(var/mob/living/carbon/M in oviewers(3, null))
- var/safety = M:eyecheck()
+ for(var/mob/living/carbon/C in oviewers(3, null))
+ var/safety = C.eyecheck()
if(!safety)
- if(!M.blinded)
- M.flash_eyes()
+ if(!C.blinded)
+ C.flash_eyes()
return
/obj/item/device/flash/emp_act(severity)
if(broken) return
flash_recharge()
- switch(times_used)
- if(0 to 5)
- if(prob(2*times_used))
- broken = 1
- icon_state = "flashburnt"
- return
- times_used++
- if(istype(loc, /mob/living/carbon))
- var/mob/living/carbon/M = loc
- var/safety = M.eyecheck()
- if(safety <= 0)
- M.Weaken(10)
- M.flash_eyes()
- for(var/mob/O in viewers(M, null))
- O.show_message("[M] is blinded by the flash!")
+ if(!check_capacitor())
+ return
+
+ if(istype(loc, /mob/living/carbon))
+ var/mob/living/carbon/C = loc
+ var/safety = C.eyecheck()
+ if(safety <= 0)
+ C.adjustHalLoss(halloss_per_flash)
+ //C.Weaken(10)
+ C.flash_eyes()
+ for(var/mob/M in viewers(C, null))
+ M.show_message("[C] is blinded by the flash!")
..()
/obj/item/device/flash/synthetic
diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm
index 72d4c50685..216e2e59d0 100644
--- a/code/game/objects/items/devices/radio/intercom.dm
+++ b/code/game/objects/items/devices/radio/intercom.dm
@@ -43,6 +43,10 @@
icon_state = "medintercom"
frequency = SEC_I_FREQ
+/obj/item/device/radio/intercom/entertainment
+ name = "entertainment intercom"
+ frequency = ENT_FREQ
+
/obj/item/device/radio/intercom/New()
..()
processing_objects += src
@@ -59,6 +63,13 @@
num2text(SEC_I_FREQ) = list(access_security)
)
+/obj/item/device/radio/intercom/entertainment/New()
+ ..()
+ internal_channels = list(
+ num2text(PUB_FREQ) = list(),
+ num2text(ENT_FREQ) = list()
+ )
+
/obj/item/device/radio/intercom/syndicate
name = "illicit intercom"
desc = "Talk through this. Evilly"
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index c173e0344e..cec23e818f 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -2,6 +2,7 @@
var/global/list/default_internal_channels = list(
num2text(PUB_FREQ) = list(),
num2text(AI_FREQ) = list(access_synth),
+ num2text(ENT_FREQ) = list(),
num2text(ERT_FREQ) = list(access_cent_specops),
num2text(COMM_FREQ)= list(access_heads),
num2text(ENG_FREQ) = list(access_engine_equip, access_atmospherics),
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index f9d414267f..56311a52df 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -81,12 +81,14 @@ REAGENT SCANNER
user.show_message("Localized Damage, Brute/Burn:",1)
if(length(damaged)>0)
for(var/obj/item/organ/external/org in damaged)
- user.show_message(text(" [][]: [][] - []",
- capitalize(org.name),
- (org.robotic >= ORGAN_ROBOT) ? "(Cybernetic)" : "",
- (org.brute_dam > 0) ? "[org.brute_dam]" : 0,
- (org.status & ORGAN_BLEEDING)?"\[Bleeding\]":"",
- (org.burn_dam > 0) ? "[org.burn_dam]" : 0),1)
+ if(org.robotic >= ORGAN_ROBOT)
+ continue
+ else
+ user.show_message(text(" []: [][] - []",
+ capitalize(org.name),
+ (org.brute_dam > 0) ? "[org.brute_dam]" : 0,
+ (org.status & ORGAN_BLEEDING)?"\[Bleeding\]":"",
+ (org.burn_dam > 0) ? "[org.burn_dam]" : 0),1)
else
user.show_message(" Limbs are OK.",1)
diff --git a/code/game/objects/items/devices/tvcamera.dm b/code/game/objects/items/devices/tvcamera.dm
new file mode 100644
index 0000000000..b449a6ce3b
--- /dev/null
+++ b/code/game/objects/items/devices/tvcamera.dm
@@ -0,0 +1,95 @@
+/obj/item/device/tvcamera
+ name = "press camera drone"
+ desc = "A Ward-Takahashi EyeBuddy media streaming hovercam. Weapon of choice for war correspondents and reality show cameramen."
+ icon_state = "camcorder"
+ item_state = "camcorder"
+ w_class = ITEMSIZE_LARGE
+ slot_flags = SLOT_BELT
+ var/channel = "NCS Northern Star News Feed"
+ var/obj/machinery/camera/network/thunder/camera
+ var/obj/item/device/radio/radio
+
+/obj/item/device/tvcamera/New()
+ ..()
+ listening_objects += src
+
+/obj/item/device/tvcamera/Destroy()
+ listening_objects -= src
+ qdel(camera)
+ qdel(radio)
+ camera = null
+ radio = null
+ ..()
+
+/obj/item/device/tvcamera/examine()
+ ..()
+ to_chat(usr, "Video feed is [camera.status ? "on" : "off"]")
+ to_chat(usr, "Audio feed is [radio.broadcasting ? "on" : "off"]")
+
+/obj/item/device/tvcamera/initialize()
+ ..()
+ camera = new(src)
+ camera.c_tag = channel
+ camera.status = FALSE
+ radio = new(src)
+ radio.listening = FALSE
+ radio.set_frequency(ENT_FREQ)
+ radio.icon = src.icon
+ radio.icon_state = src.icon_state
+ update_icon()
+
+/obj/item/device/tvcamera/hear_talk(mob/living/M, msg, var/verb="says", datum/language/speaking=null)
+ radio.hear_talk(M,msg,verb,speaking)
+ ..()
+
+/obj/item/device/tvcamera/attack_self(mob/user)
+ add_fingerprint(user)
+ user.set_machine(src)
+ var/dat = list()
+ dat += "Channel name is: [channel ? channel : "unidentified broadcast"]
"
+ dat += "Video streaming is [camera.status ? "on" : "off"]
"
+ dat += "Mic is [radio.broadcasting ? "on" : "off"]
"
+ dat += "Sound is being broadcasted on frequency [format_frequency(radio.frequency)] ([get_frequency_name(radio.frequency)])
"
+ var/datum/browser/popup = new(user, "Hovercamera", "Eye Buddy", 300, 390, src)
+ popup.set_content(jointext(dat,null))
+ popup.open()
+
+/obj/item/device/tvcamera/Topic(bred, href_list, state = physical_state)
+ if(..())
+ return 1
+ if(href_list["channel"])
+ var/nc = input(usr, "Channel name", "Select new channel name", channel) as text|null
+ if(nc)
+ channel = nc
+ camera.c_tag = channel
+ to_chat(usr, "New channel name - '[channel]' is set")
+ if(href_list["video"])
+ camera.set_status(!camera.status)
+ if(camera.status)
+ to_chat(usr,"Video streaming activated. Broadcasting on channel '[channel]'")
+ else
+ to_chat(usr,"Video streaming deactivated.")
+ update_icon()
+ if(href_list["sound"])
+ radio.ToggleBroadcast()
+ if(radio.broadcasting)
+ to_chat(usr,"Audio streaming activated. Broadcasting on frequency [format_frequency(radio.frequency)].")
+ else
+ to_chat(usr,"Audio streaming deactivated.")
+ if(!href_list["close"])
+ attack_self(usr)
+
+/obj/item/device/tvcamera/update_icon()
+ ..()
+ if(camera.status)
+ icon_state = "camcorder_on"
+ item_state = "camcorder_on"
+ else
+ icon_state = "camcorder"
+ item_state = "camcorder"
+ var/mob/living/carbon/human/H = loc
+ if(istype(H))
+ H.update_inv_r_hand()
+ H.update_inv_l_hand()
+ H.update_inv_belt()
+
diff --git a/code/game/objects/items/weapons/implants/implant.dm b/code/game/objects/items/weapons/implants/implant.dm
index 17d3e32f92..eec5638523 100644
--- a/code/game/objects/items/weapons/implants/implant.dm
+++ b/code/game/objects/items/weapons/implants/implant.dm
@@ -54,6 +54,18 @@
part.implants.Remove(src)
..()
+/obj/item/weapon/implant/attackby(obj/item/I, mob/user)
+ if(istype(I, /obj/item/weapon/implanter))
+ var/obj/item/weapon/implanter/implanter = I
+ if(implanter.imp)
+ return // It's full.
+ user.drop_from_inventory(src)
+ forceMove(implanter)
+ implanter.imp = src
+ implanter.update()
+ else
+ ..()
+
/obj/item/weapon/implant/tracking
name = "tracking implant"
desc = "Track with this."
diff --git a/code/game/objects/items/weapons/implants/implantcircuits.dm b/code/game/objects/items/weapons/implants/implantcircuits.dm
new file mode 100644
index 0000000000..cf55cbd706
--- /dev/null
+++ b/code/game/objects/items/weapons/implants/implantcircuits.dm
@@ -0,0 +1,44 @@
+/obj/item/weapon/implant/integrated_circuit
+ name = "electronic implant"
+ icon = 'icons/obj/electronic_assemblies.dmi'
+ icon_state = "setup_implant"
+ var/obj/item/device/electronic_assembly/implant/IC = null
+
+/obj/item/weapon/implant/integrated_circuit/islegal()
+ return TRUE
+
+/obj/item/weapon/implant/integrated_circuit/New()
+ ..()
+ IC = new(src)
+ IC.implant = src
+
+/obj/item/weapon/implant/integrated_circuit/Destroy()
+ IC.implant = null
+ qdel(IC)
+ ..()
+
+/obj/item/weapon/implant/integrated_circuit/get_data()
+ var/dat = {"
+ Implant Specifications:
+ Name: Modular Implant
+ Life: 3 years.
+ Important Notes: EMP can cause malfunctions in the internal electronics of this implant.
+