diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_jettisoned_reactor.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_jettisoned_reactor.dmm new file mode 100644 index 000000000..f5d7f9aaf --- /dev/null +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_jettisoned_reactor.dmm @@ -0,0 +1,658 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/mapping_helpers/no_lava, +/turf/open/chasm/lavaland, +/area/template_noop) +"b" = ( +/turf/open/floor/plating/asteroid/basalt/lava_land_surface, +/area/template_noop) +"c" = ( +/turf/closed/wall/mineral/plastitanium, +/area/ruin/unpowered/reactor) +"d" = ( +/turf/open/floor/plating, +/area/template_noop) +"e" = ( +/turf/closed/wall/mineral/titanium, +/area/ruin/unpowered/reactor) +"f" = ( +/turf/closed/wall/mineral/titanium/nodiagonal, +/area/ruin/unpowered/reactor) +"g" = ( +/obj/structure/alien/weeds, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"h" = ( +/obj/machinery/door/airlock/public/glass, +/obj/machinery/door/poddoor/shutters/radiation, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"i" = ( +/turf/open/floor/plating, +/area/ruin/unpowered/reactor) +"j" = ( +/obj/structure/fluff/destroyed_nuclear_reactor, +/turf/open/floor/plating, +/area/ruin/unpowered/reactor) +"k" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/plating, +/area/ruin/unpowered/reactor) +"l" = ( +/obj/effect/decal/nuclear_waste, +/turf/open/floor/plating/asteroid/basalt/lava_land_surface, +/area/template_noop) +"m" = ( +/obj/structure/alien/weeds/node, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"n" = ( +/obj/machinery/door/airlock/public/glass, +/obj/structure/alien/weeds, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"o" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/resin/wall, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"p" = ( +/obj/structure/alien/gelpod, +/obj/structure/alien/weeds, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"q" = ( +/obj/structure/alien/resin/membrane, +/obj/machinery/door/poddoor/shutters/radiation, +/turf/open/floor/plating, +/area/ruin/unpowered/reactor) +"r" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/resin/wall, +/turf/open/floor/plating, +/area/ruin/unpowered/reactor) +"s" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/gelpod, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"t" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/egg/grown, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"u" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/egg/burst, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"v" = ( +/obj/effect/mob_spawn/human/corpse/charredskeleton, +/turf/open/floor/plating/asteroid/basalt/lava_land_surface, +/area/template_noop) +"w" = ( +/obj/structure/alien/weeds, +/mob/living/simple_animal/hostile/alien/drone, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"x" = ( +/obj/structure/alien/weeds, +/mob/living/simple_animal/hostile/alien/sentinel, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) +"y" = ( +/obj/effect/decal/nuclear_waste, +/turf/open/floor/plating, +/area/template_noop) +"z" = ( +/obj/effect/decal/nuclear_waste, +/turf/open/floor/plating, +/area/ruin/unpowered/reactor) +"A" = ( +/turf/closed/mineral/volcanic/lava_land_surface, +/area/template_noop) +"B" = ( +/turf/open/chasm/lavaland, +/area/ruin/unpowered/reactor) +"C" = ( +/obj/effect/mob_spawn/human/corpse/charredskeleton, +/turf/open/floor/plating, +/area/template_noop) +"D" = ( +/obj/effect/gibspawner/robot, +/turf/open/floor/plating/asteroid/basalt/lava_land_surface, +/area/template_noop) +"F" = ( +/obj/structure/alien/weeds, +/obj/effect/gibspawner/robot, +/turf/open/floor/plasteel, +/area/ruin/unpowered/reactor) + +(1,1,1) = {" +A +A +A +A +A +A +A +A +A +A +A +A +A +c +c +c +a +a +a +a +a +a +"} +(2,1,1) = {" +A +l +A +A +A +A +l +D +v +A +l +A +c +c +c +c +c +a +a +a +a +a +"} +(3,1,1) = {" +A +l +l +l +A +A +l +b +b +l +l +A +c +c +c +c +c +c +a +a +a +a +"} +(4,1,1) = {" +A +A +l +A +A +l +l +b +A +l +A +A +e +c +e +e +e +e +c +a +a +a +"} +(5,1,1) = {" +A +A +D +A +A +l +b +A +A +l +l +A +f +e +f +u +s +e +c +c +a +a +"} +(6,1,1) = {" +A +A +b +A +A +l +b +A +A +l +d +C +h +o +s +m +u +e +c +c +c +a +"} +(7,1,1) = {" +A +v +b +b +l +l +b +b +l +l +k +k +f +o +t +g +f +e +c +c +c +a +"} +(8,1,1) = {" +A +l +l +b +b +b +b +b +A +B +B +z +z +f +p +g +e +c +c +c +c +a +"} +(9,1,1) = {" +A +A +l +A +b +v +A +A +A +B +B +B +z +z +f +n +f +e +c +c +a +a +"} +(10,1,1) = {" +A +l +l +l +b +b +y +z +B +i +i +i +B +B +q +g +r +c +c +c +a +a +"} +(11,1,1) = {" +A +l +A +l +b +b +k +z +B +i +j +i +B +B +q +F +r +c +c +c +c +a +"} +(12,1,1) = {" +A +A +A +A +A +d +k +B +B +i +i +i +B +z +q +g +r +c +c +c +a +a +"} +(13,1,1) = {" +A +c +c +e +f +h +f +z +z +B +B +B +z +z +f +n +f +e +c +c +a +a +"} +(14,1,1) = {" +c +c +c +c +e +g +o +f +z +z +B +B +z +f +s +x +e +c +c +c +c +a +"} +(15,1,1) = {" +c +c +c +e +f +w +u +o +f +q +q +q +f +s +o +g +f +e +c +c +c +a +"} +(16,1,1) = {" +c +c +c +e +u +m +g +g +n +g +F +g +n +g +g +m +u +e +c +c +c +a +"} +(17,1,1) = {" +a +c +c +e +o +u +f +e +f +r +r +r +f +e +f +s +o +e +c +c +a +a +"} +(18,1,1) = {" +a +a +c +e +e +e +e +c +e +c +c +c +e +c +e +e +e +e +c +a +a +a +"} +(19,1,1) = {" +a +a +a +c +c +c +c +c +c +c +c +c +c +c +c +c +c +c +a +a +a +a +"} +(20,1,1) = {" +a +a +a +a +c +c +c +c +c +c +c +c +c +c +c +c +c +a +a +a +a +a +"} +(21,1,1) = {" +a +a +a +a +a +c +c +c +a +a +c +a +a +c +c +c +a +a +a +a +a +a +"} +(22,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm index d54a1896a..2c9b4d2ab 100644 --- a/_maps/map_files/generic/CentCom.dmm +++ b/_maps/map_files/generic/CentCom.dmm @@ -10943,9 +10943,6 @@ /turf/open/floor/plasteel, /area/tdome/arena) "kjW" = ( -/obj/item/twohanded/required/kirbyplants{ - icon_state = "plant-22" - }, /obj/effect/turf_decal/tile{ color = "#630000"; dir = 1 @@ -10961,6 +10958,7 @@ /obj/effect/turf_decal/tile{ color = "#630000" }, +/obj/structure/jacuzzi, /turf/open/floor/mineral/basaltstone_floor, /area/centcom/ferry) "kke" = ( diff --git a/_maps/templates/new_pod_luxury.dmm b/_maps/templates/new_pod_luxury.dmm index eb5e5e4d5..e4bb3b5a7 100644 --- a/_maps/templates/new_pod_luxury.dmm +++ b/_maps/templates/new_pod_luxury.dmm @@ -31,18 +31,21 @@ /area/survivalpod) "f" = ( /obj/machinery/chem_dispenser/drinks/beer{ + density = 0; pixel_y = 24 }, /turf/open/floor/carpet/black, /area/survivalpod) "g" = ( /obj/machinery/chem_dispenser/drinks{ + density = 0; pixel_y = 24 }, /turf/open/floor/carpet/black, /area/survivalpod) "h" = ( /obj/machinery/vending/boozeomat/all_access{ + density = 0; pixel_y = 32 }, /turf/open/floor/carpet/black, diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 32ec833bd..ef8fd6748 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -736,6 +736,9 @@ ///from datum/action/cyborg_small_sprite and sends when a cyborg changes modules #define COMSIG_CYBORG_MODULE_CHANGE "cyborg_module_change" +// /datum/element/ventcrawling signals +#define COMSIG_HANDLE_VENTCRAWL "handle_ventcrawl" //when atom with ventcrawling element attempts to ventcrawl +#define COMSIG_CHECK_VENTCRAWL "check_ventcrawl" //to check an atom's ventcrawling element tier (if applicable) /* Attack signals. They should share the returned flags, to standardize the attack chain. */ /// tool_act -> pre_attack -> target.attackby (item.attack) -> afterattack diff --git a/code/__DEFINES/hyper_arousal.dm b/code/__DEFINES/hyper_arousal.dm new file mode 100644 index 000000000..bedb5c860 --- /dev/null +++ b/code/__DEFINES/hyper_arousal.dm @@ -0,0 +1,8 @@ +#define BELLY_MIN_SIZE 0 +#define BELLY_MAX_SIZE 8 + +#define BELLY_STRAIN_SIZE 3 // for flavor text +#define BELLY_STRETCH_SIZE 6 // for flavor text + +#define BUTT_MIN_SIZE 0 +#define BUTT_MAX_SIZE 5 \ No newline at end of file diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index d994d5159..d45280be4 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -285,3 +285,7 @@ #define HUMAN_CARRY_SLOWDOWN 0 #define TYPING_INDICATOR_TIMEOUT 10 MINUTES + +//Gremlins +#define NPC_TAMPER_ACT_FORGET 1 //Don't try to tamper with this again +#define NPC_TAMPER_ACT_NOMSG 2 //Don't produce a visible message diff --git a/code/__DEFINES/radiation.dm b/code/__DEFINES/radiation.dm index ad2daffb8..f1d7bc8c2 100644 --- a/code/__DEFINES/radiation.dm +++ b/code/__DEFINES/radiation.dm @@ -36,6 +36,7 @@ Ask ninjanomnom if they're around #define RAD_MEDIUM_INSULATION 0.7 // What common walls have #define RAD_HEAVY_INSULATION 0.6 // What reinforced walls have #define RAD_EXTREME_INSULATION 0.5 // What rad collectors have +#define RAD_NEAR_FULL_INSULATION 0.2 // What radiation shutters and specialised walls for the RBMK use. #define RAD_FULL_INSULATION 0 // Unused // WARNING: The deines below could have disastrous consequences if tweaked incorrectly. See: The great SM purge of Oct.6.2017 diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index 80356bec2..9502db55f 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -58,6 +58,7 @@ #define SPAN_COMMAND "command_headset" #define SPAN_CLOWN "clown" #define SPAN_SINGING "singing" +#define SPAN_SMALL "small" //bitflag #defines for return value of the radio() proc. #define ITALICS 1 diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 349af6d3c..20ddeac19 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -206,6 +206,8 @@ #define TRAIT_ZAOCORP_NOGUNS "zao_no_guns" +#define INNATE_TRAIT "innate" + // common trait sources #define TRAIT_GENERIC "generic" #define EYE_DAMAGE "eye_damage" diff --git a/code/__HELPERS/markov.dm b/code/__HELPERS/markov.dm new file mode 100644 index 000000000..7e195f8b9 --- /dev/null +++ b/code/__HELPERS/markov.dm @@ -0,0 +1,61 @@ +#define MAXIMUM_MARKOV_LENGTH 25000 + +/proc/markov_chain(var/text, var/order = 4, var/length = 250) + if(!text || order < 0 || order > 20 || length < 1 || length > MAXIMUM_MARKOV_LENGTH) + return + + var/table = markov_table(text, order) + var/markov = markov_text(length, table, order) + return markov + +/proc/markov_table(var/text, var/look_forward = 4) + if(!text) + return + var/list/table = list() + + for(var/i = 1, i <= length(text), i++) + var/char = copytext(text, i, look_forward+i) + if(!table[char]) + table[char] = list() + + for(var/i = 1, i <= (length(text) - look_forward), i++) + var/char_index = copytext(text, i, look_forward+i) + var/char_count = copytext(text, i+look_forward, (look_forward*2)+i) + + if(table[char_index][char_count]) + table[char_index][char_count]++ + else + table[char_index][char_count] = 1 + + return table + +/proc/markov_text(var/length = 250, var/table, var/look_forward = 4) + if(!table) + return + var/char = pick(table) + var/o = char + + for(var/i = 0, i <= (length / look_forward), i++) + var/newchar = markov_weighted_char(table[char]) + + if(newchar) + char = newchar + o += "[newchar]" + else + char = pick(table) + + return o + +/proc/markov_weighted_char(var/list/array) + if(!array || !array.len) + return + + var/total = 0 + for(var/i in array) + total += array[i] + var/r = rand(1, total) + for(var/i in array) + var/weight = array[i] + if(r <= weight) + return i + r -= weight diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 3ceb1547a..1ceef52c5 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1109,7 +1109,7 @@ B --><-- A /proc/get_random_station_turf() return safepick(get_area_turfs(pick(GLOB.the_station_areas))) - + /proc/get_safe_random_station_turf() for (var/i in 1 to 5) var/list/L = get_area_turfs(pick(GLOB.the_station_areas)) @@ -1273,6 +1273,14 @@ GLOBAL_REAL_VAR(list/stack_trace_storage) animate(src, transform = M, time = halftime, easing = ELASTIC_EASING) animate(src, transform = OM, time = halftime, easing = ELASTIC_EASING) +/atom/proc/do_twist(targetangle = 45, timer = 20) + var/matrix/OM = matrix(transform) + var/matrix/M = matrix(transform) + var/halftime = timer * 0.5 + M.Turn(pick(-targetangle, targetangle)) + animate(src, transform = M, time = halftime, easing = ELASTIC_EASING) + animate(src, transform = OM, time = halftime, easing = QUAD_EASING) + /atom/proc/do_squish(squishx = 1.2, squishy = 0.6, timer = 20) var/matrix/OM = matrix(transform) var/matrix/M = matrix(transform) @@ -1526,7 +1534,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) blocked |= typesof(/obj/item/reagent_containers/food/drinks/prospacillin, /obj/item/reagent_containers/food/drinks/diminicillin ) - + return pick(subtypesof(/obj/item/reagent_containers/food/drinks) - blocked) //For these two procs refs MUST be ref = TRUE format like typecaches! diff --git a/code/datums/elements/ventcrawling.dm b/code/datums/elements/ventcrawling.dm new file mode 100644 index 000000000..254345a97 --- /dev/null +++ b/code/datums/elements/ventcrawling.dm @@ -0,0 +1,36 @@ +/datum/element/ventcrawling + element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH + id_arg_index = 2 + var/tier + +/datum/element/ventcrawling/Attach(datum/target, duration = 0, given_tier = VENTCRAWLER_NUDE) + . = ..() + + var/mob/living/person = target + if(!istype(person)) + return FALSE + + src.tier = given_tier + + RegisterSignal(target, COMSIG_HANDLE_VENTCRAWL, .proc/handle_ventcrawl) + RegisterSignal(target, COMSIG_CHECK_VENTCRAWL, .proc/check_ventcrawl) + to_chat(target, "You can ventcrawl! Use alt+click on vents to quickly travel about the station.") + + if(duration!=0) + addtimer(CALLBACK(src, .proc/Detach, target), duration) + +/datum/element/ventcrawling/Detach(datum/target) + UnregisterSignal(target, list(COMSIG_HANDLE_VENTCRAWL, COMSIG_CHECK_VENTCRAWL)) + to_chat(target, "You can no longer ventcrawl.") + + return ..() + +/datum/element/ventcrawling/proc/handle_ventcrawl(datum/target,atom/A) + var/mob/living/person = target + if(!istype(person)) + return FALSE + + person.handle_ventcrawl(A,tier) + +/datum/element/ventcrawling/proc/check_ventcrawl() + return tier diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm index 75c812be4..26e9d0d83 100644 --- a/code/datums/ruins/lavaland.dm +++ b/code/datums/ruins/lavaland.dm @@ -255,3 +255,11 @@ suffix = "lavaland_surface_cozy_cabin.dmm" allow_duplicates = FALSE cost = 0 + +/datum/map_template/ruin/lavaland/jettisoned_reactor + name = "Jettisoned Reactor" + id = "jettreactor" + description = "An incredibly dangerous ruin, filled with radiation and unknown lifeforms." + suffix = "lavaland_surface_jettisoned_reactor.dmm" + allow_duplicates = FALSE + cost = 15//Not too important, if you disregard the rads and facehugger. diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm index cf66d0574..fa7413bec 100644 --- a/code/datums/wires/_wires.dm +++ b/code/datums/wires/_wires.dm @@ -295,3 +295,15 @@ to_chat(L, "You need an attachable assembly!") #undef MAXIMUM_EMP_WIRES + +//gremlins +/datum/wires/proc/npc_tamper(mob/living/L) + if(!wires.len) + return + + var/wire_to_screw = pick(wires) + + if(is_color_cut(wire_to_screw) || prob(50)) //CutWireColour() proc handles both cutting and mending wires. If the wire is already cut, always mend it back. Otherwise, 50% to cut it and 50% to pulse it + cut(wire_to_screw) + else + pulse(wire_to_screw, L) diff --git a/code/game/area/areas/ruins/lavaland.dm b/code/game/area/areas/ruins/lavaland.dm index 99245f719..3441b9348 100644 --- a/code/game/area/areas/ruins/lavaland.dm +++ b/code/game/area/areas/ruins/lavaland.dm @@ -81,7 +81,6 @@ icon_state = "red" //Xeno Nest - /area/ruin/unpowered/xenonest name = "The Hive" always_unpowered = TRUE @@ -90,6 +89,10 @@ power_light = FALSE poweralm = FALSE -//ash walker nest +//Ash Walker Nest /area/ruin/unpowered/ash_walkers icon_state = "red" + +//Jettisoned Reactor +/area/ruin/unpowered/reactor + icon_state = "red" diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm index 634d57bf4..758f3b168 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm @@ -844,7 +844,7 @@ typepath = /datum/round_event/spontaneous_appendicitis enemy_roles = list("Medical Doctor","Chief Medical Officer") required_enemies = list(2,2,2,2,2,2,2,1,1,1) - requirements = list(5,5,5,5,5,5,5,5,0,0) + requirements = list(101,101,20,18,16,14,12,10,8,6) high_population_requirement = 10 weight = 5 cost = 6 diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm index 3c4963968..d9f34616b 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -91,3 +91,55 @@ /obj/machinery/door/poddoor/try_to_crowbar(obj/item/I, mob/user) if(stat & NOPOWER) open(1) + +//Multi-tile poddoors don't turn invisible automatically, so we change the opacity of the turfs below instead one by one. +/obj/machinery/door/poddoor/multi_tile/proc/apply_opacity_to_my_turfs(var/new_opacity) + for(var/turf/T in locs) + T.opacity = new_opacity + T.has_opaque_atom = new_opacity + T.reconsider_lights() + T.air_update_turf(1) + update_freelook_sight() + +/obj/machinery/door/poddoor/multi_tile + rad_insulation = RAD_NEAR_FULL_INSULATION + +/obj/machinery/door/poddoor/multi_tile/open() + . = ..() + rad_insulation = RAD_NO_INSULATION + apply_opacity_to_my_turfs(0) + +/obj/machinery/door/poddoor/multi_tile/close() + . = ..() + rad_insulation = RAD_NEAR_FULL_INSULATION + apply_opacity_to_my_turfs(1) + +/obj/machinery/door/poddoor/multi_tile/four_tile_ver/ + icon = 'icons/obj/doors/1x4blast_vert.dmi' + bound_height = 128 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/three_tile_ver/ + icon = 'icons/obj/doors/1x3blast_vert.dmi' + bound_height = 96 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/two_tile_ver/ + icon = 'icons/obj/doors/1x2blast_vert.dmi' + bound_height = 64 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/four_tile_hor/ + icon = 'icons/obj/doors/1x4blast_hor.dmi' + bound_width = 128 + dir = EAST + +/obj/machinery/door/poddoor/multi_tile/three_tile_hor/ + icon = 'icons/obj/doors/1x3blast_hor.dmi' + bound_width = 96 + dir = EAST + +/obj/machinery/door/poddoor/multi_tile/two_tile_hor/ + icon = 'icons/obj/doors/1x2blast_hor.dmi' + bound_width = 64 + dir = EAST \ No newline at end of file diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm index 05d1b3ae7..3dc863958 100644 --- a/code/game/machinery/doors/shutters.dm +++ b/code/game/machinery/doors/shutters.dm @@ -42,23 +42,21 @@ desc = "Lead-lined shutters painted yellow with a radioactive hazard symbol on it. Blocks out most radiation" icon = 'icons/obj/doors/shutters_radiation.dmi' icon_state = "closed" - rad_insulation = 0.2 + rad_insulation = RAD_NEAR_FULL_INSULATION /obj/machinery/door/poddoor/shutters/radiation/preopen icon_state = "open" density = FALSE - opacity = 0 - rad_insulation = 1 + opacity = FALSE + rad_insulation = RAD_NO_INSULATION -/obj/machinery/door/poddoor/shutters/radiation/do_animate(animation) - ..() - switch(animation) - if("opening") - rad_insulation = 1 - if("closing") - rad_insulation = -0.5 +/obj/machinery/door/poddoor/shutters/radiation/open() + . = ..() + rad_insulation = RAD_NO_INSULATION -// A 3x3 N2 SM setup won't irradiate you if you're behind the shutter at -0.9 insulation. If it starts to delam, it'll start irradiating you slowly. Keep the value between -0.1 to -0.9 +/obj/machinery/door/poddoor/shutters/radiation/close() + . = ..() + rad_insulation = RAD_NEAR_FULL_INSULATION /obj/machinery/door/poddoor/shutters/window name = "windowed shutters" diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 8b483bfe2..9840dad1b 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -249,6 +249,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ new/datum/stack_recipe("display case chassis", /obj/structure/displaycase_chassis, 5, one_per_turf = TRUE, on_floor = TRUE), \ new/datum/stack_recipe("loom", /obj/structure/loom, 10, time = 15, one_per_turf = TRUE, on_floor = TRUE), \ new/datum/stack_recipe("apiary", /obj/structure/beebox, 40, time = 50),\ + new/datum/stack_recipe("nestbox", /obj/structure/nestbox, 4, time = 30, one_per_turf = TRUE, on_floor = TRUE),\ null, \ new/datum/stack_recipe("picture frame", /obj/item/wallframe/picture, 1, time = 10),\ new/datum/stack_recipe("mortar", /obj/item/reagent_containers/glass/mortar, 3), \ @@ -485,8 +486,8 @@ GLOBAL_LIST_INIT(cardboard_recipes, list ( \ null, \ new/datum/stack_recipe("colored brown", /obj/item/storage/box/brown), \ new/datum/stack_recipe("colored green", /obj/item/storage/box/green), \ - new/datum/stack_recipe("colored red", /obj/item/storage/box/blue), \ - new/datum/stack_recipe("colored blue", /obj/item/storage/box/red), \ + new/datum/stack_recipe("colored red", /obj/item/storage/box/red), \ + new/datum/stack_recipe("colored blue", /obj/item/storage/box/blue), \ new/datum/stack_recipe("colored yellow", /obj/item/storage/box/yellow), \ new/datum/stack_recipe("colored pink", /obj/item/storage/box/pink), \ new/datum/stack_recipe("colored purple", /obj/item/storage/box/purple), \ diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 2368bb7a1..e50c0ccec 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -613,12 +613,11 @@ if(prob(5)) name = "drone (760) action figure" desc = "An action figure modeled after 'drone (760)', it seems to be a bit dusty." + + /* || A Deck of Cards for playing various games of chance || */ - - - /obj/item/toy/cards resistance_flags = FLAMMABLE max_integrity = 50 @@ -636,7 +635,8 @@ playsound(src, 'sound/items/cardshuffle.ogg', 50, 1) return BRUTELOSS -/obj/item/toy/cards/proc/apply_card_vars(obj/item/toy/cards/newobj, obj/item/toy/cards/sourceobj) // Applies variables for supporting multiple types of card deck +/obj/item/toy/cards/proc/apply_card_vars(obj/item/toy/cards/newobj, obj/item/toy/cards/sourceobj) +// Applies variables for supporting multiple types of card deck if(!istype(sourceobj)) return @@ -658,53 +658,24 @@ /obj/item/toy/cards/deck/proc/populate_deck() icon_state = "deck_[deckstyle]_full" - for(var/i in 2 to 10) - cards += "[i] of Hearts" - cards += "[i] of Spades" - cards += "[i] of Clubs" - cards += "[i] of Diamonds" - cards += "King of Hearts" - cards += "King of Spades" - cards += "King of Clubs" - cards += "King of Diamonds" - cards += "Queen of Hearts" - cards += "Queen of Spades" - cards += "Queen of Clubs" - cards += "Queen of Diamonds" - cards += "Jack of Hearts" - cards += "Jack of Spades" - cards += "Jack of Clubs" - cards += "Jack of Diamonds" - cards += "Ace of Hearts" - cards += "Ace of Spades" - cards += "Ace of Clubs" - cards += "Ace of Diamonds" + var/_suits = list("Hearts", "Spades", "Clubs", "Diamonds") + var/_faces = list("Ace", "King", "Queen", "Jack") + for(var/suit in _suits) + for(var/i in 2 to 10) + cards += list(list( + "name" = "[i] of [suit]", + "icon_state" = "[i]of[lowertext(suit)]", + "rotation" = null, + "face_up" = null + )) + for(var/i in _faces) + cards += list(list( + "name" = "[i] of [suit]", + "icon_state" = "[lowertext(i)]of[lowertext(suit)]", + "rotation" = null, + "face_up" = null + )) -//ATTACK HAND IGNORING PARENT RETURN VALUE -//ATTACK HAND NOT CALLING PARENT -/obj/item/toy/cards/deck/attack_hand(mob/user) - draw_card(user) - -/obj/item/toy/cards/deck/proc/draw_card(mob/user) - if(user.lying) - return - var/choice = null - if(cards.len == 0) - to_chat(user, "There are no more cards to draw!") - return - var/obj/item/toy/cards/singlecard/H = new/obj/item/toy/cards/singlecard(user.loc) - if(holo) - holo.spawned += H // track them leaving the holodeck - choice = cards[1] - H.cardname = choice - H.parentdeck = src - var/O = src - H.apply_card_vars(H,O) - src.cards.Cut(1,2) //Removes the top card from the list - H.pickup(user) - user.put_in_hands(H) - user.visible_message("[user] draws a card from the deck.", "You draw a card from the deck.") - update_icon() /obj/item/toy/cards/deck/update_icon() if(cards.len > original_size/2) @@ -716,60 +687,6 @@ else if(cards.len == 0) icon_state = "deck_[deckstyle]_empty" -/obj/item/toy/cards/deck/attack_self(mob/user) - if(cooldown < world.time - 50) - cards = shuffle(cards) - playsound(src, 'sound/items/cardshuffle.ogg', 50, 1) - user.visible_message("[user] shuffles the deck.", "You shuffle the deck.") - cooldown = world.time - -/obj/item/toy/cards/deck/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/toy/cards/singlecard)) - var/obj/item/toy/cards/singlecard/SC = I - if(SC.parentdeck == src) - if(!user.temporarilyRemoveItemFromInventory(SC)) - to_chat(user, "The card is stuck to your hand, you can't add it to the deck!") - return - cards += SC.cardname - user.visible_message("[user] adds a card to the bottom of the deck.","You add the card to the bottom of the deck.") - qdel(SC) - else - to_chat(user, "You can't mix cards from other decks!") - update_icon() - else if(istype(I, /obj/item/toy/cards/cardhand)) - var/obj/item/toy/cards/cardhand/CH = I - if(CH.parentdeck == src) - if(!user.temporarilyRemoveItemFromInventory(CH)) - to_chat(user, "The hand of cards is stuck to your hand, you can't add it to the deck!") - return - cards += CH.currenthand - user.visible_message("[user] puts [user.p_their()] hand of cards in the deck.", "You put the hand of cards in the deck.") - qdel(CH) - else - to_chat(user, "You can't mix cards from other decks!") - update_icon() - else - return ..() - -/obj/item/toy/cards/deck/MouseDrop(atom/over_object) - . = ..() - var/mob/living/M = usr - if(!istype(M) || usr.incapacitated() || usr.lying) - return - if(Adjacent(usr)) - if(over_object == M && loc != M) - M.put_in_hands(src) - to_chat(usr, "You pick up the deck.") - - else if(istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - if(M.putItemFromInventoryInHandIfPossible(src, H.held_index)) - to_chat(usr, "You pick up the deck.") - - else - to_chat(usr, "You can't reach it from here!") - - /obj/item/toy/cards/cardhand name = "hand of cards" @@ -780,59 +697,9 @@ var/list/currenthand = list() var/choice = null -/obj/item/toy/cards/cardhand/attack_self(mob/user) - var/list/handradial = list() - interact(user) - - for(var/t in currenthand) - handradial[t] = image(icon = src.icon, icon_state = "sc_[t]_[deckstyle]") - - if(usr.stat || !ishuman(usr)) - return - var/mob/living/carbon/human/cardUser = usr - var/O = src - var/choice = show_radial_menu(usr,src, handradial, custom_check = CALLBACK(src, .proc/check_menu, user), radius = 36, require_near = TRUE) - if(!choice) - return FALSE - var/obj/item/toy/cards/singlecard/C = new/obj/item/toy/cards/singlecard(cardUser.loc) - currenthand -= choice - handradial -= choice - C.parentdeck = parentdeck - C.cardname = choice - C.apply_card_vars(C,O) - C.pickup(cardUser) - cardUser.put_in_hands(C) - cardUser.visible_message("[cardUser] draws a card from [cardUser.p_their()] hand.", "You take the [C.cardname] from your hand.") - - interact(cardUser) - update_sprite() - if(length(currenthand) == 1) - var/obj/item/toy/cards/singlecard/N = new/obj/item/toy/cards/singlecard(loc) - N.parentdeck = parentdeck - N.cardname = currenthand[1] - N.apply_card_vars(N,O) - qdel(src) - N.pickup(cardUser) - cardUser.put_in_hands(N) - to_chat(cardUser, "You also take [currenthand[1]] and hold it.") - -/obj/item/toy/cards/cardhand/attackby(obj/item/toy/cards/singlecard/C, mob/living/user, params) - if(istype(C)) - if(C.parentdeck == src.parentdeck) - src.currenthand += C.cardname - user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [C.cardname] to your hand.") - qdel(C) - interact(user) - update_sprite(src) - else - to_chat(user, "You can't mix cards from other decks!") - else - return ..() - /obj/item/toy/cards/cardhand/apply_card_vars(obj/item/toy/cards/newobj,obj/item/toy/cards/sourceobj) ..() newobj.deckstyle = sourceobj.deckstyle - update_sprite() newobj.card_hitsound = sourceobj.card_hitsound newobj.card_force = sourceobj.card_force newobj.card_throwforce = sourceobj.card_throwforce @@ -840,6 +707,7 @@ newobj.card_throw_range = sourceobj.card_throw_range newobj.card_attack_verb = sourceobj.card_attack_verb newobj.resistance_flags = sourceobj.resistance_flags + update_icon() /** * check_menu: Checks if we are allowed to interact with a radial menu @@ -857,13 +725,23 @@ /** * This proc updates the sprite for when you create a hand of cards */ -/obj/item/toy/cards/cardhand/proc/update_sprite() +/obj/item/toy/cards/cardhand/update_icon() cut_overlays() var/overlay_cards = currenthand.len if(overlay_cards) var/k = overlay_cards == 2 ? 1 : overlay_cards - 2 for(var/i = k; i <= overlay_cards; i++) - var/card_overlay = image(icon=src.icon,icon_state="sc_[currenthand[i]]_[deckstyle]",pixel_x=(1-i+k)*3,pixel_y=(1-i+k)*3) + var/_card = currenthand[i] + var/_angle = _card["rotation"] || rotation + var/matrix/rot_matrix = matrix() + rot_matrix.Turn(GetAngle(_angle)) + var/image/card_overlay = image( + icon = src.icon, + icon_state = "sc_[_card["icon_state"] || _card["name"]]_[deckstyle]", + pixel_x = (1 - i + k)*3, + pixel_y = (1 - i + k)*3, + ) + card_overlay.transform = rot_matrix add_overlay(card_overlay) /obj/item/toy/cards/singlecard @@ -872,8 +750,7 @@ icon = 'icons/obj/toy.dmi' icon_state = "singlecard_down_nanotrasen" w_class = WEIGHT_CLASS_TINY - var/cardname = null - var/flipped = 0 + var/card = null pixel_x = -5 @@ -882,7 +759,9 @@ if(ishuman(user)) var/mob/living/carbon/human/cardUser = user if(cardUser.is_holding(src)) - cardUser.visible_message("[cardUser] checks [cardUser.p_their()] card.", "The card reads: [cardname].") + cardUser.visible_message( + "[cardUser] checks [cardUser.p_their()] card.", + "The card reads: [card["name"]]. [card["desc"] || ""]") else . += "You need to have the card in your hand to check it!" @@ -892,65 +771,11 @@ set src in range(1) if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) return - if(!flipped) - src.flipped = 1 - if (cardname) - src.icon_state = "sc_[cardname]_[deckstyle]" - src.name = src.cardname - else - src.icon_state = "sc_Ace of Spades_[deckstyle]" - src.name = "What Card" - src.pixel_x = 5 - else if(flipped) - src.flipped = 0 - src.icon_state = "singlecard_down_[deckstyle]" - src.name = "card" - src.pixel_x = -5 - -/obj/item/toy/cards/singlecard/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/toy/cards/singlecard/)) - var/obj/item/toy/cards/singlecard/C = I - if(C.parentdeck == src.parentdeck) - var/obj/item/toy/cards/cardhand/H = new/obj/item/toy/cards/cardhand(user.loc) - H.currenthand += C.cardname - H.currenthand += src.cardname - H.parentdeck = C.parentdeck - H.apply_card_vars(H,C) - to_chat(user, "You combine the [C.cardname] and the [src.cardname] into a hand.") - qdel(C) - qdel(src) - H.pickup(user) - user.put_in_active_hand(H) - else - to_chat(user, "You can't mix cards from other decks!") - - if(istype(I, /obj/item/toy/cards/cardhand/)) - var/obj/item/toy/cards/cardhand/H = I - if(H.parentdeck == parentdeck) - H.currenthand += cardname - user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [cardname] to your hand.") - qdel(src) - H.interact(user) - if(H.currenthand.len > 4) - H.icon_state = "[deckstyle]_hand5" - else if(H.currenthand.len > 3) - H.icon_state = "[deckstyle]_hand4" - else if(H.currenthand.len > 2) - H.icon_state = "[deckstyle]_hand3" - else - to_chat(user, "You can't mix cards from other decks!") - else - return ..() - -/obj/item/toy/cards/singlecard/attack_self(mob/user) - if(usr.stat || !ishuman(usr) || !usr.canmove || usr.restrained()) - return - Flip() + FlipCard() /obj/item/toy/cards/singlecard/apply_card_vars(obj/item/toy/cards/singlecard/newobj,obj/item/toy/cards/sourceobj) ..() newobj.deckstyle = sourceobj.deckstyle - newobj.icon_state = "singlecard_down_[deckstyle]" // Without this the card is invisible until flipped. It's an ugly hack, but it works. newobj.card_hitsound = sourceobj.card_hitsound newobj.hitsound = newobj.card_hitsound newobj.card_force = sourceobj.card_force @@ -963,6 +788,7 @@ newobj.throw_range = newobj.card_throw_range newobj.card_attack_verb = sourceobj.card_attack_verb newobj.attack_verb = newobj.card_attack_verb + newobj.update_icon() /* || Syndicate playing cards, for pretending you're Gambit and playing poker for the nuke disk. || diff --git a/code/game/turfs/simulated/wall/misc_walls.dm b/code/game/turfs/simulated/wall/misc_walls.dm index fbc448053..1b2c4767f 100644 --- a/code/game/turfs/simulated/wall/misc_walls.dm +++ b/code/game/turfs/simulated/wall/misc_walls.dm @@ -209,3 +209,15 @@ sheet_type = /obj/item/stack/tile/bronze sheet_amount = 2 girder_type = /obj/structure/girder/bronze + +/turf/closed/wall/mineral/lead + name = "lead lined metal wall" + desc = "A hefty wall with lead inserts." + icon = 'icons/turf/walls/riveted.dmi' + icon_state = "riveted" + sheet_type = /obj/item/stack/rods + hardness = 10 + girder_type = /obj/structure/girder/reinforced + explosion_block = 2 + rad_insulation = RAD_NEAR_FULL_INSULATION + canSmoothWith = list(/turf/closed/wall/mineral/lead) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index f2ea43bab..34f5c5158 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -2435,14 +2435,14 @@ GLOBAL_LIST_EMPTY(preferences_datums) to_chat(user,"Invalid color. Your color is not bright enough.") if("belly_size") - var/new_bellysize = input(user, "Belly size :\n(1-3)", "Character Preference") as num|null - if(new_bellysize) - features["belly_size"] = clamp(new_bellysize, 1, 3) + var/new_bellysize = input(user, "Belly size :\n([BELLY_MIN_SIZE]-[BELLY_MAX_SIZE])", "Character Preference") as num|null + if(new_bellysize != null) + features["belly_size"] = clamp(new_bellysize, BELLY_MIN_SIZE, BELLY_MAX_SIZE) if("butt_size") - var/new_buttsize = input(user, "Butt size :\n(0-5)", "Character Preference") as num|null - if(new_buttsize) - features["butt_size"] = clamp(new_buttsize, 0, 5) + var/new_buttsize = input(user, "Butt size :\n([BUTT_MIN_SIZE]-[BUTT_MAX_SIZE])", "Character Preference") as num|null + if(new_buttsize != null) + features["butt_size"] = clamp(new_buttsize, BUTT_MIN_SIZE, BUTT_MAX_SIZE) if("vag_shape") var/new_shape @@ -2577,7 +2577,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) if(features["has_belly"] == FALSE) features["hide_belly"] = FALSE features["inflatable_belly"] = FALSE - features["belly_size"] = 1 + features["belly_size"] = 0 if("inflatable_belly") features["inflatable_belly"] = !features["inflatable_belly"] diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index 8b2a34a08..c675bce3e 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -438,7 +438,7 @@ resistance_flags = NONE armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) clothing_flags = BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR + flags_inv = HIDEEYES|HIDEFACE|HIDEFACIALHAIR gas_transfer_coefficient = 0.01 permeability_coefficient = 0.01 flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 107350fca..5aeb76da3 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -3,7 +3,7 @@ desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate icon_state = "gas_alt" clothing_flags = BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT + flags_inv = HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT w_class = WEIGHT_CLASS_NORMAL item_state = "gas_alt" gas_transfer_coefficient = 0.01 diff --git a/code/modules/clothing/masks/hailer.dm b/code/modules/clothing/masks/hailer.dm index 9c100208d..5d53688cd 100644 --- a/code/modules/clothing/masks/hailer.dm +++ b/code/modules/clothing/masks/hailer.dm @@ -27,7 +27,7 @@ icon_state = "swat" item_state = "swat" aggressiveness = 3 - flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEEARS|HIDEHAIR + flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEHAIR visor_flags_inv = 0 /obj/item/clothing/mask/gas/sechailer/cyborg diff --git a/code/modules/games/cas.dm b/code/modules/games/cas.dm index 4fbb931b3..5911e26aa 100644 --- a/code/modules/games/cas.dm +++ b/code/modules/games/cas.dm @@ -2,39 +2,36 @@ // This is a parody of Cards Against Humanity (https://en.wikipedia.org/wiki/Cards_Against_Humanity) // which is licensed under CC BY-NC-SA 2.0, the full text of which can be found at the following URL: // https://creativecommons.org/licenses/by-nc-sa/2.0/legalcode -// Original code by Zuhayr, Polaris Station, ported with modifications -/datum/playingcard - var/name = "playing card" - var/card_icon = "card_back" - var/suit - var/number +// Original code by Zuhayr, Polaris Station, ported with modifications +// April 2022: modified by sarcoph for hyperstation /obj/item/toy/cards/deck/cas name = "\improper CAS deck (white)" desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the white deck." icon = 'icons/obj/toy.dmi' icon_state = "deck_caswhite_full" - deckstyle = "caswhite" - var/card_face = "cas_white" + deckstyle = "cas" + var/card_face = "white" var/blanks = 25 var/decksize = 150 var/card_text_file = "strings/cas_white.txt" - var/list/allcards = list() /obj/item/toy/cards/deck/cas/black name = "\improper CAS deck (black)" desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the black deck." icon_state = "deck_casblack_full" - deckstyle = "casblack" - card_face = "cas_black" + card_face = "black" blanks = 0 decksize = 50 card_text_file = "strings/cas_black.txt" /obj/item/toy/cards/deck/cas/populate_deck() - var/static/list/cards_against_space = list("cas_white" = world.file2list("strings/cas_white.txt"),"cas_black" = world.file2list("strings/cas_black.txt")) - allcards = cards_against_space[card_face] - var/list/possiblecards = allcards.Copy() + var/static/list/cards_against_space = list( + "white" = world.file2list("strings/cas_white.txt"), + "black" = world.file2list("strings/cas_black.txt") + ) + var/list/_cards = cards_against_space[card_face] + var/list/possiblecards = _cards.Copy() if(possiblecards.len < decksize) // sanity check decksize = (possiblecards.len - 1) var/list/randomcards = list() @@ -42,105 +39,74 @@ randomcards += pick_n_take(possiblecards) for(var/x in 1 to randomcards.len) var/cardtext = randomcards[x] - var/datum/playingcard/P - P = new() - P.name = "[cardtext]" - P.card_icon = "[src.card_face]" - cards += P + cards += list(list( + "name" = cardtext, + "icon_state" = src.card_face, + "rotation" = null, + "face_up" = null + )) if(!blanks) return for(var/x in 1 to blanks) - var/datum/playingcard/P - P = new() - P.name = "Blank Card" - P.card_icon = "cas_white" - cards += P - shuffle_inplace(cards) // distribute blank cards throughout deck + cards += list(list( + "name" = "Blank Card", + "icon_state" = "white", + "rotation" = null, + "face_up" = null + )) -/obj/item/toy/cards/deck/cas/draw_card(mob/user) - if(user.lying) - return - if(cards.len == 0) - to_chat(user, "There are no more cards to draw!") - return - var/obj/item/toy/cards/singlecard/cas/H = new/obj/item/toy/cards/singlecard/cas(user.loc) - var/datum/playingcard/choice = cards[1] - if (choice.name == "Blank Card") - H.blank = 1 - H.name = choice.name - H.buffertext = choice.name - H.icon_state = choice.card_icon - H.card_face = choice.card_icon - H.parentdeck = src - src.cards -= choice - H.pickup(user) - user.put_in_hands(H) - user.visible_message("[user] draws a card from the deck.", "You draw a card from the deck.") - update_icon() - -/obj/item/toy/cards/deck/cas/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/toy/cards/singlecard/cas)) - var/obj/item/toy/cards/singlecard/cas/SC = I - if(!user.temporarilyRemoveItemFromInventory(SC)) - to_chat(user, "The card is stuck to your hand, you can't add it to the deck!") - return - var/datum/playingcard/RC // replace null datum for the re-added card - RC = new() - RC.name = "[SC.name]" - RC.card_icon = SC.card_face - cards += RC - user.visible_message("[user] adds a card to the bottom of the deck.","You add the card to the bottom of the deck.") - qdel(SC) +/obj/item/toy/cards/deck/cas/DrawOneCard(list/card_indices) + var/obj/item/toy/cards/singlecard/cas/S = new/obj/item/toy/cards/singlecard/cas(usr.loc) + var/_card = cards[card_indices[1]] + _card["rotation"] = rotation + _card["face_up"] = face_up + if(_card["name"] == "Blank Card") + S.blank = TRUE + S.card = _card + S.card_face = card_face + S.rotation = _card["rotation"] + S.face_up = _card["face_up"] + S.parentdeck = src + S.apply_card_vars(S,src) + cards -= list(_card) update_icon() + return S /obj/item/toy/cards/deck/cas/update_icon() if(cards.len < 26) - icon_state = "deck_[deckstyle]_low" + icon_state = "deck_[deckstyle][card_face]_low" + + + +// ----------- SINGLE CARD ----------- /obj/item/toy/cards/singlecard/cas name = "CAS card" desc = "A CAS card." - icon_state = "cas_white" - flipped = 0 - var/card_face = "cas_white" + icon_state = "sc_cas_white" + var/card_face = "white" var/blank = 0 var/buffertext = "A funny bit of text." /obj/item/toy/cards/singlecard/cas/examine(mob/user) . = ..() - if (flipped) + if (!face_up) . += "The card is face down." else if (blank) . += "The card is blank. Write on it with a pen." else . += "The card reads: [name]" - . += "Alt-click to flip it." - -/obj/item/toy/cards/singlecard/cas/Flip() - set name = "Flip Card" - set category = "Object" - set src in range(1) - if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) - return - if(!flipped) - name = "CAS card" - else if(flipped) - name = buffertext - flipped = !flipped - update_icon() - -/obj/item/toy/cards/singlecard/cas/AltClick(mob/living/user) - . = ..() - if(!ishuman(user) || !user.canUseTopic(src, BE_CLOSE)) - return - Flip() - return TRUE /obj/item/toy/cards/singlecard/cas/update_icon() - if(flipped) - icon_state = "[card_face]_flipped" + var/matrix/rot_matrix = matrix() + rot_matrix.Turn(GetAngle(rotation)) + transform = rot_matrix + if(face_up) + icon_state = "sc_[deckstyle]_[card_face]" + name = card["name"] else - icon_state = "[card_face]" + icon_state = "sc_[deckstyle]_[card_face]_flipped" + name = "CAS card" /obj/item/toy/cards/singlecard/cas/attackby(obj/item/I, mob/living/user, params) if(istype(I, /obj/item/pen)) diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 2e66809e5..4ebe01313 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -181,147 +181,6 @@ else ..() -/mob/living/simple_animal/chick - name = "\improper chick" - desc = "Adorable! They make such a racket though." - icon_state = "chick" - icon_living = "chick" - icon_dead = "chick_dead" - icon_gib = "chick_gib" - gender = FEMALE - mob_biotypes = MOB_ORGANIC|MOB_BEAST - speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") - speak_emote = list("cheeps") - emote_hear = list("cheeps.") - emote_see = list("pecks at the ground.","flaps its tiny wings.") - density = FALSE - speak_chance = 2 - turns_per_move = 2 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 1) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - attacktext = "kicks" - health = 3 - maxHealth = 3 - ventcrawler = VENTCRAWLER_ALWAYS - var/amount_grown = 0 - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - mob_size = MOB_SIZE_TINY - gold_core_spawnable = FRIENDLY_SPAWN - - do_footstep = TRUE - -/mob/living/simple_animal/chick/Initialize() - . = ..() - pixel_x = rand(-6, 6) - pixel_y = rand(0, 10) - -/mob/living/simple_animal/chick/Life() - . =..() - if(!.) - return - if(!stat && !ckey) - amount_grown += rand(1,2) - if(amount_grown >= 100) - new /mob/living/simple_animal/chicken(src.loc) - qdel(src) - -/mob/living/simple_animal/chick/holo/Life() - ..() - amount_grown = 0 - -/mob/living/simple_animal/chicken - name = "\improper chicken" - desc = "Hopefully the eggs are good this season." - gender = FEMALE - mob_biotypes = MOB_ORGANIC|MOB_BEAST - icon_state = "chicken_brown" - icon_living = "chicken_brown" - icon_dead = "chicken_brown_dead" - speak = list("Cluck!","BWAAAAARK BWAK BWAK BWAK!","Bwaak bwak.") - speak_emote = list("clucks","croons") - emote_hear = list("clucks.") - emote_see = list("pecks at the ground.","flaps its wings viciously.") - density = FALSE - speak_chance = 2 - turns_per_move = 3 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 2) - var/egg_type = /obj/item/reagent_containers/food/snacks/egg - var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - attacktext = "kicks" - health = 15 - maxHealth = 15 - ventcrawler = VENTCRAWLER_ALWAYS - var/eggsleft = 0 - var/eggsFertile = TRUE - var/body_color - var/icon_prefix = "chicken" - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - var/list/feedMessages = list("It clucks happily.","It clucks happily.") - var/list/layMessage = EGG_LAYING_MESSAGES - var/list/validColors = list("brown","black","white") - gold_core_spawnable = FRIENDLY_SPAWN - var/static/chicken_count = 0 - - do_footstep = TRUE - -/mob/living/simple_animal/chicken/Initialize() - . = ..() - if(!body_color) - body_color = pick(validColors) - icon_state = "[icon_prefix]_[body_color]" - icon_living = "[icon_prefix]_[body_color]" - icon_dead = "[icon_prefix]_[body_color]_dead" - pixel_x = rand(-6, 6) - pixel_y = rand(0, 10) - ++chicken_count - -/mob/living/simple_animal/chicken/Destroy() - --chicken_count - return ..() - -/mob/living/simple_animal/chicken/attackby(obj/item/O, mob/user, params) - if(istype(O, food_type)) //feedin' dem chickens - if(!stat && eggsleft < 8) - var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]" - user.visible_message(feedmsg) - qdel(O) - eggsleft += rand(1, 4) - else - to_chat(user, "[name] doesn't seem hungry!") - else - ..() - -/mob/living/simple_animal/chicken/Life() - . =..() - if(!.) - return - if((!stat && prob(3) && eggsleft > 0) && egg_type) - visible_message("[src] [pick(layMessage)]") - eggsleft-- - var/obj/item/E = new egg_type(get_turf(src)) - E.pixel_x = rand(-6,6) - E.pixel_y = rand(-6,6) - if(eggsFertile) - if(chicken_count < MAX_CHICKENS && prob(25)) - START_PROCESSING(SSobj, E) - -/obj/item/reagent_containers/food/snacks/egg/var/amount_grown = 0 -/obj/item/reagent_containers/food/snacks/egg/process() - if(isturf(loc)) - amount_grown += rand(1,2) - if(amount_grown >= 100) - visible_message("[src] hatches with a quiet cracking sound.") - new /mob/living/simple_animal/chick(get_turf(src)) - STOP_PROCESSING(SSobj, src) - qdel(src) - else - STOP_PROCESSING(SSobj, src) // Space kiwis, ergo quite a copypasta of chickens. diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin.dm new file mode 100644 index 000000000..b4694b6f0 --- /dev/null +++ b/code/modules/mob/living/simple_animal/gremlin/gremlin.dm @@ -0,0 +1,263 @@ +#define GREMLIN_VENT_CHANCE 1.75 + +//Gremlins +//Small monsters that don't attack humans or other animals. Instead they mess with electronics, computers and machinery + +//List of objects that gremlins can't tamper with (because nobody coded an interaction for it) +//List starts out empty. Whenever a gremlin finds a machine that it couldn't tamper with, the machine's type is added here, and all machines of such type are ignored from then on (NOT SUBTYPES) +GLOBAL_LIST(bad_gremlin_items) + +/mob/living/simple_animal/hostile/gremlin + name = "gremlin" + desc = "This tiny creature finds great joy in discovering and using technology. Nothing excites it more than pushing random buttons on a computer to see what it might do." + icon = 'icons/mob/mob.dmi' + icon_state = "gremlin" + icon_living = "gremlin" + icon_dead = "gremlin_dead" + + var/body_color + + var/in_vent = FALSE + + health = 20 + maxHealth = 20 + search_objects = 3 //Completely ignore mobs + + //Tampering is handled by the 'npc_tamper()' obj proc + wanted_objects = list( + /obj/machinery, + /obj/item/reagent_containers/food, + /obj/structure/sink + ) + + var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent + var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent + + dextrous = TRUE + possible_a_intents = list(INTENT_HELP, INTENT_GRAB, INTENT_DISARM, INTENT_HARM) + faction = list("meme", "gremlin") + speed = 0.5 + gold_core_spawnable = 2 + unique_name = TRUE + + //Ensure gremlins don't attack other mobs + melee_damage_upper = 0 + melee_damage_lower = 0 + attack_sound = null + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + + //List of objects that we don't even want to try to tamper with + //Subtypes of these are calculated too + var/list/unwanted_objects = list(/obj/machinery/atmospherics/pipe, /turf, /obj/structure) //ensure gremlins dont try to fuck with walls / normal pipes / glass / etc + + var/min_next_vent = 0 + + //Amount of ticks spent pathing to the target. If it gets above a certain amount, assume that the target is unreachable and stop + var/time_chasing_target = 0 + + //If you're going to make gremlins slower, increase this value - otherwise gremlins will abandon their targets too early + var/max_time_chasing_target = 2 + + var/next_eat = 0 + + //Last 20 heard messages are remembered by gremlins, and will be used to generate messages for comms console tampering, etc... + var/list/hear_memory = list() + var/const/max_hear_memory = 20 + +/mob/living/simple_animal/hostile/gremlin/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, INNATE_TRAIT) + access_card = new /obj/item/card/id(src) + var/datum/job/captain/C = new /datum/job/captain + access_card.access = C.get_access() + if(!body_color) + body_color = pick(list("orange","blue","purple", "green", "crystal")) + AddElement(/datum/element/mob_holder, "gremlin_[body_color]") + icon_state = "gremlin_[body_color]" + icon_living = "gremlin_[body_color]" + icon_dead = "gremlin_[body_color]_dead" + +/mob/living/simple_animal/hostile/gremlin/AttackingTarget() + var/is_hungry = world.time >= next_eat || prob(25) + if(istype(target, /obj/item/reagent_containers/food) && is_hungry) //eat food if we're hungry or bored + visible_message("[src] hungrily devours [target]!") + playsound(src, 'sound/items/eatfood.ogg', 50, 1) + qdel(target) + LoseTarget() + next_eat = world.time + rand(700, 3000) //anywhere from 70 seconds to 5 minutes until the gremlin is hungry again + return + if(istype(target, /obj)) + var/obj/M = target + tamper(M) + if(prob(50)) //50% chance to move to the next machine + LoseTarget() + +/mob/living/simple_animal/hostile/gremlin/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(message) + hear_memory.Insert(1, raw_message) + if(hear_memory.len > max_hear_memory) + hear_memory.Cut(hear_memory.len) + +/mob/living/simple_animal/hostile/gremlin/proc/generate_markov_input() + var/result = "" + + for(var/memory in hear_memory) + result += memory + " " + + return result + +/mob/living/simple_animal/hostile/gremlin/proc/generate_markov_chain() + return markov_chain(generate_markov_input(), rand(2,5), rand(100,700)) //The numbers are chosen arbitarily + +/mob/living/simple_animal/hostile/gremlin/proc/tamper(obj/M) + switch(M.npc_tamper_act(src)) + if(NPC_TAMPER_ACT_FORGET) + visible_message(pick( + "\The [src] plays around with \the [M], but finds it rather boring.", + "\The [src] tries to think of some more ways to screw \the [M] up, but fails miserably.", + "\The [src] decides to ignore \the [M], and starts looking for something more fun.")) + + LAZYADD(GLOB.bad_gremlin_items,M.type) + return FALSE + if(NPC_TAMPER_ACT_NOMSG) + //Don't create a visible message + return TRUE + + else + visible_message(pick( + "\The [src]'s eyes light up as \he tampers with \the [M].", + "\The [src] twists some knobs around on \the [M] and bursts into laughter!", + "\The [src] presses a few buttons on \the [M] and giggles mischievously.", + "\The [src] rubs its hands devilishly and starts messing with \the [M].", + "\The [src] turns a small valve on \the [M].")) + + //Add a clue for detectives to find. The clue is only added if no such clue already existed on that machine + return TRUE + +/mob/living/simple_animal/hostile/gremlin/CanAttack(atom/new_target) + if(LAZYFIND(GLOB.bad_gremlin_items,new_target.type)) + return FALSE + if(is_type_in_list(new_target, unwanted_objects)) + return FALSE + if(istype(new_target, /obj/machinery)) + var/obj/machinery/M = new_target + if(M.stat) //Unpowered or broken + return FALSE + else if(istype(new_target, /obj/machinery/door/firedoor)) + var/obj/machinery/door/firedoor/F = new_target + //Only tamper with firelocks that are closed, opening them! + if(!F.density) + return FALSE + + return ..() + +/mob/living/simple_animal/hostile/gremlin/death(gibbed) + walk(src,0) + QDEL_NULL(access_card) + return ..() + +/mob/living/simple_animal/hostile/gremlin/Life() + . = ..() + if(!health || stat == DEAD) + return + //Don't try to path to one target for too long. If it takes longer than a certain amount of time, assume it can't be reached and find a new one + if(!client) //don't do this shit if there's a client, they're capable of ventcrawling manually + if(in_vent) + target = null + if(entry_vent && get_dist(src, entry_vent) <= 1) + var/list/vents = list() + var/datum/pipeline/entry_vent_parent = entry_vent.parents[1] + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmosmch) + vents += temp_vent + if(!vents.len) + entry_vent = null + in_vent = FALSE + return + exit_vent = pick(vents) + visible_message("[src] crawls into the ventilation ducts!") + + loc = exit_vent + var/travel_time = round(get_dist(loc, exit_vent.loc) / 2) + addtimer(CALLBACK(src, .proc/exit_vents), travel_time) //come out at exit vent in 2 to 20 seconds + + + if(world.time > min_next_vent && !entry_vent && !in_vent && prob(GREMLIN_VENT_CHANCE)) //small chance to go into a vent + for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src)) + if(!v.welded) + entry_vent = v + in_vent = TRUE + walk_to(src, entry_vent) + break + if(!target) + time_chasing_target = 0 + else + if(++time_chasing_target > max_time_chasing_target) + LoseTarget() + time_chasing_target = 0 + . = ..() + +/mob/living/simple_animal/hostile/gremlin/EscapeConfinement() + if(istype(loc, /obj) && CanAttack(loc)) //If we're inside a machine, screw with it + var/obj/M = loc + tamper(M) + + return ..() + +/mob/living/simple_animal/hostile/gremlin/proc/exit_vents() + if(!exit_vent || exit_vent.welded) + loc = entry_vent + entry_vent = null + return + loc = exit_vent.loc + entry_vent = null + exit_vent = null + in_vent = FALSE + var/area/new_area = get_area(loc) + message_admins("[src] came out at [new_area][ADMIN_JMP(loc)]!") + if(new_area) + new_area.Entered(src) + visible_message("[src] climbs out of the ventilation ducts!") + min_next_vent = world.time + 900 //90 seconds between ventcrawls + +//This allows player-controlled gremlins to tamper with machinery +/mob/living/simple_animal/hostile/gremlin/UnarmedAttack(var/atom/A) + if(istype(A, /obj/machinery) || istype(A, /obj/structure)) + tamper(A) + if(istype(target, /obj/item/reagent_containers/food)) //eat food + visible_message("[src] hungrily devours [target]!", "You hungrily devour [target]!") + playsound(src, 'sound/items/eatfood.ogg', 50, 1) + qdel(target) + LoseTarget() + next_eat = world.time + rand(700, 3000) //anywhere from 70 seconds to 5 minutes until the gremlin is hungry again + + return ..() + +/mob/living/simple_animal/hostile/gremlin/IsAdvancedToolUser() + return 1 + +/mob/living/simple_animal/hostile/gremlin/proc/divide() + //Health is halved and then reduced by 2. A new gremlin is spawned with the same health as the parent + //Need to have at least 6 health for this, otherwise resulting health would be less than 1 + if(health < 7.5) + return + + visible_message("\The [src] splits into two!") + var/mob/living/simple_animal/hostile/gremlin/G = new /mob/living/simple_animal/hostile/gremlin(get_turf(src)) + + if(mind) + mind.transfer_to(G) + + health = round(health * 0.5) - 2 + maxHealth = health + resize *= 0.9 + + G.health = health + G.maxHealth = maxHealth + +/mob/living/simple_animal/hostile/gremlin/traitor + health = 85 + maxHealth = 85 + gold_core_spawnable = 0 diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm new file mode 100644 index 000000000..ff0eb13dc --- /dev/null +++ b/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm @@ -0,0 +1,214 @@ +/obj/proc/npc_tamper_act(mob/living/L) + return NPC_TAMPER_ACT_FORGET + +/obj/machinery/atmospherics/components/binary/passive_gate/npc_tamper_act(mob/living/L) + if(prob(50)) //Turn on/off + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS) + else //Change pressure + target_pressure = rand(0, MAX_OUTPUT_PRESSURE) + investigate_log("was set to [target_pressure] kPa by [key_name(L)]", INVESTIGATE_ATMOS) + update_icon() + +/obj/machinery/atmospherics/components/binary/pump/npc_tamper_act(mob/living/L) + if(prob(50)) //Turn on/off + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS) + else //Change pressure + target_pressure = rand(0, MAX_OUTPUT_PRESSURE) + investigate_log("was set to [target_pressure] kPa by [key_name(L)]", INVESTIGATE_ATMOS) + update_icon() + +/obj/machinery/atmospherics/components/binary/volume_pump/npc_tamper_act(mob/living/L) + if(prob(50)) //Turn on/off + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS) + else //Change pressure + transfer_rate = rand(0, MAX_TRANSFER_RATE) + investigate_log("was set to [transfer_rate] L/s by [key_name(L)]", INVESTIGATE_ATMOS) + update_icon() + +/obj/machinery/atmospherics/components/binary/valve/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/space_heater/npc_tamper_act(mob/living/L) + var/list/choose_modes = list("standby", "heat", "cool") + if(prob(50)) + choose_modes -= mode + mode = pick(choose_modes) + else + on = !on + update_icon() + +/obj/machinery/shield_gen/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/firealarm/npc_tamper_act(mob/living/L) + alarm() + +/obj/machinery/airalarm/npc_tamper_act(mob/living/L) + if(panel_open) + wires.npc_tamper(L) + else + panel_open = !panel_open + +/obj/machinery/ignition_switch/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/flasher_button/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/crema_switch/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/camera/npc_tamper_act(mob/living/L) + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + +/obj/machinery/atmospherics/components/unary/cryo_cell/npc_tamper_act(mob/living/L) + if(prob(50)) + if(beaker) + beaker.forceMove(loc) + beaker = null + else + if(occupant) + if(state_open) + if (close_machine() == usr) + on = TRUE + else + open_machine() + +/obj/machinery/door_control/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/door/airlock/npc_tamper_act(mob/living/L) + //Open the firelocks as well, otherwise they block the way for our gremlin which isn't fun + for(var/obj/machinery/door/firedoor/F in get_turf(src)) + if(F.density) + F.npc_tamper_act(L) + + if(prob(40)) //40% - mess with wires + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + else //60% - just open it + open() + +/obj/machinery/gibber/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/light_switch/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/turretid/npc_tamper_act(mob/living/L) + enabled = rand(0, 1) + lethal = rand(0, 1) + updateTurrets() + +/obj/machinery/vending/npc_tamper_act(mob/living/L) + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + +/obj/machinery/shower/npc_tamper_act(mob/living/L) + attack_hand(L) + + +/obj/machinery/deepfryer/npc_tamper_act(mob/living/L) + //Deepfry a random nearby item + var/list/pickable_items = list() + + for(var/obj/item/I in range(1, L)) + pickable_items.Add(I) + + if(!pickable_items.len) + return + + var/obj/item/I = pick(pickable_items) + + attackby(I, L) //shove the item in, even if it can't be deepfried normally + +/obj/machinery/power/apc/npc_tamper_act(mob/living/L) + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + +/obj/machinery/power/rad_collector/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/power/emitter/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/particle_accelerator/control_box/npc_tamper_act(mob/living/L) + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + +/obj/machinery/computer/communications/npc_tamper_act(mob/living/user) + if(!authenticated) + if(prob(20)) //20% chance to log in + authenticated = TRUE + + else //Already logged in + if(prob(50)) //50% chance to log off + authenticated = FALSE + else if(istype(user, /mob/living/simple_animal/hostile/gremlin)) //make a hilarious public message + var/mob/living/simple_animal/hostile/gremlin/G = user + var/result = G.generate_markov_chain() + + if(result) + if(prob(85)) + SScommunications.make_announcement(G, FALSE, result) + var/turf/T = get_turf(G) + log_say("[key_name(usr)] ([ADMIN_JMP(T)]) has made a captain announcement: [result]") + message_admins("[key_name_admin(G)] has made a captain announcement.", 1) + else + if(SSshuttle.emergency.mode == SHUTTLE_IDLE) + SSshuttle.requestEvac(G, result) + else if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE) + SSshuttle.cancelEvac(G) + +/obj/machinery/button/door/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/sleeper/npc_tamper_act(mob/living/L) + if(prob(75)) + inject_chem(pick(available_chems)) + else + if(state_open) + close_machine() + else + open_machine() + +/obj/machinery/power/smes/npc_tamper_act(mob/living/L) + if(prob(50)) //mess with input + input_level = rand(0, input_level_max) + else //mess with output + output_level = rand(0, output_level_max) + +/obj/machinery/syndicatebomb/npc_tamper_act(mob/living/L) //suicide bomber gremlins + if(!open_panel) + open_panel = !open_panel + if(wires) + wires.npc_tamper(L) + +/obj/machinery/computer/bank_machine/npc_tamper_act(mob/living/L) + siphoning = !siphoning + +/obj/machinery/computer/slot_machine/npc_tamper_act(mob/living/L) + spin(L) + +/obj/structure/sink/npc_tamper_act(mob/living/L) + if(istype(L, /mob/living/simple_animal/hostile/gremlin)) + visible_message("\The [L] climbs into \the [src] and turns the faucet on!") + + var/mob/living/simple_animal/hostile/gremlin/G = L + G.divide() + + return NPC_TAMPER_ACT_NOMSG diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm new file mode 100644 index 000000000..af0fd12de --- /dev/null +++ b/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm @@ -0,0 +1,44 @@ +/datum/round_event_control/gremlin + name = "Spawn Gremlins" + typepath = /datum/round_event/gremlin + weight = 15 + max_occurrences = 2 + earliest_start = 20 MINUTES + min_players = 5 + + + +/datum/round_event/gremlin + var/static/list/acceptable_spawns = list("xeno_spawn", "generic event spawn", "blobstart", "Assistant") + +/datum/round_event/gremlin/announce() + priority_announce("Bioscans indicate that some gremlins entered through the vents. Deal with them!", "Gremlin Alert", 'sound/ai/beep.ogg') + +/datum/round_event/gremlin/start() + + var/list/spawn_locs = list() + + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(isturf(L.loc) && !isspaceturf(L.loc)) + if(L.name in acceptable_spawns) + spawn_locs += L.loc + if(!spawn_locs.len) //If we can't find any gremlin spawns, try the xeno spawns + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(isturf(L.loc)) + switch(L.name) + if("Assistant") + spawn_locs += L.loc + if(!spawn_locs.len) //If we can't find THAT, then just give up and cry + return MAP_ERROR + + var/gremlins_to_spawn = rand(2,5) + var/list/gremlin_areas = list() + for(var/i = 0, i <= gremlins_to_spawn, i++) + var/spawnat = pick(spawn_locs) + spawn_locs -= spawnat + gremlin_areas += get_area(spawnat) + new /mob/living/simple_animal/hostile/gremlin(spawnat) + var/grems = gremlin_areas.Join(", ") + message_admins("Gremlins have been spawned at the areas: [grems]") + log_game("Gremlins have been spawned at the areas: [grems]") + return SUCCESSFUL_SPAWN diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm index de4a4b684..2cb562cff 100644 --- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -1001,3 +1001,5 @@ glass_desc = "A Summer time drink that can be frozen and eaten or Drinked from a glass!" glass_name = "Orange Creamsicle" hydration = 4 + + diff --git a/code/modules/spells/spell_types/adminbussed.dm b/code/modules/spells/spell_types/adminbussed.dm index 24f1be236..335066d3a 100644 --- a/code/modules/spells/spell_types/adminbussed.dm +++ b/code/modules/spells/spell_types/adminbussed.dm @@ -158,10 +158,10 @@ var/altered_height altered_height = input(user, "Choose your desired sprite size:\n([MIN_BODYSIZE]-400%)", "Height Alteration") as num|null if(altered_height) - H.size_multiplier = (max(min( round(text2num(altered_height)),400),MIN_BODYSIZE))/100 + H.size_multiplier = (max(min( round(text2num(altered_height)),1000),MIN_BODYSIZE))/100 playsound(user.loc, pshoom_or_beepboopblorpzingshadashwoosh, 40, 1) do_sparks(5, FALSE, user.loc) H.visible_message("[pick("[H] shifts in size!", "[H] alters in height!", "[H] reshapes into a new stature!")]") else - return \ No newline at end of file + return diff --git a/code/modules/surgery/bodyparts/bodyparts.dm b/code/modules/surgery/bodyparts/bodyparts.dm index 7f3b477bc..4a1bc2113 100644 --- a/code/modules/surgery/bodyparts/bodyparts.dm +++ b/code/modules/surgery/bodyparts/bodyparts.dm @@ -195,7 +195,7 @@ burn_dam += burn if(status == BODYPART_ORGANIC) //pain is only applied to organic organs, because nerves. - pain_dam += (brute+burn)*1.2 //add the total damage applied to the limb as pain damage, build pain quicker, because sudden pain is more.. painful. + pain_dam += (brute+burn)*1.05 //add the total damage applied to the limb as pain damage, build pain quicker, because sudden pain is more.. painful. //We've dealt the physical damages, if there's room lets apply the stamina damage. var/current_damage = get_damage(TRUE) //This time around, count stamina loss too. diff --git a/code/modules/vending/autodrobe.dm b/code/modules/vending/autodrobe.dm index e53d8a471..2dbaf5394 100644 --- a/code/modules/vending/autodrobe.dm +++ b/code/modules/vending/autodrobe.dm @@ -111,7 +111,8 @@ /obj/item/clothing/ears/headphones = 2, /obj/item/clothing/head/wig/random = 3, /obj/item/clothing/suit/ran = 2, - /obj/item/clothing/head/ran = 2) + /obj/item/clothing/head/ran = 2, + /obj/item/clothing/under/vaultsuit = 3) contraband = list(/obj/item/clothing/suit/judgerobe = 1, /obj/item/clothing/head/powdered_wig = 1, /obj/item/gun/magic/wand = 2, diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm index 5ceb898cf..0a3044a6f 100644 --- a/code/modules/vending/clothesmate.dm +++ b/code/modules/vending/clothesmate.dm @@ -111,6 +111,7 @@ /obj/item/clothing/suit/toggle/jacket_white = 3, /obj/item/clothing/suit/jacket/letterman_red = 3, /obj/item/clothing/suit/fluffyhalfcrop = 3, + /obj/item/clothing/under/officesexy = 3, /obj/item/clothing/ears/headphones = 10, /obj/item/clothing/suit/apron/purple_bartender = 4, /obj/item/clothing/under/rank/bartender/purple = 4, @@ -133,6 +134,7 @@ /obj/item/clothing/head/cowboyhat/pink = 3, /obj/item/clothing/shoes/cowboyboots = 3, /obj/item/clothing/shoes/cowboyboots/black = 3, + /obj/item/clothing/under/raccveralls = 3, /obj/item/clothing/under/polychromic/vsweater = 3) contraband = list(/obj/item/clothing/under/syndicate/tacticool = 3, /obj/item/clothing/under/syndicate/tacticool/skirt = 3, diff --git a/code/modules/vending/megaseed.dm b/code/modules/vending/megaseed.dm index e8e243c7f..5b87fd02d 100644 --- a/code/modules/vending/megaseed.dm +++ b/code/modules/vending/megaseed.dm @@ -46,7 +46,8 @@ /obj/item/seeds/tower = 3, /obj/item/seeds/watermelon = 3, /obj/item/seeds/wheat = 3, - /obj/item/seeds/whitebeet = 3) + /obj/item/seeds/whitebeet = 3, + /obj/item/seeds/kalyna = 3) //Hyperstation addition contraband=list(/obj/item/seeds/amanita = 2, /obj/item/seeds/glowshroom = 2, diff --git a/hyperstation/code/datums/elements/spooky.dm b/hyperstation/code/datums/elements/spooky.dm new file mode 100644 index 000000000..793f0f555 --- /dev/null +++ b/hyperstation/code/datums/elements/spooky.dm @@ -0,0 +1,38 @@ +//A spooky element, this can be added to pretty much anything, take your pick. It is effectively harmless however. +//target.AddElement(/datum/element/spooky) for example. + +/datum/element/spooky + element_flags = ELEMENT_DETACH + var/datum/C + +/datum/element/spooky/Attach(datum/target) + . = ..() + /* + if(!iscarbon(target)) + return ELEMENT_INCOMPATIBLE //keeping this uncommented would make it so it only works with carbons, which is not really needed unless one adds procs that require the user to be a carbon in the future. + */ + C = target + START_PROCESSING(SSobj, src) + +/datum/element/spooky/Detach(datum/source, force) + . = ..() + STOP_PROCESSING(SSobj, src) + +/datum/element/spooky/process() + var/spookythings = rand(120) + switch(spookythings) + if(119 to 120) //Plays a spooky ambient sound as long as one hasn't been played in a while. + var/sound = pick(SPOOKY) + for(var/mob/living/carbon/L in view(6, C)) + if(L.client && !L.client.played) + SEND_SOUND(L, sound(sound, repeat = 0, wait = 0, volume = 35, channel = CHANNEL_AMBIENCE)) + L.client.played = TRUE + addtimer(CALLBACK(L.client, /client/proc/ResetAmbiencePlayed), 600) + if(65 to 70) //Lights flicker. + var/obj/machinery/light/L = locate(/obj/machinery/light) in view(4, C) + if(L) + L.flicker() + if(1 to 5) //Very small chance to cause an unburnt tile to change to its burnt sprite. + var/turf/open/floor/T = locate(/turf/open/floor) in view(4, C) + if(T && prob(5) && !isplatingturf(T) && !istype(T, /turf/open/floor/engine/cult) && !T.burnt) + T.burn_tile() diff --git a/hyperstation/code/game/objects/items/cards.dm b/hyperstation/code/game/objects/items/cards.dm new file mode 100644 index 000000000..a2c0c1e1b --- /dev/null +++ b/hyperstation/code/game/objects/items/cards.dm @@ -0,0 +1,464 @@ +/* +* cards rework; uses TGUI and allows in-depth manipulation of cards, such as +* angles, flip states, drawing a number of cards at once, peeking, etc. +* +* sarcoph march 2022 +*/ + +#define CARD_ROTATION_UP "upright" +#define CARD_ROTATION_SIDE "sideways" +#define CARD_ROTATION_DOWN "reversed" + +// ================================= GENERAL CARDS ================================= + +/obj/item/toy/cards + var/face_up = FALSE + var/rotation = CARD_ROTATION_UP + var/merge_rank = 0 + +/obj/item/toy/cards/proc/GetAngle(_dir) + var/_angle = 0 + switch(_dir) + if(CARD_ROTATION_UP) + _angle = 0 + if(CARD_ROTATION_SIDE) + _angle = 90 + if(CARD_ROTATION_DOWN) + _angle = 180 + return _angle + +/obj/item/toy/cards/proc/GetNextAngle(angle) + var/list/_rotations = list(CARD_ROTATION_UP, CARD_ROTATION_SIDE, CARD_ROTATION_DOWN) + var/list/_indexof = _rotations.Find(rotation) + return _rotations[_indexof%3 + 1] + + +/obj/item/toy/cards/proc/RotateCards(angle) + if(angle) + rotation = angle + else + rotation = GetNextAngle(angle) + update_icon() + +/obj/item/toy/cards/proc/FlipCards(side) + if(side != null) face_up = side + else face_up = !face_up + update_icon() + + +/** + * Handles functionality for merging different types of cards. + * + * Arguments: + * * target - The "greater" card item that this object is going to merge into. + * * user - The mob that is performing this merge. + * + * Returns: + * * TRUE/FALSE: Whether or not this logic is considered "processed" - i.e., a merge + * was actually attempted. + */ +/obj/item/toy/cards/proc/MergeInto(obj/item/toy/cards/target, mob/living/user) + if(target.parentdeck != src && src.parentdeck != target && src.parentdeck != target.parentdeck) + to_chat(user, "You can't mix cards from other decks!") + return TRUE + if(!user.temporarilyRemoveItemFromInventory(src)) + to_chat(user, "\The [src] is stuck to your hand, you can't add it to \the [target]!") + return TRUE + return FALSE + // unimplemented + +/** + * Announces card(s) being added to a deck, and then deletes the card(s). + */ +/obj/item/toy/cards/proc/FinishMergingCards(obj/item/toy/cards/target, mob/living/user) + return FALSE // unimplemented + +/obj/item/toy/cards/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/toy/cards)) + var/obj/item/toy/cards/C = I + var/obj/item/toy/cards/greater = merge_rank > C.merge_rank ? src : C + var/obj/item/toy/cards/lesser = greater == src ? C : src + if(lesser.MergeInto(greater, user)) return + else + return ..() + +// ================================= DECK OF CARDS ================================= + +/obj/item/toy/cards/deck + var/peeking = FALSE + var/dealing = FALSE + merge_rank = 3 + +/** + * Randomizes positions of all cards in a deck, plays a nice sound, + * and announces this to the `user`'s surroundings. There is a small + * cooldown. + * + * Arguments: + * * user - The `mob` shuffling this deck of cards. + */ +/obj/item/toy/cards/deck/proc/ShuffleCards(mob/user) + if(cooldown < world.time - 5 SECONDS) + cards = shuffle(cards) + playsound(src, 'sound/items/cardshuffle.ogg', 50, 1) + user.visible_message("[user] shuffles the deck.", "You shuffle the deck.") + cooldown = world.time + +/** + * Draws cards into a new hand from a list of indices, turning those new + * cards into a hand. + * + * Arguments: + * * card_indices - The `list` of card indices to remove from `cards`. + * + * Returns: + * * /obj/item/toy/cards/cardhand - A new hand containing the removed cards. + * OR + * * /obj/item/toy/cards/singlecard - A single card, if there is only one index. + */ +/obj/item/toy/cards/deck/proc/DrawCards(list/card_indices) + if(card_indices.len == 1) return DrawOneCard(card_indices) + var/obj/item/toy/cards/cardhand/H = new/obj/item/toy/cards/cardhand(usr.loc) + var/list/cards_to_remove = list() + for(var/C in card_indices) + var/card_to_add = cards[C] + card_to_add["rotation"] = rotation + card_to_add["face_up"] = face_up + H.currenthand += list(card_to_add) + cards_to_remove += list(card_to_add) + cards -= cards_to_remove + H.parentdeck = src + H.apply_card_vars(H,src) + update_icon() + return H + +/obj/item/toy/cards/deck/proc/DrawOneCard(list/card_indices) + var/obj/item/toy/cards/singlecard/S = new/obj/item/toy/cards/singlecard(usr.loc) + var/_card = cards[card_indices[1]] + _card["rotation"] = rotation + _card["face_up"] = face_up + S.card = _card + S.rotation = _card["rotation"] + S.face_up = _card["face_up"] + S.parentdeck = src + S.apply_card_vars(S,src) + cards -= list(_card) + return S + +/obj/item/toy/cards/deck/FinishMergingCards(obj/item/toy/cards/target, mob/living/user) + var/message = "[user] adds \the [target] to the bottom of \the [src]." + var/self_message = "You add \the [target] to the bottom of \the [src]." + user.visible_message(message, self_message) + qdel(target) + update_icon() + +/obj/item/toy/cards/deck/examine(mob/user) + . = ..() + . += "Alt-Click to quick-draw a card." + +/obj/item/toy/cards/deck/AltClick(mob/user) + . = ..() + if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) + return + var/obj/item/toy/cards/drawn = DrawCards(list(1)) + drawn.pickup(user) + user.put_in_hands(drawn) + to_chat(user, "You draw \a [drawn] from \the [src].") + + +// =================== TGUI stuff =================== + +/obj/item/toy/cards/deck/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/tgui_state/state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "CardsDeck", name, 300, 400, master_ui, state) + ui.open() + +/obj/item/toy/cards/deck/ui_act(action, params) + if(..()) + return + var/list/targets = list(usr, src) + switch(action) + if("deal") + var/n_cards = text2num(params["count"]) + var/n_hands = text2num(params["hands"]) + if(dealing) return FALSE + if(!n_cards || !n_hands) return FALSE + if(n_cards * n_hands > cards.len) + to_chat(usr, "You can't deal more cards than there are in the deck!") + return FALSE + dealing = TRUE + to_chat(usr, "You get ready to deal cards...") + if(do_after_mob(usr, targets, 1 SECONDS, progress=TRUE)) + visible_message("[usr] begins dealing cards.") + var/list/holder = list() + var/broken = FALSE + for(var/C = 1, C <= n_cards, C++) holder += C // card indices placeholder + for(var/H = 1, H <= n_hands, H++) + if(do_after_mob(usr, targets, 0.5 SECONDS, progress=TRUE) && dealing) + var/obj/item/toy/cards/hand = DrawCards(holder) + step(hand, GLOB.alldirs[H]) + else + broken = TRUE + break + if(broken) + visible_message("[usr] stops in the middle of dealing cards!") + dealing = FALSE + if("draw") + if(!params["cards"]) return FALSE + var/list/P = splittext(params["cards"], ",") + var/list/P_cards = list() + for(var/T in P) + P_cards += text2num(T) + 1 + var/obj/item/toy/cards/H = DrawCards(P_cards) + H.pickup(usr) + usr.put_in_hands(H) + if("flip") + var/P_side = params["side"] + var/flip_side = null + if(P_side) + flip_side = P_side == "face_down" ? FALSE : P_side == "face_up" ? TRUE : null + FlipCards(flip_side) + return TRUE + if("peek") + visible_message("[usr] is peeking in \the [name]!") + if(do_after_mob(usr, targets, 3 SECONDS, progress=TRUE)) + to_chat(usr, "You peek into \the [name].") + peeking = TRUE + if("rotate") + RotateCards(params["angle"]) + return TRUE + if("shuffle") + peeking = FALSE + ShuffleCards(usr) + update_icon() + return TRUE + +/obj/item/toy/cards/deck/ui_data(mob/user) + var/list/data = list() + data["face_up"] = face_up + data["rotation"] = rotation + data["cards"] = cards + data["name"] = name + data["peeking"] = peeking + return data + +/obj/item/toy/cards/deck/ui_static_data(mob/user) + var/list/data = list() + data["possible_rotations"] = list(CARD_ROTATION_UP, CARD_ROTATION_SIDE, CARD_ROTATION_DOWN) + return data + +/obj/item/toy/cards/deck/ui_close() + dealing = FALSE + peeking = FALSE + + +// ================================= HAND OF CARDS ================================= + +/obj/item/toy/cards/cardhand + merge_rank = 2 + face_up = TRUE + +/obj/item/toy/cards/cardhand/proc/QuickAnnounce(mob/living/user) + if(!user.is_holding(src)) + to_chat(user, "You need to be holding \the [src] to show it!") + return + if(user.stat || user.restrained()) + return + var/list/temp_cards = list() + var/facedown = 0 + for(var/C in currenthand) + var/_flipped = C["face_up"] || face_up + var/_angle = C["rotation"] || rotation + if(_flipped) + if(is_all_same_direction()) + temp_cards += "\a [C["name"]]" + else + temp_cards += "\a [_angle] [C["name"]]" + else + facedown++ + if(facedown > 0) + temp_cards += "[facedown] unrevealed card\s" + visible_message("[user] shows [user.p_their()] hand: [english_list(temp_cards)].") + +/obj/item/toy/cards/cardhand/proc/is_all_same_direction() + var/compare_orient = currenthand[1]["rotation"] + for(var/list/C in currenthand) + var/_rotation = C["rotation"] || rotation + if(_rotation != compare_orient) return FALSE + return TRUE + +/obj/item/toy/cards/cardhand/proc/DrawOneCard(list/card_indices) + var/obj/item/toy/cards/singlecard/S = new/obj/item/toy/cards/singlecard(usr.loc) + var/_card = currenthand[card_indices[1]] + S.card = _card + S.rotation = _card["rotation"] + S.face_up = _card["face_up"] + S.parentdeck = src.parentdeck + S.apply_card_vars(S,src) + currenthand -= list(_card) + S.update_icon() + update_icon() + return S + +/obj/item/toy/cards/cardhand/MergeInto(obj/item/toy/cards/target, mob/living/user) + if(..()) + return TRUE + if(istype(target, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/C = target + for(var/_card = 1, _card < currenthand.len, _card++) + currenthand[_card]["rotation"] = null + currenthand[_card]["face_up"] = null + C.cards += currenthand + C.FinishMergingCards(src, user) + else if(istype(target, /obj/item/toy/cards/cardhand)) + if(do_after_mob(user, list(user,src,target), 0.5 SECONDS)) + var/obj/item/toy/cards/cardhand/C = target + C.currenthand += currenthand + C.FinishMergingCards(src, user) + else + return FALSE + return TRUE + +/obj/item/toy/cards/cardhand/FinishMergingCards(obj/item/toy/cards/target, mob/living/user) + user.visible_message("[user] combines \the [target] into [user.p_their()] hand.",\ + "You combine \the [target] into the [src].") + qdel(target) + update_icon() + +/obj/item/toy/cards/cardhand/examine(mob/user) + . = ..() + . += "Alt-Click to quick-announce your deck." + +/obj/item/toy/cards/cardhand/AltClick(mob/user) + . = ..() + QuickAnnounce(user) + + +// =================== TGUI stuff =================== + +/obj/item/toy/cards/cardhand/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/tgui_state/state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "CardsHand", name, 300, 400, master_ui, state) + ui.open() + +/obj/item/toy/cards/cardhand/ui_act(action, params) + if(..()) + return + switch(action) + if("draw") + if(!params["cards"]) return FALSE + var/list/P = splittext(params["cards"], ",") + var/list/P_cards = list() + for(var/T in P) + P_cards += text2num(T) + 1 + var/obj/item/toy/cards/H = DrawOneCard(P_cards) + H.pickup(usr) + usr.put_in_hands(H) + if("flip") + if(!params["card"]) return FALSE + var/card = currenthand[text2num(params["card"]) + 1] + card["face_up"] = !card["face_up"] + if("rotate") + if(!params["card"]) return FALSE + var/card = currenthand[text2num(params["card"]) + 1] + if(params["angle"]) + card["rotation"] = params["angle"] + update_icon() + return TRUE + +/obj/item/toy/cards/cardhand/ui_data(mob/user) + var/list/data = list() + data["cards"] = currenthand + data["name"] = name + return data + +/obj/item/toy/cards/cardhand/ui_static_data(mob/user) + var/list/data = list() + data["possible_rotations"] = list(CARD_ROTATION_UP, CARD_ROTATION_SIDE, CARD_ROTATION_DOWN) + return data + + +// ================================= SINGLE CARDS ================================= + +/obj/item/toy/cards/singlecard + merge_rank = 1 + +/obj/item/toy/cards/singlecard/MergeInto(obj/item/toy/cards/target, mob/living/user) + if(..()) + return TRUE + if(istype(target, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/C = target + card["rotation"] = null + card["face_up"] = null + C.cards += list(card) + C.FinishMergingCards(src, user) + else if(istype(target, /obj/item/toy/cards/cardhand)) + var/obj/item/toy/cards/cardhand/C = target + C.currenthand += list(card) + C.FinishMergingCards(src, user) + else if(istype(target, /obj/item/toy/cards/singlecard)) + var/obj/item/toy/cards/singlecard/C = target + var/obj/item/toy/cards/cardhand/H = new/obj/item/toy/cards/cardhand(user.loc) + H.currenthand += list(C.card) + H.currenthand += list(src.card) + H.parentdeck = C.parentdeck + H.apply_card_vars(H,C) + H.pickup(user) + user.put_in_hands(H) + C.FinishMergingCards(src, user) + else + return FALSE + return TRUE + +/obj/item/toy/cards/singlecard/proc/FlipCard(mob/user) + if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) + return + FlipCards() + to_chat(user, "You flip \the [src] [(face_up ? "face-up" : "face-down")].") + +/obj/item/toy/cards/singlecard/proc/RotateCard(mob/user, var/rotation_angle) + if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) + return + to_chat(user, "You turn \the [src] to \a [rotation_angle] position.") + RotateCards(rotation_angle) + + +/obj/item/toy/cards/singlecard/FinishMergingCards(obj/item/toy/cards/singlecard/target, mob/living/user) + to_chat(user, "You combine the [target.card["name"]] and the [src.card["name"]] into a hand.") + qdel(target) + qdel(src) + update_icon() + +/obj/item/toy/cards/singlecard/examine(mob/user) + . = ..() + . += "Click to flip. Alt-Click to rotate." + +/obj/item/toy/cards/singlecard/attack_self(mob/user) + . = ..() + FlipCard(user) + +/obj/item/toy/cards/singlecard/AltClick(mob/user) + . = ..() + RotateCard(user, GetNextAngle(rotation)) + +/obj/item/toy/cards/singlecard/update_icon() + . = ..() + var/matrix/rot_matrix = matrix() + rot_matrix.Turn(GetAngle(rotation)) + transform = rot_matrix + + var/rotation_name = (rotation == CARD_ROTATION_UP ? "" : rotation + " ") + if(face_up) + if(card) + src.icon_state = "sc_[card["icon_state"] || card["name"]]_[deckstyle]" + src.name = rotation_name + src.card["name"] + else + src.icon_state = "sc_aceofspades_[deckstyle]" + src.name = "What Card" + src.pixel_x = 5 + else + src.icon_state = "singlecard_down_[deckstyle]" + src.name = rotation_name + "card" + src.pixel_x = -5 \ No newline at end of file diff --git a/hyperstation/code/game/objects/items/lovedice.dm b/hyperstation/code/game/objects/items/lovedice.dm new file mode 100644 index 000000000..2048fce55 --- /dev/null +++ b/hyperstation/code/game/objects/items/lovedice.dm @@ -0,0 +1,92 @@ +/* +lover's dice: based off a really funny yakuza gif i saw on tumblr years ago +these give suggestions for sex acts to perform. it's stupid but fun. + +sarcoph mar 2022 +*/ + +// dice bag + +/obj/item/storage/pill_bottle/lovedice + name = "bag of love dice" + desc = "Contains all the intimate ideas you'll ever need. A game that everyone wins!" + icon = 'hyperstation/icons/obj/toy.dmi' + icon_state = "lovedicebag" + price = 1 + +/obj/item/storage/pill_bottle/lovedice/Initialize() + . = ..() + new /obj/item/dice/lover/d6_gesture(src) + new /obj/item/dice/lover/d6_location(src) + new /obj/item/dice/lover/d6_action(src) + new /obj/item/dice/lover/d6_bodypart(src) + + +// dice + +/obj/item/dice/lover + desc = "A die with six sides to inspire some bedroom action." + icon = 'hyperstation/icons/obj/toy.dmi' + sides = 6 + +/obj/item/dice/lover/update_icon() + return // override the dice proc for this, there is no overlay + +/obj/item/dice/lover/examine(mob/user) + . = ..() // again, no overlays + . += "The top reads [result]." + + +// actual dice + +/obj/item/dice/lover/d6_gesture + name = "lover's d6 (v1)" + desc = "A die with six sides to inspire some bedroom action. This one has intimate gestures." + icon_state = "loved6_1" + special_faces = list( + "Let's hug", + "Let's kiss", + "Let's play", + "Let's fuck", // it actually says "let's do it" on the irl ones lol + "Let's wrestle", + "Let's ?" + ) + +/obj/item/dice/lover/d6_location + name = "lover's d6 (v2)" + desc = "A die with six sides to inspire some bedroom action. This one has different locations." + icon_state = "loved6_2" + special_faces = list( + "On a chair", + "On the bed", + "On the floor", + "In the closet", + "In the bathtub", + "In the ?" + ) + +/obj/item/dice/lover/d6_action + name = "lover's d6 (v3)" + desc = "A die with six sides to inspire some bedroom action. This one has different actions." + icon_state = "loved6_3" + special_faces = list( + "Caress my", + "Kiss my", + "Grab my", + "Rub my", + "Tickle my", + "Surprise!" + ) + +/obj/item/dice/lover/d6_bodypart + name = "lover's d6 (v4)" + desc = "A die with six sides to inspire some bedroom action. This one has body parts." + icon_state = "loved6_4" + special_faces = list( + "Back", + "Chest", + "Face", + "Ass", + "Genitals", + "Surprise!" + ) \ No newline at end of file diff --git a/hyperstation/code/game/objects/railings.dm b/hyperstation/code/game/objects/railings.dm index 561765f68..2c01a1721 100644 --- a/hyperstation/code/game/objects/railings.dm +++ b/hyperstation/code/game/objects/railings.dm @@ -151,15 +151,18 @@ usr.visible_message("[user] starts climbing onto \the [src]!") - if(!do_after(user, 20)) + if(!do_after(user, 15, src)) return if(get_turf(user) == get_turf(src)) + usr.dir = get_dir(usr.loc, get_step(src, src.dir))//turn and face railing usr.forceMove(get_step(src, src.dir)) else + usr.dir = get_dir(usr.loc, loc)//turn and face railing usr.forceMove(get_turf(src)) usr.visible_message("[user] climbed over \the [src]!") + usr.do_twist(targetangle = 45, timer = 8) /obj/structure/railing/handrail name = "handrail" diff --git a/hyperstation/code/mobs/crystal.dm b/hyperstation/code/mobs/crystal.dm index a949237a0..1a375672d 100644 --- a/hyperstation/code/mobs/crystal.dm +++ b/hyperstation/code/mobs/crystal.dm @@ -7,7 +7,8 @@ icon_state = "crystal" icon_living = "crystal" icon_dead = "crystal" - mob_biotypes = MOB_BEAST + gender = NEUTER + mob_biotypes = MOB_INORGANIC speak_chance = 0 turns_per_move = 10 response_help = "touches" @@ -29,6 +30,7 @@ atmos_requirements = list("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 maxbodytemp = 3500 + blood_volume = 0 //It's a fucking rock faction = list("carp") movement_type = FLYING pressure_resistance = 500 diff --git a/hyperstation/code/modules/arousal/arousalhud.dm b/hyperstation/code/modules/arousal/arousalhud.dm index ff7725ddb..509438813 100644 --- a/hyperstation/code/modules/arousal/arousalhud.dm +++ b/hyperstation/code/modules/arousal/arousalhud.dm @@ -211,13 +211,7 @@ return if(href_list["shrink_belly"]) - var/obj/item/organ/genital/belly/E = usr.getorganslot("belly") - if(E.size > 0) - to_chat(usr, "You feel your belly diminish.") - E.size -= 1 - H.update_genitals() - else - to_chat(usr, "Your belly is already at the minimum size! ") + H.shrink_belly(1) if(href_list["removecondom"]) H.menuremovecondom() diff --git a/hyperstation/code/modules/arousal/expansion.dm b/hyperstation/code/modules/arousal/expansion.dm new file mode 100644 index 000000000..5000c16d3 --- /dev/null +++ b/hyperstation/code/modules/arousal/expansion.dm @@ -0,0 +1,35 @@ +/mob/living/carbon/proc/expand_belly(expansion = 1) + var/obj/item/organ/genital/belly/_belly = getorganslot("belly") + if(!expansion || !_belly || !_belly.inflatable) + return + var/original_size = _belly.size + _belly.size = clamp(_belly.size + expansion, BELLY_MIN_SIZE, BELLY_MAX_SIZE) + if(_belly.size == original_size) + return + var/_verb = "expand" + switch(_belly.size) + if(BELLY_MAX_SIZE to INFINITY) + _verb = "stretch to its limit" + if(BELLY_STRETCH_SIZE to BELLY_MAX_SIZE) + _verb = "stretch" + if(BELLY_STRAIN_SIZE to BELLY_STRETCH_SIZE) + _verb = "strain" + to_chat(src, "You feel your belly [_verb].") + _belly.update() + + +/mob/living/carbon/proc/shrink_belly(shrinkage = 1) + var/obj/item/organ/genital/belly/_belly = getorganslot("belly") + if(!shrinkage ||!_belly || !_belly.inflatable) + return + var/original_size = _belly.size + _belly.size = clamp(_belly.size - shrinkage, BELLY_MIN_SIZE, BELLY_MAX_SIZE) + if(_belly.size == original_size) + return + var/_verb = "diminish" + var/_class = "userlove" + if(_belly.size == BELLY_MIN_SIZE) + _verb = "can't shrink anymore" + _class = "warning" + to_chat(src, "You feel your belly [_verb].") + _belly.update() \ No newline at end of file diff --git a/hyperstation/code/modules/client/loadout/gloves.dm b/hyperstation/code/modules/client/loadout/gloves.dm new file mode 100644 index 000000000..ad12c3e3e --- /dev/null +++ b/hyperstation/code/modules/client/loadout/gloves.dm @@ -0,0 +1,11 @@ +/datum/gear/syntech/ring + name = "Normalizer Ring" + category = SLOT_GLOVES + path = /obj/item/clothing/gloves/ring/syntech + cost = 6 + +/datum/gear/syntech/band + name = "Normalizer Band" + category = SLOT_GLOVES + path = /obj/item/clothing/gloves/ring/syntech/band + cost = 6 diff --git a/hyperstation/code/modules/client/loadout/mask.dm b/hyperstation/code/modules/client/loadout/mask.dm new file mode 100644 index 000000000..0406a96f7 --- /dev/null +++ b/hyperstation/code/modules/client/loadout/mask.dm @@ -0,0 +1,5 @@ +/datum/gear/gasmask + name = "gas mask" + category = SLOT_WEAR_MASK + path = /obj/item/clothing/mask/gas + cost = 3 \ No newline at end of file diff --git a/hyperstation/code/modules/client/loadout/neck.dm b/hyperstation/code/modules/client/loadout/neck.dm new file mode 100644 index 000000000..a21c87434 --- /dev/null +++ b/hyperstation/code/modules/client/loadout/neck.dm @@ -0,0 +1,17 @@ +/datum/gear/syntech/pendant + name = "Normalizer Pendant" + category = SLOT_NECK + path = /obj/item/clothing/neck/syntech + cost = 6 + +/datum/gear/syntech/choker + name = "Normalizer Choker" + category = SLOT_NECK + path = /obj/item/clothing/neck/syntech/choker + cost = 6 + +/datum/gear/syntech/collar + name = "Normalizer Collar" + category = SLOT_NECK + path = /obj/item/clothing/neck/syntech/collar + cost = 6 diff --git a/hyperstation/code/modules/clothing/sizeaccessories.dm b/hyperstation/code/modules/clothing/sizeaccessories.dm new file mode 100644 index 000000000..e891672c2 --- /dev/null +++ b/hyperstation/code/modules/clothing/sizeaccessories.dm @@ -0,0 +1,115 @@ +//Clothing vars and procs +/obj/item/clothing + var/normalize_size = RESIZE_NORMAL //This number is used as the "normal" height people will be given when wearing one of these accessories + var/natural_size = null //The value of the wearer's body_size var in prefs. Unused for now. + var/recorded_size = null //the user's height prior to equipping + +//For applying a normalization +/obj/item/clothing/proc/normalize_mob_size(mob/living/carbon/human/H) + if(H.normalized) //First we make a check to see if they're already normalized, from wearing another article of SynTech jewelry + to_chat(H, "This accessory buzzes, being overwritten by another.") + playsound(H, 'sound/machines/buzz-sigh.ogg', 50, 1) + return + recorded_size = H.get_effective_size() //If not, grab their current size + playsound(H, 'sound/effects/magic.ogg', 50, 1) + flash_lighting_fx(3, 3, LIGHT_COLOR_PURPLE) + H.visible_message("A flash of purple light engulfs [H], before they change to normal!","You feel warm for a moment, before everything scales to your size...") + H.resize(normalize_size) //Then apply the size + H.normalized = TRUE //And set normalization + +//For removing a normalization, and reverting back to normal +/obj/item/clothing/proc/denormalize_mob_size(mob/living/carbon/human/H) + if(H.normalized) //sanity check + playsound(H,'sound/weapons/emitter2.ogg', 50, 1) + flash_lighting_fx(3, 3, LIGHT_COLOR_YELLOW) + H.visible_message("Golden light engulfs [H], and they shoot back to their default height!","Energy rushes through your body, and you return to normal.") + H.resize(recorded_size) + H.normalized = FALSE + + +//For storing normalization on mobs +/mob/living + var/normalized = FALSE + //normalized is a check for instances where more than one accessory of jewelry is worn. For all intensive purposes, only the first worn accessory stores the user's size. + //Anything else is just extra. + +//Clothing below. Code could be compressed more, but until I make jewelry slots, this will do. -Dahl + +//GLOVE SLOT ITEMS... +//SynTech ring +/obj/item/clothing/gloves/ring/syntech + name = "normalizer ring" + desc = "An expensive, shimmering SynTech ring gilded with golden Kinaris markings. It will 'normalize' the size of the user to a specified height approved for work-conditions, as long as it is equipped. The artificial violet gem inside twinkles ominously." + icon = 'hyperstation/icons/obj/clothing/sizeaccessories.dmi' + icon_state = "ring" + item_state = "sring" //No use in a unique sprite since it's just one pixel + w_class = WEIGHT_CLASS_TINY + body_parts_covered = 0 + transfer_prints = TRUE + strip_delay = 40 + //These are already defined under the parent ring, but I wanna leave em here for reference purposes + +//For glove slots +/obj/item/clothing/gloves/ring/syntech/equipped(mob/living/user, slot) + if(ishuman(user)) + var/mob/living/carbon/human/human_target = user + if(slot == SLOT_GLOVES) + + if(human_target.custom_body_size) + normalize_mob_size(human_target) + +/obj/item/clothing/gloves/ring/syntech/dropped(mob/living/user, slot) + if(ishuman(user)) + var/mob/living/carbon/human/human_target = user + + if(human_target.normalized) + denormalize_mob_size(human_target) + + + +//SynTech Wristband +/obj/item/clothing/gloves/ring/syntech/band + name = "normalizer wristband" + desc = "An expensive technological wristband cast in SynTech purples with shimmering Kinaris golds. It will 'normalize' the size of the user to a specified height for approved work-conditions, as long as it is equipped. There is a small screen buzzing with information." + icon_state = "wristband" + item_state = "syntechband" + + +//NECK SLOT ITEMS... +//Syntech Pendant +/obj/item/clothing/neck/syntech + name = "normalizer pendant" + desc = "A vibrant violet jewel cast in silvery-gold metals, sporting the elegance of Kinaris with SynTech prowess. It will 'normalize' the size of the user to a specified height for approved work-conditions, as long as it is equipped. The artificial violet gem inside twinkles ominously." + icon = 'hyperstation/icons/obj/clothing/sizeaccessories.dmi' + icon_state = "pendant" + item_state = "pendant" + +//For neck items +/obj/item/clothing/neck/syntech/equipped(mob/living/user, slot) + if(ishuman(user)) + var/mob/living/carbon/human/human_target = user + if(slot == SLOT_NECK) + + if(human_target.custom_body_size) + normalize_mob_size(human_target) + +/obj/item/clothing/neck/syntech/dropped(mob/living/user, slot) + if(ishuman(user)) + var/mob/living/carbon/human/human_target = user + + if(human_target.normalized) + denormalize_mob_size(human_target) + +//Syntech Choker +/obj/item/clothing/neck/syntech/choker + name = "normalizer choker" + desc = "A sleek, tight-fitting choker embezzled with silver to gold, adorned with vibrant purple studs; combined technology of Kinaris and SynTech. It will 'normalize' the size of the user to a specified height for approved work-conditions, as long as it is equipped. There is a small screen buzzing with information." + icon_state = "choker" + item_state = "collar" + +//Syntech Collar +/obj/item/clothing/neck/syntech/collar + name = "normalizer collar" + desc = "A cute pet collar, technologically designed with vibrant purples and smooth silvers. There is a small gem bordered by gold at the front, reading 'SYNTECH' engraved within the metal. It will 'normalize' the size of the user to a specified height for approved work-conditions, as long as it is equipped. The artificial violet gem inside twinkles ominously." + icon_state = "collar" + item_state = "collar" diff --git a/hyperstation/code/modules/food_and_drinks/recipes/drinks_recipes.dm b/hyperstation/code/modules/food_and_drinks/recipes/drinks_recipes.dm index 05fedabf7..83e49f04f 100644 --- a/hyperstation/code/modules/food_and_drinks/recipes/drinks_recipes.dm +++ b/hyperstation/code/modules/food_and_drinks/recipes/drinks_recipes.dm @@ -3,3 +3,9 @@ id = /datum/reagent/consumable/pilk results = list(/datum/reagent/consumable/pilk = 2) required_reagents = list(/datum/reagent/consumable/milk = 1, /datum/reagent/consumable/space_cola = 1) + +/datum/chemical_reaction/javelin + name = "Javelin" + id = /datum/reagent/consumable/ethanol/javelin + results = list(/datum/reagent/consumable/ethanol/javelin = 5) + required_reagents = list(/datum/reagent/consumable/kalynajuice = 1, /datum/reagent/consumable/ethanol/whiskey = 2, /datum/reagent/consumable/ethanol/vodka = 2) diff --git a/hyperstation/code/modules/hydroponics/grown/kalyna.dm b/hyperstation/code/modules/hydroponics/grown/kalyna.dm new file mode 100644 index 000000000..55a681ab4 --- /dev/null +++ b/hyperstation/code/modules/hydroponics/grown/kalyna.dm @@ -0,0 +1,40 @@ +// Kalyna Berries +/obj/item/seeds/kalyna + name = "pack of kalyna berry seeds" + desc = "Seeds that grow into Kalyna plants. Take that Red Kalyna and rise it up." + icon = 'hyperstation/icons/obj/hydroponics/seeds.dmi' + icon_state = "seed-kalyna" + species = "kalyna" + plantname = "Kalyna Shrub Tree" + product = /obj/item/reagent_containers/food/snacks/grown/kalyna + lifespan = 20 + maturation = 5 + production = 5 + growthstages = 5 + yield = 4 + growing_icon = 'hyperstation/icons/obj/hydroponics/growing_fruits.dmi' + icon_grow = "kalyna-grow" + icon_dead = "kalyna-dead" + genes = list(/datum/plant_gene/trait/repeated_harvest) + reagents_add = list(/datum/reagent/consumable/nutriment/vitamin = 0.04, /datum/reagent/consumable/nutriment = 0.1) + +/obj/item/reagent_containers/food/snacks/grown/kalyna + seed = /obj/item/seeds/kalyna + name = "branch of kalyna" + desc = "Red berries, attached to a branch. Just looking at it makes you feel an aura of unity." + icon = 'hyperstation/icons/obj/hydroponics/harvest.dmi' + icon_state = "kalynaberries" + gender = PLURAL + filling_color = "#FF0000" + bitesize_mod = 2 + foodtype = FRUIT + juice_results = list(/datum/reagent/consumable/kalynajuice = 1) + tastes = list("sweet cranberries" = 1) + distill_reagent = /datum/reagent/consumable/ethanol/gin + +//For those of you wondering, yes, this is very much an addition in support of Ukraine. I decided to make these after a distant Ukranian relative of mine sent me Go_A's 'Kalyna' song. +//Our future as a free world could very well depend on the heroism of the people of glorious Ukraine. We owe our freedom to them. +//I know that this game should be for escapism, but there's only so much we should escape from. When people's lives are being upturned-- Or taken by a brutal invasive reigime, perhaps then it's when we should take a stand. +//And for those that oppose the freedom of the people of Ukraine, Russia, and Belarus; Prykhyl'nyk Putina, idy na khuy. And show some basic empathy for once, it's not that hard. +//Glory to Ukraine, Glory to the Heroes! +// - Arctaisia, Hyperstation developer and spriter, 23/04/2022 #StandWithUkraine diff --git a/hyperstation/code/modules/mob/dead/new_player/sprite_accessories.dm b/hyperstation/code/modules/mob/dead/new_player/sprite_accessories.dm index af6f7fe04..f76d2ddf0 100644 --- a/hyperstation/code/modules/mob/dead/new_player/sprite_accessories.dm +++ b/hyperstation/code/modules/mob/dead/new_player/sprite_accessories.dm @@ -67,6 +67,35 @@ SNOUTS icon = 'hyperstation/icons/mob/char_snouts.dmi' recommended_species = list("insect") +/datum/sprite_accessory/mam_snouts/easterndragon + name = "Eastern Dragon (Hyper)" + icon_state = "easterndw" + icon = 'hyperstation/icons/mob/char_snouts.dmi' + +/datum/sprite_accessory/mam_snouts/feasterndragon + name = "Eastern Dragon (Top) (Hyper)" + icon_state = "feasterndw" + icon = 'hyperstation/icons/mob/char_snouts.dmi' + +/datum/sprite_accessory/mam_snouts/easterndragonnowhiskers + name = "Eastern Dragon - No Whiskers (Hyper)" + icon_state = "easterndnw" + icon = 'hyperstation/icons/mob/char_snouts.dmi' + +/datum/sprite_accessory/mam_snouts/feasterndragonnowhiskers + name = "Eastern Dragon - No Whiskers (Top) (Hyper)" + icon_state = "feasterndnw" + icon = 'hyperstation/icons/mob/char_snouts.dmi' + +/datum/sprite_accessory/mam_snouts/fchemlight + name = "RadDog (Top) (Hyper)" + icon_state = "fchemlight" + icon = 'hyperstation/icons/mob/char_snouts.dmi' + +/datum/sprite_accessory/mam_snouts/chemlight + name = "RadDog (Hyper)" + icon_state = "chemlight" + icon = 'hyperstation/icons/mob/char_snouts.dmi' /* EARS @@ -109,6 +138,15 @@ EARS icon = 'hyperstation/icons/mob/char_ears.dmi' recommended_species = list("insect") +/datum/sprite_accessory/mam_ears/easterndragon + name = "Eastern Dragon (Hyper)" + icon_state = "easternd" + icon = 'hyperstation/icons/mob/char_ears.dmi' + +/datum/sprite_accessory/mam_ears/chemlight + name = "RadDog (Hyper)" + icon_state = "chemlight" + icon = 'hyperstation/icons/mob/char_ears.dmi' /* WINGS @@ -276,6 +314,26 @@ TAILS + ANIMATED TAILS icon_state = "swallowstriped" icon = 'hyperstation/icons/mob/char_tails.dmi' +/datum/sprite_accessory/mam_tails/easterndragon //Pulled base from Virgo, seriously love the server and love you guys, stay lovely. + name = "Eastern Dragon (Hyper)" + icon_state = "easternd" + icon = 'hyperstation/icons/mob/char_tails.dmi' + +/datum/sprite_accessory/mam_tails_animated/easterndragon + name = "Eastern Dragon (Hyper)" + icon_state = "easternd" + icon = 'hyperstation/icons/mob/char_tails.dmi' + +/datum/sprite_accessory/mam_tails/chemlight + name = "RadDog (Hyper)" + icon_state = "chemlight" + icon = 'hyperstation/icons/mob/char_tails.dmi' + +/datum/sprite_accessory/mam_tails_animated/chemlight + name = "RadDog (Hyper)" + icon_state = "chemlight" + icon = 'hyperstation/icons/mob/char_tails.dmi' + /* BODY MARKINGS @@ -335,6 +393,22 @@ from modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm: icon = 'hyperstation/icons/mob/char_markings.dmi' recommended_species = list("avian") +/datum/sprite_accessory/mam_body_markings/easterndragon + name = "Eastern Dragon (Hyper)" + icon_state = "easternd" + icon = 'hyperstation/icons/mob/char_markings.dmi' + +//doged was here +/datum/sprite_accessory/mam_body_markings/chemlight + name = "RadDog (Hyper)" + icon_state = "chemlight" + icon = 'hyperstation/icons/mob/char_markings.dmi' + +//racc do a code maybe it won't explode +/datum/sprite_accessory/mam_body_markings/raccalt + name = "RaccAlt (Hyper)" + icon_state = "raccalt" + icon = 'hyperstation/icons/mob/char_markings.dmi' /* TAUR BODIES @@ -355,7 +429,17 @@ from modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm: color_src = MATRIXED recommended_species = list("human", "lizard", "insect", "mammal", "xeno", "jelly", "slimeperson", "podweak", "avian", "aquatic") */ +/datum/sprite_accessory/taur/chemnaga //Chemlight experimental sprites for future spriting + name = "RadDog Naga (Hyper)" + icon_state = "chemnaga" + taur_mode = SNEK_TAURIC + ckeys_allowed = list("chemlight") +/datum/sprite_accessory/taur/chemlight + name = "RadDog Taur (Hyper)" + icon_state = "chemlight" + taur_mode = PAW_TAURIC + ckeys_allowed = list("chemlight") /* HAIRSTYLES diff --git a/hyperstation/code/modules/mob/living/carbon/carbon.dm b/hyperstation/code/modules/mob/living/carbon/carbon.dm index 27d4e3283..ec2f69c1d 100644 --- a/hyperstation/code/modules/mob/living/carbon/carbon.dm +++ b/hyperstation/code/modules/mob/living/carbon/carbon.dm @@ -97,7 +97,7 @@ stuttering += 3 //stutter words //if they are asleep, this wont trigger. - if (total_pain > 110 && stat == 0) //taking 77 all damage at once from full health, will put you into shock and kill you. This cant be achived with chip damage (or fist fights), because youll die before you reach this pain level. + if (total_pain > 120 && stat == 0) //taking 130 all damage at once from full health, will put you into shock and kill you. This cant be achived with chip damage (or fist fights), because youll die before you reach this pain level. if(prob(50)) emote("scream")//scream to_chat(src, "You give into the pain...") diff --git a/hyperstation/code/modules/mob/living/simple_animal/friendly/chicken_types.dm b/hyperstation/code/modules/mob/living/simple_animal/friendly/chicken_types.dm new file mode 100644 index 000000000..e69de29bb diff --git a/hyperstation/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/hyperstation/code/modules/mob/living/simple_animal/friendly/farm_animals.dm new file mode 100644 index 000000000..3d12f301b --- /dev/null +++ b/hyperstation/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -0,0 +1,246 @@ +/mob/living/simple_animal/ + var/happiness = 50 //how happy they are. + + + + ///////////// +//////////////// CHICKEN ///////////////// + ///////////// + +/mob/living/simple_animal/chick + icon = 'hyperstation/icons/mob/chickens.dmi' + name = "\improper chick" + desc = "Adorable! They make such a racket though." + icon_state = "chick" + icon_living = "chick" + icon_dead = "chick_dead" + icon_gib = "chick_gib" + gender = FEMALE + mob_biotypes = MOB_ORGANIC|MOB_BEAST + speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") + speak_emote = list("cheeps") + emote_hear = list("cheeps.") + emote_see = list("pecks at the ground.","flaps its tiny wings.") + density = FALSE + speak_chance = 2 + turns_per_move = 2 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 1) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = "kicks" + health = 3 + maxHealth = 3 + ventcrawler = VENTCRAWLER_ALWAYS + var/amount_grown = 0 + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + mob_size = MOB_SIZE_TINY + gold_core_spawnable = FRIENDLY_SPAWN + + do_footstep = TRUE + +/mob/living/simple_animal/chick/Initialize() + . = ..() + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + +/mob/living/simple_animal/chick/Life() + . =..() + if(!.) + return + if(!stat && !ckey) + amount_grown += rand(1,2) + if(amount_grown >= 100) + new /mob/living/simple_animal/chicken(src.loc) + qdel(src) + +/mob/living/simple_animal/chick/holo/Life() + ..() + amount_grown = 0 + +/mob/living/simple_animal/chicken + icon = 'hyperstation/icons/mob/chickens.dmi' + name = "\improper chicken" + desc = "Hopefully the eggs are good this season." + gender = FEMALE + mob_biotypes = MOB_ORGANIC|MOB_BEAST + icon_state = "chicken_brown" + icon_living = "chicken_brown" + icon_dead = "chicken_brown_dead" + speak = list("Cluck!","BWAAAAARK BWAK BWAK BWAK!","Bwaak bwak.") + speak_emote = list("clucks","croons") + emote_hear = list("clucks.") + emote_see = list("pecks at the ground.","flaps its wings viciously.") + density = FALSE + speak_chance = 2 + turns_per_move = 3 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 2) + var/egg_type = /obj/item/reagent_containers/food/snacks/egg + var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = "kicks" + health = 15 + maxHealth = 15 + ventcrawler = VENTCRAWLER_ALWAYS + var/eggsleft = 0 + var/eggsFertile = FALSE + var/body_color + var/icon_prefix = "chicken" + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + var/list/feedMessages = list("It clucks happily.","It clucks happily.") + var/list/layMessage = EGG_LAYING_MESSAGES + var/list/validColors = list("brown","black","white") + gold_core_spawnable = FRIENDLY_SPAWN + var/static/chicken_count = 0 + + var/partner //for the sex. + var/egglay_timer = 0 + var/obj/structure/nestbox/nest_target + + var/last_egg + var/force_gender //for map loading + + + do_footstep = TRUE + +/mob/living/simple_animal/chicken/Initialize() + . = ..() + if((prob(30) && !force_gender) || force_gender == "male") //30% of a male. or setup if already male. + gender = MALE + name = "rooster" + + if(!body_color) + body_color = pick(validColors) + icon_state = "[icon_prefix]_[body_color]" + icon_living = "[icon_prefix]_[body_color]" + icon_dead = "[icon_prefix]_[body_color]_dead" + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + ++chicken_count + +/mob/living/simple_animal/chicken/Destroy() + --chicken_count + return ..() + +/mob/living/simple_animal/chicken/attackby(obj/item/O, mob/user, params) + if(istype(O, food_type)) //feedin' dem chickens + if(!stat && eggsleft < 2) + var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]" + user.visible_message(feedmsg) + qdel(O) + eggsleft += 1 + egglay_timer = 0 + else + to_chat(user, "[name] doesn't seem hungry!") + else + ..() + + +/mob/living/simple_animal/chicken/Life() + . = ..() + if(!.) + return + if (istype(get_turf(src), /turf/open/floor)) + var/turf/open/floor/turfon = get_turf(src) + if (turfon.farm_quality > happiness) + happiness ++ + if (prob(5)) + visible_message("[src] pecks happily at the ground.") //CLUCK CLUCK CLUCK + else + happiness -- + else + happiness -- + + happiness = clamp(happiness,0,100) //clamp + + + if (!(gender == FEMALE)) //only females lay eggs and do the rest of the code. + return + + + //Breeding. + if (gender == FEMALE && eggsFertile == 0 && eggsleft > 0) + for(var/mob/living/simple_animal/chicken/C in view(2,src)) //look for a male near them, or on them. + if(C) + if(C.gender == MALE) //rooster + eggsFertile = 1 //they had sex, just go with it. + partner = C //you know who the partner is. + //EEGGGG TIME + + //after 10mins, lay a egg regardless. + if(world.time > (last_egg+600 SECONDS) && !eggsleft) + if(prob(15)) //just to offset + eggsleft ++ + last_egg = world.time + + + if((!stat && eggsleft > 0) && egg_type) + egglay_timer ++ + else + egglay_timer = 0 + + + //chance to lay egg on their own. + + + //look for nest! + + //We have found a nest, override movement + if(nest_target) //if alive and we have a target. + stop_automated_movement = 1 + walk_to(src,nest_target,0,8) + else + stop_automated_movement = 0 + walk_to(src,0) //reset walk_to + + if (egglay_timer > 10 && !stat) //time to lay a egg and not dead. + if((!stat && eggsleft > 0) && egg_type) + if (!nest_target) + for(var/obj/structure/nestbox/N in view(3,src)) // look for a eggbox if you dont have one already + nest_target = N + break + + //We are at the nest we have chosen. + if (nest_target) + for(var/obj/structure/nestbox/B in get_turf(src)) + if((prob(25) && eggsleft > 0) && egg_type) + visible_message("[src] [pick(layMessage)]") + eggsleft-- + var/obj/item/E = new egg_type(get_turf(src)) + E.pixel_x = rand(-6,6) + E.pixel_y = rand(-6,6) + egglay_timer = 0 //set timer + nest_target = null //layed the egg, time to move on + if(eggsFertile && partner) + if(chicken_count < MAX_CHICKENS && prob(happiness)) + START_PROCESSING(SSobj, E) + eggsFertile = 0 //youve layed your fertile egg. + E.name = "fertile egg" + partner = null + last_egg = world.time + break + + +/obj/item/reagent_containers/food/snacks/egg/var/amount_grown = 0 +/obj/item/reagent_containers/food/snacks/egg/process() + if(isturf(loc)) + for(var/obj/structure/nestbox/B in get_turf(src)) //can only hatch in a nestbox or incubator + amount_grown += rand(1,2) + if(amount_grown >= 100) + visible_message("[src] hatches with a quiet cracking sound.") + new /mob/living/simple_animal/chick(get_turf(src)) + STOP_PROCESSING(SSobj, src) + qdel(src) + break + else + STOP_PROCESSING(SSobj, src) + + +/mob/living/simple_animal/chicken/examine() + . = ..() + . += "this one is [gender]." + if(happiness<20) + . += "It looks stressed." diff --git a/hyperstation/code/modules/power/reactor/fluffed.dm b/hyperstation/code/modules/power/reactor/fluffed.dm new file mode 100644 index 000000000..e3d69be5b --- /dev/null +++ b/hyperstation/code/modules/power/reactor/fluffed.dm @@ -0,0 +1,23 @@ +/* +This is a pre-destroyed nuclear reactor for the sake of mapping special fluff stuff. +Not actually a reactor, just uses the icon and irradiates the surrounding area a bit. +Nowhere else to really put this. +*/ + +/obj/structure/fluff/destroyed_nuclear_reactor + name = "Destroyed Nuclear Reactor" + desc = "What in the hell happened here?" + icon = 'hyperstation/icons/obj/machinery/rbmk.dmi' + icon_state = "reactor_slagged" + pixel_x = -32 + pixel_y = -32 + density = FALSE + anchored = TRUE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF + light_color = LIGHT_COLOR_CYAN + dir = 8 //Less headache inducing :)) + +/obj/structure/fluff/destroyed_nuclear_reactor/Initialize() + . = ..() + set_light(3) + AddComponent(/datum/component/radioactive, 15000 , src) diff --git a/hyperstation/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/hyperstation/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm new file mode 100644 index 000000000..76805f825 --- /dev/null +++ b/hyperstation/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -0,0 +1,11 @@ +//Hope you're not a Russian T-90 +/datum/reagent/consumable/ethanol/javelin + name = "Javelin Cocktail" + boozepwr = 80 + color = "#F54F10" + quality = DRINK_FANTASTIC + taste_description = "explosive fireballs" + glass_icon_state = "javelin" + shot_glass_icon_state = "javelin_warhead" + glass_name = "Javelin Cocktail" + glass_desc = "A man-portable, delicious glass of justice." diff --git a/hyperstation/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/hyperstation/code/modules/reagents/chemistry/reagents/drink_reagents.dm index 3656de158..3c088d34f 100644 --- a/hyperstation/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/hyperstation/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -21,3 +21,13 @@ if(holder.has_reagent(/datum/reagent/consumable/capsaicin)) holder.remove_reagent(/datum/reagent/consumable/capsaicin, 1) ..() + +/datum/reagent/consumable/kalynajuice + name = "Red Kalyna Juice" + description = "Juice from Kalyna plants." + color = "#E90501" // rgb: 233, 5, 1 + taste_description = "sweet cranberries" + glass_icon_state = "kalyna" + glass_name = "glass of red kalyna juice" + glass_desc = "A vibrantly red juice!" + hydration = 4 diff --git a/hyperstation/code/modules/resize/resizing.dm b/hyperstation/code/modules/resize/resizing.dm index 137723add..71ad20f9f 100644 --- a/hyperstation/code/modules/resize/resizing.dm +++ b/hyperstation/code/modules/resize/resizing.dm @@ -12,6 +12,7 @@ var/const/RESIZE_MICRO = 0.25 /mob/living var/size_multiplier = 1 //multiplier for the mob's icon size atm var/previous_size = 1 + var/small_speech = FALSE //Cyanosis - Action that resizes the sprite for the client but nobody else. Say goodbye to attacking yourself when someone's above you lmao var/datum/action/sizecode_resize/small_sprite @@ -237,8 +238,14 @@ mob/living/get_effective_size() //Proc for changing mob_size to be grabbed for item weight classes /mob/living/proc/update_mobsize(var/mob/living/tmob) + if(small_speech == TRUE) //if they have small speech reset it. + small_speech = FALSE + UnregisterSignal(src, COMSIG_MOB_SAY) + if(size_multiplier <= 0.50) mob_size = 0 + RegisterSignal(src, COMSIG_MOB_SAY, .proc/handle_small_speech) + small_speech = TRUE if(size_multiplier < 1) mob_size = 1 if(size_multiplier == 1) @@ -246,6 +253,9 @@ mob/living/get_effective_size() if(size_multiplier > 1) mob_size = 3 +/mob/living/proc/handle_small_speech(owner, list/speech_args) //for making peoples text small + speech_args[SPEECH_SPANS] |= SPAN_SMALL + //Proc for instantly grabbing valid size difference. Code optimizations soon(TM) /* /mob/living/proc/sizeinteractioncheck(var/mob/living/tmob) diff --git a/hyperstation/code/obj/farming/farming.dm b/hyperstation/code/obj/farming/farming.dm new file mode 100644 index 000000000..8c203d78d --- /dev/null +++ b/hyperstation/code/obj/farming/farming.dm @@ -0,0 +1,30 @@ +/obj/item/reagent_containers/food/snacks/grown/wheat/ + var stacktype = /obj/item/stack/tile/hay + var/tile_coefficient = 0.02 // same as grass + +/obj/item/reagent_containers/food/snacks/grown/wheat/attack_self(mob/user) + to_chat(user, "You prepare the hay bedding.") + var/grassAmt = 1 + round(seed.potency * tile_coefficient) // The grass we're holding + for(var/obj/item/reagent_containers/food/snacks/grown/wheat/G in user.loc) // The grass on the floor + if(G.type != type) + continue + grassAmt += 1 + round(G.seed.potency * tile_coefficient) + qdel(G) + new stacktype(user.drop_location(), grassAmt) + qdel(src) + + +/obj/structure/nestbox + name = "nest box" + icon = 'hyperstation/icons/obj/hydroponics/farming.dmi' + icon_state = "nestbox" + desc = "A little nest box, for collecting eggs" + density = FALSE + anchored = TRUE + +/obj/structure/nestbox/wrench_act(mob/living/user, obj/item/I) + user.visible_message("[user] disassembles [src].", + "You start to disassemble [src]...", "You hear clanking and banging noises.") + if(I.use_tool(src, user, 20, volume=50)) + new /obj/item/stack/sheet/mineral/wood (loc, 4) + qdel(src) \ No newline at end of file diff --git a/hyperstation/code/obj/jacuzzi.dm b/hyperstation/code/obj/jacuzzi.dm new file mode 100644 index 000000000..b2699f3bb --- /dev/null +++ b/hyperstation/code/obj/jacuzzi.dm @@ -0,0 +1,44 @@ +/obj/structure/jacuzzi + name = "jacuzzi" + icon = 'hyperstation/icons/obj/jacuzzi.dmi' + icon_state = "tub" + desc = "A luxurious pool, but with bubbles!" + var/filled = TRUE + density = FALSE + var/mutable_appearance/waterlower + var/mutable_appearance/water + var/mutable_appearance/top + var/mutable_appearance/mist + anchored = TRUE + +//Dont move, it goes in walls and is shit. +///obj/structure/jacuzzi/attackby(obj/item/W, mob/user, params) +// if(istype(W, /obj/item/wrench)) +// W.play_tool_sound(src) +// anchored = !anchored + +/obj/structure/jacuzzi/Initialize() + . = ..() + top = mutable_appearance('hyperstation/icons/obj/jacuzzi.dmi', "tub_top") + top.layer = 5 + add_overlay(top) + +/obj/structure/jacuzzi/attack_hand(mob/user) + . = ..() + filled = !filled + if(!filled) + cut_overlay(waterlower) + cut_overlay(water) + cut_overlay(mist) + else + waterlower = mutable_appearance('hyperstation/icons/obj/jacuzzi.dmi', "water") + water = mutable_appearance('hyperstation/icons/obj/jacuzzi.dmi', "over_water") + mist = mutable_appearance('hyperstation/icons/obj/jacuzzi.dmi', "mist") + waterlower.layer = 3 + water.layer = 10 + mist.layer = 11 + mist.pixel_y = 5 + add_overlay(waterlower) + add_overlay(water) + add_overlay(mist) + diff --git a/hyperstation/code/obj/kinkyclothes.dm b/hyperstation/code/obj/kinkyclothes.dm index 1e74de37c..3a5e44318 100644 --- a/hyperstation/code/obj/kinkyclothes.dm +++ b/hyperstation/code/obj/kinkyclothes.dm @@ -129,3 +129,72 @@ obj/item/clothing/neck/stole/black max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT heat_protection = CHEST|GROIN|ARMS armor = list("melee" = 60, "bullet" = 80, "laser" = 80, "energy" = 90, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 50) + +/obj/item/clothing/under/raccveralls + name = "form fitting overalls" + desc = "A tight form fitting pair of overalls." + icon = 'hyperstation/icons/obj/clothing/uniforms.dmi' + icon_state = "raccveralls" + alternate_worn_icon = 'hyperstation/icons/mobs/uniforms.dmi' + item_state = "raccveralls" + can_adjust = FALSE + +/obj/item/clothing/under/officesexy + name = "Revealing office uniform" + desc = "A sexy office uniform, that has a low cropped front to show off some chest, or bra. And a tall dress that covers the stomach, complete with a set of buttons." + icon = 'hyperstation/icons/obj/clothing/uniforms.dmi' + icon_state = "office_revealing" + alternate_worn_icon = 'hyperstation/icons/mobs/uniforms.dmi' + item_state = "office_revealing" + can_adjust = FALSE + mutantrace_variation = NO_MUTANTRACE_VARIATION + +/obj/item/clothing/under/vaultsuit + name = "vault suit" + desc = "A tight form fitting Vault-Tec standard issue Vault Jumpsuit! Snazzy!" + icon = 'hyperstation/icons/obj/clothing/uniforms.dmi' + icon_state = "vaultsuit" + alternate_worn_icon = 'hyperstation/icons/mobs/uniforms.dmi' + item_state = "b_suit" + can_adjust = FALSE + var/firstpickup = TRUE + var/pickupsound = TRUE + +/obj/item/clothing/under/vaultsuit/no_sound + pickupsound = FALSE + +/obj/item/clothing/under/vaultsuit/equipped(mob/user, slot) + . = ..() + if(!pickupsound) + return + if(!ishuman(user)) + return + if(slot == SLOT_W_UNIFORM) + if(!firstpickup) + SEND_SOUND(user, sound('hyperstation/sound/effects/vaultsuit/FalloutEXPUp.ogg', volume = 50)) + else + firstpickup = FALSE + SEND_SOUND(user, sound('hyperstation/sound/effects/vaultsuit/FalloutLevelUp.ogg', volume = 50)) + SEND_SOUND(user, sound('hyperstation/sound/effects/vaultsuit/InkSpotsSting.ogg', volume = 60)) + return + +/obj/item/clothing/suit/tunnelfox + name = "tunnel fox jacket" + desc = "Tunnel Foxes Rule!" + icon = 'hyperstation/icons/obj/clothing/suits.dmi' + icon_state = "tunnelfox" + alternate_worn_icon = 'hyperstation/icons/mobs/suits.dmi' + item_state = "tunnelfox" + body_parts_covered = CHEST|LEGS|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + +/obj/item/clothing/suit/tunnelfox_t + name = "opened tunnel fox jacket" + desc = "Tunnel Foxes Rule!" + icon = 'hyperstation/icons/obj/clothing/suits.dmi' + icon_state = "tunnelfox_t" + alternate_worn_icon = 'hyperstation/icons/mobs/suits.dmi' + item_state = "tunnelfox_t" + body_parts_covered = CHEST|LEGS|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + diff --git a/hyperstation/icons/mob/char_ears.dmi b/hyperstation/icons/mob/char_ears.dmi index 26709be52..bf248620b 100644 Binary files a/hyperstation/icons/mob/char_ears.dmi and b/hyperstation/icons/mob/char_ears.dmi differ diff --git a/hyperstation/icons/mob/char_markings.dmi b/hyperstation/icons/mob/char_markings.dmi index f38c1db49..e311b1d0f 100644 Binary files a/hyperstation/icons/mob/char_markings.dmi and b/hyperstation/icons/mob/char_markings.dmi differ diff --git a/hyperstation/icons/mob/char_snouts.dmi b/hyperstation/icons/mob/char_snouts.dmi index c7ad9dca1..06ce5d3be 100644 Binary files a/hyperstation/icons/mob/char_snouts.dmi and b/hyperstation/icons/mob/char_snouts.dmi differ diff --git a/hyperstation/icons/mob/char_tails.dmi b/hyperstation/icons/mob/char_tails.dmi index c9faf1b2e..6e2005e12 100644 Binary files a/hyperstation/icons/mob/char_tails.dmi and b/hyperstation/icons/mob/char_tails.dmi differ diff --git a/hyperstation/icons/mob/chickens.dmi b/hyperstation/icons/mob/chickens.dmi new file mode 100644 index 000000000..70fbbdda0 Binary files /dev/null and b/hyperstation/icons/mob/chickens.dmi differ diff --git a/hyperstation/icons/mob/clothes/extra.dmi b/hyperstation/icons/mob/clothes/extra.dmi index 1dd717c37..bd28845a8 100644 Binary files a/hyperstation/icons/mob/clothes/extra.dmi and b/hyperstation/icons/mob/clothes/extra.dmi differ diff --git a/hyperstation/icons/obj/clothing/sizeaccessories.dmi b/hyperstation/icons/obj/clothing/sizeaccessories.dmi new file mode 100644 index 000000000..b9079b353 Binary files /dev/null and b/hyperstation/icons/obj/clothing/sizeaccessories.dmi differ diff --git a/hyperstation/icons/obj/genitals/belly.dmi b/hyperstation/icons/obj/genitals/belly.dmi index feac6b40c..3324744b7 100644 Binary files a/hyperstation/icons/obj/genitals/belly.dmi and b/hyperstation/icons/obj/genitals/belly.dmi differ diff --git a/hyperstation/icons/obj/genitals/butt.dmi b/hyperstation/icons/obj/genitals/butt.dmi index e4b8f5c6b..55f746287 100644 Binary files a/hyperstation/icons/obj/genitals/butt.dmi and b/hyperstation/icons/obj/genitals/butt.dmi differ diff --git a/hyperstation/icons/obj/hydroponics/farming.dmi b/hyperstation/icons/obj/hydroponics/farming.dmi new file mode 100644 index 000000000..1a478a052 Binary files /dev/null and b/hyperstation/icons/obj/hydroponics/farming.dmi differ diff --git a/hyperstation/icons/obj/hydroponics/growing_fruits.dmi b/hyperstation/icons/obj/hydroponics/growing_fruits.dmi new file mode 100644 index 000000000..691f93859 Binary files /dev/null and b/hyperstation/icons/obj/hydroponics/growing_fruits.dmi differ diff --git a/hyperstation/icons/obj/hydroponics/harvest.dmi b/hyperstation/icons/obj/hydroponics/harvest.dmi new file mode 100644 index 000000000..7ef398455 Binary files /dev/null and b/hyperstation/icons/obj/hydroponics/harvest.dmi differ diff --git a/hyperstation/icons/obj/hydroponics/seeds.dmi b/hyperstation/icons/obj/hydroponics/seeds.dmi new file mode 100644 index 000000000..89c8a1f87 Binary files /dev/null and b/hyperstation/icons/obj/hydroponics/seeds.dmi differ diff --git a/hyperstation/icons/obj/jacuzzi.dmi b/hyperstation/icons/obj/jacuzzi.dmi new file mode 100644 index 000000000..c87e39a6d Binary files /dev/null and b/hyperstation/icons/obj/jacuzzi.dmi differ diff --git a/hyperstation/icons/obj/toy.dmi b/hyperstation/icons/obj/toy.dmi new file mode 100644 index 000000000..98e2dabb4 Binary files /dev/null and b/hyperstation/icons/obj/toy.dmi differ diff --git a/hyperstation/sound/effects/vaultsuit/FalloutEXPUp.ogg b/hyperstation/sound/effects/vaultsuit/FalloutEXPUp.ogg new file mode 100644 index 000000000..8175df7dd Binary files /dev/null and b/hyperstation/sound/effects/vaultsuit/FalloutEXPUp.ogg differ diff --git a/hyperstation/sound/effects/vaultsuit/FalloutLevelUp.ogg b/hyperstation/sound/effects/vaultsuit/FalloutLevelUp.ogg new file mode 100644 index 000000000..c11c47674 Binary files /dev/null and b/hyperstation/sound/effects/vaultsuit/FalloutLevelUp.ogg differ diff --git a/hyperstation/sound/effects/vaultsuit/InkSpotsSting.ogg b/hyperstation/sound/effects/vaultsuit/InkSpotsSting.ogg new file mode 100644 index 000000000..4acde860c Binary files /dev/null and b/hyperstation/sound/effects/vaultsuit/InkSpotsSting.ogg differ diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi index 806511877..2d5b7140e 100644 Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ diff --git a/icons/mob/hands.dmi b/icons/mob/hands.dmi index 1339c6a35..80effd7be 100644 Binary files a/icons/mob/hands.dmi and b/icons/mob/hands.dmi differ diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index 9beedfb41..d98f9dad9 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/icons/mob/neck.dmi b/icons/mob/neck.dmi index 96d6d793e..46ba0e0d7 100644 Binary files a/icons/mob/neck.dmi and b/icons/mob/neck.dmi differ diff --git a/icons/obj/doors/1x2blast_hor.dmi b/icons/obj/doors/1x2blast_hor.dmi index a0a3d9351..59827a280 100644 Binary files a/icons/obj/doors/1x2blast_hor.dmi and b/icons/obj/doors/1x2blast_hor.dmi differ diff --git a/icons/obj/doors/1x2blast_vert.dmi b/icons/obj/doors/1x2blast_vert.dmi index 6e810457d..f3fe9da8b 100644 Binary files a/icons/obj/doors/1x2blast_vert.dmi and b/icons/obj/doors/1x2blast_vert.dmi differ diff --git a/icons/obj/doors/1x3blast_hor.dmi b/icons/obj/doors/1x3blast_hor.dmi new file mode 100644 index 000000000..72d8898ae Binary files /dev/null and b/icons/obj/doors/1x3blast_hor.dmi differ diff --git a/icons/obj/doors/1x3blast_vert.dmi b/icons/obj/doors/1x3blast_vert.dmi new file mode 100644 index 000000000..6fe220f28 Binary files /dev/null and b/icons/obj/doors/1x3blast_vert.dmi differ diff --git a/icons/obj/doors/1x4blast_hor.dmi b/icons/obj/doors/1x4blast_hor.dmi index d43298973..41165639a 100644 Binary files a/icons/obj/doors/1x4blast_hor.dmi and b/icons/obj/doors/1x4blast_hor.dmi differ diff --git a/icons/obj/doors/1x4blast_vert.dmi b/icons/obj/doors/1x4blast_vert.dmi index 26c730b98..2835732ad 100644 Binary files a/icons/obj/doors/1x4blast_vert.dmi and b/icons/obj/doors/1x4blast_vert.dmi differ diff --git a/icons/obj/drinks.dmi b/icons/obj/drinks.dmi index 4ba28ee99..250fb561a 100644 Binary files a/icons/obj/drinks.dmi and b/icons/obj/drinks.dmi differ diff --git a/icons/obj/hydroponics/harvest.dmi b/icons/obj/hydroponics/harvest.dmi index 368b0e5c0..7b83313a7 100644 Binary files a/icons/obj/hydroponics/harvest.dmi and b/icons/obj/hydroponics/harvest.dmi differ diff --git a/icons/obj/tiles.dmi b/icons/obj/tiles.dmi index 59e8fdec0..65defc25f 100644 Binary files a/icons/obj/tiles.dmi and b/icons/obj/tiles.dmi differ diff --git a/icons/obj/toy.dmi b/icons/obj/toy.dmi index 333c8ca0d..403cba517 100644 Binary files a/icons/obj/toy.dmi and b/icons/obj/toy.dmi differ diff --git a/modular_citadel/code/game/machinery/vending.dm b/modular_citadel/code/game/machinery/vending.dm index efbaa9f95..9f1157c99 100644 --- a/modular_citadel/code/game/machinery/vending.dm +++ b/modular_citadel/code/game/machinery/vending.dm @@ -70,6 +70,7 @@ /obj/item/electropack/vibrator = 2, /obj/item/fleshlight = 2, /obj/item/storage/box/portallight = 1, + /obj/item/storage/pill_bottle/lovedice = 5, ) contraband = list( /obj/item/clothing/under/gear_harness = 3, diff --git a/modular_citadel/code/modules/arousal/arousal.dm b/modular_citadel/code/modules/arousal/arousal.dm index c8d4ea191..5e37cbbe7 100644 --- a/modular_citadel/code/modules/arousal/arousal.dm +++ b/modular_citadel/code/modules/arousal/arousal.dm @@ -437,17 +437,9 @@ if(!remote && !in_range(src, L)) return if(do_after(src, mb_time, target = src)) - - if(!spillage) //hyper inflation - var/obj/item/organ/genital/belly/B = L.getorganslot("belly") - if(B) - if(B.inflatable && total_fluids > 80) //requires a big cumshot to expand. - if(B.size < 3) - B.size += 1 - to_chat(L, "You feel your belly expand.") - else - to_chat(L, "You feel your belly strain.") - + var/mob/living/carbon/H = L + if(!spillage && total_fluids > 80) //hyper inflation; requires a big cumshot to expand + H.expand_belly(1) var/obj/item/organ/genital/penis/P = G if (P.condom)//condomed. src.condomclimax() @@ -673,8 +665,6 @@ var/mob/living/carbon/C = check_target if(C.exposed_genitals.len || C.is_groin_exposed() || C.is_chest_exposed()) //Are they naked enough? partner = C - else //A cat is fine too - partner = check_target if(partner) //Did they pass the clothing checks? mob_climax_partner(G, partner, mb_time = 0) //Instant climax due to forced continue //You've climaxed once with this organ, continue on diff --git a/modular_citadel/code/modules/arousal/organs/genitals.dm b/modular_citadel/code/modules/arousal/organs/genitals.dm index ac28f594a..bdf4d7856 100644 --- a/modular_citadel/code/modules/arousal/organs/genitals.dm +++ b/modular_citadel/code/modules/arousal/organs/genitals.dm @@ -229,7 +229,7 @@ if(!getorganslot("belly")) var/obj/item/organ/genital/belly/B = new if(dna.features["belly_size"]) - B.size = dna.features["belly_size"]-1 + B.size = dna.features["belly_size"] B.Insert(src) if(B) if(dna.species.use_skintones && dna.features["genitals_use_skintone"]) @@ -478,32 +478,39 @@ genital_overlay.icon_state = "[G.slot]_[S.icon_state]_[size]_[aroused_state]_[layertext]" colourcode = S.color_src - if(G.slot == "belly") //we have a different size system + if(G.slot == "belly") + size = round(size) genital_overlay.icon = 'hyperstation/icons/obj/genitals/belly.dmi' - genital_overlay.icon_state = "belly_[size]" + genital_overlay.icon_state = "belly_[size]_OTHER" colourcode = "belly_color" - //sizecheck added to prevent rendering blank icons - if(G.slot == "anus" && G.size > 0) //we have a different size system + //creates directional layering by rendering twice. + genital_overlay_directional.icon = 'hyperstation/icons/obj/genitals/belly.dmi' //Added directionals for larger bellies! + genital_overlay_directional.icon_state = "belly_[size]_NORTH" + genital_overlay_directional.layer = -GENITALS_BEHIND_LAYER + if(G.slot == "anus") + size = round(size) genital_overlay.icon = 'hyperstation/icons/obj/genitals/butt.dmi' - genital_overlay.icon_state = "butt_[round(size)]_OTHER" + genital_overlay.icon_state = "butt_[size]_OTHER" genital_overlay.layer = -ID_LAYER //in front of suit, behind bellies. //creates directional layering by rendering twice. North has higher layer priority to occlude hands. genital_overlay_directional.icon = 'hyperstation/icons/obj/genitals/butt.dmi' - genital_overlay_directional.icon_state = "butt_[round(size)]_NORTH" + genital_overlay_directional.icon_state = "butt_[size]_NORTH" genital_overlay_directional.layer = -NECK_LAYER colourcode = "butt_color" - if(use_skintones) //butts are forced a colour, either skin tones, or main colour. how ever, mutants use a darker version, because of their body tone. + if(use_skintones && H.dna.features["genitals_use_skintone"]) + //butts are forced a colour, either skin tones, or main colour. + //how ever, mutants use a darker version, because of their body tone. genital_overlay.color = "#[skintone2hex(H.skin_tone)]" - genital_overlay.icon_state = "butt_[round(size)]_OTHER" - genital_overlay_directional.icon_state = "butt_[round(size)]_NORTH" + genital_overlay.icon_state = "butt_[size]_OTHER" + genital_overlay_directional.icon_state = "butt_[size]_NORTH" else genital_overlay.color = "#[H.dna.features["mcolor"]]" - genital_overlay.icon_state = "butt_[round(size)]_OTHER_m" - genital_overlay_directional.icon_state = "butt_[round(size)]_NORTH_m" + genital_overlay.icon_state = "butt_[size]_OTHER_m" + genital_overlay_directional.icon_state = "butt_[size]_NORTH_m" if(S.center) @@ -514,21 +521,10 @@ if (colourtint) genital_overlay.color = "#[colourtint]" else - switch(colourcode) - if("cock_color") - genital_overlay.color = "#[H.dna.features["cock_color"]]" - if (colourtint) - genital_overlay.color = "#[colourtint]" - if("balls_color") - genital_overlay.color = "#[H.dna.features["balls_color"]]" - if("breasts_color") - genital_overlay.color = "#[H.dna.features["breasts_color"]]" - if("vag_color") - genital_overlay.color = "#[H.dna.features["vag_color"]]" - if("belly_color") - genital_overlay.color = "#[H.dna.features["belly_color"]]" - if("butt_color") - genital_overlay.color = "#[H.dna.features["butt_color"]]" + if(colourcode != MUTCOLORS) + genital_overlay.color = "#[H.dna.features[colourcode]]" + if(colourcode == "cock_color" && colourtint) + genital_overlay.color = "#[colourtint]" standing += genital_overlay @@ -542,7 +538,7 @@ if (colourtint) genital_overlay_directional.color = "#[colourtint]" else - genital_overlay_directional.color = "#[H.dna.features["butt_color"]]" + genital_overlay_directional.color = "#[H.dna.features[colourcode]]" standing += genital_overlay_directional diff --git a/modular_citadel/code/modules/client/loadout/uniform.dm b/modular_citadel/code/modules/client/loadout/uniform.dm index ac65dfe85..fcc6846c2 100644 --- a/modular_citadel/code/modules/client/loadout/uniform.dm +++ b/modular_citadel/code/modules/client/loadout/uniform.dm @@ -523,4 +523,22 @@ name = "Tunic" category = SLOT_W_UNIFORM path = /obj/item/clothing/under/tunic + cost = 1 + +/datum/gear/overalls + name = "form fitting overalls" + category = SLOT_W_UNIFORM + path = /obj/item/clothing/under/raccveralls + cost = 1 + +/datum/gear/sexyoffice + name = "Revealing office uniform" + category = SLOT_W_UNIFORM + path = /obj/item/clothing/under/officesexy + cost = 1 + +/datum/gear/vaultsuit + name = "vault jumpsuit" + category = SLOT_W_UNIFORM + path = /obj/item/clothing/under/vaultsuit cost = 1 \ No newline at end of file diff --git a/modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm b/modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm index 10f15e0fd..c2f6f2e58 100644 --- a/modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm +++ b/modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm @@ -1655,6 +1655,7 @@ datum/sprite_accessory/ears/human/gret name = "Dog" icon_state = "dog" + /datum/sprite_accessory/mam_body_markings/eevee name = "Eevee" icon_state = "eevee" @@ -2302,44 +2303,6 @@ datum/sprite_accessory/ears/human/gret icon_state = "sabresune" ckeys_allowed = list("poojawa") -//doged was here -/datum/sprite_accessory/mam_body_markings/chemlight - name = "RadDog" - icon_state = "chemlight" - -/datum/sprite_accessory/mam_tails/chemlight - name = "RadDog" - icon_state = "chemlight" - -/datum/sprite_accessory/mam_tails_animated/chemlight - name = "RadDog" - icon_state = "chemlight" - -/datum/sprite_accessory/mam_ears/chemlight - name = "RadDog" - icon_state = "chemlight" - -/datum/sprite_accessory/mam_snouts/fchemlight - name = "RadDog (Top)" - icon_state = "fchemlight" - -/datum/sprite_accessory/mam_snouts/chemlight - name = "RadDog" - icon_state = "chemlight" - -/datum/sprite_accessory/taur/chemnaga - name = "Naga Chemlight" - icon_state = "chemnaga" - taur_mode = SNEK_TAURIC - ckeys_allowed = list("chemlight") - -/datum/sprite_accessory/taur/chemlight - name = "Chem taur" - icon_state = "chemlight" - taur_mode = PAW_TAURIC - ckeys_allowed = list("chemlight") - - //Lunasune /datum/sprite_accessory/mam_ears/lunasune name = "lunasune" diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/enlargement.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/enlargement.dm index e0d8e8417..95f58780c 100644 --- a/modular_citadel/code/modules/reagents/chemistry/reagents/enlargement.dm +++ b/modular_citadel/code/modules/reagents/chemistry/reagents/enlargement.dm @@ -412,7 +412,7 @@ B = nB //If they have & opt in, increase size. if(H.client?.prefs.cit_toggles & ASS_ENLARGEMENT) - if(B.size < 5) //just make sure you dont break sprites + if(B.size < BUTT_MAX_SIZE) //just make sure you dont break sprites B.size = B.size + 0.05 B.update() ..() diff --git a/modular_citadel/icons/mob/mam_ears.dmi b/modular_citadel/icons/mob/mam_ears.dmi index bb25e3749..fde1d6f02 100644 Binary files a/modular_citadel/icons/mob/mam_ears.dmi and b/modular_citadel/icons/mob/mam_ears.dmi differ diff --git a/modular_citadel/icons/mob/mam_markings.dmi b/modular_citadel/icons/mob/mam_markings.dmi index cf7da287c..fa2b16223 100644 Binary files a/modular_citadel/icons/mob/mam_markings.dmi and b/modular_citadel/icons/mob/mam_markings.dmi differ diff --git a/modular_citadel/icons/mob/mam_snouts.dmi b/modular_citadel/icons/mob/mam_snouts.dmi index 4b06a1f7e..7ccd40676 100644 Binary files a/modular_citadel/icons/mob/mam_snouts.dmi and b/modular_citadel/icons/mob/mam_snouts.dmi differ diff --git a/modular_citadel/icons/mob/mam_tails.dmi b/modular_citadel/icons/mob/mam_tails.dmi index 583772d84..c8a8e3ecf 100644 Binary files a/modular_citadel/icons/mob/mam_tails.dmi and b/modular_citadel/icons/mob/mam_tails.dmi differ diff --git a/modular_citadel/icons/mob/mam_taur.dmi b/modular_citadel/icons/mob/mam_taur.dmi index fc8486cdb..e9d7de0f2 100644 Binary files a/modular_citadel/icons/mob/mam_taur.dmi and b/modular_citadel/icons/mob/mam_taur.dmi differ diff --git a/tgui/packages/tgui/interfaces/Cards/CardStatus.js b/tgui/packages/tgui/interfaces/Cards/CardStatus.js new file mode 100644 index 000000000..4885a0412 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Cards/CardStatus.js @@ -0,0 +1,27 @@ +import { useBackend } from '../../backend'; +import { Box, Section } from '../../components'; + +export const CardStatus = (props, context) => { + const { data } = useBackend(context); + const { face_up, rotation, cards, name } = data; + const { noRotation = false, notOrdered = false, children } = props; + + return ( +
+ + This {name} contains {cards.length} cards. + + {!noRotation && ( + + The deck is {rotation} and turned {face_up ? "face-up" : "face-down"}. + + )} + {!notOrdered & !!face_up && cards.length > 0 && ( + + You can see a {cards[cards.length - 1].name} on top. + + ) || null} + {...children} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Cards/CardsControlList.js b/tgui/packages/tgui/interfaces/Cards/CardsControlList.js new file mode 100644 index 000000000..3713cda75 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Cards/CardsControlList.js @@ -0,0 +1,82 @@ +import { useBackend, useLocalState } from '../../backend'; +import { Box, Button, Dropdown, LabeledList, NumberInput } from '../../components'; + +export const CardsControlList = (props, context) => { + const { data, act } = useBackend(context); + const { face_up, rotation, cards, possible_rotations } = data; + const { flip, rotate, draw, deal, shuffle } = props; + + const [drawCount, setDrawCount] = useLocalState(context, 'drawCount', 1); + const [dealCardsCount, setDealCardsCount] = useLocalState(context, 'dealCards', 1); + const [dealHandsCount, setDealHandsCount] = useLocalState(context, 'dealHands', 1); + const [selectedCards, setSelectedCards] = useLocalState(context, 'selectedCards', []); + + return ( + + {!!flip && ( + +