diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index d790514614..44137b0431 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -30,6 +30,7 @@ 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_PLANETS -4 #define INIT_ORDER_OVERLAY -6 #define INIT_ORDER_XENOARCH -20 @@ -42,6 +43,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/controllers/Processes/planet.dm b/code/controllers/Processes/planet.dm deleted file mode 100644 index 770b3a5e87..0000000000 --- a/code/controllers/Processes/planet.dm +++ /dev/null @@ -1,95 +0,0 @@ -var/datum/controller/process/planet/planet_controller = null - -/datum/controller/process/planet - var/list/planets = list() - var/list/z_to_planet = list() - -/datum/controller/process/planet/setup() - name = "planet controller" - planet_controller = src - schedule_interval = 1 MINUTE - - var/list/planet_datums = typesof(/datum/planet) - /datum/planet - for(var/P in planet_datums) - var/datum/planet/NP = new P() - planets.Add(NP) - - allocateTurfs() - -/datum/controller/process/planet/proc/allocateTurfs() - for(var/turf/simulated/OT in outdoor_turfs) - for(var/datum/planet/P in planets) - if(OT.z in P.expected_z_levels) - P.planet_floors |= OT - OT.vis_contents |= P.weather_holder.visuals - break - outdoor_turfs.Cut() //Why were you in there INCORRECTLY? - - for(var/turf/unsimulated/wall/planetary/PW in planetary_walls) - for(var/datum/planet/P in planets) - if(PW.type == P.planetary_wall_type) - P.planet_walls |= PW - break - planetary_walls.Cut() - -/datum/controller/process/planet/proc/unallocateTurf(var/turf/T) - for(var/planet in planets) - var/datum/planet/P = planet - if(T.z in P.expected_z_levels) - P.planet_floors -= T - T.vis_contents -= P.weather_holder.visuals - -/datum/controller/process/planet/doWork() - if(outdoor_turfs.len || planetary_walls.len) - allocateTurfs() - - for(var/datum/planet/P in planets) - P.process(schedule_interval / 10) - SCHECK //Your process() really shouldn't take this long... - - //Sun light needs changing - if(P.needs_work & PLANET_PROCESS_SUN) - P.needs_work &= ~PLANET_PROCESS_SUN - // 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 P.sunlit_corners) - var/datum/lighting_corner/LC = C - LC.update_lumcount(old_lum_r, old_lum_g, old_lum_b) - SCHECK - 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) - SCHECK - update_gen-- - P.sun["lum_r"] = lum_r - P.sun["lum_g"] = lum_g - P.sun["lum_b"] = lum_b - - //Temperature needs updating - if(P.needs_work & PLANET_PROCESS_TEMP) - P.needs_work &= ~PLANET_PROCESS_TEMP - //Set new temperatures - for(var/W in P.planet_walls) - var/turf/unsimulated/wall/planetary/wall = W - wall.set_temperature(P.weather_holder.temperature) - SCHECK diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index f6db4f65d4..9e29ba9998 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 8d02fda2eb..f1249cfe0c 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -133,8 +133,5 @@ 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/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/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index 90f708ff9d..0369b91d63 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -248,6 +248,6 @@ var/global/list/image/splatter_cache=list() //This version should be used for admin spawns and pre-mapped virus vectors (e.g. in PoIs), this version does not dry /obj/effect/decal/cleanable/mucus/mapped/New() - ...() + ..() virus2 = new /datum/disease2/disease virus2.makerandom() 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 4d6ff75c50..fca4cf4866 100644 --- a/code/game/objects/items/devices/communicator/UI.dm +++ b/code/game/objects/items/devices/communicator/UI.dm @@ -69,17 +69,16 @@ 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 + 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
" diff --git a/code/game/objects/items/weapons/grenades/explosive.dm b/code/game/objects/items/weapons/grenades/explosive.dm index ccf8b89ee2..67e00dc0bc 100644 --- a/code/game/objects/items/weapons/grenades/explosive.dm +++ b/code/game/objects/items/weapons/grenades/explosive.dm @@ -53,7 +53,8 @@ if(explosion_size) on_explosion(O) - src.fragmentate(O, num_fragments, spread_range, fragment_types) + if(num_fragments) + fragmentate(O, num_fragments, spread_range, fragment_types) qdel(src) @@ -98,3 +99,9 @@ num_fragments = 200 //total number of fragments produced by the grenade //The radius of the circle used to launch projectiles. Lower values mean less projectiles are used but if set too low gaps may appear in the spread pattern + +// This thing literally just explodes +/obj/item/weapon/grenade/explosive/concussion + name = "concussion grenade" + desc = "A small explosive grenade" + num_fragments = 0 \ No newline at end of file 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 969c77f4fb..861b34d9ea 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/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 50bc79d8fe..c6f2c0321e 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/ai/__readme.dm b/code/modules/ai/__readme.dm index 583fc1e0ca..b27731e3b1 100644 --- a/code/modules/ai/__readme.dm +++ b/code/modules/ai/__readme.dm @@ -23,9 +23,9 @@ as most other objects ( process() ) or mobs ( Life() ) do. Flow of Execution: - / - Every 0.5s - > /datum/ai_holder/handle_tactics() - > /datum/ai_holder/handle_stance_tactical() - > switch(stance)... - AI Subsystem - * - \ - Every 2.0s - > /datum/ai_holder/handle_strategicals() - > /datum/ai_holder/handle_stance_strategical() - > switch(stance)... + AI Subsystem + - > Every 0.5s - > /datum/ai_holder/handle_tactics() - > /datum/ai_holder/handle_stance_tactical() - > switch(stance)... + - > Every 2.0s - > /datum/ai_holder/handle_strategicals() - > /datum/ai_holder/handle_stance_strategical() - > switch(stance)... The datum is not driven by its mob, as previous implementations did, meaning Life() is not involved. Instead, it is processed by a specific Master Controller Subsystem titled 'AI', which by default ticks every half a second. Each instance of the ai_holder datum that is not 'asleep' is part of a list, containing ai_holders that are awake. diff --git a/code/modules/ai/interfaces.dm b/code/modules/ai/interfaces.dm index 974ce1b64f..59079c8a04 100644 --- a/code/modules/ai/interfaces.dm +++ b/code/modules/ai/interfaces.dm @@ -24,6 +24,10 @@ return FALSE /mob/living/simple_mob/ICheckRangedAttack(atom/A) + if(needs_reload) + if(reload_count >= reload_max) + try_reload() + return FALSE return projectiletype ? TRUE : FALSE /mob/living/proc/ISpecialAttack(atom/A) diff --git a/code/modules/ai/say_list.dm b/code/modules/ai/say_list.dm index 05c38f978f..7e7e5c02db 100644 --- a/code/modules/ai/say_list.dm +++ b/code/modules/ai/say_list.dm @@ -55,6 +55,23 @@ say_stand_down = list("Good.") say_escalate = list("Yarr! The booty is mine!") +// Mercs! +/datum/say_list/merc + speak = list("When are we gonna get out of this chicken-shit outfit?", + "Wish I had better equipment...", + "I knew I should have been a line chef...", + "Fuckin' helmet keeps fogging up.", + "Anyone else smell that?") + emote_see = list("sniffs", "coughs", "taps his foot", "looks around", "checks his equipment") + + say_understood = list("Understood!", "Affirmative!") + say_cannot = list("Negative!") + say_maybe_target = list("Who's there?") + say_got_target = list("Engaging!") + say_threaten = list("Get out of here!", "Hey! Private Property!") + say_stand_down = list("Good.") + say_escalate = list("Your funeral!", "Bring it!") + /datum/say_list/malf_drone speak = list("ALERT.","Hostile-ile-ile entities dee-twhoooo-wected.","Threat parameterszzzz- szzet.","Bring sub-sub-sub-systems uuuup to combat alert alpha-a-a.") emote_see = list("beeps menacingly","whirrs threateningly","scans its immediate vicinity") 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/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 927216190a..60b552013d 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -685,6 +685,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 d102f9e7cb..599eacdfa9 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 @@ -96,6 +99,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 @@ -128,14 +132,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. @@ -214,15 +226,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])") @@ -235,9 +250,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 @@ -263,26 +279,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() @@ -291,6 +322,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() @@ -348,14 +394,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_mob/combat.dm b/code/modules/mob/living/simple_mob/combat.dm index a0e86e5bdd..3a4af2b4a8 100644 --- a/code/modules/mob/living/simple_mob/combat.dm +++ b/code/modules/mob/living/simple_mob/combat.dm @@ -78,6 +78,11 @@ ranged_pre_animation(A) handle_attack_delay(A) // This will sleep this proc for a bit, which is why waitfor is false. + if(needs_reload) + if(reload_count >= reload_max) + try_reload() + return FALSE + visible_message("\The [src] fires at \the [A]!") shoot(A, src.loc, src) if(casingtype) @@ -99,10 +104,21 @@ if(!P) return P.launch(A) + if(needs_reload) + reload_count ++ // if(distance >= special_attack_min_range && distance <= special_attack_max_range) // return TRUE +/mob/living/simple_mob/proc/try_reload() + if(do_after(src, reload_time)) + if(reload_sound) + playsound(src.loc, reload_sound, 50, 1) + reload_count = 0 + return TRUE + else + return FALSE + // Can we currently do a special attack? /mob/living/simple_mob/proc/can_special_attack(atom/A) // Validity check. diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm index f874a9a43f..381ed0f9f1 100644 --- a/code/modules/mob/living/simple_mob/simple_mob.dm +++ b/code/modules/mob/living/simple_mob/simple_mob.dm @@ -78,6 +78,14 @@ var/projectilesound // The sound I make when I do it var/casingtype // What to make the hugely laggy casings pile out of + // Reloading settings, part of ranged code + var/needs_reload = FALSE // If TRUE, mob needs to reload occasionally + var/reload_max = 1 // How many shots the mob gets before it has to reload, will not be used if needs_reload is FALSE + var/reload_count = 0 // A counter to keep track of how many shots the mob has fired so far. Reloads when it hits reload_max. + var/reload_time = 1 SECONDS // How long it takes for a mob to reload. This is to buy a player a bit of time to run or fight. + var/reload_sound = 'sound/weapons/flipblade.ogg' // What sound gets played when the mob successfully reloads. Defaults to the same sound as reloading guns. Can be null. + + //Mob melee settings var/melee_damage_lower = 2 // Lower bound of randomized melee damage var/melee_damage_upper = 6 // Upper bound of randomized melee damage diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/humanoid.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/humanoid.dm new file mode 100644 index 0000000000..ed19064c47 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/humanoid.dm @@ -0,0 +1,23 @@ +/mob/living/simple_mob/humanoid + mob_class = MOB_CLASS_HUMANOID + + // Generic humanoid mob tolerances + min_oxy = 5 + max_oxy = 0 + min_tox = 0 + max_tox = 1 + min_co2 = 0 + max_co2 = 5 + min_n2 = 0 + max_n2 = 0 + unsuitable_atoms_damage = 15 + + // Most humans leave a corpse + var/corpse = null + +/mob/living/simple_mob/humanoid/death() + ..() + if(corpse) + new corpse (src.loc) + qdel(src) + return \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm new file mode 100644 index 0000000000..d762a75015 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm @@ -0,0 +1,278 @@ +/////////////////////////////// +// Merc Mobs Go Here +/////////////////////////////// + +// Probably shouldn't use this directly, there are a bunch of sub-classes that are more complete. +/mob/living/simple_mob/humanoid/merc + name = "mercenary" + desc = "A tough looking heavily-armed individual." + tt_desc = "E Homo sapiens" + icon_state = "syndicate" + icon_living = "syndicate" + icon_dead = "syndicate_dead" + icon_gib = "syndicate_gib" + + faction = "syndicate" + health = 150 // Point of human crit, as of commenting + maxHealth = 150 + movement_cooldown = 4 + + status_flags = 0 + + response_help = "pokes" + response_disarm = "shoves" + response_harm = "hits" + + harm_intent_damage = 5 + melee_damage_lower = 15 //Tac Knife damage + melee_damage_upper = 15 + attack_sharp = 1 + attack_edge = 1 + attacktext = list("slashed", "stabbed") + armor = list(melee = 40, bullet = 30, laser = 30, energy = 10, bomb = 10, bio = 100, rad = 100) // Same armor values as the vest they drop, plus simple mob immunities + + corpse = /obj/effect/landmark/mobcorpse/syndicatesoldier + loot_list = list(/obj/item/weapon/material/knife/tacknife = 100) // Might as well give it the knife + + ai_holder_type = /datum/ai_holder/simple_mob/merc + say_list_type = /datum/say_list/merc + + // Grenade special attack vars + var/grenade_type = /obj/item/weapon/grenade/explosive/concussion + special_attack_cooldown = 45 SECONDS + special_attack_min_range = 2 + special_attack_max_range = 7 + +//////////////////////////////// +// Grenade Attack +//////////////////////////////// + +// Any merc can use this, just set special_attack_charges to a positive value + +// Check if we should bother with the grenade +/mob/living/simple_mob/humanoid/merc/should_special_attack(atom/A) + var/mob_count = 0 // Checks to see if you can + var/turf/T = get_turf(A) + for(var/mob/M in range(T, 2)) + if(M.faction == faction) // Don't grenade our friends + return FALSE + if(M in oview(src, special_attack_max_range)) // And lets check if we can actually see at least two people before we throw a grenade + mob_count ++ + if(mob_count < 2) + return FALSE + else + return TRUE + +// Yes? Throw the grenade +/mob/living/simple_mob/humanoid/merc/do_special_attack(atom/A) + set waitfor = FALSE + set_AI_busy(TRUE) + + var/obj/item/weapon/grenade/G = new grenade_type(get_turf(src)) + if(istype(G)) + G.throw_at(A, G.throw_range, G.throw_speed, src) + G.attack_self(src) + + set_AI_busy(FALSE) + + +//////////////////////////////// +// Merc AI Types +//////////////////////////////// +/datum/ai_holder/simple_mob/merc + threaten = TRUE + returns_home = TRUE // Stay close to the base... + wander = TRUE // ... but "patrol" a little. + +/datum/ai_holder/simple_mob/merc/ranged + pointblank = TRUE // They get close? Just shoot 'em! + firing_lanes = TRUE // But not your buddies! + conserve_ammo = TRUE // And don't go wasting bullets! + + +//////////////////////////////// +// Melee +//////////////////////////////// +/mob/living/simple_mob/humanoid/merc/melee // Defined in case we add non-sword-and-board mercs + loot_list = list(/obj/item/weapon/material/knife/tacknife = 100) + +// Sword and Shield Merc +/mob/living/simple_mob/humanoid/merc/melee/sword + icon_state = "syndicatemelee" + icon_living = "syndicatemelee" + + melee_damage_lower = 30 + melee_damage_upper = 30 + attack_armor_pen = 50 + attack_sharp = 1 + attack_edge = 1 + attacktext = list("slashed") + + loot_list = list(/obj/item/weapon/melee/energy/sword/red = 100, /obj/item/weapon/shield/energy = 100) + +// They have a shield, so they try to block +/mob/living/simple_mob/humanoid/merc/melee/sword/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(O.force) + if(prob(20)) + visible_message("\The [src] blocks \the [O] with its shield!") + if(user) + ai_holder.react_to_attack(user) + return + else + ..() + else + usr << "This weapon is ineffective, it does no damage." + visible_message("\The [user] gently taps [src] with \the [O].") + +/mob/living/simple_mob/humanoid/merc/melee/sword/bullet_act(var/obj/item/projectile/Proj) + if(!Proj) return + if(prob(35)) + visible_message("[src] blocks [Proj] with its shield!") + if(Proj.firer) + ai_holder.react_to_attack(Proj.firer) + return + else + ..() + + +//////////////////////////////// +// Ranged +//////////////////////////////// + +// Base Ranged Merc, so we don't have to redefine a million vars for every subtype. Uses a pistol. +/mob/living/simple_mob/humanoid/merc/ranged + icon_state = "syndicateranged" + icon_living = "syndicateranged" + projectiletype = /obj/item/projectile/bullet/pistol/medium +// casingtype = /obj/item/ammo_casing/spent //Makes infinite stacks of bullets when put in PoIs. + projectilesound = 'sound/weapons/Gunshot_light.ogg' + loot_list = list(/obj/item/weapon/gun/projectile/colt = 100) + + needs_reload = TRUE + reload_max = 7 // Not the best default, but it fits the pistol + ai_holder_type = /datum/ai_holder/simple_mob/merc/ranged + +// C20r SMG +/mob/living/simple_mob/humanoid/merc/ranged/smg + icon_state = "syndicateranged_smg" + icon_living = "syndicateranged_smg" + + loot_list = list(/obj/item/weapon/gun/projectile/automatic/c20r = 100) + + base_attack_cooldown = 5 // Two attacks a second or so. + reload_max = 20 + +// Laser Rifle +/mob/living/simple_mob/humanoid/merc/ranged/laser + icon_state = "syndicateranged_laser" + icon_living = "syndicateranged_laser" + projectiletype = /obj/item/projectile/beam/midlaser + projectilesound = 'sound/weapons/Laser.ogg' + + loot_list = list(/obj/item/weapon/gun/energy/laser = 100) + + reload_max = 10 + +// Ion Rifle +/mob/living/simple_mob/humanoid/merc/ranged/ionrifle + icon_state = "syndicateranged_ionrifle" + icon_living = "syndicateranged_ionrifle" + projectiletype = /obj/item/projectile/ion + projectilesound = 'sound/weapons/Laser.ogg' + + loot_list = list(/obj/item/weapon/gun/energy/ionrifle = 100) + + reload_max = 10 + +// Grenadier +/mob/living/simple_mob/humanoid/merc/ranged/grenadier + icon_state = "syndicateranged_shotgun" + icon_living = "syndicateranged_shotgun" + projectiletype = /obj/item/projectile/bullet/pellet/shotgun // Buckshot + projectilesound = 'sound/weapons/gunshot/shotgun.ogg' + + loot_list = list(/obj/item/weapon/gun/projectile/shotgun/pump = 100) + + reload_max = 4 + reload_time = 1.5 SECONDS // It's a shotgun, it takes a moment + + special_attack_charges = 5 + + +//////////////////////////////// +// Space Mercs +//////////////////////////////// + +// Sword Space Merc +/mob/living/simple_mob/humanoid/merc/melee/sword/space + name = "syndicate commando" + icon_state = "syndicatemeleespace" + icon_living = "syndicatemeleespace" + + movement_cooldown = 0 + + armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 35, bio = 100, rad = 100) // Same armor as their voidsuit + + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + + corpse = /obj/effect/landmark/mobcorpse/syndicatecommando + +/mob/living/simple_mob/humanoid/merc/melee/sword/space/Process_Spacemove(var/check_drift = 0) + return + +// Ranged Space Merc +/mob/living/simple_mob/humanoid/merc/ranged/space + name = "syndicate sommando" + icon_state = "syndicaterangedpsace" + icon_living = "syndicaterangedpsace" + + movement_cooldown = 0 + + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + + corpse = /obj/effect/landmark/mobcorpse/syndicatecommando + +/mob/living/simple_animal/hostile/syndicate/ranged/space/Process_Spacemove(var/check_drift = 0) + return + +//////////////////////////////// +// PoI Mercs +//////////////////////////////// + +// None of these drop weapons, until we have a better way to balance them +/mob/living/simple_mob/humanoid/merc/melee/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/melee/sword/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/smg/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/laser + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/ionrifle + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/grenadier/poi + loot_list = list() \ No newline at end of file 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 11c902f77f..724540c875 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/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/animal.dmi b/icons/mob/animal.dmi index 15af8f105f..b360135495 100644 Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.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/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/polaris.dme b/polaris.dme index 13b07fa9f1..1e8d0408af 100644 --- a/polaris.dme +++ b/polaris.dme @@ -171,7 +171,6 @@ #include "code\controllers\Processes\mob.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" @@ -190,6 +189,7 @@ #include "code\controllers\subsystems\machines.dm" #include "code\controllers\subsystems\orbits.dm" #include "code\controllers\subsystems\overlays.dm" +#include "code\controllers\subsystems\planets.dm" #include "code\controllers\subsystems\shuttles.dm" #include "code\controllers\subsystems\xenoarch.dm" #include "code\datums\ai_law_sets.dm" @@ -1040,7 +1040,6 @@ #include "code\game\objects\random\misc.dm" #include "code\game\objects\random\mob.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" @@ -1112,6 +1111,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" @@ -1889,6 +1893,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" @@ -1970,6 +1975,8 @@ #include "code\modules\mob\living\simple_mob\subtypes\animal\giant_spider\tunneler.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\sif\hooligan_crab.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\sif\sif.dm" +#include "code\modules\mob\living\simple_mob\subtypes\humanoid\humanoid.dm" +#include "code\modules\mob\living\simple_mob\subtypes\humanoid\mercs\mercs.dm" #include "code\modules\mob\living\simple_mob\subtypes\mechanical\combat_drone.dm" #include "code\modules\mob\living\simple_mob\subtypes\mechanical\mechanical.dm" #include "code\modules\mob\living\simple_mob\subtypes\mechanical\viscerator.dm" @@ -2487,7 +2494,7 @@ #include "code\ZAS\Zone.dm" #include "interface\interface.dm" #include "interface\skin.dmf" -#include "maps\example\example.dm" +#include "maps\southern_cross\southern_cross.dm" #include "maps\submaps\_readme.dm" #include "maps\submaps\space_submaps\space.dm" #include "maps\submaps\surface_submaps\mountains\mountains.dm"