diff --git a/code/controllers/Processes/planet.dm b/code/controllers/Processes/planet.dm new file mode 100644 index 00000000000..31406e92a09 --- /dev/null +++ b/code/controllers/Processes/planet.dm @@ -0,0 +1,12 @@ +/datum/controller/process/planet + var/list/planets = list() + +/datum/controller/process/planet/setup() + name = "planet" + schedule_interval = 600 // every minute + planet_sif = new() + planets.Add(planet_sif) + +/datum/controller/process/planet/doWork() + for(var/datum/planet/P in planets) + P.process(schedule_interval / 10) \ No newline at end of file diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index 7a1b12509c6..d1ab11e7067 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -347,7 +347,7 @@ put_mob(usr) return -/atom/proc/return_air_for_internal_lifeform() +/atom/proc/return_air_for_internal_lifeform(var/mob/living/lifeform) return return_air() /obj/machinery/atmospherics/unary/cryo_cell/return_air_for_internal_lifeform() diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm index 0ad1fb84045..9b731a0fa8d 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -32,6 +32,19 @@ ..() icon_state = "tree_[rand(1, 6)]" +/obj/structure/flora/tree/sif + name = "glowing tree" + desc = "It's a tree, except this one seems quite alien. It glows a deep blue." + icon = 'icons/obj/flora/deadtrees.dmi' + icon_state = "tree_sif" + +/obj/structure/flora/tree/sif/New() + update_icon() + +/obj/structure/flora/tree/sif/update_icon() + set_light(5, 1, "#33ccff") + overlays.Cut() + overlays.Add(image(icon = 'icons/obj/flora/deadtrees.dmi', icon_state = "[icon_state]_glow", layer = LIGHTING_LAYER + 0.1)) //grass /obj/structure/flora/grass diff --git a/code/game/turfs/simulated/floor_icon.dm b/code/game/turfs/simulated/floor_icon.dm index cdde744bd45..0ad44622b76 100644 --- a/code/game/turfs/simulated/floor_icon.dm +++ b/code/game/turfs/simulated/floor_icon.dm @@ -72,6 +72,9 @@ var/list/flooring_cache = list() if(!isnull(burnt) && (flooring.flags & TURF_CAN_BURN)) overlays |= get_flooring_overlay("[flooring.icon_base]-burned-[burnt]","[flooring.icon_base]_burned[burnt]") + if(weather_overlay) + overlays += weather_overlay + if(update_neighbors) for(var/turf/simulated/floor/F in range(src, 1)) if(F == src) diff --git a/code/game/turfs/simulated/outdoors/dirt.dm b/code/game/turfs/simulated/outdoors/dirt.dm new file mode 100644 index 00000000000..1d735d81be2 --- /dev/null +++ b/code/game/turfs/simulated/outdoors/dirt.dm @@ -0,0 +1,6 @@ +/turf/simulated/floor/outdoors/dirt + name = "dirt" + desc = "Quite dirty!" + icon_state = "dirt-dark" + edge_blending_priority = 1 + turf_layers = list(/turf/simulated/floor/outdoors/rocks) \ No newline at end of file diff --git a/code/game/turfs/simulated/outdoors/grass.dm b/code/game/turfs/simulated/outdoors/grass.dm new file mode 100644 index 00000000000..98b7d3dfba4 --- /dev/null +++ b/code/game/turfs/simulated/outdoors/grass.dm @@ -0,0 +1,42 @@ +var/list/grass_types = list( + /obj/structure/flora/ausbushes/sparsegrass, + /obj/structure/flora/ausbushes/fullgrass +) + +/turf/simulated/floor/outdoors/grass + name = "grass" + icon_state = "grass" + edge_blending_priority = 3 + turf_layers = list( + /turf/simulated/floor/outdoors/rocks, + /turf/simulated/floor/outdoors/dirt + ) + var/grass_chance = 20 + +/turf/simulated/floor/outdoors/grass/sif + name = "growth" + icon_state = "grass_sif" + edge_blending_priority = 3 + grass_chance = 0 + +/turf/simulated/floor/outdoors/grass/New() + if(prob(50)) + icon_state += "2" + //edge_blending_priority++ + + if(grass_chance && prob(grass_chance)) + var/grass_type = pick(grass_types) + new grass_type(src) + ..() + +/turf/simulated/floor/outdoors/grass/forest + name = "thick grass" + icon_state = "grass-dark" + grass_chance = 80 + //tree_prob = 20 + edge_blending_priority = 4 + +/turf/simulated/floor/outdoors/grass/sif/forest + name = "thick growth" + icon_state = "grass_sif_dark" + edge_blending_priority = 4 \ No newline at end of file diff --git a/code/game/turfs/simulated/outdoors/outdoors.dm b/code/game/turfs/simulated/outdoors/outdoors.dm new file mode 100644 index 00000000000..0ee844f3207 --- /dev/null +++ b/code/game/turfs/simulated/outdoors/outdoors.dm @@ -0,0 +1,109 @@ +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, + // and if those adjacent turfs have a lower edge_blending_priority. + var/edge_blending_priority = 0 + // Outdoors var determines if the game should consider the turf to be 'outdoors', which controls certain things such as weather effects. + var/outdoors = FALSE + // This holds the image for the current weather effect. + var/image/weather_overlay = null + +/turf/simulated/floor/outdoors + name = "generic ground" + desc = "Rather boring." + icon = 'icons/turf/outdoors.dmi' + icon_state = null + edge_blending_priority = 1 + outdoors = TRUE // This variable is used for weather effects. + // When a turf gets demoted or promoted, this list gets adjusted. The top-most layer is the layer on the bottom of the list, due to how pop() works. + var/list/turf_layers = list(/turf/simulated/floor/outdoors/rocks) + +/turf/simulated/floor/outdoors/initialize() + update_icon() + ..() + +/turf/simulated/floor/New() + if(outdoors) + outdoor_turfs.Add(src) + ..() + +/turf/simulated/floor/Destroy() + if(outdoors) + outdoor_turfs.Remove(src) + ..() + +/turf/simulated/floor/proc/update_icon_edge() + if(edge_blending_priority) + for(var/checkdir in cardinal) + var/turf/simulated/T = get_step(src, checkdir) + if(istype(T) && T.edge_blending_priority && edge_blending_priority < T.edge_blending_priority && icon_state != T.icon_state) + var/cache_key = "[T.get_edge_icon_state()]-[checkdir]" + if(!turf_edge_cache[cache_key]) + turf_edge_cache[cache_key] = image(icon = 'icons/turf/outdoors_edge.dmi', icon_state = "[T.get_edge_icon_state()]-edge", dir = checkdir) + overlays += turf_edge_cache[cache_key] + +/turf/simulated/proc/get_edge_icon_state() + return icon_state + +/turf/simulated/floor/outdoors/update_icon() + overlays.Cut() + update_icon_edge() + ..() + +/turf/simulated/floor/outdoors/mud + name = "grass" + icon_state = "mud_dark" + edge_blending_priority = 2 + +/turf/simulated/floor/outdoors/rocks + name = "rocks" + desc = "Hard as a rock." + icon_state = "rock" + edge_blending_priority = 1 + + +// This proc adds a 'layer' on top of the turf. +/turf/simulated/floor/outdoors/proc/promote(var/new_turf_type) + var/list/new_turf_layer_list = turf_layers.Copy() + var/list/coords = list(x, y, z) + + new_turf_layer_list.Add(src.type) + + ChangeTurf(new_turf_type) + var/turf/simulated/floor/outdoors/T = locate(coords[1], coords[2], coords[3]) + if(istype(T)) + T.turf_layers = new_turf_layer_list.Copy() + +// This proc removes the topmost layer. +/turf/simulated/floor/outdoors/proc/demote() + if(!turf_layers.len) + return // Cannot demote further. + var/list/new_turf_layer_list = turf_layers.Copy() + var/list/coords = list(x, y, z) + + ChangeTurf(pop(new_turf_layer_list)) + var/turf/simulated/floor/outdoors/T = locate(coords[1], coords[2], coords[3]) + if(istype(T)) + T.turf_layers = new_turf_layer_list.Copy() + +// Called by weather processes, and maybe technomancers in the future. +/turf/simulated/floor/proc/chill() + return + +/turf/simulated/floor/outdoors/chill() + promote(/turf/simulated/floor/outdoors/snow) + +/turf/simulated/floor/outdoors/snow/chill() + return // Todo: Add heavy snow. + +/turf/simulated/floor/outdoors/ex_act(severity) + switch(severity) + if(2) + if(prob(33)) + return + if(3) + if(prob(66)) + return + demote() \ No newline at end of file diff --git a/code/game/turfs/simulated/outdoors/snow.dm b/code/game/turfs/simulated/outdoors/snow.dm new file mode 100644 index 00000000000..474bdb171c0 --- /dev/null +++ b/code/game/turfs/simulated/outdoors/snow.dm @@ -0,0 +1,35 @@ +/turf/simulated/floor/outdoors/snow + name = "snow" + icon_state = "snow" + edge_blending_priority = 5 + movement_cost = 2 + turf_layers = list( + /turf/simulated/floor/outdoors/rocks, + /turf/simulated/floor/outdoors/dirt + ) + var/list/crossed_dirs = list() + +/turf/simulated/floor/outdoors/snow/Entered(atom/A) + if(isliving(A)) + var/mdir = "[A.dir]" + crossed_dirs[mdir] = 1 + update_icon() + . = ..() + +/turf/simulated/floor/outdoors/snow/update_icon() + overlays.Cut() + ..() + for(var/d in crossed_dirs) + overlays += image(icon = 'icons/turf/outdoors.dmi', icon_state = "snow_footprints", dir = text2num(d)) + +/turf/simulated/floor/outdoors/snow/attackby(var/obj/item/W, var/mob/user) + if(istype(W, /obj/item/weapon/shovel)) + to_chat(user, "You begin to remove \the [src] with your [W].") + if(do_after(user, 4 SECONDS)) + to_chat(user, "\The [src] has been dug up, and now lies in a pile nearby.") + new /obj/item/stack/material/snow(src) + demote() + else + to_chat(user, "You decide to not finish removing \the [src].") + else + ..() diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm new file mode 100644 index 00000000000..cad62cc8de2 --- /dev/null +++ b/code/game/turfs/simulated/water.dm @@ -0,0 +1,96 @@ +// This doesn't inherit from /outdoors/ so that the pool can use it as well. +/turf/simulated/floor/water + name = "shallow water" + desc = "A body of water. It seems shallow enough to walk through, if needed." + icon = 'icons/turf/outdoors.dmi' + icon_state = "water_shallow" + var/under_state = "rock" + edge_blending_priority = -1 + movement_cost = 4 + outdoors = TRUE + var/movement_message = "You are slowed considerably from the water as you move across it." // Displayed to mobs crossing from one water tile to another. + var/depth = 1 // Higher numbers indicates deeper water. + +/turf/simulated/floor/water/New() + update_icon() + ..() + +/turf/simulated/floor/water/update_icon() + ..() // To get the edges. This also gets rid of other overlays so it needs to go first. + var/image/floorbed_sprite = image(icon = 'icons/turf/outdoors.dmi', icon_state = under_state) + underlays.Add(floorbed_sprite) + update_icon_edge() + +/turf/simulated/floor/water/get_edge_icon_state() + return "water_shallow" + +/turf/simulated/floor/water/return_air_for_internal_lifeform(var/mob/living/L) + if(L && L.lying) + if(L.can_breathe_water()) // For squid. + var/datum/gas_mixture/water_breath = new() + var/datum/gas_mixture/above_air = return_air() + var/amount = 300 + water_breath.adjust_gas("oxygen", amount) // Assuming water breathes just extract the oxygen directly from the water. + water_breath.temperature = above_air.temperature + return water_breath + else + return null // Lying down means they're submerged, which means no air. + return return_air() // Otherwise their head is above the water, so get the air from the atmosphere instead. + +/turf/simulated/floor/water/Entered(atom/movable/AM, atom/oldloc) + if(istype(AM, /mob/living)) + var/mob/living/L = AM + L.update_water() + if(!istype(oldloc, /turf/simulated/floor/water)) + to_chat(L, "You get drenched in water from entering \the [src]!") + else + to_chat(L, "[movement_message]") + AM.water_act(5) + ..() + +/turf/simulated/floor/water/Exited(atom/movable/AM, atom/newloc) + if(istype(AM, /mob/living)) + var/mob/living/L = AM + L.update_water() + if(!istype(newloc, /turf/simulated/floor/water)) + to_chat(L, "You climb out of \the [src].") + ..() + +/turf/simulated/floor/water/deep + name = "deep water" + desc = "A body of water. It seems quite deep." + icon_state = "seadeep" // So it shows up in the map editor as water. + under_state = "abyss" + edge_blending_priority = -2 + movement_cost = 8 + movement_message = "You swim forwards." + depth = 2 + +/turf/simulated/floor/water/pool + name = "pool" + desc = "Don't worry, it's not closed." + under_state = "pool" + outdoors = FALSE + +/mob/living/proc/can_breathe_water() + return FALSE + +/mob/living/carbon/human/can_breathe_water() + if(species) + return species.can_breathe_water() + return ..() + +/mob/living/proc/check_submerged() + var/turf/simulated/floor/water/T = loc + if(istype(T)) + return T.depth + return 0 + +// Use this to have things react to having water applied to them. +/atom/movable/proc/water_act(amount) + return + +/mob/living/water_act(amount) + adjust_fire_stacks(amount * 5) + for(var/atom/movable/AM in contents) + AM.water_act(amount) \ No newline at end of file diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index ecc8c49c4b0..1074af1b901 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -25,6 +25,8 @@ var/list/decals + var/movement_cost = 0 // How much the turf slows down movement, if any. + /turf/New() ..() for(var/atom/movable/AM as mob|obj in src) @@ -38,6 +40,9 @@ else luminosity = 1 + if(movement_cost && pathweight == 1) // This updates pathweight automatically. + pathweight = movement_cost + /turf/Destroy() turfs -= src ..() diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm index f26ba433ffc..9b9638d9086 100644 --- a/code/game/turfs/turf_changing.dm +++ b/code/game/turfs/turf_changing.dm @@ -25,6 +25,7 @@ var/old_dynamic_lighting = dynamic_lighting var/list/old_affecting_lights = affecting_lights var/old_lighting_overlay = lighting_overlay + var/old_weather_overlay = weather_overlay //world << "Replacing [src.type] with [N]" @@ -42,6 +43,9 @@ if(old_fire) fire = old_fire + if(old_weather_overlay) + W.weather_overlay = old_weather_overlay + if (istype(W,/turf/simulated/floor)) W.RemoveLattice() @@ -55,6 +59,7 @@ S.update_starlight() W.levelupdate() + W.update_icon(1) . = W else @@ -64,6 +69,9 @@ if(old_fire) old_fire.RemoveFire() + if(old_weather_overlay) + W.weather_overlay = old_weather_overlay + if(tell_universe) universe.OnTurfChange(W) @@ -74,6 +82,7 @@ S.update_starlight() W.levelupdate() + W.update_icon(1) . = W lighting_overlay = old_lighting_overlay diff --git a/code/game/turfs/unsimulated/planetary.dm b/code/game/turfs/unsimulated/planetary.dm index 17cd62ffbbd..ce4ffa44c0f 100644 --- a/code/game/turfs/unsimulated/planetary.dm +++ b/code/game/turfs/unsimulated/planetary.dm @@ -1,5 +1,7 @@ // 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!" @@ -17,6 +19,14 @@ phoron = 0 temperature = T20C +/turf/unsimulated/wall/planetary/New() + ..() + planetary_walls.Add(src) + +/turf/unsimulated/wall/planetary/Destroy() + planetary_walls.Remove(src) + ..() + // Normal station/earth air. /turf/unsimulated/wall/planetary/normal oxygen = MOLES_O2STANDARD @@ -44,4 +54,4 @@ /turf/unsimulated/wall/planetary/desert oxygen = MOLES_O2STANDARD nitrogen = MOLES_N2STANDARD - temperature = 310.92 // About 37.7C / 100F \ No newline at end of file + temperature = 310.92 // About 37.7C / 100F diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 91a504f421c..bf5ee47cba1 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -200,7 +200,9 @@ var/list/admin_verbs_debug = list( /client/proc/toggle_debug_logs, /client/proc/admin_ghost, //allows us to ghost/reenter body at will, /datum/admins/proc/view_runtimes, - /client/proc/show_gm_status + /client/proc/show_gm_status, + /datum/admins/proc/change_weather, + /datum/admins/proc/change_time ) var/list/admin_verbs_paranoid_debug = list( diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index fa2c51e7e51..a45b9ec1d6b 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -968,3 +968,46 @@ return error_cache.showTo(usr) + +/datum/admins/proc/change_weather() + set category = "Debug" + set name = "Change Weather" + set desc = "Changes the current weather." + + 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 list(planet_sif) + 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) + var/log = "[key_name(src)] changed [planet.name]'s weather to [new_weather]." + message_admins(log) + log_admin(log) + +/datum/admins/proc/change_time() + set category = "Debug" + set name = "Change Planet Time" + set desc = "Changes the time of a planet." + + if(!check_rights(R_DEBUG)) + return + + var/datum/planet/planet = input(usr, "Which planet do you want to modify time on?", "Change Time") in list(planet_sif) + + 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 + if(!isnull(new_hour)) + var/new_minute = input(usr, "What minute do you want to change to?", "Change Time", text2num(current_time_datum.show_time("mm")) ) as null|num + if(!isnull(new_minute)) + var/type_needed = current_time_datum.type + var/datum/time/new_time = new type_needed() + new_time = new_time.add_hours(new_hour) + new_time = new_time.add_minutes(new_minute) + planet.current_time = new_time + spawn(1) + planet.update_sun() + + var/log = "[key_name(src)] changed [planet.name]'s time to [planet.current_time.show_time("hh:mm")]." + message_admins(log) + log_admin(log) \ No newline at end of file diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm index 74c00c36d9f..3adb5c6458a 100644 --- a/code/modules/materials/material_recipes.dm +++ b/code/modules/materials/material_recipes.dm @@ -146,3 +146,6 @@ new/datum/stack_recipe("white folder", /obj/item/weapon/folder/white), \ new/datum/stack_recipe("yellow folder", /obj/item/weapon/folder/yellow), \ )) + +/material/snow/generate_recipes() + return // Snowmen and snowballs may come here later. \ No newline at end of file diff --git a/code/modules/materials/material_sheets.dm b/code/modules/materials/material_sheets.dm index b6e8170e150..84bd14d4175 100644 --- a/code/modules/materials/material_sheets.dm +++ b/code/modules/materials/material_sheets.dm @@ -189,6 +189,12 @@ icon_state = "sheet-card" default_type = "cardboard" +/obj/item/stack/material/snow + name = "snow" + desc = "The temptation to build a snowfort rises." + icon_state = "sheet-snow" + default_type = "snow" + /obj/item/stack/material/leather name = "leather" desc = "The by-product of mob grinding." diff --git a/code/modules/materials/materials.dm b/code/modules/materials/materials.dm index b6e2a49fe07..fb3af280150 100644 --- a/code/modules/materials/materials.dm +++ b/code/modules/materials/materials.dm @@ -628,6 +628,22 @@ var/list/name_to_material door_icon_base = "wood" destruction_desc = "crumples" +/material/snow + name = "snow" + stack_type = /obj/item/stack/material/snow + flags = MATERIAL_BRITTLE + icon_base = "solid" + icon_reinf = "reinf_over" + icon_colour = "#FFFFFF" + integrity = 1 + hardness = 1 + weight = 1 + stack_origin_tech = list(TECH_MATERIAL = 1) + melting_point = T0C+1 + destruction_desc = "crumples" + sheet_singular_name = "pile" + sheet_plural_name = "piles" + /material/cloth //todo name = "cloth" stack_origin_tech = list(TECH_MATERIAL = 2) diff --git a/code/modules/mob/living/carbon/breathe.dm b/code/modules/mob/living/carbon/breathe.dm index 6b8a3c43b3c..354a26221c4 100644 --- a/code/modules/mob/living/carbon/breathe.dm +++ b/code/modules/mob/living/carbon/breathe.dm @@ -53,7 +53,7 @@ var/datum/gas_mixture/environment if(loc) - environment = loc.return_air_for_internal_lifeform() + environment = loc.return_air_for_internal_lifeform(src) if(environment) breath = environment.remove_volume(volume_needed) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 204730a8e82..29422641df4 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -457,7 +457,7 @@ /mob/living/carbon/proc/should_have_organ(var/organ_check) return 0 -/mob/living/carbon/proc/can_feel_pain(var/check_organ) +/mob/living/carbon/can_feel_pain(var/check_organ) if(isSynthetic()) return 0 return !(species.flags & NO_PAIN) \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 80bfd2259d9..287401ebb40 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -76,12 +76,15 @@ var/obj/item/pulled = pulling tally += max(pulled.slowdown, 0) + var/turf/T = get_turf(src) + if(T && T.movement_cost) + tally += T.movement_cost + if(CE_SPEEDBOOST in chem_effects) if (tally >= 0) // cut any penalties in half tally = tally/2 tally -= 1 // give 'em a buff on top. - - return (tally+config.human_delay) + return (tally+config.human_delay) /mob/living/carbon/human/Process_Spacemove(var/check_drift = 0) //Can we act? diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm index 6a3b4c7ad1d..ddbef02432a 100644 --- a/code/modules/mob/living/carbon/human/species/species.dm +++ b/code/modules/mob/living/carbon/human/species/species.dm @@ -305,3 +305,7 @@ // Called in life() when the mob has no client. /datum/species/proc/handle_npc(var/mob/living/carbon/human/H) return + +// Called when lying down on a water tile. +/datum/species/proc/can_breathe_water() + return FALSE \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/species/station/station.dm b/code/modules/mob/living/carbon/human/species/station/station.dm index 28eb05bc253..88c696103d1 100644 --- a/code/modules/mob/living/carbon/human/species/station/station.dm +++ b/code/modules/mob/living/carbon/human/species/station/station.dm @@ -193,6 +193,9 @@ BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right) ) +/datum/species/skrell/can_breathe_water() + return TRUE + /datum/species/diona name = "Diona" name_plural = "Dionaea" diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 3f4c00f8a57..2629e51f0b7 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -132,8 +132,9 @@ Please contact me on #coderbus IRC. ~Carn x #define L_HAND_LAYER 24 #define R_HAND_LAYER 25 #define FIRE_LAYER 26 //If you're on fire -#define TARGETED_LAYER 27 //BS12: Layer for the target overlay from weapon targeting system -#define TOTAL_LAYERS 27 +#define WATER_LAYER 27 //If you're submerged in water. +#define TARGETED_LAYER 28 //BS12: Layer for the target overlay from weapon targeting system +#define TOTAL_LAYERS 29 ////////////////////////////////// /mob/living/carbon/human @@ -461,6 +462,7 @@ var/global/list/damage_icon_parts = list() update_inv_legcuffed(0) update_inv_pockets(0) update_fire(0) + update_water(0) update_surgery(0) UpdateDamageIcon() update_icons() @@ -1116,6 +1118,17 @@ var/global/list/damage_icon_parts = list() if(update_icons) update_icons() +/mob/living/carbon/human/update_water(var/update_icons=1) + overlays_standing[WATER_LAYER] = null + var/depth = check_submerged() + if(depth) + if(!lying) + overlays_standing[WATER_LAYER] = image("icon" = 'icons/mob/submerged.dmi', "icon_state" = "human_swimming_[depth]") + // Lying sideways with the overlay looked strange. Another overlay will be needed in the future. + + if(update_icons) + update_icons() + /mob/living/carbon/human/proc/update_surgery(var/update_icons=1) overlays_standing[SURGERY_LEVEL] = null var/image/total = new @@ -1151,4 +1164,5 @@ var/global/list/damage_icon_parts = list() #undef R_HAND_LAYER #undef TARGETED_LAYER #undef FIRE_LAYER +#undef WATER_LAYER #undef TOTAL_LAYERS diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index d31a8f2b0c4..7680b6e163c 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -774,6 +774,7 @@ default behaviour is: density = 0 if(l_hand) unEquip(l_hand) if(r_hand) unEquip(r_hand) + update_water() // Submerges the mob. else density = initial(density) @@ -792,3 +793,10 @@ default behaviour is: update_icons() return canmove +/mob/living/proc/update_water() // Involves overlays for humans. Maybe we'll get submerged sprites for borgs in the future? + return + +/mob/living/proc/can_feel_pain(var/check_organ) + if(isSynthetic()) + return FALSE + return TRUE diff --git a/code/modules/planet/planet.dm b/code/modules/planet/planet.dm new file mode 100644 index 00000000000..65a2de98456 --- /dev/null +++ b/code/modules/planet/planet.dm @@ -0,0 +1,42 @@ +// This holds information about a specific 'planetside' area, such as its time, weather, etc. This will most likely be used to model Sif, +// but away missions may also have use for this. + +/datum/planet + var/name = "a rock" + var/desc = "Someone neglected to write a nice description for this poor rock." + + var/datum/time/current_time = new() // Holds the current time for sun positioning. Note that we assume day and night is the same length because simplicity. + var/sun_process_interval = 1 HOUR + var/sun_last_process = null // world.time + + var/datum/weather_holder/weather_holder + + var/sun_position = 0 // 0 means midnight, 1 means noon. + var/expected_z_levels = list() + +/datum/planet/New() + ..() + weather_holder = new(src) + current_time = current_time.make_random_time() + update_sun() + +/datum/planet/proc/process(amount) + if(current_time) + current_time = current_time.add_seconds(amount) + 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() + +// This changes the position of the sun on the planet. +/datum/planet/proc/update_sun() + sun_last_process = world.time + + +/datum/planet/proc/update_weather() + if(weather_holder) + weather_holder.process() + +// Returns the time datum of Sif. +/proc/get_sif_time() + if(planet_sif) + return planet_sif.current_time \ No newline at end of file diff --git a/code/modules/planet/sif.dm b/code/modules/planet/sif.dm new file mode 100644 index 00000000000..edc169d50e8 --- /dev/null +++ b/code/modules/planet/sif.dm @@ -0,0 +1,96 @@ +var/datum/planet/sif/planet_sif = null + +/datum/planet/sif + name = "Sif" + +/datum/planet/sif + name = "Sif" + desc = "Sif is a terrestrial planet in the Vir system. It is somewhat earth-like, in that it has oceans, a \ + breathable atmosphere, a magnetic field, weather, and similar gravity to Earth. It is currently the capital planet of Vir. \ + Its center of government is the equatorial city and site of first settlement, New Reykjavik." // Ripped straight from the wiki. + current_time = new /datum/time/sif() // 32 hour clocks are nice. + expected_z_levels = list(1) // To be changed when real map is finished. + +/datum/planet/sif/New() + ..() + weather_holder = new /datum/weather_holder/sif(src) // Cold weather is also nice. + +// This code is horrible. +/datum/planet/sif/update_sun() + ..() + var/datum/time/time = current_time + var/length_of_day = time.seconds_in_day / 10 / 60 / 60 // 32 + var/noon = length_of_day / 2 + var/distance_from_noon = abs(text2num(time.show_time("hh")) - noon) + sun_position = distance_from_noon / noon + sun_position = abs(sun_position - 1) + + var/low_brightness = null + var/high_brightness = null + + var/low_color = null + var/high_color = null + var/min = 0 + + switch(sun_position) + if(0 to 0.40) // Night + low_brightness = 0.2 + low_color = "#000066" + + high_brightness = 0.5 + high_color = "#66004D" + min = 0 + + if(0.40 to 0.50) // Twilight + low_brightness = 0.6 + low_color = "#66004D" + + high_brightness = 0.8 + high_color = "#CC3300" + min = 0.40 + + if(0.50 to 0.70) // Sunrise/set + low_brightness = 0.8 + low_color = "#CC3300" + + high_brightness = 0.9 + high_color = "#FF9933" + min = 0.50 + + if(0.70 to 1.00) // Noon + low_brightness = 0.9 + low_color = "#DDDDDD" + + high_brightness = 1.0 + high_color = "#FFFFFF" + min = 0.70 + + var/lerp_weight = (abs(min - sun_position)) * 4 + var/weather_light_modifier = 1 + if(weather_holder && weather_holder.current_weather) + weather_light_modifier = weather_holder.current_weather.light_modifier + + var/new_brightness = (Interpolate(low_brightness, high_brightness, weight = lerp_weight) ) * weather_light_modifier + + var/list/low_color_list = hex2rgb(low_color) + var/low_r = low_color_list[1] + var/low_g = low_color_list[2] + var/low_b = low_color_list[3] + + var/list/high_color_list = hex2rgb(high_color) + var/high_r = high_color_list[1] + var/high_g = high_color_list[2] + var/high_b = high_color_list[3] + + var/new_r = Interpolate(low_r, high_r, weight = lerp_weight) + var/new_g = Interpolate(low_g, high_g, weight = lerp_weight) + var/new_b = Interpolate(low_b, high_b, weight = lerp_weight) + + var/new_color = rgb(new_r, new_g, new_b) + + var/i = 0 + for(var/turf/simulated/floor/T in outdoor_turfs) + T.set_light(2, new_brightness, new_color) + i++ + if(i % 30 == 0) + sleep(1) diff --git a/code/modules/planet/time.dm b/code/modules/planet/time.dm new file mode 100644 index 00000000000..936ae0eadda --- /dev/null +++ b/code/modules/planet/time.dm @@ -0,0 +1,76 @@ +// This datum allows one to add and subtract various lengths of time. It really shines when using an alternate time system. + +/datum/time + // Note that the following is actually measured in 10ths of a second. + var/seconds_in_day = 60 * 60 * 24 * 10 // 86,400 seconds + var/seconds_in_hour = 60 * 60 * 10 // 3,600 seconds + var/seconds_in_minute = 60 * 10 + + // This holds the amount of seconds. + var/seconds_stored = 0 + +/datum/time/New(new_time) + if(new_time) + seconds_stored = new_time + ..() + +/datum/time/proc/add_seconds(amount) + var/answer = seconds_stored + amount * 10 + if(answer >= seconds_in_day) + rollover(answer) + return new type(answer) + +/datum/time/proc/add_minutes(amount) + var/real_amount = amount * seconds_in_minute + var/answer = real_amount + seconds_stored + if(answer >= seconds_in_day) + rollover(answer) + return new type(answer) + +/datum/time/proc/add_hours(amount) + var/real_amount = amount * seconds_in_hour + var/answer = real_amount + seconds_stored + if(answer >= seconds_in_day) + rollover(answer) + return new type(answer) + +/datum/time/proc/rollover(time) + return max(time - seconds_in_day, 0) + +/datum/time/proc/make_random_time() + return new type(rand(0, seconds_in_day - 1)) + +// This works almost exactly like time2text. +// The advantage of this is that it can handle time systems beyond 24h. +// The downside is a lack of date capability. +/datum/time/proc/show_time(format) + var/time = seconds_stored + while(time >= seconds_in_day) // First, get rid of extra days. + time = rollover(time) + + var/hours = time / seconds_in_hour + var/remaining_hour = time % seconds_in_hour + var/minutes = remaining_hour / seconds_in_minute + var/seconds = remaining_hour % seconds_in_minute / 10 + + + var/hour_text = num2text(Floor(hours)) + if(length(hour_text) < 2) + hour_text = "0[hour_text]" // Add padding if needed, to look more like time2text(). + + var/minute_text = num2text(Floor(minutes)) + if(length(minute_text) < 2) + minute_text = "0[minute_text]" + + var/second_text = num2text(Floor(seconds)) + if(length(second_text) < 2) + second_text = "0[second_text]" + + var/answer = replacetext(format, "hh", hour_text) + answer = replacetext(answer, "mm", minute_text) + answer = replacetext(answer, "ss", second_text) + return answer + +// We're gonna pretend there are 32 hours in a Sif day instead of 32.64 for the purposes of not losing sanity. We lose 38m 24s but the alternative is a path to madness. +/datum/time/sif + seconds_in_day = 60 * 60 * 32 * 10 // 115,200 seconds. If we did 32.64 hours/day it would be around 117,504 seconds instead. \ No newline at end of file diff --git a/code/modules/planet/weather.dm b/code/modules/planet/weather.dm new file mode 100644 index 00000000000..d4d13d7a409 --- /dev/null +++ b/code/modules/planet/weather.dm @@ -0,0 +1,268 @@ +#define WEATHER_CLEAR "clear" +#define WEATHER_OVERCAST "overcast" +#define WEATHER_LIGHT_SNOW "light snow" +#define WEATHER_SNOW "snow" +#define WEATHER_BLIZZARD "blizzard" +#define WEATHER_RAIN "rain" +#define WEATHER_STORM "storm" +#define WEATHER_HAIL "hail" +#define WEATHER_WINDY "windy" +#define WEATHER_HOT "hot" + +/datum/weather_holder + var/datum/planet/our_planet = null + var/datum/weather/current_weather = null + var/temperature = T20C + var/wind_dir = 0 + var/wind_speed = 0 + var/list/allowed_weather_types = list() + var/list/roundstart_weather_chances = list() + var/next_weather_shift = null + var/planetary_wall_type = null // Which walls to look for when updating temperature. + +/datum/weather_holder/New(var/source) + ..() + our_planet = source + for(var/A in allowed_weather_types) + var/datum/weather/W = allowed_weather_types[A] + if(istype(W)) + W.holder = src + +/datum/weather_holder/proc/change_weather(var/new_weather) + var/old_light_modifier = null + 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 + + update_icon_effects() + update_temperature() + if(old_light_modifier && current_weather.light_modifier != old_light_modifier) // Updating the sun should be done sparingly. + our_planet.update_sun() + message_admins("[our_planet.name]'s weather is now [new_weather], with a temperature of [temperature]°K ([temperature - T0C]°C | [temperature * 1.8 - 459.67]°F).") + +/datum/weather_holder/proc/process() + if(world.time >= next_weather_shift) + var/new_weather + if(!current_weather) + new_weather = pickweight(roundstart_weather_chances) + else + new_weather = pickweight(current_weather.transition_chances) + change_weather(new_weather) + else + current_weather.process_effects() + +/datum/weather_holder/proc/update_icon_effects() + if(current_weather) + for(var/turf/simulated/floor/T in outdoor_turfs) + if(T.z in our_planet.expected_z_levels) + T.overlays -= T.weather_overlay + T.weather_overlay = image(icon = current_weather.icon, icon_state = current_weather.icon_state, layer = LIGHTING_LAYER - 1) + T.overlays += T.weather_overlay + +/datum/weather_holder/proc/update_temperature() + temperature = Interpolate(current_weather.temp_low, current_weather.temp_high, weight = our_planet.sun_position) + + for(var/turf/unsimulated/wall/planetary/wall in planetary_walls) + if(ispath(wall.type, planetary_wall_type)) + wall.temperature = temperature + for(var/dir in cardinal) + var/turf/simulated/T = get_step(wall, dir) + if(istype(T)) + if(T.zone) + T.zone.rebuild() + + +/datum/weather_holder/proc/get_weather_datum(desired_type) + return allowed_weather_types[desired_type] + +/datum/weather_holder/sif + temperature = T0C + allowed_weather_types = list( + WEATHER_CLEAR = new /datum/weather/sif/clear(), + WEATHER_OVERCAST = new /datum/weather/sif/overcast(), + WEATHER_LIGHT_SNOW = new /datum/weather/sif/light_snow(), + WEATHER_SNOW = new /datum/weather/sif/snow(), + WEATHER_BLIZZARD = new /datum/weather/sif/blizzard(), + WEATHER_RAIN = new /datum/weather/sif/rain(), + WEATHER_STORM = new /datum/weather/sif/storm(), + WEATHER_HAIL = new /datum/weather/sif/hail() + ) + planetary_wall_type = /turf/unsimulated/wall/planetary/sif + roundstart_weather_chances = list( + WEATHER_CLEAR = 30, + WEATHER_OVERCAST = 30, + WEATHER_LIGHT_SNOW = 20, + WEATHER_SNOW = 5, + WEATHER_BLIZZARD = 5, + WEATHER_RAIN = 5, + WEATHER_STORM = 2.5, + WEATHER_HAIL = 2.5 + ) + +/datum/weather + var/name = "weather base" + var/icon = 'icons/effects/weather.dmi' + var/icon_state = null // Icon to apply to turf undergoing weather. + var/temp_high = T20C + var/temp_low = T0C + var/light_modifier = 1.0 // Lower numbers means more darkness. + var/transition_chances = list() // Assoc list + var/datum/weather_holder/holder = null + +/datum/weather/proc/process_effects() + return + +/datum/weather/sif + name = "sif base" + temp_high = 243.15 // -20c + temp_low = 233.15 // -30c + +/datum/weather/sif/clear + name = "clear" + transition_chances = list( + WEATHER_CLEAR = 60, + WEATHER_OVERCAST = 40 + ) + +/datum/weather/sif/overcast + name = "overcast" + light_modifier = 0.8 + transition_chances = list( + WEATHER_CLEAR = 25, + WEATHER_OVERCAST = 50, + WEATHER_LIGHT_SNOW = 10, + WEATHER_SNOW = 5, + WEATHER_RAIN = 5, + WEATHER_HAIL = 5 + ) + +/datum/weather/sif/light_snow + name = "light snow" + icon_state = "snowfall_light" + temp_high = 238.15 // -25c + temp_low = 228.15 // -35c + light_modifier = 0.7 + transition_chances = list( + WEATHER_OVERCAST = 20, + WEATHER_LIGHT_SNOW = 50, + WEATHER_SNOW = 25, + WEATHER_HAIL = 5 + ) + +/datum/weather/sif/snow + name = "moderate snow" + icon_state = "snowfall_med" + temp_high = 233.15 // -30c + temp_low = 223.15 // -40c + light_modifier = 0.5 + transition_chances = list( + WEATHER_LIGHT_SNOW = 20, + WEATHER_SNOW = 50, + WEATHER_BLIZZARD = 20, + WEATHER_HAIL = 5, + WEATHER_OVERCAST = 5 + ) + +/datum/weather/sif/snow/process_effects() + for(var/turf/simulated/floor/outdoors/snow/S in outdoor_turfs) + for(var/dir_checked in cardinal) + var/turf/simulated/floor/T = get_step(S, dir_checked) + if(istype(T)) + if(istype(T, /turf/simulated/floor/outdoors) && prob(33)) + T.chill() + +/datum/weather/sif/blizzard + name = "blizzard" + icon_state = "snowfall_heavy" + temp_high = 223.15 // -40c + temp_low = 203.15 // -60c + light_modifier = 0.3 + transition_chances = list( + WEATHER_SNOW = 45, + WEATHER_BLIZZARD = 40, + WEATHER_HAIL = 10, + WEATHER_OVERCAST = 5 + ) + +/datum/weather/sif/blizzard/process_effects() + for(var/turf/simulated/floor/outdoors/snow/S in outdoor_turfs) + for(var/dir_checked in cardinal) + var/turf/simulated/floor/T = get_step(S, dir_checked) + if(istype(T)) + if(istype(T, /turf/simulated/floor/outdoors) && prob(50)) + T.chill() + +/datum/weather/sif/rain + name = "rain" + icon_state = "rain" + light_modifier = 0.5 + transition_chances = list( + WEATHER_OVERCAST = 25, + WEATHER_LIGHT_SNOW = 10, + WEATHER_RAIN = 50, + WEATHER_STORM = 10, + WEATHER_HAIL = 5 + ) + +/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) + if(!T.outdoors) + return // They're indoors, so no need to rain on them. + + L.adjust_fire_stacks(-5) + to_chat(L, "Rain falls on you.") + +/datum/weather/sif/storm + name = "storm" + icon_state = "storm" + temp_high = 233.15 // -30c + temp_low = 213.15 // -50c + light_modifier = 0.3 + transition_chances = list( + WEATHER_RAIN = 45, + WEATHER_STORM = 40, + WEATHER_HAIL = 10, + WEATHER_OVERCAST = 5 + ) + +/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) + if(!T.outdoors) + return // They're indoors, so no need to rain on them. + + L.adjust_fire_stacks(-10) + to_chat(L, "Rain falls on you, drenching you in water.") + +/datum/weather/sif/hail + name = "hail" + icon_state = "hail" + temp_high = 233.15 // -30c + temp_low = 213.15 // -50c + light_modifier = 0.3 + transition_chances = list( + WEATHER_RAIN = 45, + WEATHER_STORM = 10, + WEATHER_HAIL = 40, + WEATHER_OVERCAST = 5 + ) + +/datum/weather/sif/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) + if(!T.outdoors) + return // They're indoors, so no need to pelt them with ice. + + var/target_zone = pick(BP_ALL) + var/amount_blocked = L.run_armor_check(target_zone, "melee") + + if(amount_blocked >= 100) + return // No need to apply damage. + + L.apply_damage(rand(5, 10), BRUTE, target_zone, amount_blocked, used_weapon = "hail") + to_chat(L, "The hail raining down on you [L.can_feel_pain() ? "hurts" : "damages you"]!") diff --git a/icons/effects/weather.dmi b/icons/effects/weather.dmi new file mode 100644 index 00000000000..d0df90a44a7 Binary files /dev/null and b/icons/effects/weather.dmi differ diff --git a/icons/mob/submerged.dmi b/icons/mob/submerged.dmi new file mode 100644 index 00000000000..0aaa1f3ebb1 Binary files /dev/null and b/icons/mob/submerged.dmi differ diff --git a/icons/obj/flora/deadtrees.dmi b/icons/obj/flora/deadtrees.dmi index f76ffd442f9..2ae1a5a6e07 100644 Binary files a/icons/obj/flora/deadtrees.dmi and b/icons/obj/flora/deadtrees.dmi differ diff --git a/icons/obj/items.dmi b/icons/obj/items.dmi index 88457f09c2f..c0df5a707f7 100644 Binary files a/icons/obj/items.dmi and b/icons/obj/items.dmi differ diff --git a/icons/turf/outdoors.dmi b/icons/turf/outdoors.dmi new file mode 100644 index 00000000000..c2e42d55df4 Binary files /dev/null and b/icons/turf/outdoors.dmi differ diff --git a/icons/turf/outdoors_edge.dmi b/icons/turf/outdoors_edge.dmi new file mode 100644 index 00000000000..02a52195c2b Binary files /dev/null and b/icons/turf/outdoors_edge.dmi differ diff --git a/polaris.dme b/polaris.dme index 46dbd389f4d..1531c150721 100644 --- a/polaris.dme +++ b/polaris.dme @@ -148,6 +148,7 @@ #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\scheduler.dm" #include "code\controllers\Processes\Shuttle.dm" #include "code\controllers\Processes\sun.dm" @@ -1004,6 +1005,11 @@ #include "code\game\turfs\simulated\wall_icon.dm" #include "code\game\turfs\simulated\wall_types.dm" #include "code\game\turfs\simulated\walls.dm" +#include "code\game\turfs\simulated\water.dm" +#include "code\game\turfs\simulated\outdoors\dirt.dm" +#include "code\game\turfs\simulated\outdoors\grass.dm" +#include "code\game\turfs\simulated\outdoors\outdoors.dm" +#include "code\game\turfs\simulated\outdoors\snow.dm" #include "code\game\turfs\snow\snow.dm" #include "code\game\turfs\space\cracked_asteroid.dm" #include "code\game\turfs\space\space.dm" @@ -1788,6 +1794,10 @@ #include "code\modules\paperwork\photography.dm" #include "code\modules\paperwork\silicon_photography.dm" #include "code\modules\paperwork\stamps.dm" +#include "code\modules\planet\planet.dm" +#include "code\modules\planet\sif.dm" +#include "code\modules\planet\time.dm" +#include "code\modules\planet\weather.dm" #include "code\modules\power\apc.dm" #include "code\modules\power\batteryrack.dm" #include "code\modules\power\breaker_box.dm"