diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index 92c5477a0b..4d1673a8a5 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -31,7 +31,8 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define INIT_ORDER_DEFAULT 0
#define INIT_ORDER_LIGHTING 0
#define INIT_ORDER_AIR -1
-#define INIT_ORDER_HOLOMAPS -5
+#define INIT_ORDER_PLANETS -4
+#define INIT_ORDER_HOLOMAPS -5 //VOREStation Add
#define INIT_ORDER_OVERLAY -6
#define INIT_ORDER_XENOARCH -20
@@ -44,6 +45,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define FIRE_PRIORITY_AIRFLOW 30
#define FIRE_PRIORITY_AIR 35
#define FIRE_PRIORITY_DEFAULT 50
+#define FIRE_PRIORITY_PLANETS 75
#define FIRE_PRIORITY_MACHINES 100
#define FIRE_PRIORITY_OVERLAYS 500
diff --git a/code/_helpers/events.dm b/code/_helpers/events.dm
index 74e047e811..e31d24783e 100644
--- a/code/_helpers/events.dm
+++ b/code/_helpers/events.dm
@@ -12,4 +12,16 @@
var/area/A = locate(areapath) // Check if it actually exists
if(istype(A) && A.z in using_map.player_levels)
grand_list_of_areas += A
- return grand_list_of_areas
\ No newline at end of file
+ return grand_list_of_areas
+
+/** Checks if any living humans are in a given area! */
+/proc/is_area_occupied(var/area/myarea)
+ // Testing suggests looping over human_mob_list is quicker than looping over area contents
+ for(var/mob/living/carbon/human/H in human_mob_list)
+ if(H.stat >= DEAD) //Conditions for exclusion here, like if disconnected people start blocking it.
+ continue
+ var/area/A = get_area(H)
+ if(A == myarea) //The loc of a turf is the area it is in.
+ return 1
+ return 0
+
\ No newline at end of file
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index df5bf91e61..1cdc7e2717 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -60,6 +60,7 @@ var/list/gamemode_cache = list()
var/humans_need_surnames = 0
var/allow_random_events = 0 // enables random events mid-round when set to 1
var/allow_ai = 1 // allow ai job
+ var/allow_ai_drones = 0 // allow ai controlled drones
var/hostedby = null
var/respawn = 1
var/guest_jobban = 1
@@ -400,6 +401,9 @@ var/list/gamemode_cache = list()
if ("allow_ai")
config.allow_ai = 1
+ if ("allow_ai_drones")
+ config.allow_ai_drones = 1
+
// if ("authentication")
// config.enable_authentication = 1
diff --git a/code/controllers/subsystems/planets.dm b/code/controllers/subsystems/planets.dm
new file mode 100644
index 0000000000..2bb09050f4
--- /dev/null
+++ b/code/controllers/subsystems/planets.dm
@@ -0,0 +1,183 @@
+SUBSYSTEM_DEF(planets)
+ name = "Planets"
+ init_order = INIT_ORDER_PLANETS
+ priority = FIRE_PRIORITY_PLANETS
+ wait = 2 SECONDS
+ flags = SS_BACKGROUND
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+
+ var/list/new_outdoor_turfs = list()
+ var/list/new_outdoor_walls = list()
+
+ var/list/planets = list()
+ var/list/z_to_planet = list()
+
+ var/list/currentrun = list()
+
+ var/list/needs_sun_update = list()
+ var/list/needs_temp_update = list()
+
+/datum/controller/subsystem/planets/Initialize(timeofday)
+ admin_notice("Initializing planetary weather.", R_DEBUG)
+ createPlanets()
+ allocateTurfs(TRUE)
+ ..()
+
+/datum/controller/subsystem/planets/proc/createPlanets()
+ var/list/planet_datums = subtypesof(/datum/planet)
+ for(var/P in planet_datums)
+ var/datum/planet/NP = new P()
+ planets.Add(NP)
+ for(var/Z in NP.expected_z_levels)
+ if(Z > z_to_planet.len)
+ z_to_planet.len = Z
+ if(z_to_planet[Z])
+ admin_notice("Z[Z] is shared by more than one planet!", R_DEBUG)
+ continue
+ z_to_planet[Z] = NP
+
+/datum/controller/subsystem/planets/proc/addTurf(var/turf/T,var/is_edge)
+ if(is_edge)
+ new_outdoor_walls |= T
+ else
+ new_outdoor_turfs |= T
+
+/datum/controller/subsystem/planets/proc/removeTurf(var/turf/T,var/is_edge)
+ if(is_edge)
+ new_outdoor_walls -= T
+ else
+ new_outdoor_turfs -= T
+
+ if(z_to_planet.len >= T.z)
+ var/datum/planet/P = z_to_planet[T.z]
+ if(!P)
+ return
+ if(is_edge)
+ P.planet_floors -= T
+ else
+ P.planet_walls -= T
+
+/datum/controller/subsystem/planets/proc/allocateTurfs(var/initial = FALSE)
+ var/list/currentlist = new_outdoor_turfs
+ while(currentlist.len)
+ var/turf/simulated/OT = currentlist[currentlist.len]
+ currentlist.len--
+ if(istype(OT) && z_to_planet[OT.z])
+ var/datum/planet/P = z_to_planet[OT.z]
+ P.planet_floors |= OT
+ OT.vis_contents |= P.weather_holder.visuals
+ if(!initial && MC_TICK_CHECK)
+ return
+
+ currentlist = new_outdoor_walls
+ while(currentlist.len)
+ var/turf/unsimulated/wall/planetary/PW = currentlist[currentlist.len]
+ currentlist.len--
+ if(istype(PW) && z_to_planet[PW.z])
+ var/datum/planet/P = z_to_planet[PW.z]
+ P.planet_walls |= PW
+ if(!initial && MC_TICK_CHECK)
+ return
+
+/datum/controller/subsystem/planets/proc/unallocateTurf(var/turf/simulated/T)
+ if(istype(T) && z_to_planet[T.z])
+ var/datum/planet/P = z_to_planet[T.z]
+ P.planet_floors -= T
+ T.vis_contents -= P.weather_holder.visuals
+
+
+/datum/controller/subsystem/planets/fire(resumed = 0)
+ if(new_outdoor_turfs.len || new_outdoor_walls.len)
+ allocateTurfs()
+
+ if(!resumed)
+ src.currentrun = planets.Copy()
+
+ var/list/needs_sun_update = src.needs_sun_update
+ while(needs_sun_update.len)
+ var/datum/planet/P = needs_sun_update[needs_sun_update.len]
+ needs_sun_update.len--
+ updateSunlight(P)
+ if(MC_TICK_CHECK)
+ return
+
+ var/list/needs_temp_update = src.needs_temp_update
+ while(needs_temp_update.len)
+ var/datum/planet/P = needs_temp_update[needs_temp_update.len]
+ needs_temp_update.len--
+ updateTemp(P)
+ if(MC_TICK_CHECK)
+ return
+
+ var/list/currentrun = src.currentrun
+ while(currentrun.len)
+ var/datum/planet/P = currentrun[currentrun.len]
+ currentrun.len--
+
+ P.process(last_fire)
+
+ //Sun light needs changing
+ if(P.needs_work & PLANET_PROCESS_SUN)
+ P.needs_work &= ~PLANET_PROCESS_SUN
+ needs_sun_update |= P
+
+ //Temperature needs updating
+ if(P.needs_work & PLANET_PROCESS_TEMP)
+ P.needs_work &= ~PLANET_PROCESS_TEMP
+ needs_temp_update |= P
+
+ if(MC_TICK_CHECK)
+ return
+
+/datum/controller/subsystem/planets/proc/updateSunlight(var/datum/planet/P)
+ // Remove old value from corners
+ var/list/sunlit_corners = P.sunlit_corners
+ var/old_lum_r = -P.sun["lum_r"]
+ var/old_lum_g = -P.sun["lum_g"]
+ var/old_lum_b = -P.sun["lum_b"]
+ if(old_lum_r || old_lum_g || old_lum_b)
+ for(var/C in sunlit_corners)
+ var/datum/lighting_corner/LC = C
+ LC.update_lumcount(old_lum_r, old_lum_g, old_lum_b)
+ CHECK_TICK
+ sunlit_corners.Cut()
+
+ // Calculate new values to apply
+ var/new_brightness = P.sun["brightness"]
+ var/new_color = P.sun["color"]
+ var/lum_r = new_brightness * GetRedPart (new_color) / 255
+ var/lum_g = new_brightness * GetGreenPart(new_color) / 255
+ var/lum_b = new_brightness * GetBluePart (new_color) / 255
+ var/static/update_gen = -1 // Used to prevent double-processing corners. Otherwise would happen when looping over adjacent turfs.
+ for(var/I in P.planet_floors)
+ var/turf/simulated/T = I
+ if(!T.lighting_corners_initialised)
+ T.generate_missing_corners()
+ for(var/C in T.get_corners())
+ var/datum/lighting_corner/LC = C
+ if(LC.update_gen != update_gen && LC.active)
+ sunlit_corners += LC
+ LC.update_gen = update_gen
+ LC.update_lumcount(lum_r, lum_g, lum_b)
+ CHECK_TICK
+ update_gen--
+ P.sun["lum_r"] = lum_r
+ P.sun["lum_g"] = lum_g
+ P.sun["lum_b"] = lum_b
+
+/datum/controller/subsystem/planets/proc/updateTemp(var/datum/planet/P)
+ //Set new temperatures
+ for(var/W in P.planet_walls)
+ var/turf/unsimulated/wall/planetary/wall = W
+ wall.set_temperature(P.weather_holder.temperature)
+ CHECK_TICK
+
+/datum/controller/subsystem/planets/proc/weatherDisco()
+ var/count = 100000
+ while(count > 0)
+ count--
+ for(var/planet in planets)
+ var/datum/planet/P = planet
+ if(P.weather_holder)
+ P.weather_holder.change_weather(pick(P.weather_holder.allowed_weather_types))
+ sleep(3)
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index 494e28ffa7..363acf0d69 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -133,9 +133,6 @@
if("Vote")
debug_variables(vote)
feedback_add_details("admin_verb", "DVote")
- if("Planets")
- debug_variables(planet_controller)
- feedback_add_details("admin_verb", "DPlanets")
message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
return
diff --git a/code/datums/supplypacks/munitions.dm b/code/datums/supplypacks/munitions.dm
index 3413b43014..eed77bbf2f 100644
--- a/code/datums/supplypacks/munitions.dm
+++ b/code/datums/supplypacks/munitions.dm
@@ -22,16 +22,16 @@
cost = 40
containertype = /obj/structure/closet/crate/secure/gear
containername = "Security equipment crate"
- access = access_security
+ access = access_security*/
/datum/supply_packs/munitions/egunpistol
name = "Weapons - Energy sidearms"
contains = list(/obj/item/weapon/gun/energy/gun = 2)
- cost = 40
+ cost = 50
containertype = /obj/structure/closet/crate/secure/weapon
containername = "Energy sidearms crate"
- access = access_security
-*/
+ access = access_armory //VOREStation Edit - Guns are for the armory.
+
/datum/supply_packs/munitions/flareguns
name = "Weapons - Flare guns"
contains = list(
diff --git a/code/game/gamemodes/events/dust.dm b/code/game/gamemodes/events/dust.dm
index 88964fa610..f41e040ee5 100644
--- a/code/game/gamemodes/events/dust.dm
+++ b/code/game/gamemodes/events/dust.dm
@@ -83,7 +83,7 @@ The "dust" will damage the hull of the station causin minor hull breaches.
endx = world.maxx-TRANSITIONEDGE
//VOREStation Edit - No space dust outside of space
var/list/z_levels = using_map.station_levels.Copy()
- for(var/datum/planet/P in planet_controller.planets)
+ for(var/datum/planet/P in SSplanets.planets)
z_levels.Remove(P.expected_z_levels)
var/z_level = pick(z_levels)
//VOREStation Edit End
diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm
index fa491c20e7..0e68f1e7a3 100644
--- a/code/game/machinery/computer/ai_core.dm
+++ b/code/game/machinery/computer/ai_core.dm
@@ -200,14 +200,16 @@ GLOBAL_LIST_BOILERPLATE(all_deactivated_AI_cores, /obj/structure/AIcore/deactiva
if(!istype(transfer) || locate(/mob/living/silicon/ai) in src)
return
+ if(transfer.controlling_drone)
+ transfer.controlling_drone.release_ai_control("Unit control lost. Core transfer completed.")
transfer.aiRestorePowerRoutine = 0
transfer.control_disabled = 0
transfer.aiRadio.disabledAi = 0
transfer.loc = get_turf(src)
transfer.create_eyeobj()
transfer.cancel_camera()
- user << "Transfer successful: [transfer.name] placed within stationary core."
- transfer << "You have been transferred into a stationary core. Remote device connection restored."
+ to_chat(user, "Transfer successful: [transfer.name] placed within stationary core.")
+ to_chat(transfer, "You have been transferred into a stationary core. Remote device connection restored.")
if(card)
card.clear()
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index d13dcf3e80..e5ebbc7d17 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -362,6 +362,8 @@
qdel(R.mmi)
for(var/obj/item/I in R.module) // the tools the borg has; metal, glass, guns etc
+ for(var/mob/M in I) //VOREStation edit
+ despawn_occupant(M)
for(var/obj/item/O in I) // the things inside the tools, if anything; mainly for janiborg trash bags
O.forceMove(R)
qdel(I)
diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm
index b57c67bac0..7e288dbadc 100644
--- a/code/game/objects/items/devices/aicard.dm
+++ b/code/game/objects/items/devices/aicard.dm
@@ -64,20 +64,24 @@
add_attack_logs(user,carded_ai,"Purged from AI Card")
flush = 1
carded_ai.suiciding = 1
- carded_ai << "Your power has been disabled!"
+ to_chat(carded_ai, "Your power has been disabled!")
while (carded_ai && carded_ai.stat != 2)
+ if(carded_ai.controlling_drone && prob(carded_ai.oxyloss)) //You feel it creeping? Eventually will reach 100, resulting in the second half of the AI's remaining life being lonely.
+ carded_ai.controlling_drone.release_ai_control("Unit lost. Integrity too low to maintain connection.")
carded_ai.adjustOxyLoss(2)
carded_ai.updatehealth()
sleep(10)
flush = 0
if (href_list["radio"])
carded_ai.aiRadio.disabledAi = text2num(href_list["radio"])
- carded_ai << "Your Subspace Transceiver has been [carded_ai.aiRadio.disabledAi ? "disabled" : "enabled"]!"
- user << "You [carded_ai.aiRadio.disabledAi ? "disable" : "enable"] the AI's Subspace Transceiver."
+ to_chat(carded_ai, "Your Subspace Transceiver has been [carded_ai.aiRadio.disabledAi ? "disabled" : "enabled"]!")
+ to_chat(user, "You [carded_ai.aiRadio.disabledAi ? "disable" : "enable"] the AI's Subspace Transceiver.")
if (href_list["wireless"])
carded_ai.control_disabled = text2num(href_list["wireless"])
- carded_ai << "Your wireless interface has been [carded_ai.control_disabled ? "disabled" : "enabled"]!"
- user << "You [carded_ai.control_disabled ? "disable" : "enable"] the AI's wireless interface."
+ to_chat(carded_ai, "Your wireless interface has been [carded_ai.control_disabled ? "disabled" : "enabled"]!")
+ to_chat(user, "You [carded_ai.control_disabled ? "disable" : "enable"] the AI's wireless interface.")
+ if(carded_ai.control_disabled && carded_ai.controlling_drone)
+ carded_ai.controlling_drone.release_ai_control("Unit control terminated at intellicore port.")
update_icon()
return 1
@@ -94,12 +98,12 @@
icon_state = "aicard"
/obj/item/device/aicard/proc/grab_ai(var/mob/living/silicon/ai/ai, var/mob/living/user)
- if(!ai.client)
- user << "ERROR: AI [ai.name] is offline. Unable to transfer."
+ if(!ai.client && !ai.controlling_drone)
+ to_chat(user, "ERROR: AI [ai.name] is offline. Unable to transfer.")
return 0
if(carded_ai)
- user << "Transfer failed: Existing AI found on remote device. Remove existing AI to install a new one."
+ to_chat(user, "Transfer failed: Existing AI found on remote device. Remove existing AI to install a new one.")
return 0
if(!user.IsAdvancedToolUser() && isanimal(user))
@@ -108,7 +112,9 @@
return 0
user.visible_message("\The [user] starts transferring \the [ai] into \the [src]...", "You start transferring \the [ai] into \the [src]...")
- ai << "\The [user] is transferring you into \the [src]!"
+ to_chat(ai, "\The [user] is transferring you into \the [src]!")
+ if(ai.controlling_drone)
+ to_chat(ai.controlling_drone, "\The [user] is transferring you into \the [src]!")
if(do_after(user, 100))
if(istype(ai.loc, /turf/))
@@ -124,11 +130,13 @@
ai.control_disabled = 1
ai.aiRestorePowerRoutine = 0
carded_ai = ai
+ if(ai.controlling_drone)
+ ai.controlling_drone.release_ai_control("Unit control lost.")
if(ai.client)
- ai << "You have been transferred into a mobile core. Remote access lost."
+ to_chat(ai, "You have been transferred into a mobile core. Remote access lost.")
if(user.client)
- user << "Transfer successful: [ai.name] extracted from current device and placed within mobile core."
+ to_chat(ai, "Transfer successful: [ai.name] extracted from current device and placed within mobile core.")
ai.canmove = 1
update_icon()
diff --git a/code/game/objects/items/devices/communicator/UI.dm b/code/game/objects/items/devices/communicator/UI.dm
index 9d43da5c9a..fca4cf4866 100644
--- a/code/game/objects/items/devices/communicator/UI.dm
+++ b/code/game/objects/items/devices/communicator/UI.dm
@@ -1,253 +1,252 @@
-// Proc: ui_interact()
-// Parameters: 4 (standard NanoUI arguments)
-// Description: Uses a bunch of for loops to turn lists into lists of lists, so they can be displayed in nanoUI, then displays various buttons to the user.
-/obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/key_state = null)
- // this is the data which will be sent to the ui
- var/data[0] //General nanoUI information
- var/communicators[0] //List of communicators
- var/invites[0] //Communicators and ghosts we've invited to our communicator.
- var/requests[0] //Communicators and ghosts wanting to go in our communicator.
- var/voices[0] //Current /mob/living/voice s inside the device.
- var/connected_communicators[0] //Current communicators connected to the device.
-
- var/im_contacts_ui[0] //List of communicators that have been messaged.
- var/im_list_ui[0] //List of messages.
-
- var/weather[0]
- var/injection = null
- var/modules_ui[0] //Home screen info.
-
- //First we add other 'local' communicators.
- for(var/obj/item/device/communicator/comm in known_devices)
- if(comm.network_visibility && comm.exonet)
- communicators[++communicators.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address)
-
- //Now for ghosts who we pretend have communicators.
- for(var/mob/observer/dead/O in known_devices)
- if(O.client && O.client.prefs.communicator_visibility == 1 && O.exonet)
- communicators[++communicators.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
-
- //Lists all the other communicators that we invited.
- for(var/obj/item/device/communicator/comm in voice_invites)
- if(comm.exonet)
- invites[++invites.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
-
- //Ghosts we invited.
- for(var/mob/observer/dead/O in voice_invites)
- if(O.exonet && O.client)
- invites[++invites.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
-
- //Communicators that want to talk to us.
- for(var/obj/item/device/communicator/comm in voice_requests)
- if(comm.exonet)
- requests[++requests.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
-
- //Ghosts that want to talk to us.
- for(var/mob/observer/dead/O in voice_requests)
- if(O.exonet && O.client)
- requests[++requests.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
-
- //Now for all the voice mobs inside the communicator.
- for(var/mob/living/voice/voice in contents)
- voices[++voices.len] = list("name" = sanitize("[voice.name]'s communicator"), "true_name" = sanitize(voice.name))
-
- //Finally, all the communicators linked to this one.
- for(var/obj/item/device/communicator/comm in communicating)
- connected_communicators[++connected_communicators.len] = list("name" = sanitize(comm.name), "true_name" = sanitize(comm.name), "ref" = "\ref[comm]")
-
- //Devices that have been messaged or recieved messages from.
- for(var/obj/item/device/communicator/comm in im_contacts)
- if(comm.exonet)
- im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
-
- for(var/mob/observer/dead/ghost in im_contacts)
- if(ghost.exonet)
- im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(ghost.name), "address" = ghost.exonet.address, "ref" = "\ref[ghost]")
-
- //Actual messages.
- for(var/I in im_list)
- im_list_ui[++im_list_ui.len] = list("address" = I["address"], "to_address" = I["to_address"], "im" = I["im"])
-
- //Weather reports.
- if(planet_controller)
- for(var/datum/planet/planet in planet_controller.planets)
- if(planet.weather_holder && planet.weather_holder.current_weather)
- var/list/W = list(
- "Planet" = planet.name,
- "Time" = planet.current_time.show_time("hh:mm"),
- "Weather" = planet.weather_holder.current_weather.name,
- "Temperature" = planet.weather_holder.temperature - T0C,
- "High" = planet.weather_holder.current_weather.temp_high - T0C,
- "Low" = planet.weather_holder.current_weather.temp_low - T0C)
- weather[++weather.len] = W
-
- injection = "
Test
"
-
- //Modules for homescreen.
- for(var/list/R in modules)
- modules_ui[++modules_ui.len] = R
-
- data["owner"] = owner ? owner : "Unset"
- data["occupation"] = occupation ? occupation : "Swipe ID to set."
- data["connectionStatus"] = get_connection_to_tcomms()
- data["visible"] = network_visibility
- data["address"] = exonet.address ? exonet.address : "Unallocated"
- data["targetAddress"] = target_address
- data["targetAddressName"] = target_address_name
- data["currentTab"] = selected_tab
- data["knownDevices"] = communicators
- data["invitesSent"] = invites
- data["requestsReceived"] = requests
- data["voice_mobs"] = voices
- data["communicating"] = connected_communicators
- data["video_comm"] = video_source ? "\ref[video_source.loc]" : null
- data["imContacts"] = im_contacts_ui
- data["imList"] = im_list_ui
- data["time"] = stationtime2text()
- data["ring"] = ringer
- data["homeScreen"] = modules_ui
- data["note"] = note // current notes
- data["weather"] = weather
- data["aircontents"] = src.analyze_air()
- data["flashlight"] = fon
- data["injection"] = injection
-
- // update the ui if it exists, returns null if no ui is passed/found
- ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
- if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "communicator.tmpl", "Communicator", 475, 700, state = key_state)
- // add templates for screens in common with communicator.
- ui.add_template("atmosphericScan", "atmospheric_scan.tmpl")
- // when the ui is first opened this is the data it will use
- ui.set_initial_data(data)
- // open the new ui window
- ui.open()
- // auto update every five Master Controller tick
- ui.set_auto_update(5)
-
-// Proc: Topic()
-// Parameters: 2 (standard Topic arguments)
-// Description: Responds to NanoUI button presses.
-/obj/item/device/communicator/Topic(href, href_list)
- if(..())
- return 1
- if(href_list["rename"])
- var/new_name = sanitizeSafe(input(usr,"Please enter your name.","Communicator",usr.name) )
- if(new_name)
- register_device(new_name)
-
- if(href_list["toggle_visibility"])
- switch(network_visibility)
- if(1) //Visible, becoming invisbile
- network_visibility = 0
- if(camera)
- camera.remove_network(NETWORK_COMMUNICATORS)
- if(0) //Invisible, becoming visible
- network_visibility = 1
- if(camera)
- camera.add_network(NETWORK_COMMUNICATORS)
-
- if(href_list["toggle_ringer"])
- ringer = !ringer
-
- if(href_list["add_hex"])
- var/hex = href_list["add_hex"]
- add_to_EPv2(hex)
-
- if(href_list["write_target_address"])
- var/new_address = sanitizeSafe(input(usr,"Please enter the desired target EPv2 address. Note that you must write the colons \
- yourself.","Communicator",src.target_address) )
- if(new_address)
- target_address = new_address
-
- if(href_list["clear_target_address"])
- target_address = ""
-
- if(href_list["dial"])
- if(!get_connection_to_tcomms())
- usr << "Error: Cannot connect to Exonet node."
- return
- var/their_address = href_list["dial"]
- exonet.send_message(their_address, "voice")
-
- if(href_list["decline"])
- var/ref_to_remove = href_list["decline"]
- var/atom/decline = locate(ref_to_remove)
- if(decline)
- del_request(decline)
-
- if(href_list["message"])
- if(!get_connection_to_tcomms())
- usr << "Error: Cannot connect to Exonet node."
- return
- var/their_address = href_list["message"]
- var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message"))
- if(text)
- exonet.send_message(their_address, "text", text)
- im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text))
- log_pda("(COMM: [src]) sent \"[text]\" to [exonet.get_atom_from_address(their_address)]", usr)
- for(var/mob/M in player_list)
- if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
- if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
- continue
- if(exonet.get_atom_from_address(their_address) == M)
- continue
- M.show_message("Comm IM - [src] -> [exonet.get_atom_from_address(their_address)]: [text]")
-
- if(href_list["disconnect"])
- var/name_to_disconnect = href_list["disconnect"]
- for(var/mob/living/voice/V in contents)
- if(name_to_disconnect == V.name)
- close_connection(usr, V, "[usr] hung up")
- for(var/obj/item/device/communicator/comm in communicating)
- if(name_to_disconnect == comm.name)
- close_connection(usr, comm, "[usr] hung up")
-
- if(href_list["startvideo"])
- var/ref_to_video = href_list["startvideo"]
- var/obj/item/device/communicator/comm = locate(ref_to_video)
- if(comm)
- connect_video(usr, comm)
-
- if(href_list["endvideo"])
- if(video_source)
- end_video()
-
- if(href_list["watchvideo"])
- if(video_source)
- watch_video(usr,video_source.loc)
-
- if(href_list["copy"])
- target_address = href_list["copy"]
-
- if(href_list["copy_name"])
- target_address_name = href_list["copy_name"]
-
- if(href_list["hang_up"])
- for(var/mob/living/voice/V in contents)
- close_connection(usr, V, "[usr] hung up")
- for(var/obj/item/device/communicator/comm in communicating)
- close_connection(usr, comm, "[usr] hung up")
-
- if(href_list["switch_tab"])
- selected_tab = href_list["switch_tab"]
-
- if(href_list["edit"])
- var/n = input(usr, "Please enter message", name, notehtml)
- n = sanitizeSafe(n, extra = 0)
- if(n)
- note = html_decode(n)
- notehtml = note
- note = replacetext(note, "\n", "
")
- else
- note = ""
- notehtml = note
-
- if(href_list["Light"])
- fon = !fon
- set_light(fon * flum)
-
- nanomanager.update_uis(src)
- add_fingerprint(usr)
+// Proc: ui_interact()
+// Parameters: 4 (standard NanoUI arguments)
+// Description: Uses a bunch of for loops to turn lists into lists of lists, so they can be displayed in nanoUI, then displays various buttons to the user.
+/obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/key_state = null)
+ // this is the data which will be sent to the ui
+ var/data[0] //General nanoUI information
+ var/communicators[0] //List of communicators
+ var/invites[0] //Communicators and ghosts we've invited to our communicator.
+ var/requests[0] //Communicators and ghosts wanting to go in our communicator.
+ var/voices[0] //Current /mob/living/voice s inside the device.
+ var/connected_communicators[0] //Current communicators connected to the device.
+
+ var/im_contacts_ui[0] //List of communicators that have been messaged.
+ var/im_list_ui[0] //List of messages.
+
+ var/weather[0]
+ var/injection = null
+ var/modules_ui[0] //Home screen info.
+
+ //First we add other 'local' communicators.
+ for(var/obj/item/device/communicator/comm in known_devices)
+ if(comm.network_visibility && comm.exonet)
+ communicators[++communicators.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address)
+
+ //Now for ghosts who we pretend have communicators.
+ for(var/mob/observer/dead/O in known_devices)
+ if(O.client && O.client.prefs.communicator_visibility == 1 && O.exonet)
+ communicators[++communicators.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
+
+ //Lists all the other communicators that we invited.
+ for(var/obj/item/device/communicator/comm in voice_invites)
+ if(comm.exonet)
+ invites[++invites.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
+
+ //Ghosts we invited.
+ for(var/mob/observer/dead/O in voice_invites)
+ if(O.exonet && O.client)
+ invites[++invites.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
+
+ //Communicators that want to talk to us.
+ for(var/obj/item/device/communicator/comm in voice_requests)
+ if(comm.exonet)
+ requests[++requests.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
+
+ //Ghosts that want to talk to us.
+ for(var/mob/observer/dead/O in voice_requests)
+ if(O.exonet && O.client)
+ requests[++requests.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
+
+ //Now for all the voice mobs inside the communicator.
+ for(var/mob/living/voice/voice in contents)
+ voices[++voices.len] = list("name" = sanitize("[voice.name]'s communicator"), "true_name" = sanitize(voice.name))
+
+ //Finally, all the communicators linked to this one.
+ for(var/obj/item/device/communicator/comm in communicating)
+ connected_communicators[++connected_communicators.len] = list("name" = sanitize(comm.name), "true_name" = sanitize(comm.name), "ref" = "\ref[comm]")
+
+ //Devices that have been messaged or recieved messages from.
+ for(var/obj/item/device/communicator/comm in im_contacts)
+ if(comm.exonet)
+ im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
+
+ for(var/mob/observer/dead/ghost in im_contacts)
+ if(ghost.exonet)
+ im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(ghost.name), "address" = ghost.exonet.address, "ref" = "\ref[ghost]")
+
+ //Actual messages.
+ for(var/I in im_list)
+ im_list_ui[++im_list_ui.len] = list("address" = I["address"], "to_address" = I["to_address"], "im" = I["im"])
+
+ //Weather reports.
+ for(var/datum/planet/planet in SSplanets.planets)
+ if(planet.weather_holder && planet.weather_holder.current_weather)
+ var/list/W = list(
+ "Planet" = planet.name,
+ "Time" = planet.current_time.show_time("hh:mm"),
+ "Weather" = planet.weather_holder.current_weather.name,
+ "Temperature" = planet.weather_holder.temperature - T0C,
+ "High" = planet.weather_holder.current_weather.temp_high - T0C,
+ "Low" = planet.weather_holder.current_weather.temp_low - T0C)
+ weather[++weather.len] = W
+
+ injection = "Test
"
+
+ //Modules for homescreen.
+ for(var/list/R in modules)
+ modules_ui[++modules_ui.len] = R
+
+ data["owner"] = owner ? owner : "Unset"
+ data["occupation"] = occupation ? occupation : "Swipe ID to set."
+ data["connectionStatus"] = get_connection_to_tcomms()
+ data["visible"] = network_visibility
+ data["address"] = exonet.address ? exonet.address : "Unallocated"
+ data["targetAddress"] = target_address
+ data["targetAddressName"] = target_address_name
+ data["currentTab"] = selected_tab
+ data["knownDevices"] = communicators
+ data["invitesSent"] = invites
+ data["requestsReceived"] = requests
+ data["voice_mobs"] = voices
+ data["communicating"] = connected_communicators
+ data["video_comm"] = video_source ? "\ref[video_source.loc]" : null
+ data["imContacts"] = im_contacts_ui
+ data["imList"] = im_list_ui
+ data["time"] = stationtime2text()
+ data["ring"] = ringer
+ data["homeScreen"] = modules_ui
+ data["note"] = note // current notes
+ data["weather"] = weather
+ data["aircontents"] = src.analyze_air()
+ data["flashlight"] = fon
+ data["injection"] = injection
+
+ // update the ui if it exists, returns null if no ui is passed/found
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
+ if(!ui)
+ // the ui does not exist, so we'll create a new() one
+ // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
+ ui = new(user, src, ui_key, "communicator.tmpl", "Communicator", 475, 700, state = key_state)
+ // add templates for screens in common with communicator.
+ ui.add_template("atmosphericScan", "atmospheric_scan.tmpl")
+ // when the ui is first opened this is the data it will use
+ ui.set_initial_data(data)
+ // open the new ui window
+ ui.open()
+ // auto update every five Master Controller tick
+ ui.set_auto_update(5)
+
+// Proc: Topic()
+// Parameters: 2 (standard Topic arguments)
+// Description: Responds to NanoUI button presses.
+/obj/item/device/communicator/Topic(href, href_list)
+ if(..())
+ return 1
+ if(href_list["rename"])
+ var/new_name = sanitizeSafe(input(usr,"Please enter your name.","Communicator",usr.name) )
+ if(new_name)
+ register_device(new_name)
+
+ if(href_list["toggle_visibility"])
+ switch(network_visibility)
+ if(1) //Visible, becoming invisbile
+ network_visibility = 0
+ if(camera)
+ camera.remove_network(NETWORK_COMMUNICATORS)
+ if(0) //Invisible, becoming visible
+ network_visibility = 1
+ if(camera)
+ camera.add_network(NETWORK_COMMUNICATORS)
+
+ if(href_list["toggle_ringer"])
+ ringer = !ringer
+
+ if(href_list["add_hex"])
+ var/hex = href_list["add_hex"]
+ add_to_EPv2(hex)
+
+ if(href_list["write_target_address"])
+ var/new_address = sanitizeSafe(input(usr,"Please enter the desired target EPv2 address. Note that you must write the colons \
+ yourself.","Communicator",src.target_address) )
+ if(new_address)
+ target_address = new_address
+
+ if(href_list["clear_target_address"])
+ target_address = ""
+
+ if(href_list["dial"])
+ if(!get_connection_to_tcomms())
+ usr << "Error: Cannot connect to Exonet node."
+ return
+ var/their_address = href_list["dial"]
+ exonet.send_message(their_address, "voice")
+
+ if(href_list["decline"])
+ var/ref_to_remove = href_list["decline"]
+ var/atom/decline = locate(ref_to_remove)
+ if(decline)
+ del_request(decline)
+
+ if(href_list["message"])
+ if(!get_connection_to_tcomms())
+ usr << "Error: Cannot connect to Exonet node."
+ return
+ var/their_address = href_list["message"]
+ var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message"))
+ if(text)
+ exonet.send_message(their_address, "text", text)
+ im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text))
+ log_pda("(COMM: [src]) sent \"[text]\" to [exonet.get_atom_from_address(their_address)]", usr)
+ for(var/mob/M in player_list)
+ if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
+ if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
+ continue
+ if(exonet.get_atom_from_address(their_address) == M)
+ continue
+ M.show_message("Comm IM - [src] -> [exonet.get_atom_from_address(their_address)]: [text]")
+
+ if(href_list["disconnect"])
+ var/name_to_disconnect = href_list["disconnect"]
+ for(var/mob/living/voice/V in contents)
+ if(name_to_disconnect == V.name)
+ close_connection(usr, V, "[usr] hung up")
+ for(var/obj/item/device/communicator/comm in communicating)
+ if(name_to_disconnect == comm.name)
+ close_connection(usr, comm, "[usr] hung up")
+
+ if(href_list["startvideo"])
+ var/ref_to_video = href_list["startvideo"]
+ var/obj/item/device/communicator/comm = locate(ref_to_video)
+ if(comm)
+ connect_video(usr, comm)
+
+ if(href_list["endvideo"])
+ if(video_source)
+ end_video()
+
+ if(href_list["watchvideo"])
+ if(video_source)
+ watch_video(usr,video_source.loc)
+
+ if(href_list["copy"])
+ target_address = href_list["copy"]
+
+ if(href_list["copy_name"])
+ target_address_name = href_list["copy_name"]
+
+ if(href_list["hang_up"])
+ for(var/mob/living/voice/V in contents)
+ close_connection(usr, V, "[usr] hung up")
+ for(var/obj/item/device/communicator/comm in communicating)
+ close_connection(usr, comm, "[usr] hung up")
+
+ if(href_list["switch_tab"])
+ selected_tab = href_list["switch_tab"]
+
+ if(href_list["edit"])
+ var/n = input(usr, "Please enter message", name, notehtml)
+ n = sanitizeSafe(n, extra = 0)
+ if(n)
+ note = html_decode(n)
+ notehtml = note
+ note = replacetext(note, "\n", "
")
+ else
+ note = ""
+ notehtml = note
+
+ if(href_list["Light"])
+ fon = !fon
+ set_light(fon * flum)
+
+ nanomanager.update_uis(src)
+ add_fingerprint(usr)
diff --git a/code/game/objects/items/weapons/tanks/tank_types_vr.dm b/code/game/objects/items/weapons/tanks/tank_types_vr.dm
index cc191bb0f9..73d094d0f9 100644
--- a/code/game/objects/items/weapons/tanks/tank_types_vr.dm
+++ b/code/game/objects/items/weapons/tanks/tank_types_vr.dm
@@ -1,4 +1,4 @@
-/obj/item/weapon/tank/emergency/phoron_double
+/obj/item/weapon/tank/emergency/phoron/double
name = "double emergency phoron tank"
desc = "Contains dangerous phoron. Do not inhale. Warning: extremely flammable."
icon = 'icons/obj/tank_vr.dmi'
@@ -8,7 +8,7 @@
gauge_cap = 3
volume = 10
-/obj/item/weapon/tank/emergency/phoron_double/New()
+/obj/item/weapon/tank/emergency/phoron/double/New()
..()
air_contents.adjust_gas("phoron", (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
@@ -41,6 +41,8 @@
icon = 'icons/obj/tank_vr.dmi'
icon_override = 'icons/mob/back_vr.dmi'
icon_state = "phoron_vox"
+ gauge_cap = 3
+ gauge_icon = "indicator_double"
/obj/item/weapon/tank/emergency
icon = 'icons/obj/tank_vr.dmi'
diff --git a/code/game/objects/structures/catwalk.dm b/code/game/objects/structures/catwalk.dm
index 6d5a1cbe32..60cb0753b1 100644
--- a/code/game/objects/structures/catwalk.dm
+++ b/code/game/objects/structures/catwalk.dm
@@ -2,7 +2,7 @@
/obj/structure/catwalk
name = "catwalk"
desc = "Cats really don't like these things."
- plane = TURF_PLANE
+ plane = DECAL_PLANE
layer = ABOVE_UTILITY
icon = 'icons/turf/catwalks.dmi'
icon_state = "catwalk"
diff --git a/code/game/objects/structures/alien_props.dm b/code/game/objects/structures/props/alien_props.dm
similarity index 90%
rename from code/game/objects/structures/alien_props.dm
rename to code/game/objects/structures/props/alien_props.dm
index eb8ae9d19f..fa11e27b15 100644
--- a/code/game/objects/structures/alien_props.dm
+++ b/code/game/objects/structures/props/alien_props.dm
@@ -6,15 +6,6 @@
icon = 'icons/obj/abductor.dmi'
density = TRUE
anchored = TRUE
- var/interaction_message = null
-
-/obj/structure/prop/alien/attack_hand(mob/living/user) // Used to tell the player that this isn't useful for anything.
- if(!istype(user))
- return FALSE
- if(!interaction_message)
- return ..()
- else
- to_chat(user, interaction_message)
/obj/structure/prop/alien/computer
name = "alien console"
diff --git a/code/game/objects/structures/props/beam_prism.dm b/code/game/objects/structures/props/beam_prism.dm
new file mode 100644
index 0000000000..44df2eb68b
--- /dev/null
+++ b/code/game/objects/structures/props/beam_prism.dm
@@ -0,0 +1,215 @@
+//A series(?) of prisms for PoIs. The base one only works for beams.
+
+/obj/structure/prop/prism
+ name = "prismatic turret"
+ desc = "A raised, externally powered 'turret'. It seems to have a massive crystal ring around its base."
+ description_info = "This device is capable of redirecting any beam projectile."
+ icon = 'icons/obj/props/prism.dmi'
+ icon_state = "prism"
+ density = TRUE
+ anchored = TRUE
+
+ layer = 3.1 //Layer over projectiles.
+ plane = -10 //Layer over projectiles.
+
+ var/rotation_lock = 0 // Can you rotate the prism at all?
+ var/free_rotate = 1 // Does the prism rotate in any direction, or only in the eight standard compass directions?
+ var/external_control_lock = 0 // Does the prism only rotate from the controls of an external switch?
+ var/degrees_from_north = 0 // How far is it rotated clockwise?
+ var/compass_directions = list("North" = 0, "South" = 180, "East" = 90, "West" = 270, "Northwest" = 315, "Northeast" = 45, "Southeast" = 135, "Southwest" = 225)
+ var/interaction_sound = 'sound/mecha/mechmove04.ogg'
+
+ var/redirect_type = /obj/item/projectile/beam
+
+ var/dialID = null
+ var/obj/structure/prop/prismcontrol/remote_dial = null
+
+ interaction_message = "The prismatic turret seems to be able to rotate."
+
+/obj/structure/prop/prism/initialize()
+ if(degrees_from_north)
+ animate(src, transform = turn(NORTH, degrees_from_north), time = 3)
+
+/obj/structure/prop/prism/Destroy()
+ if(remote_dial)
+ remote_dial.my_turrets -= src
+ remote_dial = null
+ ..()
+
+/obj/structure/prop/prism/proc/reset_rotation()
+ var/degrees_to_rotate = -1 * degrees_from_north
+ animate(src, transform = turn(src.transform, degrees_to_rotate), time = 2)
+
+/obj/structure/prop/prism/attack_hand(mob/living/user)
+ ..()
+
+ if(rotation_lock)
+ to_chat(user, "\The [src] is locked at its current bearing.")
+ return
+ if(external_control_lock)
+ to_chat(user, "\The [src]'s motors resist your efforts to rotate it. You may need to find some form of controller.")
+ return
+
+ var/confirm = input("Do you want to try to rotate \the [src]?", "[name]") in list("Yes", "No")
+ if(confirm == "No")
+ visible_message(\
+ "[user.name] decides not to try turning \the [src].",\
+ "You decide not to try turning \the [src].")
+ return
+
+ var/new_bearing
+ if(free_rotate)
+ new_bearing = input("What bearing do you want to rotate \the [src] to?", "[name]") as num
+ new_bearing = round(new_bearing)
+ if(new_bearing <= -1 || new_bearing > 360)
+ to_chat(user, "Rotating \the [src] [new_bearing] degrees would be a waste of time.")
+ return
+ else
+ var/choice = input("What point do you want to set \the [src] to?", "[name]") as null|anything in compass_directions
+ new_bearing = round(compass_directions[choice])
+
+ var/rotate_degrees = new_bearing - degrees_from_north
+
+ if(new_bearing == 360) // Weird artifact.
+ new_bearing = 0
+ degrees_from_north = new_bearing
+
+ var/two_stage = 0
+ if(rotate_degrees == 180 || rotate_degrees == -180)
+ two_stage = 1
+ var/multiplier = pick(-1, 1)
+ rotate_degrees = multiplier * (rotate_degrees / 2)
+
+ playsound(src, interaction_sound, 50, 1)
+ if(two_stage)
+ animate(src, transform = turn(src.transform, rotate_degrees), time = 3)
+ spawn(3)
+ animate(src, transform = turn(src.transform, rotate_degrees), time = 3)
+ else
+ animate(src, transform = turn(src.transform, rotate_degrees), time = 6) //Can't update transform because it will reset the angle.
+
+/obj/structure/prop/prism/proc/rotate_auto(var/new_bearing)
+ if(rotation_lock)
+ visible_message("\The [src] shudders.")
+ playsound(src, 'sound/effects/clang.ogg', 50, 1)
+ return
+
+ visible_message("\The [src] rotates to a bearing of [new_bearing].")
+
+ var/rotate_degrees = new_bearing - degrees_from_north
+
+ if(new_bearing == 360)
+ new_bearing = 0
+ degrees_from_north = new_bearing
+
+ var/two_stage = 0
+ if(rotate_degrees == 180 || rotate_degrees == -180)
+ two_stage = 1
+ var/multiplier = pick(-1, 1)
+ rotate_degrees = multiplier * (rotate_degrees / 2)
+
+ playsound(src, interaction_sound, 50, 1)
+ if(two_stage)
+ animate(src, transform = turn(src.transform, rotate_degrees), time = 3)
+ spawn(3)
+ animate(src, transform = turn(src.transform, rotate_degrees), time = 3)
+ else
+ animate(src, transform = turn(src.transform, rotate_degrees), time = 6)
+
+/obj/structure/prop/prism/bullet_act(var/obj/item/projectile/Proj)
+ if(istype(Proj, redirect_type))
+ visible_message("\The [src] redirects \the [Proj]!")
+ flick("[initial(icon_state)]+glow", src)
+
+ var/new_x = (1 * round(10 * cos(degrees_from_north - 90))) + x //Vectors vectors vectors.
+ var/new_y = (-1 * round(10 * sin(degrees_from_north - 90))) + y
+ var/turf/curloc = get_turf(src)
+
+ Proj.penetrating += 1 // Needed for the beam to get out of the turret.
+
+ Proj.redirect(new_x, new_y, curloc, null)
+
+/obj/structure/prop/prism/incremental
+ free_rotate = 0
+ description_info = "This device is capable of redirecting any beam projectile, but only locks to specific positions in rotation."
+
+/obj/structure/prop/prism/incremental/externalcont
+ external_control_lock = 1
+ description_info = "This device is capable of redirecting any beam projectile, but can only be rotated by a control dial to specific positions."
+
+/obj/structure/prop/prism/externalcont
+ external_control_lock = 1
+ description_info = "This device is capable of redirecting any beam projectile, but can only be rotated by an external control dial."
+
+/obj/structure/prop/prismcontrol
+ name = "prismatic dial"
+ desc = "A large dial with a crystalline ring."
+ icon = 'icons/obj/props/prism.dmi'
+ icon_state = "dial"
+ density = FALSE
+ anchored = TRUE
+
+ interaction_message = "The dial pulses as your hand nears it."
+ var/list/my_turrets = list()
+ var/dialID = null
+
+/obj/structure/prop/prismcontrol/attack_hand(mob/living/user)
+ ..()
+
+ var/confirm = input("Do you want to try to rotate \the [src]?", "[name]") in list("Yes", "No")
+ if(confirm == "No")
+ visible_message(\
+ "[user.name] decides not to try turning \the [src].",\
+ "You decide not to try turning \the [src].")
+ return
+
+ if(!my_turrets || !my_turrets.len)
+ to_chat(user, "\The [src] doesn't seem to do anything.")
+ return
+
+ var/free_rotate = 1
+ var/list/compass_directions = list()
+ for(var/obj/structure/prop/prism/P in my_turrets)
+ if(!P.free_rotate) //Doesn't use bearing, it uses compass points.
+ free_rotate = 0
+ compass_directions |= P.compass_directions
+
+ var/new_bearing
+ if(free_rotate)
+ new_bearing = input("What bearing do you want to rotate \the [src] to?", "[name]") as num
+ new_bearing = round(new_bearing)
+ if(new_bearing <= -1 || new_bearing > 360)
+ to_chat(user, "Rotating \the [src] [new_bearing] degrees would be a waste of time.")
+ return
+ else
+ var/choice = input("What point do you want to set \the [src] to?", "[name]") as null|anything in compass_directions
+ new_bearing = round(compass_directions[choice])
+
+ confirm = input("Are you certain you want to rotate \the [src]?", "[name]") in list("Yes", "No")
+ if(confirm == "No")
+ visible_message(\
+ "[user.name] decides not to try turning \the [src].",\
+ "You decide not to try turning \the [src].")
+ return
+
+ to_chat(user, "\The [src] clicks into place.")
+ for(var/obj/structure/prop/prism/P in my_turrets)
+ P.rotate_auto(new_bearing)
+
+/obj/structure/prop/prismcontrol/initialize()
+ ..()
+ if(my_turrets.len) //Preset controls.
+ for(var/obj/structure/prop/prism/P in my_turrets)
+ P.remote_dial = src
+ return
+ spawn()
+ for(var/obj/structure/prop/prism/P in orange(src, world.view)) //Don't search a huge area.
+ if(P.dialID == dialID && !P.remote_dial && P.external_control_lock)
+ my_turrets |= P
+ P.remote_dial = src
+
+/obj/structure/prop/prismcontrol/Destroy()
+ for(var/obj/structure/prop/prism/P in my_turrets)
+ P.remote_dial = null
+ my_turrets = list()
+ ..()
diff --git a/code/game/objects/structures/props/projectile_lock.dm b/code/game/objects/structures/props/projectile_lock.dm
new file mode 100644
index 0000000000..5c3fcd2ba0
--- /dev/null
+++ b/code/game/objects/structures/props/projectile_lock.dm
@@ -0,0 +1,53 @@
+//A locking mechanism that pulses when hit by a projectile. The base one responds to high-power lasers.
+
+/obj/structure/prop/lock
+ name = "weird lock"
+ desc = "An esoteric object that responds to.. something."
+ icon = 'icons/obj/props/prism.dmi'
+ icon_state = "lock"
+
+ var/enabled = 0
+ var/lockID = null
+
+ var/list/linked_objects = list()
+
+/obj/structure/prop/lock/Destroy()
+ if(linked_objects.len)
+ for(var/obj/O in linked_objects)
+ if(istype(O, /obj/machinery/door/blast/puzzle))
+ var/obj/machinery/door/blast/puzzle/P = O
+ P.locks -= src
+ linked_objects -= P
+ ..()
+
+/obj/structure/prop/lock/proc/toggle_lock()
+ enabled = !enabled
+
+ if(enabled)
+ icon_state = "[initial(icon_state)]-active"
+ else
+ icon_state = "[initial(icon_state)]"
+
+/obj/structure/prop/lock/projectile
+ name = "beam lock"
+ desc = "An esoteric object that responds to high intensity light."
+
+ var/projectile_key = /obj/item/projectile/beam
+ var/timed = 0
+ var/timing = 0
+ var/time_limit = 1500 // In ticks. Ten is one second.
+
+ interaction_message = "The object remains inert to your touch."
+
+/obj/structure/prop/lock/projectile/bullet_act(var/obj/item/projectile/Proj)
+ if(!istype(Proj, projectile_key) || timing)
+ return
+
+ if(istype(Proj, /obj/item/projectile/beam/heavylaser/cannon) || istype(Proj, /obj/item/projectile/beam/emitter) || (Proj.damage >= 80 && Proj.damtype == BURN))
+ toggle_lock()
+ visible_message("\The [src] [enabled ? "disengages" : "engages"] its locking mechanism.")
+
+ if(timed)
+ timing = 1
+ spawn(time_limit)
+ toggle_lock()
diff --git a/code/game/objects/structures/props/prop.dm b/code/game/objects/structures/props/prop.dm
new file mode 100644
index 0000000000..fea5815674
--- /dev/null
+++ b/code/game/objects/structures/props/prop.dm
@@ -0,0 +1,18 @@
+//The base 'prop' for PoIs or other large junk.
+
+/obj/structure/prop
+ name = "something"
+ desc = "My description is broken, bug a developer."
+ icon = 'icons/obj/structures.dmi'
+ icon_state = "safe"
+ density = TRUE
+ anchored = TRUE
+ var/interaction_message = null
+
+/obj/structure/prop/attack_hand(mob/living/user) // Used to tell the player that this isn't useful for anything.
+ if(!istype(user))
+ return FALSE
+ if(!interaction_message)
+ return ..()
+ else
+ to_chat(user, interaction_message)
diff --git a/code/game/objects/structures/props/puzzledoor.dm b/code/game/objects/structures/props/puzzledoor.dm
new file mode 100644
index 0000000000..b9a32fc0dc
--- /dev/null
+++ b/code/game/objects/structures/props/puzzledoor.dm
@@ -0,0 +1,92 @@
+// An indestructible blast door that can only be opened once its puzzle requirements are completed.
+
+/obj/machinery/door/blast/puzzle
+ name = "puzzle door"
+ desc = "A large, virtually indestructible door that will not open unless certain requirements are met."
+ icon_state_open = "pdoor0"
+ icon_state_opening = "pdoorc0"
+ icon_state_closed = "pdoor1"
+ icon_state_closing = "pdoorc1"
+ icon_state = "pdoor1"
+
+ explosion_resistance = 100
+
+ maxhealth = 9999999 //No.
+
+ var/list/locks = list()
+ var/lockID = null
+ var/checkrange_mult = 1
+
+/obj/machinery/door/blast/puzzle/proc/check_locks()
+ for(var/obj/structure/prop/lock/L in locks)
+ if(!L.enabled)
+ return 0
+ return 1
+
+/obj/machinery/door/blast/puzzle/bullet_act(var/obj/item/projectile/Proj)
+ visible_message("\The [src] is completely unaffected by \the [Proj].")
+ qdel(Proj) //No piercing. No.
+
+/obj/machinery/door/blast/puzzle/ex_act(severity)
+ visible_message("\The [src] is completely unaffected by the blast.")
+ return
+
+/obj/machinery/door/blast/puzzle/initialize()
+ . = ..()
+ implicit_material = get_material_by_name("dungeonium")
+ if(locks.len)
+ return
+ var/check_range = world.view * checkrange_mult
+ for(var/obj/structure/prop/lock/L in orange(src, check_range))
+ if(L.lockID == lockID)
+ L.linked_objects |= src
+ locks |= L
+
+/obj/machinery/door/blast/puzzle/Destroy()
+ if(locks.len)
+ for(var/obj/structure/prop/lock/L in locks)
+ L.linked_objects -= src
+ locks -= L
+ ..()
+
+/obj/machinery/door/blast/puzzle/attack_hand(mob/user as mob)
+ if(check_locks())
+ force_toggle(1, user)
+ else
+ to_chat(user, "\The [src] does not respond to your touch.")
+
+/obj/machinery/door/blast/puzzle/attackby(obj/item/weapon/C as obj, mob/user as mob)
+ if(istype(C, /obj/item/weapon))
+ if(C.pry == 1 && (user.a_intent != I_HURT || (stat & BROKEN)))
+ if(istype(C,/obj/item/weapon/material/twohanded/fireaxe))
+ var/obj/item/weapon/material/twohanded/fireaxe/F = C
+ if(!F.wielded)
+ to_chat(user, "You need to be wielding \the [F] to do that.")
+ return
+
+ if(check_locks())
+ force_toggle(1, user)
+
+ else
+ to_chat(user, "[src]'s arcane workings resist your effort.")
+ return
+
+ else if(src.density && (user.a_intent == I_HURT))
+ var/obj/item/weapon/W = C
+ user.setClickCooldown(user.get_attack_speed(W))
+ if(W.damtype == BRUTE || W.damtype == BURN)
+ user.do_attack_animation(src)
+ user.visible_message("\The [user] hits \the [src] with \the [W] with no visible effect.")
+
+ else if(istype(C, /obj/item/weapon/plastique))
+ to_chat(user, "On contacting \the [src], a flash of light envelops \the [C] as it is turned to ash. Oh.")
+ qdel(C)
+ return 0
+
+/obj/machinery/door/blast/puzzle/attack_generic(var/mob/user, var/damage)
+ if(check_locks())
+ force_toggle(1, user)
+
+/obj/machinery/door/blast/puzzle/attack_alien(var/mob/user)
+ if(check_locks())
+ force_toggle(1, user)
diff --git a/code/game/turfs/simulated/outdoors/outdoors.dm b/code/game/turfs/simulated/outdoors/outdoors.dm
index 729095a666..cfdc3cc444 100644
--- a/code/game/turfs/simulated/outdoors/outdoors.dm
+++ b/code/game/turfs/simulated/outdoors/outdoors.dm
@@ -1,5 +1,4 @@
var/list/turf_edge_cache = list()
-var/list/outdoor_turfs = list()
/turf/
// If greater than 0, this turf will apply edge overlays on top of other turfs cardinally adjacent to it, if those adjacent turfs are of a different icon_state,
@@ -24,24 +23,21 @@ var/list/outdoor_turfs = list()
/turf/simulated/floor/New()
if(outdoors)
- outdoor_turfs.Add(src)
+ SSplanets.addTurf(src)
..()
/turf/simulated/floor/Destroy()
if(outdoors)
- planet_controller.unallocateTurf(src)
+ SSplanets.removeTurf(src)
return ..()
/turf/simulated/proc/make_outdoors()
outdoors = TRUE
- outdoor_turfs.Add(src)
+ SSplanets.addTurf(src)
/turf/simulated/proc/make_indoors()
outdoors = FALSE
- if(planet_controller)
- planet_controller.unallocateTurf(src)
- else // This is happening during map gen, if there's no planet_controller (hopefully).
- outdoor_turfs -= src
+ SSplanets.removeTurf(src)
/turf/simulated/post_change()
..()
diff --git a/code/game/turfs/simulated/outdoors/sky.dm b/code/game/turfs/simulated/outdoors/sky.dm
index c329fc2e4d..468b893b33 100644
--- a/code/game/turfs/simulated/outdoors/sky.dm
+++ b/code/game/turfs/simulated/outdoors/sky.dm
@@ -14,7 +14,7 @@
/turf/simulated/sky/initialize()
. = ..()
- outdoor_turfs.Add(src)
+ SSplanets.addTurf(src)
set_light(2, 2, "#FFFFFF")
/turf/simulated/sky/north
diff --git a/code/game/turfs/unsimulated/planetary.dm b/code/game/turfs/unsimulated/planetary.dm
index 613638ec80..35cd7aa4a8 100644
--- a/code/game/turfs/unsimulated/planetary.dm
+++ b/code/game/turfs/unsimulated/planetary.dm
@@ -1,7 +1,5 @@
// This is a wall you surround the area of your "planet" with, that makes the atmosphere inside stay within bounds, even if canisters
// are opened or other strange things occur.
-var/list/planetary_walls = list()
-
/turf/unsimulated/wall/planetary
name = "railroading"
desc = "Choo choo!"
@@ -21,10 +19,10 @@ var/list/planetary_walls = list()
/turf/unsimulated/wall/planetary/New()
..()
- planetary_walls.Add(src)
+ SSplanets.addTurf(src)
/turf/unsimulated/wall/planetary/Destroy()
- planetary_walls.Remove(src)
+ SSplanets.removeTurf(src)
..()
/turf/unsimulated/wall/planetary/proc/set_temperature(var/new_temperature)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 81d936dce9..fc3fba6c7c 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -659,11 +659,13 @@ var/list/admin_verbs_event_manager = list(
set desc = "Cause an explosion of varying strength at your location."
var/turf/epicenter = mob.loc
- var/list/choices = list("Small Bomb", "Medium Bomb", "Big Bomb", "Custom Bomb")
+ var/list/choices = list("Small Bomb", "Medium Bomb", "Big Bomb", "Custom Bomb", "Cancel")
var/choice = input("What size explosion would you like to produce?") in choices
switch(choice)
if(null)
return 0
+ if("Cancel")
+ return 0
if("Small Bomb")
explosion(epicenter, 1, 2, 3, 3)
if("Medium Bomb")
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 1029d1b5d5..c2167b4b4a 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -637,7 +637,7 @@
if(!check_rights(R_DEBUG))
return
- var/datum/planet/planet = input(usr, "Which planet do you want to modify the weather on?", "Change Weather") in planet_controller.planets
+ var/datum/planet/planet = input(usr, "Which planet do you want to modify the weather on?", "Change Weather") in SSplanets.planets
var/datum/weather/new_weather = input(usr, "What weather do you want to change to?", "Change Weather") as null|anything in planet.weather_holder.allowed_weather_types
if(new_weather)
planet.weather_holder.change_weather(new_weather)
@@ -653,7 +653,7 @@
if(!check_rights(R_DEBUG))
return
- var/datum/planet/planet = input(usr, "Which planet do you want to modify time on?", "Change Time") in planet_controller.planets
+ var/datum/planet/planet = input(usr, "Which planet do you want to modify time on?", "Change Time") in SSplanets.planets
var/datum/time/current_time_datum = planet.current_time
var/new_hour = input(usr, "What hour do you want to change to?", "Change Time", text2num(current_time_datum.show_time("hh"))) as null|num
diff --git a/code/modules/client/preference_setup/preference_setup_vr.dm b/code/modules/client/preference_setup/preference_setup_vr.dm
new file mode 100644
index 0000000000..67c1d1792a
--- /dev/null
+++ b/code/modules/client/preference_setup/preference_setup_vr.dm
@@ -0,0 +1,7 @@
+//Minimum limit is 18
+/datum/category_item/player_setup_item/get_min_age()
+ var/min_age = 18
+ var/datum/species/S = all_species[pref.species ? pref.species : "Human"]
+ if(!is_FBP() && S.min_age > 18)
+ min_age = S.min_age
+ return min_age
diff --git a/code/modules/events/atmos_leak.dm b/code/modules/events/atmos_leak.dm
index e9fea3ff66..6823adee96 100644
--- a/code/modules/events/atmos_leak.dm
+++ b/code/modules/events/atmos_leak.dm
@@ -51,17 +51,6 @@
kill()
return
-/** Checks if any living humans are in a given area! */
-/datum/event/atmos_leak/proc/is_area_occupied(var/area/myarea)
- // Testing suggests looping over human_mob_list is quicker than looping over area contents
- for(var/mob/living/carbon/human/H in human_mob_list)
- if(H.stat >= DEAD) //Conditions for exclusion here, like if disconnected people start blocking it.
- continue
- var/area/A = get_area(H)
- if(A == myarea) //The loc of a turf is the area it is in.
- return 1
- return 0
-
/datum/event/atmos_leak/announce()
command_announcement.Announce("Warning, hazardous [gas_data.name[gas_type]] gas leak detected in \the [target_area], evacuate the area and contain the damage!", "Hazard Alert")
diff --git a/code/modules/events/escaped_slimes.dm b/code/modules/events/escaped_slimes.dm
index b36a0d15bf..c9f4f661a3 100644
--- a/code/modules/events/escaped_slimes.dm
+++ b/code/modules/events/escaped_slimes.dm
@@ -33,7 +33,7 @@
/datum/event/escaped_slimes/start()
var/list/vents = list()
for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in machines)
- if(temp_vent.network && temp_vent.loc.z in using_map.station_levels) //borrowed from spiders event, but it works. Distribute the slimes only in rooms with vents
+ if(temp_vent.network && temp_vent.loc.z in using_map.station_levels && !is_area_occupied(temp_vent.loc.loc)) //borrowed from spiders event, but it works. Distribute the slimes only in rooms with vents
vents += temp_vent
while((spawncount > 0) && vents.len)
diff --git a/code/modules/events/gravity.dm b/code/modules/events/gravity.dm
index e0cd38dcfb..4d0881294f 100644
--- a/code/modules/events/gravity.dm
+++ b/code/modules/events/gravity.dm
@@ -6,9 +6,8 @@
endWhen = rand(15, 60)
// Setup which levels we will disrupt gravit on.
zLevels = using_map.station_levels.Copy()
- if (planet_controller)
- for(var/datum/planet/P in planet_controller.planets)
- zLevels -= P.expected_z_levels
+ for(var/datum/planet/P in SSplanets.planets)
+ zLevels -= P.expected_z_levels
/datum/event/gravity/announce()
command_announcement.Announce("Feedback surge detected in mass-distributions systems. Artificial gravity has been disabled whilst the system \
diff --git a/code/modules/events/meteor_strike_vr.dm b/code/modules/events/meteor_strike_vr.dm
index 922b641ba8..8868c577cd 100644
--- a/code/modules/events/meteor_strike_vr.dm
+++ b/code/modules/events/meteor_strike_vr.dm
@@ -50,7 +50,7 @@
new /obj/structure/meteorite(current)
var/datum/planet/impacted
- for(var/datum/planet/P in planet_controller.planets)
+ for(var/datum/planet/P in SSplanets.planets)
if(current.z in P.expected_z_levels)
impacted = P
break
diff --git a/code/modules/mob/living/carbon/human/human_species_vr.dm b/code/modules/mob/living/carbon/human/human_species_vr.dm
index 45b615c240..ed27c12e62 100644
--- a/code/modules/mob/living/carbon/human/human_species_vr.dm
+++ b/code/modules/mob/living/carbon/human/human_species_vr.dm
@@ -25,3 +25,7 @@
/mob/living/carbon/human/protean/New(var/new_loc)
..(new_loc, "Protean")
+
+
+/mob/living/carbon/human/alraune/New(var/new_loc)
+ ..(new_loc, "Alraune")
diff --git a/code/modules/mob/living/carbon/human/species/station/alraune.dm b/code/modules/mob/living/carbon/human/species/station/alraune.dm
new file mode 100644
index 0000000000..0f8d7b5622
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/station/alraune.dm
@@ -0,0 +1,336 @@
+/datum/species/alraune
+ name = SPECIES_ALRAUNE
+ name_plural = "Alraunes"
+ unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/punch, /datum/unarmed_attack/bite)
+ num_alternate_languages = 2
+ language = LANGUAGE_ROOTLOCAL
+ slowdown = 1 //slow, they're plants. Not as slow as full diona.
+ total_health = 100 //standard
+ brute_mod = 1 //nothing special
+ burn_mod = 1.5 //plants don't like fire
+ metabolic_rate = 0.75 // slow metabolism
+ item_slowdown_mod = 0.25 //while they start slow, they don't get much slower
+ bloodloss_rate = 0.1 //While they do bleed, they bleed out VERY slowly
+ min_age = 18
+ max_age = 250
+ health_hud_intensity = 1.5
+
+ body_temperature = T20C
+ breath_type = "carbon_dioxide"
+ poison_type = "phoron"
+ exhale_type = "oxygen"
+
+ // Heat and cold resistances are 20 degrees broader on the level 1 range, level 2 is default, level 3 is much weaker, halfway between L2 and normal L3.
+ // Essentially, they can tolerate a broader range of comfortable temperatures, but suffer more at extremes.
+ cold_level_1 = 240 //Default 260 - Lower is better
+ cold_level_2 = 200 //Default 200
+ cold_level_3 = 160 //Default 120
+ cold_discomfort_level = 260 //they start feeling uncomfortable around the point where humans take damage
+
+ heat_level_1 = 380 //Default 360 - Higher is better
+ heat_level_2 = 400 //Default 400
+ heat_level_3 = 700 //Default 1000
+ heat_discomfort_level = 360
+
+ breath_cold_level_1 = 240 //They don't have lungs, they breathe through their skin
+ breath_cold_level_2 = 180 //sadly for them, their breath tolerance is no better than anyone else's.
+ breath_cold_level_3 = 140 //mainly 'cause breath tolerance is more generous than body temp tolerance.
+
+ breath_heat_level_1 = 400 //slightly better heat tolerance in air though. Slightly.
+ breath_heat_level_2 = 450
+ breath_heat_level_3 = 800 //lower incineration threshold though
+
+ spawn_flags = SPECIES_CAN_JOIN | SPECIES_IS_WHITELISTED // whitelist only while WIP
+ flags = NO_SCAN | IS_PLANT | NO_MINOR_CUT
+ appearance_flags = HAS_HAIR_COLOR | HAS_LIPS | HAS_UNDERWEAR | HAS_SKIN_COLOR | HAS_EYE_COLOR
+
+ inherent_verbs = list(
+ /mob/living/carbon/human/proc/succubus_drain,
+ /mob/living/carbon/human/proc/succubus_drain_finalize,
+ /mob/living/carbon/human/proc/succubus_drain_lethal,
+ /mob/living/carbon/human/proc/bloodsuck) //Give them the voremodes related to wrapping people in vines and sapping their fluids
+
+ color_mult = 1
+ icobase = 'icons/mob/human_races/r_human_vr.dmi'
+ deform = 'icons/mob/human_races/r_def_human_vr.dmi'
+ flesh_color = "#9ee02c"
+ blood_color = "#edf4d0" //sap!
+ base_color = "#1a5600"
+
+ blurb = "Alraunes are a rare sight in space. Their bodies are reminiscent of that of plants, and yet they share many\
+ traits with other humanoid beings.\
+ \
+ Most Alraunes are not interested in traversing space, their heavy preference for natural environments and general\
+ disinterest in things outside it keeps them as a species at a rather primal stage.\
+ \
+ However, after their discovery by the angels of Sanctum, many alraunes succumbed to their curiosity, and took the offer\
+ to learn of the world and venture out, whether it's to Sanctum, or elsewhere in the galaxy."
+
+ has_limbs = list(
+ BP_TORSO = list("path" = /obj/item/organ/external/chest),
+ BP_GROIN = list("path" = /obj/item/organ/external/groin),
+ BP_HEAD = list("path" = /obj/item/organ/external/head),
+ BP_L_ARM = list("path" = /obj/item/organ/external/arm),
+ BP_R_ARM = list("path" = /obj/item/organ/external/arm/right),
+ BP_L_LEG = list("path" = /obj/item/organ/external/leg),
+ BP_R_LEG = list("path" = /obj/item/organ/external/leg/right),
+ BP_L_HAND = list("path" = /obj/item/organ/external/hand),
+ BP_R_HAND = list("path" = /obj/item/organ/external/hand/right),
+ BP_L_FOOT = list("path" = /obj/item/organ/external/foot),
+ BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right)
+ )
+
+ // limited organs, 'cause they're simple
+ has_organ = list(
+ O_LIVER = /obj/item/organ/internal/liver/alraune,
+ O_KIDNEYS = /obj/item/organ/internal/kidneys/alraune,
+ O_BRAIN = /obj/item/organ/internal/brain/alraune,
+ O_EYES = /obj/item/organ/internal/eyes/alraune,
+ )
+
+/datum/species/alraune/can_breathe_water()
+ return TRUE //eh, why not? Aquatic plants are a thing.
+
+
+/datum/species/alraune/handle_environment_special(var/mob/living/carbon/human/H)
+ if(H.inStasisNow()) // if they're in stasis, they won't need this stuff.
+ return
+
+ //setting these here 'cause ugh the defines for life are in the wrong place to compile properly
+ //set them back to HUMAN_MAX_OXYLOSS if we move the life defines to the defines folder at any point
+ var/ALRAUNE_MAX_OXYLOSS = 1 //Defines how much oxyloss humans can get per tick. A tile with no air at all (such as space) applies this value, otherwise it's a percentage of it.
+ var/ALRAUNE_CRIT_MAX_OXYLOSS = ( 2.0 / 6) //The amount of damage you'll get when in critical condition. We want this to be a 5 minute deal = 300s. There are 50HP to get through, so (1/6)*last_tick_duration per second. Breaths however only happen every 4 ticks. last_tick_duration = ~2.0 on average
+
+ //They don't have lungs so breathe() will just return. Instead, they breathe through their skin.
+ //This is mostly normal breath code with some tweaks that apply to their particular biology.
+
+ var/datum/gas_mixture/breath = null
+ var/fullysealed = FALSE //if they're wearing a fully sealed suit, their internals take priority.
+ var/environmentalair = FALSE //if no sealed suit, internals take priority in low pressure environements
+
+ if(H.wear_suit && (H.wear_suit.item_flags & STOPPRESSUREDAMAGE) && H.head && (H.head.item_flags & STOPPRESSUREDAMAGE))
+ fullysealed = TRUE
+ else // find out if local gas mixture is enough to override use of internals
+ var/datum/gas_mixture/environment = H.loc.return_air()
+ var/envpressure = environment.return_pressure()
+ if(envpressure >= hazard_low_pressure)
+ environmentalair = TRUE
+
+ if(fullysealed || !environmentalair)
+ breath = H.get_breath_from_internal()
+
+ if(!breath) //No breath from internals so let's try to get air from our location
+ // cut-down version of get_breath_from_environment - notably, gas masks provide no benefit
+ var/datum/gas_mixture/environment2
+ if(H.loc)
+ environment2 = H.loc.return_air_for_internal_lifeform(H)
+
+ if(environment2)
+ breath = environment2.remove_volume(BREATH_VOLUME)
+ H.handle_chemical_smoke(environment2) //handle chemical smoke while we're at it
+
+ // NOW a crude copypasta of handle_breath. Leaving some things out that don't apply to plants.
+ if(H.does_not_breathe)
+ H.failed_last_breath = 0
+ H.adjustOxyLoss(-5)
+ return // if somehow they don't breathe, abort breathing.
+
+ if(!breath || (breath.total_moles == 0))
+ H.failed_last_breath = 1
+ if(H.health > config.health_threshold_crit)
+ H.adjustOxyLoss(ALRAUNE_MAX_OXYLOSS)
+ else
+ H.adjustOxyLoss(ALRAUNE_CRIT_MAX_OXYLOSS)
+
+ H.oxygen_alert = max(H.oxygen_alert, 1)
+
+ return // skip air processing if there's no air
+
+ // now into the good stuff
+
+ //var/safe_pressure_min = species.minimum_breath_pressure // Minimum safe partial pressure of breathable gas in kPa
+ //just replace safe_pressure_min with minimum_breath_pressure, no need to declare a new var
+
+ var/safe_exhaled_max = 10
+ var/safe_toxins_max = 0.2
+ var/SA_para_min = 1
+ var/SA_sleep_min = 5
+ var/inhaled_gas_used = 0
+
+ var/breath_pressure = (breath.total_moles*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME
+
+ var/inhaling
+ var/poison
+ var/exhaling
+
+ var/failed_inhale = 0
+ var/failed_exhale = 0
+
+ inhaling = breath.gas[breath_type]
+ poison = breath.gas[poison_type]
+ exhaling = breath.gas[exhale_type]
+
+ var/inhale_pp = (inhaling/breath.total_moles)*breath_pressure
+ var/toxins_pp = (poison/breath.total_moles)*breath_pressure
+ var/exhaled_pp = (exhaling/breath.total_moles)*breath_pressure
+
+ // Not enough to breathe
+ if((inhale_pp + exhaled_pp) < minimum_breath_pressure) //they can breathe either oxygen OR CO2
+ if(prob(20))
+ spawn(0) H.emote("gasp")
+
+ var/ratio = (inhale_pp + exhaled_pp)/minimum_breath_pressure
+ // Don't fuck them up too fast (space only does HUMAN_MAX_OXYLOSS (1) after all!)
+ H.adjustOxyLoss(max(ALRAUNE_MAX_OXYLOSS*(1-ratio), 0))
+ failed_inhale = 1
+
+ H.oxygen_alert = max(H.oxygen_alert, 1)
+ else
+ // We're in safe limits
+ H.oxygen_alert = 0
+
+ inhaled_gas_used = inhaling/6
+ breath.adjust_gas(breath_type, -inhaled_gas_used, update = 0) //update afterwards
+ breath.adjust_gas_temp(exhale_type, inhaled_gas_used, H.bodytemperature, update = 0) //update afterwards
+
+ //Now we handle CO2.
+ if(inhale_pp > safe_exhaled_max * 0.7) // For a human, this would be too much exhaled gas in the air. But plants don't care.
+ H.co2_alert = 1 // Give them the alert on the HUD. They'll be aware when the good stuff is present.
+
+ else
+ H.co2_alert = 0
+
+ //do the CO2 buff stuff here
+
+ var/co2buff = 0
+ if(inhaling)
+ co2buff = (Clamp(inhale_pp, 0, minimum_breath_pressure))/minimum_breath_pressure //returns a value between 0 and 1.
+
+ var/light_amount = fullysealed ? H.getlightlevel() : H.getlightlevel()/5 // if they're covered, they're not going to get much light on them.
+
+ if(co2buff && !H.toxloss && light_amount >= 0.1) //if there's enough light and CO2 and you're not poisoned, heal. Note if you're wearing a sealed suit your heal rate will suck.
+ H.adjustBruteLoss(-(light_amount * co2buff * 2)) //at a full partial pressure of CO2 and full light, you'll only heal half as fast as diona.
+ H.adjustFireLoss(-(light_amount * co2buff)) //this won't let you tank environmental damage from fire. MAYBE cold until your body temp drops.
+
+ if(H.nutrition < (200 + 400*co2buff)) //if no CO2, a fully lit tile gives them 1/tick up to 200. With CO2, potentially up to 600.
+ H.nutrition += (light_amount*(1+co2buff*5))
+
+ // Too much poison in the air.
+ if(toxins_pp > safe_toxins_max)
+ var/ratio = (poison/safe_toxins_max) * 10
+ if(H.reagents)
+ H.reagents.add_reagent("toxin", Clamp(ratio, MIN_TOXIN_DAMAGE, MAX_TOXIN_DAMAGE))
+ breath.adjust_gas(poison_type, -poison/6, update = 0) //update after
+ H.phoron_alert = max(H.phoron_alert, 1)
+ else
+ H.phoron_alert = 0
+
+ // If there's some other shit in the air lets deal with it here.
+ if(breath.gas["sleeping_agent"])
+ var/SA_pp = (breath.gas["sleeping_agent"] / breath.total_moles) * breath_pressure
+
+ // Enough to make us paralysed for a bit
+ if(SA_pp > SA_para_min)
+
+ // 3 gives them one second to wake up and run away a bit!
+ H.Paralyse(3)
+
+ // Enough to make us sleep as well
+ if(SA_pp > SA_sleep_min)
+ H.Sleeping(5)
+
+ // There is sleeping gas in their lungs, but only a little, so give them a bit of a warning
+ else if(SA_pp > 0.15)
+ if(prob(20))
+ spawn(0) H.emote(pick("giggle", "laugh"))
+ breath.adjust_gas("sleeping_agent", -breath.gas["sleeping_agent"]/6, update = 0) //update after
+
+ // Were we able to breathe?
+ if (failed_inhale || failed_exhale)
+ H.failed_last_breath = 1
+ else
+ H.failed_last_breath = 0
+ H.adjustOxyLoss(-5)
+
+
+ // Hot air hurts :(
+ if((breath.temperature < breath_cold_level_1 || breath.temperature > breath_heat_level_1) && !(COLD_RESISTANCE in H.mutations))
+
+ if(breath.temperature <= breath_cold_level_1)
+ if(prob(20))
+ to_chat(H, "You feel icicles forming on your skin!")
+ else if(breath.temperature >= breath_heat_level_1)
+ if(prob(20))
+ to_chat(H, "You feel yourself smouldering in the heat!")
+
+ var/bodypart = pick(BP_L_FOOT,BP_R_FOOT,BP_L_LEG,BP_R_LEG,BP_L_ARM,BP_R_ARM,BP_L_HAND,BP_R_HAND,BP_TORSO,BP_GROIN,BP_HEAD)
+ if(breath.temperature >= breath_heat_level_1)
+ if(breath.temperature < breath_heat_level_2)
+ H.apply_damage(HEAT_GAS_DAMAGE_LEVEL_1, BURN, bodypart, used_weapon = "Excessive Heat")
+ H.fire_alert = max(H.fire_alert, 2)
+ else if(breath.temperature < breath_heat_level_3)
+ H.apply_damage(HEAT_GAS_DAMAGE_LEVEL_2, BURN, bodypart, used_weapon = "Excessive Heat")
+ H.fire_alert = max(H.fire_alert, 2)
+ else
+ H.apply_damage(HEAT_GAS_DAMAGE_LEVEL_3, BURN, bodypart, used_weapon = "Excessive Heat")
+ H.fire_alert = max(H.fire_alert, 2)
+
+ else if(breath.temperature <= breath_cold_level_1)
+ if(breath.temperature > breath_cold_level_2)
+ H.apply_damage(COLD_GAS_DAMAGE_LEVEL_1, BURN, bodypart, used_weapon = "Excessive Cold")
+ H.fire_alert = max(H.fire_alert, 1)
+ else if(breath.temperature > breath_cold_level_3)
+ H.apply_damage(COLD_GAS_DAMAGE_LEVEL_2, BURN, bodypart, used_weapon = "Excessive Cold")
+ H.fire_alert = max(H.fire_alert, 1)
+ else
+ H.apply_damage(COLD_GAS_DAMAGE_LEVEL_3, BURN, bodypart, used_weapon = "Excessive Cold")
+ H.fire_alert = max(H.fire_alert, 1)
+
+
+ //breathing in hot/cold air also heats/cools you a bit
+ var/temp_adj = breath.temperature - H.bodytemperature
+ if (temp_adj < 0)
+ temp_adj /= (BODYTEMP_COLD_DIVISOR * 5) //don't raise temperature as much as if we were directly exposed
+ else
+ temp_adj /= (BODYTEMP_HEAT_DIVISOR * 5) //don't raise temperature as much as if we were directly exposed
+
+ var/relative_density = breath.total_moles / (MOLES_CELLSTANDARD * BREATH_PERCENTAGE)
+ temp_adj *= relative_density
+
+ if (temp_adj > BODYTEMP_HEATING_MAX) temp_adj = BODYTEMP_HEATING_MAX
+ if (temp_adj < BODYTEMP_COOLING_MAX) temp_adj = BODYTEMP_COOLING_MAX
+ //world << "Breath: [breath.temperature], [src]: [bodytemperature], Adjusting: [temp_adj]"
+ H.bodytemperature += temp_adj
+
+ else if(breath.temperature >= heat_discomfort_level)
+ get_environment_discomfort(src,"heat")
+ else if(breath.temperature <= cold_discomfort_level)
+ get_environment_discomfort(src,"cold")
+
+ breath.update_values()
+ return 1
+
+/obj/item/organ/internal/brain/alraune
+ icon = 'icons/mob/species/alraune/organs.dmi'
+ icon_state = "neurostroma"
+ name = "neuro-stroma"
+ desc = "A knot of fibrous plant matter."
+ parent_organ = BP_TORSO // brains in their core
+
+/obj/item/organ/internal/eyes/alraune
+ icon = 'icons/mob/species/alraune/organs.dmi'
+ icon_state = "photoreceptors"
+ name = "photoreceptors"
+ desc = "Bulbous and fleshy plant matter."
+
+/obj/item/organ/internal/kidneys/alraune
+ icon = 'icons/mob/species/alraune/organs.dmi'
+ icon_state = "rhyzofilter"
+ name = "rhyzofilter"
+ desc = "A tangle of root nodules."
+
+/obj/item/organ/internal/liver/alraune
+ icon = 'icons/mob/species/alraune/organs.dmi'
+ icon_state = "phytoextractor"
+ name = "phytoextractor"
+ desc = "A bulbous gourd-like structure."
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/taste.dm b/code/modules/mob/living/carbon/taste.dm
index da0ee4a804..f02e704721 100644
--- a/code/modules/mob/living/carbon/taste.dm
+++ b/code/modules/mob/living/carbon/taste.dm
@@ -1,10 +1,11 @@
/mob/living/carbon/proc/ingest(var/datum/reagents/from, var/datum/reagents/target, var/amount = 1, var/multiplier = 1, var/copy = 0) //we kind of 'sneak' a proc in here for ingesting stuff so we can play with it.
+ /* VOREStation Removal - Synths should be able to taste because... reasons
if(ishuman(src))
var/mob/living/carbon/human/H = src
var/braintype = H.get_FBP_type()
if(braintype == FBP_DRONE || braintype == FBP_POSI)
return from.trans_to_holder(target,amount,multiplier,copy) //skip the taste, complete transfer
-
+ */
if(last_taste_time + 50 < world.time)
var/datum/reagents/temp = new(amount) //temporary holder used to analyse what gets transfered.
from.trans_to_holder(temp, amount, multiplier, 1)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 8a34f5ee76..3ee751caeb 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -693,6 +693,9 @@ var/list/ai_verbs_default = list(
card.grab_ai(src, user)
else if(istype(W, /obj/item/weapon/wrench))
+ if(user == controlling_drone)
+ to_chat(user, "The drone's subsystems resist your efforts to tamper with your bolts.")
+ return
if(anchored)
playsound(src, W.usesound, 50, 1)
user.visible_message("\The [user] starts to unbolt \the [src] from the plating...")
diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm
index 534117cee0..3bcbd7057b 100644
--- a/code/modules/mob/living/silicon/ai/death.dm
+++ b/code/modules/mob/living/silicon/ai/death.dm
@@ -3,6 +3,10 @@
if(stat == DEAD)
return
+ if(controlling_drone)
+ controlling_drone.release_ai_control("WARNING: Primary control loop failure. Session terminated.")
+ . = ..(gibbed)
+
if(src.eyeobj)
src.eyeobj.setLoc(get_turf(src))
diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm
index 413cf49250..c8e7dfdada 100644
--- a/code/modules/mob/living/silicon/ai/life.dm
+++ b/code/modules/mob/living/silicon/ai/life.dm
@@ -8,6 +8,8 @@
if (src.stat!=CONSCIOUS)
src.cameraFollow = null
src.reset_view(null)
+ if(controlling_drone)
+ controlling_drone.release_ai_control("WARNING: Primary control loop failure. Session terminated.")
src.updatehealth()
diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm
index 02108eb6bc..a3131b55e1 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone.dm
@@ -19,7 +19,7 @@ var/list/mob_hat_cache = list()
return mob_hat_cache[key]
/mob/living/silicon/robot/drone
- name = "drone"
+ name = "maintenance drone"
real_name = "drone"
icon = 'icons/mob/robots.dmi'
icon_state = "repairbot"
@@ -57,6 +57,8 @@ var/list/mob_hat_cache = list()
var/obj/item/hat
var/hat_x_offset = 0
var/hat_y_offset = -13
+ var/serial_number = 0
+ var/name_override = 0
holder_type = /obj/item/weapon/holder/drone
@@ -71,6 +73,7 @@ var/list/mob_hat_cache = list()
return FALSE
/mob/living/silicon/robot/drone/construction
+ name = "construction drone"
icon_state = "constructiondrone"
law_type = /datum/ai_laws/construction_drone
module_type = /obj/item/weapon/robot_module/drone/construction
@@ -95,6 +98,7 @@ var/list/mob_hat_cache = list()
remove_language("Robot Talk")
add_language("Robot Talk", 0)
add_language("Drone Talk", 1)
+ serial_number = rand(0,999)
//They are unable to be upgraded, so let's give them a bit of a better battery.
cell.maxcharge = 10000
@@ -127,14 +131,22 @@ var/list/mob_hat_cache = list()
name = real_name
/mob/living/silicon/robot/drone/updatename()
- real_name = "maintenance drone ([rand(100,999)])"
+ if(name_override)
+ return
+ if(controlling_ai)
+ real_name = "remote drone ([controlling_ai])"
+ else
+ real_name = "[initial(name)] ([serial_number])"
name = real_name
/mob/living/silicon/robot/drone/updateicon()
overlays.Cut()
if(stat == 0)
- overlays += "eyes-[icon_state]"
+ if(controlling_ai)
+ overlays += "eyes-[icon_state]-ai"
+ else
+ overlays += "eyes-[icon_state]"
else
overlays -= "eyes"
if(hat) // Let the drones wear hats.
@@ -213,15 +225,18 @@ var/list/mob_hat_cache = list()
return
if(emagged)
- to_chat(user, "\The [user] attempts to load subversive software into you, but your hacked subroutines ignore the attempt.")
+ to_chat(src, "\The [user] attempts to load subversive software into you, but your hacked subroutines ignore the attempt.")
to_chat(user, "You attempt to subvert [src], but the sequencer has no effect.")
return
to_chat(user, "You swipe the sequencer across [src]'s interface and watch its eyes flicker.")
- to_chat(user, "You feel a sudden burst of malware loaded into your execute-as-root buffer. Your tiny brain methodically parses, loads and executes the script.")
- message_admins("[key_name_admin(user)] emagged drone [key_name_admin(src)]. Laws overridden.")
- log_game("[key_name(user)] emagged drone [key_name(src)]. Laws overridden.")
+ if(controlling_ai)
+ to_chat(src, "\The [user] loads some kind of subversive software into the remote drone, corrupting its lawset but luckily sparing yours.")
+ else
+ to_chat(src, "You feel a sudden burst of malware loaded into your execute-as-root buffer. Your tiny brain methodically parses, loads and executes the script.")
+
+ log_game("[key_name(user)] emagged drone [key_name(src)][controlling_ai ? " but AI [key_name(controlling_ai)] is in remote control" : " Laws overridden"].")
var/time = time2text(world.realtime,"hh:mm:ss")
lawchanges.Add("[time] : [user.name]([user.key]) emagged [name]([key])")
@@ -234,9 +249,10 @@ var/list/mob_hat_cache = list()
var/datum/gender/TU = gender_datums[user.get_visible_gender()]
set_zeroth_law("Only [user.real_name] and people [TU.he] designate[TU.s] as being such are operatives.")
- src << "Obey these laws:"
- laws.show_laws(src)
- src << "ALERT: [user.real_name] [TU.is] your new master. Obey your new laws and [TU.his] commands."
+ if(!controlling_ai)
+ to_chat(src, "Obey these laws:")
+ laws.show_laws(src)
+ to_chat(src, "ALERT: [user.real_name] is your new master. Obey your new laws and \his commands.")
return 1
//DRONE LIFE/DEATH
@@ -262,26 +278,41 @@ var/list/mob_hat_cache = list()
return
..()
+/mob/living/silicon/robot/drone/death(gibbed)
+ if(controlling_ai)
+ release_ai_control("WARNING: remote system failure. Connection timed out.")
+ . = ..(gibbed)
+
//DRONE MOVEMENT.
/mob/living/silicon/robot/drone/Process_Spaceslipping(var/prob_slip)
return 0
//CONSOLE PROCS
/mob/living/silicon/robot/drone/proc/law_resync()
+
+ if(controlling_ai)
+ to_chat(src, "Someone issues a remote law reset order for this unit, but you disregard it.")
+ return
+
if(stat != 2)
if(emagged)
- src << "You feel something attempting to modify your programming, but your hacked subroutines are unaffected."
+ to_chat(src, "You feel something attempting to modify your programming, but your hacked subroutines are unaffected.")
else
- src << "A reset-to-factory directive packet filters through your data connection, and you obediently modify your programming to suit it."
+ to_chat(src, "A reset-to-factory directive packet filters through your data connection, and you obediently modify your programming to suit it.")
full_law_reset()
show_laws()
/mob/living/silicon/robot/drone/proc/shut_down()
+
+ if(controlling_ai && mind.special_role)
+ to_chat(src, "Someone issued a remote kill order for this unit, but you disregard it.")
+ return
+
if(stat != 2)
if(emagged)
- src << "You feel a system kill order percolate through your tiny brain, but it doesn't seem like a good idea to you."
+ to_chat(src, "You feel a system kill order percolate through [controlling_ai ? "the drones" : "your"] tiny brain, but it doesn't seem like a good idea to [controlling_ai ? "it" : "you"].")
else
- src << "You feel a system kill order percolate through your tiny brain, and you obediently destroy yourself."
+ to_chat(src, "You feel a system kill order percolate through [controlling_ai ? "the drones" : "your"] tiny brain, and [controlling_ai ? "it" : "you"] obediently destroy[controlling_ai ? "s itself" : " yourself"].")
death()
/mob/living/silicon/robot/drone/proc/full_law_reset()
@@ -290,6 +321,21 @@ var/list/mob_hat_cache = list()
clear_ion_laws(1)
laws = new law_type
+/mob/living/silicon/robot/drone/show_laws(var/everyone = 0)
+ if(!controlling_ai)
+ return..()
+ to_chat(src, "Obey these laws:")
+ controlling_ai.laws_sanity_check()
+ controlling_ai.laws.show_laws(src)
+
+/mob/living/silicon/robot/drone/robot_checklaws()
+ set category = "Silicon Commands"
+ set name = "State Laws"
+
+ if(!controlling_ai)
+ return ..()
+ controlling_ai.subsystem_law_manager()
+
//Reboot procs.
/mob/living/silicon/robot/drone/proc/request_player()
@@ -347,14 +393,6 @@ var/list/mob_hat_cache = list()
..()
flavor_text = "It's a bulky construction drone stamped with a Sol Central glyph."
-/mob/living/silicon/robot/drone/construction/updatename()
- real_name = "construction drone ([rand(100,999)])"
- name = real_name
-
/mob/living/silicon/robot/drone/mining/init()
..()
flavor_text = "It's a bulky mining drone stamped with a Grayson logo."
-
-/mob/living/silicon/robot/drone/mining/updatename()
- real_name = "mining drone ([rand(100,999)])"
- name = real_name
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
index e9589622a5..fdb9c09eb5 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
@@ -75,20 +75,22 @@
if(!produce_drones || !config.allow_drone_spawn || count_drones() >= config.max_maint_drones)
return
- if(!player || !istype(player.mob,/mob/observer/dead))
+ if(player && !istype(player.mob,/mob/observer/dead))
return
- announce_ghost_joinleave(player, 0, "They have taken control over a maintenance drone.")
visible_message("\The [src] churns and grinds as it lurches into motion, disgorging a shiny new drone after a few moments.")
flick("h_lathe_leave",src)
+ drone_progress = 0
time_last_drone = world.time
- if(player.mob && player.mob.mind) player.mob.mind.reset()
- var/mob/living/silicon/robot/drone/new_drone = new drone_type(get_turf(src))
- new_drone.transfer_personality(player)
- new_drone.master_fabricator = src
- drone_progress = 0
+ var/mob/living/silicon/robot/drone/new_drone = new drone_type(get_turf(src))
+ if(player)
+ announce_ghost_joinleave(player, 0, "They have taken control over a maintenance drone.")
+ if(player.mob && player.mob.mind) player.mob.mind.reset()
+ new_drone.transfer_personality(player)
+
+ return new_drone
/mob/observer/dead/verb/join_as_drone()
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_remote_control.dm b/code/modules/mob/living/silicon/robot/drone/drone_remote_control.dm
new file mode 100644
index 0000000000..77abb0e59d
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/drone/drone_remote_control.dm
@@ -0,0 +1,104 @@
+/mob/living/silicon/ai
+ var/mob/living/silicon/robot/drone/controlling_drone
+
+/mob/living/silicon/robot/drone
+ var/mob/living/silicon/ai/controlling_ai
+
+/mob/living/silicon/robot/drone/attack_ai(var/mob/living/silicon/ai/user)
+
+ if(!istype(user) || controlling_ai || !config.allow_drone_spawn || !config.allow_ai_drones)
+ return
+
+ if(client || key)
+ to_chat(user, "You cannot take control of an autonomous, active drone.")
+ return
+
+ if(health < -35 || emagged)
+ to_chat(user, "WARNING: connection timed out.")
+ return
+
+ user.controlling_drone = src
+ user.teleop = src
+ radio.channels = user.aiRadio.keyslot2.channels
+ controlling_ai = user
+ verbs += /mob/living/silicon/robot/drone/proc/release_ai_control_verb
+ local_transmit = FALSE
+ languages = controlling_ai.languages.Copy()
+ speech_synthesizer_langs = controlling_ai.speech_synthesizer_langs.Copy()
+ stat = CONSCIOUS
+ if(user.mind)
+ user.mind.transfer_to(src)
+ else
+ key = user.key
+ updatename()
+ to_chat(src, "You have shunted your primary control loop into \a [initial(name)]. Use the Release Control verb to return to your core.")
+
+/obj/machinery/drone_fabricator/attack_ai(var/mob/living/silicon/ai/user as mob)
+
+ if(!istype(user) || user.controlling_drone || !config.allow_drone_spawn || !config.allow_ai_drones)
+ return
+
+ if(stat & NOPOWER)
+ to_chat(user, "\The [src] is unpowered.")
+ return
+
+ if(!produce_drones)
+ to_chat(user, "\The [src] is disabled.")
+ return
+
+ if(drone_progress < 100)
+ to_chat(user, "\The [src] is not ready to produce a new drone.")
+ return
+
+ if(count_drones() >= config.max_maint_drones)
+ to_chat(user, "The drone control subsystems are tasked to capacity; they cannot support any more drones.")
+ return
+
+ var/mob/living/silicon/robot/drone/new_drone = create_drone()
+ user.controlling_drone = new_drone
+ user.teleop = new_drone
+ new_drone.radio.channels = user.aiRadio.keyslot2.channels
+ new_drone.controlling_ai = user
+ new_drone.verbs += /mob/living/silicon/robot/drone/proc/release_ai_control_verb
+ new_drone.local_transmit = FALSE
+ new_drone.languages = new_drone.controlling_ai.languages.Copy()
+ new_drone.speech_synthesizer_langs = new_drone.controlling_ai.speech_synthesizer_langs.Copy()
+
+ if(user.mind)
+ user.mind.transfer_to(new_drone)
+ else
+ new_drone.key = user.key
+ new_drone.updatename()
+
+ to_chat(new_drone, "You have shunted your primary control loop into \a [initial(new_drone.name)]. Use the Release Control verb to return to your core.")
+
+/mob/living/silicon/robot/drone/proc/release_ai_control_verb()
+ set name = "Release Control"
+ set desc = "Release control of a remote drone."
+ set category = "Silicon Commands"
+
+ release_ai_control("Remote session terminated.")
+
+/mob/living/silicon/robot/drone/proc/release_ai_control(var/message = "Connection terminated.")
+
+ if(controlling_ai)
+ if(mind)
+ mind.transfer_to(controlling_ai)
+ else
+ controlling_ai.key = key
+ to_chat(controlling_ai, "[message]")
+ controlling_ai.controlling_drone = null
+ controlling_ai.teleop = null
+ controlling_ai = null
+
+ radio.channels = module.channels
+ verbs -= /mob/living/silicon/robot/drone/proc/release_ai_control_verb
+ languages = initial(languages)
+ speech_synthesizer_langs = initial(speech_synthesizer_langs)
+ remove_language("Robot Talk")
+ add_language("Robot Talk", 0)
+ add_language("Drone Talk", 1)
+ local_transmit = TRUE
+ full_law_reset()
+ updatename()
+ death()
diff --git a/code/modules/mob/living/simple_animal/aliens/mimic.dm b/code/modules/mob/living/simple_animal/aliens/mimic.dm
index fff75ecbe7..17fc70008d 100644
--- a/code/modules/mob/living/simple_animal/aliens/mimic.dm
+++ b/code/modules/mob/living/simple_animal/aliens/mimic.dm
@@ -114,6 +114,8 @@
var/obj/structure/closet/crate/C = new(get_turf(src))
// Put loot in crate
for(var/obj/O in src)
+ if(isbelly(O)) //VOREStation edit
+ continue
O.forceMove(C)
..()
@@ -146,6 +148,8 @@ var/global/list/protected_objects = list(/obj/structure/table, /obj/structure/ca
/mob/living/simple_animal/hostile/mimic/copy/death()
for(var/atom/movable/M in src)
+ if(isbelly(M)) //VOREStation edit
+ continue
M.forceMove(get_turf(src))
..()
diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm
index 6cbef8586b..ea1a8b4706 100644
--- a/code/modules/multiz/movement.dm
+++ b/code/modules/multiz/movement.dm
@@ -451,7 +451,7 @@
if(!silent)
if(planetary)
visible_message("\A [src] falls out of the sky and crashes into \the [landing]!", \
- " You fall out of the skiy and crash into \the [landing]!", \
+ " You fall out of the sky and crash into \the [landing]!", \
"You hear something slam into \the [landing].")
var/turf/T = get_turf(landing)
explosion(T, 0, 1, 2)
diff --git a/code/modules/organs/internal/eyes.dm b/code/modules/organs/internal/eyes.dm
index 3b44682095..9d82a7749b 100644
--- a/code/modules/organs/internal/eyes.dm
+++ b/code/modules/organs/internal/eyes.dm
@@ -50,7 +50,7 @@
// Now sync the organ's eye_colour list.
update_colour()
// Finally, update the eye icon on the mob.
- owner.update_eyes()
+ owner.regenerate_icons()
/obj/item/organ/internal/eyes/replaced(var/mob/living/carbon/human/target)
diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm
index 789dce7dfb..777ff047e1 100644
--- a/code/modules/organs/organ_external.dm
+++ b/code/modules/organs/organ_external.dm
@@ -1105,6 +1105,8 @@ Note that amputating the affected organ does in fact remove the infection from t
R = basic_robolimb
if(R)
force_icon = R.icon
+ brute_mod *= R.robo_brute_mod
+ burn_mod *= R.robo_burn_mod
if(R.lifelike)
robotic = ORGAN_LIFELIKE
name = "[initial(name)]"
diff --git a/code/modules/organs/organ_icon.dm b/code/modules/organs/organ_icon.dm
index f7b0f4d4b8..9de48edc90 100644
--- a/code/modules/organs/organ_icon.dm
+++ b/code/modules/organs/organ_icon.dm
@@ -4,13 +4,13 @@ var/global/list/limb_icon_cache = list()
return
/obj/item/organ/external/proc/compile_icon()
- overlays.Cut()
+ cut_overlays()
// This is a kludge, only one icon has more than one generation of children though.
for(var/obj/item/organ/external/organ in contents)
if(organ.children && organ.children.len)
for(var/obj/item/organ/external/child in organ.children)
overlays += child.mob_icon
- overlays += organ.mob_icon
+ add_overlay(organ.mob_icon)
/obj/item/organ/external/proc/sync_colour_to_human(var/mob/living/carbon/human/human)
s_tone = null
@@ -94,7 +94,7 @@ var/global/list/limb_icon_cache = list()
mob_icon.Blend(mark_s, ICON_OVERLAY) //So when it's on your body, it has icons
icon_cache_key += "[M][markings[M]["color"]]"
- overlays |= get_hair_icon()
+ add_overlay(get_hair_icon())
return mob_icon
@@ -107,7 +107,7 @@ var/global/list/limb_icon_cache = list()
var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s")
if(facial_hair_style.do_colouration)
facial_s.Blend(rgb(owner.r_facial, owner.g_facial, owner.b_facial), ICON_MULTIPLY) // VOREStation edit
- add_overlay(facial_s)
+ res.add_overlay(facial_s)
//Head hair
if(owner.h_style && !(owner.head && (owner.head.flags_inv & BLOCKHEADHAIR)))
@@ -122,7 +122,7 @@ var/global/list/limb_icon_cache = list()
if(hair_style.do_colouration && islist(h_col) && h_col.len >= 3)
hair_s.Blend(rgb(h_col[1], h_col[2], h_col[3]), ICON_MULTIPLY)
hair_s.Blend(hair_s_add, ICON_ADD)
- res.overlays |= hair_s
+ res.add_overlay(hair_s)
return res
@@ -164,7 +164,7 @@ var/global/list/limb_icon_cache = list()
var/datum/sprite_accessory/marking/mark_style = markings[M]["datum"]
var/icon/mark_s = new/icon("icon" = mark_style.icon, "icon_state" = "[mark_style.icon_state]-[organ_tag]")
mark_s.Blend(markings[M]["color"], mark_style.color_blend_mode) // VOREStation edit
- overlays |= mark_s //So when it's not on your body, it has icons
+ add_overlay(mark_s) //So when it's not on your body, it has icons
mob_icon.Blend(mark_s, ICON_OVERLAY) //So when it's on your body, it has icons
icon_cache_key += "[M][markings[M]["color"]]"
@@ -280,7 +280,7 @@ var/list/robot_hud_colours = list("#CFCFCF","#AFAFAF","#8F8F8F","#6F6F6F","#4F4F
var/b = 0.11 * R.health_hud_intensity
temp.color = list(r, r, r, g, g, g, b, b, b)
hud_damage_image = image(null)
- hud_damage_image.overlays += temp
+ hud_damage_image.add_overlay(temp)
// Calculate the required color index.
var/dam_state = min(1,((brute_dam+burn_dam)/max_damage))
diff --git a/code/modules/organs/robolimbs.dm b/code/modules/organs/robolimbs.dm
index 1f15f125c2..5a55b1bcd5 100644
--- a/code/modules/organs/robolimbs.dm
+++ b/code/modules/organs/robolimbs.dm
@@ -49,6 +49,9 @@ var/const/standard_monitor_styles = "blank=ipc_blank;\
var/suggested_species = "Human" //If it should make the torso a species
var/speech_bubble_appearance = "synthetic" // What icon_state to use for speech bubbles when talking. Check talk.dmi for all the icons.
+ var/robo_brute_mod = 1 // Multiplier for incoming brute damage.
+ var/robo_burn_mod = 1 // As above for burn.
+
/datum/robolimb/unbranded_monitor
company = "Unbranded Monitor"
desc = "A generic unbranded interpretation of a popular prosthetic head model. It looks rudimentary and cheaply constructed."
@@ -214,6 +217,8 @@ var/const/standard_monitor_styles = "blank=ipc_blank;\
skin_tone = 1
blood_color = "#CCCCCC"
speech_bubble_appearance = "normal"
+ //robo_brute_mod = 1.1 //VOREStation Edit
+ //robo_burn_mod = 1.1 //VOREStation Edit
/datum/robolimb/wardtakahashi
company = "Ward-Takahashi"
diff --git a/code/modules/planet/planet.dm b/code/modules/planet/planet.dm
index 061ae73398..8fb7604417 100644
--- a/code/modules/planet/planet.dm
+++ b/code/modules/planet/planet.dm
@@ -30,9 +30,10 @@
current_time = current_time.make_random_time()
update_sun()
-/datum/planet/proc/process(amount)
+/datum/planet/proc/process(last_fire)
if(current_time)
- current_time = current_time.add_seconds(amount)
+ var/difference = world.time - last_fire
+ current_time = current_time.add_seconds(difference SECONDS)
update_weather() // We update this first, because some weather types decease the brightness of the sun.
if(sun_last_process <= world.time - sun_process_interval)
update_sun()
diff --git a/code/modules/planet/sif.dm b/code/modules/planet/sif.dm
index 8717245b24..b8bbf7ba6a 100644
--- a/code/modules/planet/sif.dm
+++ b/code/modules/planet/sif.dm
@@ -184,7 +184,8 @@ datum/weather/sif
)
/datum/weather/sif/snow/process_effects()
- for(var/turf/simulated/floor/outdoors/snow/S in outdoor_turfs)
+ ..()
+ for(var/turf/simulated/floor/outdoors/snow/S in SSplanets.new_outdoor_turfs) //This didn't make any sense before SSplanets, either
if(S.z in holder.our_planet.expected_z_levels)
for(var/dir_checked in cardinal)
var/turf/simulated/floor/T = get_step(S, dir_checked)
@@ -207,7 +208,8 @@ datum/weather/sif
)
/datum/weather/sif/blizzard/process_effects()
- for(var/turf/simulated/floor/outdoors/snow/S in outdoor_turfs)
+ ..()
+ for(var/turf/simulated/floor/outdoors/snow/S in SSplanets.new_outdoor_turfs) //This didn't make any sense before SSplanets, either
if(S.z in holder.our_planet.expected_z_levels)
for(var/dir_checked in cardinal)
var/turf/simulated/floor/T = get_step(S, dir_checked)
@@ -219,6 +221,8 @@ datum/weather/sif
name = "rain"
icon_state = "rain"
light_modifier = 0.5
+ effect_message = "Rain falls on you."
+
transition_chances = list(
WEATHER_OVERCAST = 25,
WEATHER_LIGHT_SNOW = 10,
@@ -228,6 +232,7 @@ datum/weather/sif
)
/datum/weather/sif/rain/process_effects()
+ ..()
for(var/mob/living/L in living_mob_list)
if(L.z in holder.our_planet.expected_z_levels)
var/turf/T = get_turf(L)
@@ -238,16 +243,19 @@ datum/weather/sif
if(istype(L.get_active_hand(), /obj/item/weapon/melee/umbrella))
var/obj/item/weapon/melee/umbrella/U = L.get_active_hand()
if(U.open)
- to_chat(L, "Rain patters softly onto your umbrella")
+ if(show_message)
+ to_chat(L, "Rain patters softly onto your umbrella")
continue
else if(istype(L.get_inactive_hand(), /obj/item/weapon/melee/umbrella))
var/obj/item/weapon/melee/umbrella/U = L.get_inactive_hand()
if(U.open)
- to_chat(L, "Rain patters softly onto your umbrella")
+ if(show_message)
+ to_chat(L, "Rain patters softly onto your umbrella")
continue
L.water_act(1)
- to_chat(L, "Rain falls on you.")
+ if(show_message)
+ to_chat(L, effect_message)
/datum/weather/sif/storm
name = "storm"
@@ -256,6 +264,8 @@ datum/weather/sif
temp_low = 233.15 // -40c
light_modifier = 0.3
flight_failure_modifier = 10
+
+
transition_chances = list(
WEATHER_RAIN = 45,
WEATHER_STORM = 40,
@@ -264,6 +274,7 @@ datum/weather/sif
)
/datum/weather/sif/storm/process_effects()
+ ..()
for(var/mob/living/L in living_mob_list)
if(L.z in holder.our_planet.expected_z_levels)
var/turf/T = get_turf(L)
@@ -294,6 +305,10 @@ datum/weather/sif
temp_low = 243.15 // -30c
light_modifier = 0.3
flight_failure_modifier = 15
+ timer_low_bound = 2
+ timer_high_bound = 5
+ effect_message = "The hail smacks into you!"
+
transition_chances = list(
WEATHER_RAIN = 45,
WEATHER_STORM = 40,
@@ -302,6 +317,7 @@ datum/weather/sif
)
/datum/weather/sif/hail/process_effects()
+ ..()
for(var/mob/living/carbon/human/H in living_mob_list)
if(H.z in holder.our_planet.expected_z_levels)
var/turf/T = get_turf(H)
@@ -309,15 +325,18 @@ datum/weather/sif
continue // They're indoors, so no need to pelt them with ice.
// If they have an open umbrella, it'll guard from rain
+ // Message plays every time the umbrella gets stolen, just so they're especially aware of what's happening
if(istype(H.get_active_hand(), /obj/item/weapon/melee/umbrella))
var/obj/item/weapon/melee/umbrella/U = H.get_active_hand()
if(U.open)
- to_chat(H, "Hail patters gently onto your umbrella.")
+ if(show_message)
+ to_chat(H, "Hail patters gently onto your umbrella.")
continue
else if(istype(H.get_inactive_hand(), /obj/item/weapon/melee/umbrella))
var/obj/item/weapon/melee/umbrella/U = H.get_inactive_hand()
if(U.open)
- to_chat(H, "Hail patters gently onto your umbrella.")
+ if(show_message)
+ to_chat(H, "Hail patters gently onto your umbrella.")
continue
var/target_zone = pick(BP_ALL)
@@ -330,8 +349,9 @@ datum/weather/sif
if(amount_soaked >= 10)
continue // No need to apply damage.
- H.apply_damage(rand(5, 10), BRUTE, target_zone, amount_blocked, amount_soaked, used_weapon = "hail")
- to_chat(H, "The hail smacks into you!")
+ H.apply_damage(rand(1, 3), BRUTE, target_zone, amount_blocked, amount_soaked, used_weapon = "hail")
+ if(show_message)
+ to_chat(H, effect_message)
/datum/weather/sif/blood_moon
name = "blood moon"
diff --git a/code/modules/planet/weather.dm b/code/modules/planet/weather.dm
index a0fa603980..96f3660121 100644
--- a/code/modules/planet/weather.dm
+++ b/code/modules/planet/weather.dm
@@ -25,7 +25,7 @@
if(current_weather)
old_light_modifier = current_weather.light_modifier // We store the old one, so we can determine if recalculating the sun is needed.
current_weather = allowed_weather_types[new_weather]
- next_weather_shift = world.time + rand(20, 30) MINUTES
+ next_weather_shift = world.time + rand(current_weather.timer_low_bound, current_weather.timer_high_bound) MINUTES
update_icon_effects()
update_temperature()
@@ -66,8 +66,20 @@
var/flight_failure_modifier = 0 // Some types of weather make flying harder, and therefore make crashes more likely.
var/transition_chances = list() // Assoc list
var/datum/weather_holder/holder = null
+ var/timer_low_bound = 5 // How long this weather must run before it tries to change, in minutes
+ var/timer_high_bound = 10 // How long this weather can run before it tries to change, in minutes
+
+ var/effect_message = null // Should be a string, this is what is shown to a mob caught in the weather
+ var/last_message = 0 // Keeps track of when the weather last tells EVERY player it's hitting them
+ var/message_delay = 10 SECONDS // Delay in between weather hit messages
+ var/show_message = FALSE // Is set to TRUE and plays the messsage every [message_delay]
/datum/weather/proc/process_effects()
+ show_message = FALSE // Need to reset the show_message var, just in case
+ if(effect_message) // Only bother with the code below if we actually need to display something
+ if(world.time >= last_message + message_delay)
+ last_message = world.time // Reset the timer
+ show_message = TRUE // Tell the rest of the process that we need to make a message
return
// All this does is hold the weather icon.
diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm
index 26481d8141..56c93f56f1 100644
--- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm
+++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm
@@ -909,9 +909,9 @@
cup_icon_state = "cup_coffee"
cup_name = "cup of coffee"
- cup_desc = "Don't drop it, or you'll send scalding liquid and porcelain shards everywhere."
+ cup_desc = "Don't drop it, or you'll send scalding liquid and ceramic shards everywhere."
- glass_name = "cup of coffee"
+ glass_name = "coffee"
glass_desc = "Don't drop it, or you'll send scalding liquid and glass shards everywhere."
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index dc35852eed..494659fb1e 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -5,8 +5,7 @@
desc = "..."
icon = 'icons/obj/objects.dmi'
icon_state = "watertank"
- plane = TURF_PLANE
- layer = TABLE_LAYER // Above catwalks, hopefully below other things
+ layer = TABLE_LAYER
density = 1
anchored = 0
pressure_resistance = 2*ONE_ATMOSPHERE
diff --git a/code/modules/vore/appearance/sprite_accessories_vr.dm b/code/modules/vore/appearance/sprite_accessories_vr.dm
index 13c2f7b2da..a58fc9f6ff 100644
--- a/code/modules/vore/appearance/sprite_accessories_vr.dm
+++ b/code/modules/vore/appearance/sprite_accessories_vr.dm
@@ -88,16 +88,58 @@
do_colouration = 1
color_blend_mode = ICON_MULTIPLY
+/datum/sprite_accessory/ears/curly_bug
+ name = "curly antennae, colorable"
+ desc = ""
+ icon_state = "curly_bug"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+
+/datum/sprite_accessory/ears/dual_robot
+ name = "synth antennae, colorable"
+ desc = ""
+ icon_state = "dual_robot_antennae"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+
+/datum/sprite_accessory/ears/right_robot
+ name = "right synth, colorable"
+ desc = ""
+ icon_state = "right_robot_antennae"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+
+/datum/sprite_accessory/ears/left_robot
+ name = "left synth, colorable"
+ desc = ""
+ icon_state = "left_robot_antennae"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+
/datum/sprite_accessory/ears/oni_h1
name = "oni horns"
desc = ""
icon_state = "oni-h1"
+/datum/sprite_accessory/ears/oni_h1_c
+ name = "oni horns, colorable"
+ desc = ""
+ icon_state = "oni-h1_c"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+
/datum/sprite_accessory/ears/demon_horns1
name = "demon horns"
desc = ""
icon_state = "demon-horns1"
+/datum/sprite_accessory/ears/demon_horns1_c
+ name = "demon horns, colorable"
+ desc = ""
+ icon_state = "demon-horns1_c"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+
/datum/sprite_accessory/ears/demon_horns2
name = "demon horns, colorable(outward)"
desc = ""
@@ -105,6 +147,13 @@
do_colouration = 1
color_blend_mode = ICON_MULTIPLY
+/datum/sprite_accessory/ears/dragon_horns
+ name = "dragon horns, colorable"
+ desc = ""
+ icon_state = "dragon-horns"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+
/datum/sprite_accessory/ears/foxears
name = "highlander zorren ears"
desc = ""
@@ -413,6 +462,7 @@
desc = ""
icon_state = "spider-legs"
color_blend_mode = ICON_MULTIPLY
+
/datum/sprite_accessory/wing/moth
name = "moth wings"
desc = ""
@@ -682,12 +732,14 @@
icon_state = "fantail"
do_colouration = 1
color_blend_mode = ICON_MULTIPLY
+
/datum/sprite_accessory/tail/wagtail
name = "avian wagtail, colorable"
desc = ""
icon_state = "wagtail"
do_colouration = 1
color_blend_mode = ICON_MULTIPLY
+
/datum/sprite_accessory/tail/crossfox
name = "cross fox"
desc = ""
diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm
index 4b1d8ef0a5..e7f3df020c 100644
--- a/code/modules/vore/eating/belly_obj_vr.dm
+++ b/code/modules/vore/eating/belly_obj_vr.dm
@@ -578,6 +578,11 @@
if(!(content in src) || !istype(target))
return
content.forceMove(target)
+ if(isitem(content))
+ var/obj/item/I = content
+ if(I.gurgled && (target.mode_flags & DM_FLAG_ITEMWEAK))
+ I.decontaminate()
+ I.gurgle_contaminate(target.contents, target.cont_flavor)
items_preserved -= content
if(!silent && target.vore_sound && !recent_sound)
var/soundfile = vore_sounds[target.vore_sound]
diff --git a/code/modules/vore/fluffstuff/custom_boxes_vr.dm b/code/modules/vore/fluffstuff/custom_boxes_vr.dm
index 57b83c9876..3c05cdbc41 100644
--- a/code/modules/vore/fluffstuff/custom_boxes_vr.dm
+++ b/code/modules/vore/fluffstuff/custom_boxes_vr.dm
@@ -217,6 +217,13 @@
/obj/item/clothing/gloves/fluff/morsleeves,
/obj/item/clothing/under/fluff/morunder)
+// Mewchild: Phi Vietsi
+/obj/item/weapon/storage/box/fluff/vietsi
+ name = "Phi's Personal Items"
+ desc = "A small box containing Phi's small things"
+ has_items = list(
+ /obj/item/clothing/accessory/medal/bronze_heart,
+ /obj/item/clothing/gloves/ring/seal/signet/fluff/vietsi)
/*
Swimsuits, for general use, to avoid arriving to work with your swimsuit.
diff --git a/code/modules/vore/fluffstuff/custom_clothes_vr.dm b/code/modules/vore/fluffstuff/custom_clothes_vr.dm
index dfa6965978..9e62b46e28 100644
--- a/code/modules/vore/fluffstuff/custom_clothes_vr.dm
+++ b/code/modules/vore/fluffstuff/custom_clothes_vr.dm
@@ -1680,9 +1680,10 @@ Departamental Swimsuits, for general use
//Mewchild: Phi Vietsi
/obj/item/clothing/gloves/ring/seal/signet/fluff/vietsi
- name = "signet ring"
- desc = "A signet ring carved from the bones of something long extinct, as a ward against bad luck."
-
+ name = "Phi Vietsi's Bone Signet Ring"
+ desc = "A signet ring belonging to Phi Vietsi, carved from the bones of something long extinct, as a ward against bad luck."
+ var/signet_name = "Phi Vietsi"
+
icon = 'icons/vore/custom_clothes_vr.dmi'
icon_state = "vietsi_ring"
diff --git a/config/custom_items.txt b/config/custom_items.txt
index 5c7bf1b9af..c442f1892d 100644
--- a/config/custom_items.txt
+++ b/config/custom_items.txt
@@ -534,7 +534,7 @@ item_path: /obj/item/weapon/implanter/reagent_generator/savannah
{
ckey: mewchild
character_name: Phi Vietsi
-item_path: /obj/item/clothing/gloves/ring/seal/signet/fluff/vietsi
+item_path: /obj/item/weapon/storage/box/fluff/vietsi
}
{
diff --git a/config/jobwhitelist.txt b/config/jobwhitelist.txt
index a6224b4fe4..168eccbcda 100644
--- a/config/jobwhitelist.txt
+++ b/config/jobwhitelist.txt
@@ -6,3 +6,4 @@ suicidalpickles - mime
whiskyrose - clown
tinybear16 - clown
chargae - mime
+verkister - clown
diff --git a/html/changelogs/Anewbe - Robolimb Fragility.yml b/html/changelogs/Anewbe - Robolimb Fragility.yml
new file mode 100644
index 0000000000..74ba573412
--- /dev/null
+++ b/html/changelogs/Anewbe - Robolimb Fragility.yml
@@ -0,0 +1,37 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+#################################
+
+# Your name.
+author: Anewbe
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - rscadd: "Adds the ability to make robolimb brands more or less vulnerable to brute or burn."
+ - rscdel: "Makes VeyMed limbs more vulnerable to brute and burn."
diff --git a/html/changelogs/Mechoid - AI_Drones.yml b/html/changelogs/Mechoid - AI_Drones.yml
new file mode 100644
index 0000000000..df2c200589
--- /dev/null
+++ b/html/changelogs/Mechoid - AI_Drones.yml
@@ -0,0 +1,7 @@
+
+author: Mechoid
+
+delete-after: True
+
+changes:
+ - rscadd: "Allow AIs to create and take control of mindless drones from fabricators."
diff --git a/icons/mob/human_face.dmi b/icons/mob/human_face.dmi
index 78b0b2e1d4..49f3738c97 100644
Binary files a/icons/mob/human_face.dmi and b/icons/mob/human_face.dmi differ
diff --git a/icons/mob/human_races/cyberlimbs/morpheus/morpheus_alt1.dmi b/icons/mob/human_races/cyberlimbs/morpheus/morpheus_alt1.dmi
index 7a0211e603..798d4ae73d 100644
Binary files a/icons/mob/human_races/cyberlimbs/morpheus/morpheus_alt1.dmi and b/icons/mob/human_races/cyberlimbs/morpheus/morpheus_alt1.dmi differ
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
index 8b9a9c9cc4..fbbc875bc6 100644
Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ
diff --git a/icons/mob/species/alraune/organs.dmi b/icons/mob/species/alraune/organs.dmi
new file mode 100644
index 0000000000..e2da475f67
Binary files /dev/null and b/icons/mob/species/alraune/organs.dmi differ
diff --git a/icons/mob/vore/ears_vr.dmi b/icons/mob/vore/ears_vr.dmi
index 126f2ba426..37bf23b174 100644
Binary files a/icons/mob/vore/ears_vr.dmi and b/icons/mob/vore/ears_vr.dmi differ
diff --git a/icons/obj/props/prism.dmi b/icons/obj/props/prism.dmi
new file mode 100644
index 0000000000..0b938c7ef0
Binary files /dev/null and b/icons/obj/props/prism.dmi differ
diff --git a/icons/obj/props/projectile_lock.dmi b/icons/obj/props/projectile_lock.dmi
new file mode 100644
index 0000000000..cf74d73796
Binary files /dev/null and b/icons/obj/props/projectile_lock.dmi differ
diff --git a/icons/obj/tank_vr.dmi b/icons/obj/tank_vr.dmi
index d809db2d50..021565d004 100644
Binary files a/icons/obj/tank_vr.dmi and b/icons/obj/tank_vr.dmi differ
diff --git a/maps/tether/tether-01-surface1.dmm b/maps/tether/tether-01-surface1.dmm
index 1221accd89..2dd899a2f0 100644
--- a/maps/tether/tether-01-surface1.dmm
+++ b/maps/tether/tether-01-surface1.dmm
@@ -1001,6 +1001,9 @@
dir = 1
},
/obj/machinery/camera/network/cargo,
+/obj/structure/sign/nosmoking_2{
+ pixel_y = 30
+ },
/turf/simulated/floor/tiled,
/area/tether/surfacebase/mining_main/storage)
"ace" = (
@@ -1078,6 +1081,9 @@
},
/obj/machinery/atmospherics/pipe/simple/hidden/cyan,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
+/obj/structure/sign/nosmoking_2{
+ pixel_x = -32
+ },
/turf/simulated/floor/tiled,
/area/tether/surfacebase/mining_main/storage)
"ack" = (
@@ -2911,6 +2917,9 @@
/obj/effect/floor_decal/steeldecal/steel_decals7{
dir = 6
},
+/obj/structure/sign/nosmoking_2{
+ pixel_x = -32
+ },
/turf/simulated/floor/tiled,
/area/tether/surfacebase/mining_main/lobby)
"agP" = (
@@ -4474,6 +4483,9 @@
/obj/machinery/washing_machine,
/obj/machinery/atmospherics/unary/vent_scrubber/on,
/obj/machinery/camera/network/civilian,
+/obj/structure/sign/nosmoking_2{
+ pixel_y = 30
+ },
/turf/simulated/floor/tiled,
/area/storage/surface_eva)
"akg" = (
@@ -5142,6 +5154,9 @@
dir = 4
},
/obj/structure/curtain/open/shower,
+/obj/structure/sign/fire{
+ pixel_y = -32
+ },
/turf/simulated/floor/tiled/monotile,
/area/storage/surface_eva)
"amd" = (
@@ -10226,6 +10241,9 @@
frequency = 1379;
scrub_id = "civ_airlock_scrubber"
},
+/obj/structure/sign/nosmoking_2{
+ pixel_y = 30
+ },
/turf/simulated/floor/tiled/techmaint,
/area/storage/surface_eva/external)
"ayO" = (
@@ -11343,6 +11361,9 @@
/obj/item/clothing/mask/gas,
/obj/item/clothing/suit/storage/hooded/wintercoat,
/obj/item/weapon/tank/emergency/oxygen/engi,
+/obj/structure/sign/nosmoking_2{
+ pixel_y = 30
+ },
/turf/simulated/floor/tiled/dark,
/area/storage/surface_eva)
"aAO" = (
@@ -12776,6 +12797,9 @@
frequency = 1379;
scrub_id = "civ_airlock_scrubber"
},
+/obj/structure/sign/fire{
+ pixel_y = -32
+ },
/turf/simulated/floor/tiled/techmaint,
/area/storage/surface_eva/external)
"aEo" = (
@@ -12875,6 +12899,9 @@
/obj/item/clothing/mask/gas,
/obj/item/clothing/suit/storage/hooded/wintercoat,
/obj/item/weapon/tank/emergency/oxygen/engi,
+/obj/structure/sign/fire{
+ pixel_y = -32
+ },
/turf/simulated/floor/tiled/dark,
/area/storage/surface_eva)
"aEu" = (
@@ -27598,6 +27625,16 @@
"gPz" = (
/turf/simulated/floor/tiled,
/area/tether/surfacebase/public_garden_one)
+"gRx" = (
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary{
+ frequency = 1379;
+ scrub_id = "mining_airlock_scrubber"
+ },
+/obj/structure/sign/nosmoking_2{
+ pixel_x = -32
+ },
+/turf/simulated/floor/tiled/techmaint,
+/area/tether/surfacebase/mining_main/airlock)
"gUy" = (
/obj/structure/railing{
dir = 4
@@ -27697,6 +27734,16 @@
},
/turf/simulated/floor/tiled,
/area/tether/surfacebase/public_garden_one)
+"iAg" = (
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary{
+ frequency = 1379;
+ scrub_id = "mining_airlock_scrubber"
+ },
+/obj/structure/sign/fire{
+ pixel_x = -32
+ },
+/turf/simulated/floor/tiled/techmaint,
+/area/tether/surfacebase/mining_main/airlock)
"iPi" = (
/obj/effect/floor_decal/techfloor/corner{
dir = 1
@@ -28266,6 +28313,16 @@
/obj/structure/closet/crate,
/turf/simulated/floor/tiled/techfloor,
/area/tether/surfacebase/public_garden_one)
+"tNF" = (
+/obj/structure/grille,
+/obj/structure/railing{
+ dir = 8
+ },
+/obj/structure/sign/nosmoking_2{
+ pixel_y = 30
+ },
+/turf/simulated/floor/tiled/techmaint,
+/area/rnd/external)
"tPq" = (
/obj/structure/bed/chair/wood{
dir = 1
@@ -28392,6 +28449,16 @@
},
/turf/simulated/floor/tiled,
/area/tether/surfacebase/public_garden)
+"vNR" = (
+/obj/structure/grille,
+/obj/structure/railing{
+ dir = 4
+ },
+/obj/structure/sign/fire{
+ pixel_y = 32
+ },
+/turf/simulated/floor/tiled/techmaint,
+/area/rnd/external)
"vOI" = (
/obj/structure/table/bench/steel,
/obj/effect/floor_decal/borderfloor{
@@ -33973,7 +34040,7 @@ aGi
aGw
aHA
aHe
-aHC
+vNR
aHC
aHe
azr
@@ -34683,7 +34750,7 @@ aGA
bIj
aGR
aHh
-aHH
+tNF
aHH
aHh
aah
@@ -39024,8 +39091,8 @@ aaf
aaf
aao
aau
-aau
-aau
+gRx
+iAg
aau
aao
abn
diff --git a/maps/tether/tether-06-station2.dmm b/maps/tether/tether-06-station2.dmm
index 67d3a75d22..6210eb8245 100644
--- a/maps/tether/tether-06-station2.dmm
+++ b/maps/tether/tether-06-station2.dmm
@@ -17600,6 +17600,9 @@
},
/turf/simulated/floor/tiled,
/area/security/prison)
+"Ey" = (
+/turf/simulated/floor/reinforced/airless,
+/area/space)
"ED" = (
/obj/structure/table/rack{
dir = 8;
@@ -18101,6 +18104,11 @@
/obj/random/maintenance/medical,
/turf/simulated/floor,
/area/maintenance/station/sec_lower)
+"NS" = (
+/obj/structure/lattice,
+/obj/structure/grille,
+/turf/space,
+/area/space)
"NY" = (
/obj/machinery/light/small{
dir = 8
@@ -22628,16 +22636,16 @@ aa
aa
aa
aa
-aa
-aa
-aa
-aa
-aa
-aa
-aa
-aa
-aa
-aa
+NS
+NS
+NS
+NS
+NS
+NS
+NS
+eM
+NS
+NS
aa
aa
aa
@@ -22770,6 +22778,7 @@ aa
aa
aa
aa
+NS
aa
aa
aa
@@ -22778,8 +22787,7 @@ aa
aa
aa
aa
-aa
-aa
+NS
aa
aa
aa
@@ -22912,7 +22920,7 @@ aa
aa
aa
aa
-aa
+NS
aa
aa
aa
@@ -23054,7 +23062,7 @@ aa
aa
aa
aa
-aa
+NS
aa
aa
aa
@@ -23196,7 +23204,7 @@ aa
aa
aa
aa
-aa
+NS
aa
aa
aa
@@ -23338,14 +23346,14 @@ aa
aa
aa
aa
-aa
+NS
aa
aa
aa
ab
-ac
-ac
-at
+Ey
+Ey
+Ey
Qs
Qs
at
@@ -23480,14 +23488,14 @@ aa
aa
aa
aa
+NS
aa
aa
aa
-aa
-ac
-ac
-ac
-Qs
+ab
+Ey
+Ey
+Ey
Qs
Qs
at
@@ -23622,14 +23630,14 @@ aa
aa
aa
aa
-aa
+eM
aa
aa
aa
ac
-ac
-at
-Qs
+Ey
+Ey
+kk
Qs
Ug
at
@@ -23764,7 +23772,7 @@ aa
aa
aa
aa
-aa
+NS
aa
ab
ab
@@ -23906,8 +23914,8 @@ aa
aa
aa
aa
-aa
-aa
+NS
+NS
ac
ac
ac
diff --git a/maps/tether/tether-07-station3.dmm b/maps/tether/tether-07-station3.dmm
index 58e18710b9..de47d879d0 100644
--- a/maps/tether/tether-07-station3.dmm
+++ b/maps/tether/tether-07-station3.dmm
@@ -23475,6 +23475,10 @@
/obj/random/maintenance/engineering,
/turf/simulated/floor,
/area/maintenance/station/ai)
+"Wf" = (
+/obj/structure/grille,
+/turf/space,
+/area/space)
"WD" = (
/obj/effect/floor_decal/borderfloor,
/obj/effect/floor_decal/corner/red/border,
@@ -27344,16 +27348,16 @@ aa
aa
aa
aa
-aa
-aa
-aa
-aa
-aa
-aa
-aa
-aa
-aa
-aa
+nA
+nA
+nA
+nA
+Wf
+nA
+nA
+nB
+nA
+nA
aa
aa
aa
@@ -27486,6 +27490,7 @@ aa
aa
aa
aa
+nA
aa
aa
aa
@@ -27494,8 +27499,7 @@ aa
aa
aa
aa
-aa
-aa
+nA
aa
aa
aa
@@ -27628,7 +27632,7 @@ aa
aa
aa
aa
-aa
+nA
aa
aa
aa
@@ -27770,7 +27774,7 @@ aa
aa
aa
aa
-aa
+nA
aa
aa
aa
@@ -27912,7 +27916,7 @@ aa
aa
aa
aa
-aa
+nA
aa
aa
aa
@@ -28054,7 +28058,7 @@ aa
aa
aa
aa
-aa
+nA
aa
aa
aa
@@ -28196,7 +28200,7 @@ aa
aa
aa
aa
-aa
+nA
aa
aa
aa
@@ -28338,7 +28342,7 @@ aa
aa
aa
aa
-aa
+nB
aa
aa
aa
@@ -28480,7 +28484,7 @@ aa
aa
aa
aa
-aa
+nA
aa
aa
aa
@@ -28622,8 +28626,8 @@ aa
aa
aa
aa
-aa
-aa
+nA
+nA
ab
ab
ab
diff --git a/maps/tether/tether_turfs.dm b/maps/tether/tether_turfs.dm
index a705654025..b755980b95 100644
--- a/maps/tether/tether_turfs.dm
+++ b/maps/tether/tether_turfs.dm
@@ -5,7 +5,7 @@ VIRGO3B_TURF_CREATE(/turf/simulated/open)
/turf/simulated/open/virgo3b/New()
..()
if(outdoors)
- outdoor_turfs.Add(src)
+ SSplanets.addTurf(src)
VIRGO3B_TURF_CREATE(/turf/simulated/floor)
@@ -183,7 +183,7 @@ VIRGO3B_TURF_CREATE(/turf/simulated/mineral/floor)
color = "#FFBBBB"
/turf/simulated/sky/virgo3b/initialize()
- outdoor_turfs.Add(src)
+ SSplanets.addTurf(src)
set_light(2, 2, "#FFBBBB")
/turf/simulated/sky/virgo3b/north
diff --git a/maps/tether/tether_virgo3b.dm b/maps/tether/tether_virgo3b.dm
index 8fd4dc874b..284b3d2f4e 100644
--- a/maps/tether/tether_virgo3b.dm
+++ b/maps/tether/tether_virgo3b.dm
@@ -127,7 +127,7 @@ var/datum/planet/virgo3b/planet_virgo3b = null
WEATHER_HAIL = 2.5
)
-datum/weather/virgo3b
+/datum/weather/virgo3b
name = "virgo3b base"
temp_high = 243.15 // -20c
temp_low = 233.15 // -30c
@@ -154,8 +154,8 @@ datum/weather/virgo3b
/datum/weather/virgo3b/light_snow
name = "light snow"
icon_state = "snowfall_light"
- temp_high = 238.15 // -25c
- temp_low = 228.15 // -35c
+ temp_high = 235
+ temp_low = 225
light_modifier = 0.7
transition_chances = list(
WEATHER_OVERCAST = 20,
@@ -167,9 +167,10 @@ datum/weather/virgo3b
/datum/weather/virgo3b/snow
name = "moderate snow"
icon_state = "snowfall_med"
- temp_high = 233.15 // -30c
- temp_low = 223.15 // -40c
+ temp_high = 230
+ temp_low = 220
light_modifier = 0.5
+ flight_failure_modifier = 5
transition_chances = list(
WEATHER_LIGHT_SNOW = 20,
WEATHER_SNOW = 50,
@@ -179,7 +180,8 @@ datum/weather/virgo3b
)
/datum/weather/virgo3b/snow/process_effects()
- for(var/turf/simulated/floor/outdoors/snow/S in outdoor_turfs)
+ ..()
+ for(var/turf/simulated/floor/outdoors/snow/S in SSplanets.new_outdoor_turfs) //This didn't make any sense before SSplanets, either
if(S.z in holder.our_planet.expected_z_levels)
for(var/dir_checked in cardinal)
var/turf/simulated/floor/T = get_step(S, dir_checked)
@@ -190,9 +192,10 @@ datum/weather/virgo3b
/datum/weather/virgo3b/blizzard
name = "blizzard"
icon_state = "snowfall_heavy"
- temp_high = 223.15 // -40c
- temp_low = 203.15 // -60c
+ temp_high = 215
+ temp_low = 200
light_modifier = 0.3
+ flight_failure_modifier = 10
transition_chances = list(
WEATHER_SNOW = 45,
WEATHER_BLIZZARD = 40,
@@ -201,7 +204,8 @@ datum/weather/virgo3b
)
/datum/weather/virgo3b/blizzard/process_effects()
- for(var/turf/simulated/floor/outdoors/snow/S in outdoor_turfs)
+ ..()
+ for(var/turf/simulated/floor/outdoors/snow/S in SSplanets.new_outdoor_turfs) //This didn't make any sense before SSplanets, either
if(S.z in holder.our_planet.expected_z_levels)
for(var/dir_checked in cardinal)
var/turf/simulated/floor/T = get_step(S, dir_checked)
@@ -213,6 +217,8 @@ datum/weather/virgo3b
name = "rain"
icon_state = "rain"
light_modifier = 0.5
+ effect_message = "Rain falls on you."
+
transition_chances = list(
WEATHER_OVERCAST = 25,
WEATHER_LIGHT_SNOW = 10,
@@ -222,21 +228,38 @@ datum/weather/virgo3b
)
/datum/weather/virgo3b/rain/process_effects()
+ ..()
for(var/mob/living/L in living_mob_list)
if(L.z in holder.our_planet.expected_z_levels)
var/turf/T = get_turf(L)
if(!T.outdoors)
- return // They're indoors, so no need to rain on them.
+ continue // They're indoors, so no need to rain on them.
- L.adjust_fire_stacks(-5)
- to_chat(L, "Rain falls on you.")
+ // If they have an open umbrella, it'll guard from rain
+ if(istype(L.get_active_hand(), /obj/item/weapon/melee/umbrella))
+ var/obj/item/weapon/melee/umbrella/U = L.get_active_hand()
+ if(U.open)
+ if(show_message)
+ to_chat(L, "Rain patters softly onto your umbrella")
+ continue
+ else if(istype(L.get_inactive_hand(), /obj/item/weapon/melee/umbrella))
+ var/obj/item/weapon/melee/umbrella/U = L.get_inactive_hand()
+ if(U.open)
+ if(show_message)
+ to_chat(L, "Rain patters softly onto your umbrella")
+ continue
+
+ L.water_act(1)
+ if(show_message)
+ to_chat(L, effect_message)
/datum/weather/virgo3b/storm
name = "storm"
icon_state = "storm"
- temp_high = 233.15 // -30c
- temp_low = 213.15 // -50c
light_modifier = 0.3
+ flight_failure_modifier = 10
+
+
transition_chances = list(
WEATHER_RAIN = 45,
WEATHER_STORM = 40,
@@ -244,53 +267,89 @@ datum/weather/virgo3b
WEATHER_OVERCAST = 5
)
-/datum/weather/virgo3b/rain/process_effects()
+/datum/weather/virgo3b/storm/process_effects()
+ ..()
for(var/mob/living/L in living_mob_list)
if(L.z in holder.our_planet.expected_z_levels)
var/turf/T = get_turf(L)
if(!T.outdoors)
- return // They're indoors, so no need to rain on them.
+ continue // They're indoors, so no need to rain on them.
- L.adjust_fire_stacks(-10)
+ // If they have an open umbrella, it'll get stolen by the wind
+ if(istype(L.get_active_hand(), /obj/item/weapon/melee/umbrella))
+ var/obj/item/weapon/melee/umbrella/U = L.get_active_hand()
+ if(U.open)
+ to_chat(L, "A gust of wind yanks the umbrella from your hand!")
+ L.drop_from_inventory(U)
+ U.throw_at(get_edge_target_turf(U, pick(alldirs)), 8, 1, L)
+ else if(istype(L.get_inactive_hand(), /obj/item/weapon/melee/umbrella))
+ var/obj/item/weapon/melee/umbrella/U = L.get_inactive_hand()
+ if(U.open)
+ to_chat(L, "A gust of wind yanks the umbrella from your hand!")
+ L.drop_from_inventory(U)
+ U.throw_at(get_edge_target_turf(U, pick(alldirs)), 8, 1, L)
+
+ L.water_act(2)
to_chat(L, "Rain falls on you, drenching you in water.")
/datum/weather/virgo3b/hail
name = "hail"
icon_state = "hail"
- temp_high = 233.15 // -30c
- temp_low = 213.15 // -50c
light_modifier = 0.3
+ flight_failure_modifier = 15
+ timer_low_bound = 2
+ timer_high_bound = 5
+ effect_message = "The hail smacks into you!"
+
transition_chances = list(
WEATHER_RAIN = 45,
- WEATHER_STORM = 10,
- WEATHER_HAIL = 40,
+ WEATHER_STORM = 40,
+ WEATHER_HAIL = 10,
WEATHER_OVERCAST = 5
)
/datum/weather/virgo3b/hail/process_effects()
- for(var/mob/living/L in living_mob_list)
- if(L.z in holder.our_planet.expected_z_levels)
- var/turf/T = get_turf(L)
+ ..()
+ for(var/mob/living/carbon/human/H in living_mob_list)
+ if(H.z in holder.our_planet.expected_z_levels)
+ var/turf/T = get_turf(H)
if(!T.outdoors)
- return // They're indoors, so no need to pelt them with ice.
+ continue // They're indoors, so no need to pelt them with ice.
+
+ // If they have an open umbrella, it'll guard from rain
+ // Message plays every time the umbrella gets stolen, just so they're especially aware of what's happening
+ if(istype(H.get_active_hand(), /obj/item/weapon/melee/umbrella))
+ var/obj/item/weapon/melee/umbrella/U = H.get_active_hand()
+ if(U.open)
+ if(show_message)
+ to_chat(H, "Hail patters gently onto your umbrella.")
+ continue
+ else if(istype(H.get_inactive_hand(), /obj/item/weapon/melee/umbrella))
+ var/obj/item/weapon/melee/umbrella/U = H.get_inactive_hand()
+ if(U.open)
+ if(show_message)
+ to_chat(H, "Hail patters gently onto your umbrella.")
+ continue
var/target_zone = pick(BP_ALL)
- var/amount_blocked = L.run_armor_check(target_zone, "melee")
- var/amount_soaked = L.get_armor_soak(target_zone, "melee")
+ var/amount_blocked = H.run_armor_check(target_zone, "melee")
+ var/amount_soaked = H.get_armor_soak(target_zone, "melee")
if(amount_blocked >= 100)
- return // No need to apply damage.
+ continue // No need to apply damage.
if(amount_soaked >= 10)
- return // No need to apply damage.
+ continue // No need to apply damage.
- L.apply_damage(rand(5, 10), BRUTE, target_zone, amount_blocked, amount_soaked, used_weapon = "hail")
- to_chat(L, "The hail raining down on you [L.can_feel_pain() ? "hurts" : "damages you"]!")
+ H.apply_damage(rand(1, 3), BRUTE, target_zone, amount_blocked, amount_soaked, used_weapon = "hail")
+ if(show_message)
+ to_chat(H, effect_message)
/datum/weather/virgo3b/blood_moon
name = "blood moon"
light_modifier = 0.5
light_color = "#FF0000"
+ flight_failure_modifier = 25
transition_chances = list(
WEATHER_BLOODMOON = 100
- )
\ No newline at end of file
+ )
diff --git a/vorestation.dme b/vorestation.dme
index 01841717d0..83dd5c93d4 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -195,7 +195,6 @@
#include "code\controllers\Processes\inactivity.dm"
#include "code\controllers\Processes\nanoui.dm"
#include "code\controllers\Processes\obj.dm"
-#include "code\controllers\Processes\planet.dm"
#include "code\controllers\Processes\radiation.dm"
#include "code\controllers\Processes\scheduler.dm"
#include "code\controllers\Processes\sun.dm"
@@ -218,6 +217,7 @@
#include "code\controllers\subsystems\orbits.dm"
#include "code\controllers\subsystems\overlays.dm"
#include "code\controllers\subsystems\persist_vr.dm"
+#include "code\controllers\subsystems\planets.dm"
#include "code\controllers\subsystems\shuttles.dm"
#include "code\controllers\subsystems\transcore_vr.dm"
#include "code\controllers\subsystems\xenoarch.dm"
@@ -1156,7 +1156,6 @@
#include "code\game\objects\random\mob.dm"
#include "code\game\objects\random\random_vr.dm"
#include "code\game\objects\random\spacesuits.dm"
-#include "code\game\objects\structures\alien_props.dm"
#include "code\game\objects\structures\barsign.dm"
#include "code\game\objects\structures\bedsheet_bin.dm"
#include "code\game\objects\structures\bonfire.dm"
@@ -1238,6 +1237,11 @@
#include "code\game\objects\structures\flora\trees.dm"
#include "code\game\objects\structures\ghost_pods\ghost_pods.dm"
#include "code\game\objects\structures\ghost_pods\silicon.dm"
+#include "code\game\objects\structures\props\alien_props.dm"
+#include "code\game\objects\structures\props\beam_prism.dm"
+#include "code\game\objects\structures\props\projectile_lock.dm"
+#include "code\game\objects\structures\props\prop.dm"
+#include "code\game\objects\structures\props\puzzledoor.dm"
#include "code\game\objects\structures\stool_bed_chair_nest\alien_nests.dm"
#include "code\game\objects\structures\stool_bed_chair_nest\bed.dm"
#include "code\game\objects\structures\stool_bed_chair_nest\chairs.dm"
@@ -1452,6 +1456,7 @@
#include "code\modules\client\ui_style.dm"
#include "code\modules\client\preference_setup\_defines.dm"
#include "code\modules\client\preference_setup\preference_setup.dm"
+#include "code\modules\client\preference_setup\preference_setup_vr.dm"
#include "code\modules\client\preference_setup\antagonism\01_basic.dm"
#include "code\modules\client\preference_setup\antagonism\02_candidacy.dm"
#include "code\modules\client\preference_setup\general\01_basic.dm"
@@ -2048,6 +2053,7 @@
#include "code\modules\mob\living\carbon\human\species\outsider\shadow.dm"
#include "code\modules\mob\living\carbon\human\species\outsider\skeleton.dm"
#include "code\modules\mob\living\carbon\human\species\outsider\vox.dm"
+#include "code\modules\mob\living\carbon\human\species\station\alraune.dm"
#include "code\modules\mob\living\carbon\human\species\station\blank_vr.dm"
#include "code\modules\mob\living\carbon\human\species\station\golem.dm"
#include "code\modules\mob\living\carbon\human\species\station\human_subspecies.dm"
@@ -2142,6 +2148,7 @@
#include "code\modules\mob\living\silicon\robot\drone\drone_damage.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_items.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_manufacturer.dm"
+#include "code\modules\mob\living\silicon\robot\drone\drone_remote_control.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_say.dm"
#include "code\modules\mob\living\silicon\robot\robot_modules\event.dm"
#include "code\modules\mob\living\silicon\robot\robot_modules\station.dm"