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"