diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm
index e1c5d574f7..b84f46e650 100644
--- a/code/_helpers/game.dm
+++ b/code/_helpers/game.dm
@@ -728,4 +728,22 @@
result |= P
result |= get_all_prey_recursive(P, client_check)
- return result
\ No newline at end of file
+ return result
+
+/proc/random_color(saturated) //Returns a random color. If saturated is true, it will avoid pure white or pure black
+ var/r = rand(1,255)
+ var/g = rand(1,255)
+ var/b = rand(1,255)
+
+ if(saturated) //Let's make sure we don't get too close to pure black or pure white, as they won't look good with grayscale sprites
+ if(r + g + b < 50)
+ r = r + rand(5,20)
+ g = g + rand(5,20)
+ b = b + rand(5,20)
+ else if (r + g + b > 700)
+ r = r - rand(5,50)
+ g = g - rand(5,50)
+ b = b - rand(5,50)
+
+ var/color = rgb(r, g, b)
+ return color
diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index 1408896e55..95b7758922 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -14,6 +14,7 @@ var/global/list/cleanbot_reserved_turfs = list() //List of all turfs currently t
var/global/list/cable_list = list() //Index for all cables, so that powernets don't have to look through the entire world all the time
var/global/list/landmarks_list = list() //list of all landmarks created
+var/global/list/event_triggers = list() //Associative list of creator_ckey:list(landmark references) for event triggers
var/global/list/surgery_steps = list() //list of all surgery steps |BS12
var/global/list/side_effects = list() //list of all medical sideeffects types by thier names |BS12
var/global/list/mechas_list = list() //list of all mechs. Used by hostile mobs target tracking.
diff --git a/code/controllers/subsystems/mapping.dm b/code/controllers/subsystems/mapping.dm
index cf9593d35a..00e3153b3e 100644
--- a/code/controllers/subsystems/mapping.dm
+++ b/code/controllers/subsystems/mapping.dm
@@ -89,6 +89,7 @@ SUBSYSTEM_DEF(mapping)
if(!istype(MT))
error("Lateload Z level \"[mapname]\" is not a valid map!")
continue
+ admin_notice("Lateload: [MT]", R_DEBUG)
MT.load_new_z(centered = FALSE)
CHECK_TICK
@@ -111,6 +112,7 @@ SUBSYSTEM_DEF(mapping)
if(!istype(MT))
error("Randompick Z level \"[map]\" is not a valid map!")
else
+ admin_notice("Gateway: [MT]", R_DEBUG)
MT.load_new_z(centered = FALSE)
if(LAZYLEN(also_load)) //Just copied from gateway picking, this is so we can have a kind of OM map version of the same concept.
@@ -132,6 +134,7 @@ SUBSYSTEM_DEF(mapping)
if(!istype(MT))
error("Randompick Z level \"[map]\" is not a valid map!")
else
+ admin_notice("OM Adventure: [MT]", R_DEBUG)
MT.load_new_z(centered = FALSE)
if(LAZYLEN(redgate_load))
@@ -153,6 +156,7 @@ SUBSYSTEM_DEF(mapping)
if(!istype(MT))
error("Randompick Z level \"[map]\" is not a valid map!")
else
+ admin_notice("Redgate: [MT]", R_DEBUG)
MT.load_new_z(centered = FALSE)
diff --git a/code/datums/underwear/bottom.dm b/code/datums/underwear/bottom.dm
index 7f16ba66b7..358fe20104 100644
--- a/code/datums/underwear/bottom.dm
+++ b/code/datums/underwear/bottom.dm
@@ -10,6 +10,10 @@
/datum/category_item/underwear/bottom/briefs/is_default(var/gender)
return gender != FEMALE
+/datum/category_item/underwear/bottom/briefs_hyper
+ name = "HYPER Briefs"
+ icon_state = "hyper_briefs"
+
/datum/category_item/underwear/bottom/boxers_loveheart
name = "Boxers, Loveheart"
icon_state = "boxers_loveheart"
@@ -19,6 +23,10 @@
icon_state = "boxers"
has_color = TRUE
+/datum/category_item/underwear/bottom/boxers_hyper
+ name = "HYPER Boxers"
+ icon_state = "hyper_boxers"
+
/datum/category_item/underwear/bottom/boxers_green_and_blue
name = "Boxers, green & blue striped"
icon_state = "boxers_green_and_blue"
diff --git a/code/game/area/Away Mission areas.dm b/code/game/area/Away Mission areas.dm
index 5079e8b311..fc6ff0b01d 100644
--- a/code/game/area/Away Mission areas.dm
+++ b/code/game/area/Away Mission areas.dm
@@ -11,14 +11,15 @@
var/semirandom_group_min = 0
var/semirandom_group_max = 10
var/mob_intent = "default" //"default" uses default settings, use "hostile", "retaliate", or "passive" respectively
+ var/ghostjoin = FALSE //If true, enables ghost join on semirandom spawned mobs
/area/proc/EvalValidSpawnTurfs()
//Adds turfs to the valid)turfs list, used for spawning.
if(mobcountmax || floracountmax || semirandom)
for(var/turf/simulated/floor/F in src)
- valid_spawn_turfs += F
+ valid_spawn_turfs |= F
for(var/turf/unsimulated/floor/F in src)
- valid_spawn_turfs += F
+ valid_spawn_turfs |= F
/area/LateInitialize()
..()
@@ -44,28 +45,33 @@
var/mob/M
var/turf/Turf
if(semirandom)
- for(var/groupscount = 0 to (semirandom_groups - 1))
+ for(var/groupscount = 1 to semirandom_groups)
var/ourgroup = pickweight(valid_mobs)
var/goodnum = rand(semirandom_group_min, semirandom_group_max)
- for(var/mobscount = 0 to (goodnum - 1))
+ for(var/mobscount = 1 to goodnum)
M = pickweight(ourgroup)
Turf = pick(valid_spawn_turfs)
valid_spawn_turfs -= Turf
var/mob/ourmob = new M(Turf)
adjust_mob(ourmob)
else
- for(var/mobscount = 0 to mobcountmax)
+ for(var/mobscount = 1 to mobcountmax)
M = pickweight(valid_mobs)
Turf = pick(valid_spawn_turfs)
valid_spawn_turfs -= Turf
var/mob/ourmob = new M(Turf)
adjust_mob(ourmob)
-/area/proc/adjust_mob(var/mob/living/M)
- if(!isliving(M))
- log_admin("[src] spawned [M.type], which is not mob/living, FIXIT")
+/area/proc/adjust_mob(var/mob/living/simple_mob/M)
+ if(!isanimal(M))
+ log_admin("[src] spawned [M.type], which is not a simplemob, FIXIT")
return
var/datum/ai_holder/AI = M.ai_holder
+ if(ghostjoin)
+ M.ghostjoin = TRUE
+ M.ghostjoin_icon()
+ if(!AI)
+ return
switch(mob_intent)
if("default")
return
@@ -86,7 +92,7 @@
var/obj/F
var/turf/Turf
- for(var/floracount = 0 to floracountmax)
+ for(var/floracount = 1 to floracountmax)
F = pick(valid_flora)
Turf = pick(valid_spawn_turfs)
valid_spawn_turfs -= Turf
diff --git a/code/game/area/areas_vr.dm b/code/game/area/areas_vr.dm
index f932674566..8d7b54bdf4 100644
--- a/code/game/area/areas_vr.dm
+++ b/code/game/area/areas_vr.dm
@@ -8,6 +8,8 @@
// Size of the area in open turfs, only calculated for indoors areas.
var/areasize = 0
+ var/no_comms = FALSE //When true, blocks radios from working in the area
+
/area/Entered(var/atom/movable/AM, oldLoc)
. = ..()
if(enter_message && isliving(AM))
@@ -69,4 +71,4 @@
power_environ = 0
power_change() // all machines set to current power level, also updates lighting icon
if(no_spoilers)
- set_spoiler_obfuscation(TRUE)
\ No newline at end of file
+ set_spoiler_obfuscation(TRUE)
diff --git a/code/game/gamemodes/events/dust.dm b/code/game/gamemodes/events/dust.dm
index 85391b7352..b3728390d0 100644
--- a/code/game/gamemodes/events/dust.dm
+++ b/code/game/gamemodes/events/dust.dm
@@ -52,6 +52,8 @@ The "dust" will damage the hull of the station causin minor hull breaches.
endy = rand(TRANSITIONEDGE, world.maxy-TRANSITIONEDGE)
endx = world.maxx-TRANSITIONEDGE
+ if(!affecting_z.len)
+ return
var/randomz = pick(affecting_z)
var/turf/startloc = locate(startx, starty, randomz)
var/turf/endloc = locate(endx, endy, randomz)
diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm
index 60935a8472..cad9e72e2f 100644
--- a/code/game/machinery/computer/computer.dm
+++ b/code/game/machinery/computer/computer.dm
@@ -64,7 +64,7 @@
/obj/machinery/computer/update_icon()
cut_overlays()
-
+
. = list()
// Connecty
@@ -128,7 +128,7 @@
else
var/B_held = B.wrapped
to_chat(user, "You use \the [B] to use \the [B_held] with \the [src].")
- playsound(src, "keyboard", 100, 1, 0)
+ playsound(src, clicksound, 100, 1, 0)
return
attack_hand(user)
return
diff --git a/code/game/objects/effects/landmarks_events.dm b/code/game/objects/effects/landmarks_events.dm
new file mode 100644
index 0000000000..d95832ee16
--- /dev/null
+++ b/code/game/objects/effects/landmarks_events.dm
@@ -0,0 +1,129 @@
+/*
+Landmark to be spawned by GMs and admins when running events.
+Do NOT directly spawn "" or buildmode spawn this.
+Use the "Manage Event Triggers" verb under "Eventkit" category.
+Admin verb is called by code\modules\admin\verbs\event_triggers.dm
+*/
+/obj/effect/landmark/event_trigger
+ name = "ONLY FOR EVENTS. DO NOT USE WHEN MAPPING"
+ desc = "Please notify staff if you see this!"
+ var/creator_ckey = ""
+ var/isTeamwork = FALSE //Whether to notify creator or whole team.
+ var/isRepeating = FALSE
+ var/coordinates = ""
+ var/cooldown = 0 //Given in seconds in set_vars() but stored in ticks
+ var/last_trigger = 0
+ var/isLoud = FALSE
+ var/isNarrate = FALSE
+
+/obj/effect/landmark/event_trigger/New()
+ ..()
+ coordinates = "(X:[loc.x];Y:[loc.y];Z:[loc.z])"
+
+/obj/effect/landmark/event_trigger/proc/set_vars(mob/M)
+ var/new_name = sanitize(tgui_input_text(M, "Input Name for the trigger", "Naming", "Event Trigger"))
+ if(!new_name)
+ return
+ name = new_name
+ creator_ckey = M.ckey
+ if(!event_triggers[creator_ckey])
+ event_triggers[creator_ckey] = list()
+ event_triggers[creator_ckey] |= list(src)
+ isTeamwork = (tgui_alert(M, "Notify rest of team?", "Teamwork", list("No", "Yes")) == "Yes" ? TRUE : FALSE)
+ if(!isTeamwork)
+ isLoud = (tgui_alert(M, "Should it make a bwoink when triggered for YOU?", "bwoink", list("No", "Yes")) == "Yes" ? TRUE : FALSE)
+ isRepeating = (tgui_alert(M, "Make it fire repeatedly?", "Repetition", list("No", "Yes")) == "Yes" ? TRUE : FALSE)
+ if(isRepeating)
+ cooldown = tgui_input_number(M, "Set cooldown in seconds. Minimum 5 seconds!", "Cooldown", 60, min_value = 5)
+ cooldown = cooldown SECONDS
+ else
+ delete_me = TRUE
+ log_admin("[M.ckey] has created a [isNarrate ? "Narrtion" : "Notification"] landmark trigger at [coordinates]")
+
+/obj/effect/landmark/event_trigger/Destroy()
+ if(event_triggers[creator_ckey])
+ event_triggers[creator_ckey] -= src
+ ..()
+
+/obj/effect/landmark/event_trigger/Crossed(var/atom/movable/AM)
+ if(!isliving(AM))
+ return FALSE
+ var/mob/living/L = AM
+ if(!L.ckey) return FALSE
+
+ if(world.time < (last_trigger + cooldown))
+ return FALSE
+ if(!isRepeating && last_trigger) //Used to avoid spam if qdel(src) fires too slowly
+ return FALSE
+ last_trigger = world.time
+
+ if(!creator_ckey) //For some reason, the user didn't have a ckey. Let's clean up
+ qdel(src)
+ return FALSE
+ var/mob/creator_reference = GLOB.directory[creator_ckey]
+ if(!creator_reference || isTeamwork) //If logged/crashed, we default to teamwork mode
+ message_admins("Player [L.name] ([L.ckey]) has triggered event narrate landmark [name] of type \
+ [isNarrate ? "Narration" : "Notification"]. \n \
+ The landmark was created by [creator_ckey], and it [isRepeating ? \
+ "will be possible to trigger after [cooldown / (1 SECOND)] seconds" : "has self-deleted"]\n \
+ COORDINATES: [coordinates]")
+ else
+ if(isLoud)
+ creator_reference << 'sound/effects/adminhelp.ogg'
+ to_chat(creator_reference, SPAN_WARNING("Player [L.name] ([L.ckey]) has triggered event \
+ narrate landmark [name] of type [isNarrate ? "Narration" : "Notification"]. \n \
+ It [isRepeating ? "will be possible to trigger after [cooldown / (1 SECOND)] seconds" : "has self-deleted"] \n \
+ COORDINATES: [coordinates]"))
+ if(!isNarrate)
+ if(!isRepeating)
+ qdel(src)
+ return FALSE
+ else
+ return L
+
+
+/obj/effect/landmark/event_trigger/auto_narrate
+ var/message
+ var/isPersonal_orVis_orAud = 0 //0 for personal, 1 for vis, 2 for aud
+ var/message_range //Leave at 0 for world.view
+ var/isWarning = FALSE //For personal messages
+ isNarrate = TRUE
+
+/obj/effect/landmark/event_trigger/auto_narrate/New()
+ message_range = world.view
+ ..()
+
+/obj/effect/landmark/event_trigger/auto_narrate/set_vars(mob/M)
+ ..()
+ message = encode_html_emphasis(sanitize(tgui_input_text(M, "What should the automatic narration say?", "Message"), encode = FALSE))
+ isPersonal_orVis_orAud = (tgui_alert(M, "Should it send directly to the player, or send to the turf?", "Target", list("Player", "Turf")) == "Player" ? 0 : 1)
+ if(isPersonal_orVis_orAud == 0)
+ isWarning = (tgui_alert(M, "Should it be a normal message or a big scary red text?", "Scary Red", list("Big Red", "Normal")) == "Big Red" ? TRUE : FALSE)
+ else
+ isPersonal_orVis_orAud = (tgui_alert(M, "Should it be visible or audible?", "Mode", list("Visible", "Audible")) == "Audible" ? 2 : 1)
+ var/range = tgui_input_number(M, "Give narration range! Input value over 10 to use world.view", "Range",default = 11, min_value = 0)
+ if(range <= 10)
+ message_range = range
+
+
+
+
+
+/obj/effect/landmark/event_trigger/auto_narrate/Crossed(var/atom/movable/AM)
+ . = ..() //Checks if AM is mob/living and notifies admin(s)
+ if(!.)
+ return
+ var/mob/living/L = .
+ var/turf/T = get_turf(src)
+ switch(isPersonal_orVis_orAud)
+ if(0)
+ if(isWarning)
+ to_chat(L, SPAN_DANGER(message))
+ else
+ to_chat(L, message)
+ if(1)
+ T.visible_message(message, range = message_range, runemessage = message)
+ if(2)
+ T.audible_message(message, hearing_distance = message_range, runemessage= message)
+ if(!isRepeating)
+ qdel(src)
diff --git a/code/game/objects/effects/map_effects/effect_emitter.dm b/code/game/objects/effects/map_effects/effect_emitter.dm
index d7086dafa6..7ea9a93eea 100644
--- a/code/game/objects/effects/map_effects/effect_emitter.dm
+++ b/code/game/objects/effects/map_effects/effect_emitter.dm
@@ -76,3 +76,7 @@
name = "steam emitter"
icon_state = "smoke_emitter"
effect_system_type = /datum/effect/effect/system/steam_spread
+
+/obj/effect/map_effect/interval/effect_emitter/steam/less_frequent
+ interval_lower_bound = 10 SECONDS
+ interval_upper_bound = 30 SECONDS
diff --git a/code/game/objects/items/devices/radio/jammer.dm b/code/game/objects/items/devices/radio/jammer.dm
index ace17fa07d..f7d8897780 100644
--- a/code/game/objects/items/devices/radio/jammer.dm
+++ b/code/game/objects/items/devices/radio/jammer.dm
@@ -4,6 +4,11 @@ var/global/list/active_radio_jammers = list()
var/turf/Tr = get_turf(radio)
if(!Tr) return 0 //Nullspace radios don't get jammed.
+ var/area/our_area = get_area(Tr)
+
+ if(our_area.no_comms)
+ return TRUE
+
for(var/obj/item/device/radio_jammer/J as anything in active_radio_jammers)
var/turf/Tj = get_turf(J)
@@ -111,4 +116,3 @@ var/global/list/active_radio_jammers = list()
cut_overlays()
add_overlay("jammer_overlay_[overlay_percent]")
last_overlay_percent = overlay_percent
-
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 12b684cb21..63f5929307 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -490,7 +490,12 @@ GLOBAL_DATUM(autospeaker, /mob/living/silicon/ai/announcer)
else if(subspace_transmission)
var/list/jamming = is_jammed(src)
if(jamming)
- var/distance = jamming["distance"]
+ var/distance = 0
+ var/area/our_area = get_area(src)
+ if(our_area.no_comms)
+ distance = 99
+ else
+ distance = jamming["distance"]
to_chat(M, "\icon[src][bicon(src)] You hear the [distance <= 2 ? "loud hiss" : "soft hiss"] of static.")
return FALSE
diff --git a/code/game/objects/items/devices/translocator_vr.dm b/code/game/objects/items/devices/translocator_vr.dm
index 57630d9c88..9bc4233ccf 100644
--- a/code/game/objects/items/devices/translocator_vr.dm
+++ b/code/game/objects/items/devices/translocator_vr.dm
@@ -217,8 +217,10 @@ This device records all warnings given and teleport events for admin review in c
//No, you can't teleport if there's a jammer.
if(is_jammed(src) || is_jammed(destination))
- to_chat(user,"\The [src] refuses to teleport you, due to strong interference!")
- return FALSE
+ var/area/our_area = get_area(src)
+ if(!our_area.no_comms) //I don't actually want this to block teleporters, just comms
+ to_chat(user,"\The [src] refuses to teleport you, due to strong interference!")
+ return FALSE
//No, you can't port to or from away missions. Stupidly complicated check.
var/turf/uT = get_turf(user)
diff --git a/code/game/objects/items/weapons/material/twohanded_vr.dm b/code/game/objects/items/weapons/material/twohanded_vr.dm
index 17f7a56caa..a2e1a229ba 100644
--- a/code/game/objects/items/weapons/material/twohanded_vr.dm
+++ b/code/game/objects/items/weapons/material/twohanded_vr.dm
@@ -26,7 +26,6 @@
edge = TRUE
sharp = TRUE
-
/obj/item/weapon/material/twohanded/saber/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
if (src.wielded == 1)
if(unique_parry_check(user, attacker, damage_source) && prob(50))
@@ -34,3 +33,49 @@
playsound(src, 'sound/weapons/punchmiss.ogg', 50, 1)
return 1
return 0
+
+/obj/item/weapon/material/twohanded/staff
+ w_class = ITEMSIZE_LARGE
+ default_material = MAT_WOOD
+ name = "staff"
+ desc = "A sturdy length of metal or wood. A common traveler's aid mostly used for support or probing unstable ground, but also a fairly effective weapon in a pinch."
+ description_info = "When wielded with two hands, staves can be used to parry incoming melee attacks. Being on disarm intent also grants them an added chance to stun or knock down opponents, and increases your chances of parrying an attack."
+ icon = 'icons/obj/weapons_vr.dmi'
+ icon_state = "mat_staff"
+ base_icon = "mat_staff"
+ item_state = "mat_staff"
+ item_icons = list(
+ slot_l_hand_str = 'icons/mob/items/lefthand_melee_vr.dmi',
+ slot_r_hand_str = 'icons/mob/items/righthand_melee_vr.dmi',
+ )
+ force_wielded = 18 //a bit stronger than a stun baton
+ force_divisor = 0.45
+ unwielded_force_divisor = 0.1
+ var/base_parry_chance = 20
+ var/disarm_defense = 1.5 //bonus multiplier to parry rate when in disarm stance
+ var/stun_chance = 25 //chance to weaken an opponent when used in disarm stance only, remembering that disarm also halves damage dealt
+ var/stun_duration = 2
+ attack_verb = list("struck","smashed","thumped","thrashed","beaten","slammed","battered")
+ edge = FALSE
+ sharp = FALSE
+
+/obj/item/weapon/material/twohanded/staff/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
+ var/parry_chance
+ if(istype(damage_source, /obj/item/projectile)) //can't block ranged attacks, only melee!
+ return 0
+ if(src.wielded == 1)
+ if(user.a_intent == I_DISARM)
+ parry_chance = base_parry_chance * disarm_defense
+ else
+ parry_chance = base_parry_chance
+ if(unique_parry_check(user, attacker, damage_source) && prob(parry_chance))
+ user.visible_message("\The [user] parries [attack_text] with \the [src]!")
+ playsound(src, 'sound/weapons/punchmiss.ogg', 50, 1)
+ return 1
+ return 0
+
+/obj/item/weapon/material/twohanded/staff/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone)
+ . = ..()
+ if(src.wielded == 1 && user.a_intent == I_DISARM && prob(stun_chance))
+ target.Weaken(stun_duration)
+ user.visible_message("\The [user] trips [target] with \the [src]!")
\ No newline at end of file
diff --git a/code/game/objects/structures/props/rocks.dm b/code/game/objects/structures/props/rocks.dm
index 82a4696887..b76c477367 100644
--- a/code/game/objects/structures/props/rocks.dm
+++ b/code/game/objects/structures/props/rocks.dm
@@ -21,6 +21,16 @@
/obj/structure/prop/rock/small/alt
icon_state = "lavarocks2"
+// Warm rocks used as atoms retention fields outside!
+
+/obj/structure/prop/warmrocks
+ name = "warm rocks"
+ desc = "A number of small rocks that are warm to the touch."
+ icon = 'icons/obj/flora/rocks.dmi'
+ icon_state = "warmrocks"
+ density = FALSE
+ can_atmos_pass = ATMOS_PASS_NO
+
//Water-trim versions for placing in water
/obj/structure/prop/rock/water
diff --git a/code/game/turfs/flooring/flooring_vr.dm b/code/game/turfs/flooring/flooring_vr.dm
index ac7f42a406..6e6fc78a27 100644
--- a/code/game/turfs/flooring/flooring_vr.dm
+++ b/code/game/turfs/flooring/flooring_vr.dm
@@ -17,6 +17,12 @@
desc = "This slick flesh ripples and squishes under your touch"
icon = 'icons/turf/stomach_vr.dmi'
icon_base = "flesh_floor"
+ footstep_sounds = list("human" = list(
+ 'sound/effects/footstep/mud1.ogg',
+ 'sound/effects/footstep/mud2.ogg',
+ 'sound/effects/footstep/mud3.ogg',
+ 'sound/effects/footstep/mud4.ogg'
+ ))
/decl/flooring/grass/outdoors
flags = 0
@@ -115,4 +121,4 @@
initial_flooring = /decl/flooring/tiling/milspec/raised
/obj/item/stack/tile/floor/milspec/raised
- name = "raised milspec floor tile"
\ No newline at end of file
+ name = "raised milspec floor tile"
diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm
index 500f719baa..725aefb854 100644
--- a/code/game/turfs/simulated/water.dm
+++ b/code/game/turfs/simulated/water.dm
@@ -4,6 +4,7 @@
desc = "A body of water. It seems shallow enough to walk through, if needed."
icon = 'icons/turf/outdoors.dmi'
icon_state = "seashallow" // So it shows up in the map editor as water.
+ var/water_icon = 'icons/turf/outdoors.dmi'
var/water_state = "water_shallow"
var/under_state = "rock"
edge_blending_priority = -1
@@ -31,7 +32,7 @@
..() // To get the edges.
icon_state = under_state // This isn't set at compile time in order for it to show as water in the map editor.
- var/image/water_sprite = image(icon = 'icons/turf/outdoors.dmi', icon_state = water_state, layer = WATER_LAYER)
+ var/image/water_sprite = image(icon = water_icon, icon_state = water_state, layer = WATER_LAYER)
add_overlay(water_sprite)
/turf/simulated/floor/water/get_edge_icon_state()
diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm
index bf6fc1f2d6..ff66ed5cea 100644
--- a/code/modules/admin/admin_verb_lists_vr.dm
+++ b/code/modules/admin/admin_verb_lists_vr.dm
@@ -171,7 +171,8 @@ var/list/admin_verbs_fun = list(
/client/proc/remove_mob_for_narration, //VOREStation Add
/client/proc/narrate_mob, //VOREStation Add
/client/proc/narrate_mob_args, //VOREStation Add
- /client/proc/getPlayerStatus //VORESTation Add
+ /client/proc/getPlayerStatus, //VORESTation Add
+ /client/proc/manage_event_triggers
)
diff --git a/code/modules/admin/verbs/buildmode.dm b/code/modules/admin/verbs/buildmode.dm
index 8dabdc627a..be587b6f41 100644
--- a/code/modules/admin/verbs/buildmode.dm
+++ b/code/modules/admin/verbs/buildmode.dm
@@ -159,8 +159,9 @@
Left Mouse Button + alt on AI mob = Toggle hostility on mob
\
Left Mouse Button + shift on AI mob = Toggle AI (also resets)
\
Left Mouse Button + ctrl on AI mob = Copy mob faction
\
+ Middle Mouse Button + alt on any atom = Add atom to entity narrate menu
\
Middle Mouse Button + shift on any = Set selected mob(s) to wander
\
- Middle Mouse Button + shift on any = Set selected mob(s) to NOT wander
\
+ Middle Mouse Button + ctrl on any = Set selected mob(s) to NOT wander
\
Right Mouse Button + ctrl on any mob = Paste mob faction copied with Left Mouse Button + shift
\
Right Mouse Button on enemy mob = Command selected mobs to attack mob
\
Right Mouse Button on allied mob = Command selected mobs to follow mob
\
@@ -558,6 +559,9 @@
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
AI.wander = FALSE
+ if(pa.Find("alt") && isatom(object))
+ to_chat(user, SPAN_NOTICE("Adding [object] to Entity Narrate List!"))
+ user.client.add_mob_for_narration(object)
if(pa.Find("right"))
diff --git a/code/modules/admin/verbs/entity_narrate.dm b/code/modules/admin/verbs/entity_narrate.dm
index 0b42058c84..a0da92eb42 100644
--- a/code/modules/admin/verbs/entity_narrate.dm
+++ b/code/modules/admin/verbs/entity_narrate.dm
@@ -57,7 +57,7 @@
add_mob_for_narration(L) //Recursively calling ourselves until cancelled or a unique name is given.
return
holder.entity_names += unique_name
- holder.entity_refs[unique_name] = L
+ holder.entity_refs[unique_name] = WEAKREF(L)
log_and_message_admins("added [L.name] for their personal list to narrate", usr) //Logging here to avoid spam, while still safeguarding abuse
//Covering functionality for turfs and objs. We need static type to access the name var
@@ -70,7 +70,7 @@
add_mob_for_narration(A)
return
holder.entity_names += unique_name
- holder.entity_refs[unique_name] = A
+ holder.entity_refs[unique_name] = WEAKREF(A)
log_and_message_admins("added [A.name] for their personal list to narrate", usr) //Logging here to avoid spam, while still safeguarding abuse
//Proc for keeping our ref list relevant, deleting mobs that are no longer relevant for our event
@@ -164,8 +164,14 @@
//Separate definition for mob/living and /obj due to .say() code allowing us to engage with languages, stuttering etc
//We also need this so we can check for .client
- if(istype(holder.entity_refs[name], /mob/living))
- var/mob/living/our_entity = holder.entity_refs[name]
+ var/datum/weakref/wref = holder.entity_refs[name]
+ var/selection = wref.resolve()
+ if(!selection)
+ to_chat(usr, SPAN_NOTICE("[name] has invalid reference, deleting"))
+ holder.entity_names -= name
+ holder.entity_refs -= name
+ if(istype(selection, /mob/living))
+ var/mob/living/our_entity = selection
if(our_entity.client) //Making sure we can't speak for players
log_and_message_admins("used entity-narrate to speak through [our_entity.ckey]'s mob", usr)
if(!message)
@@ -179,11 +185,11 @@
//This does cost us some code duplication, but I think it's worth it.
//furthermore, objs/turfs require the usr to specify the verb when speaking, otherwise it looks like an emote.
- else if(istype(holder.entity_refs[name], /atom))
- var/atom/our_entity = holder.entity_refs[name]
+ else if(istype(selection, /atom))
+ var/atom/our_entity = selection
if(!message)
message = tgui_input_text(usr, "Input what you want [our_entity] to [mode]", "narrate", null)
- message = sanitize(message)
+ message = encode_html_emphasis(sanitize(message))
if(message && mode == "Speak")
our_entity.audible_message("[our_entity.name] [message]")
else if(message && mode == "Emote")
@@ -251,7 +257,16 @@
tgui_selected_id_multi = list() //Using the same var for ease of implementation. Thus, we must reset to empty each time.
tgui_selected_id_multi += params["id_selected"]
tgui_selected_id = params["id_selected"]
- tgui_selected_refs = entity_refs[tgui_selected_id]
+ var/datum/weakref/wref = entity_refs[tgui_selected_id]
+ tgui_selected_refs = wref.resolve()
+ if(!tgui_selected_refs)
+ to_chat(usr, SPAN_NOTICE("[tgui_selected_id] has invalid reference, deleting"))
+ entity_names -= tgui_selected_id
+ entity_refs -= tgui_selected_id
+ tgui_selected_id = ""
+ tgui_selected_type = ""
+ tgui_selected_name = ""
+ tgui_selected_refs = null
if(istype(tgui_selected_refs, /mob/living))
var/mob/living/L = tgui_selected_refs
if(L.client)
@@ -273,7 +288,14 @@
var/message = params["message"] //Sanitizing before speaking it
if(tgui_selection_mode)
for(var/entity in tgui_selected_id_multi)
- var/ref = entity_refs[entity]
+ var/datum/weakref/wref = entity_refs[entity]
+ var/ref = wref.resolve()
+ if(!ref)
+ to_chat(usr, SPAN_NOTICE("[entity] has invalid reference, deleting"))
+ entity_names -= entity
+ entity_refs -= entity
+ tgui_selected_id_multi -= entity
+ continue
if(istype(ref, /mob/living))
var/mob/living/L = ref
if(L.client)
@@ -283,7 +305,17 @@
var/atom/A = ref
narrate_tgui_atom(A, message)
else
- var/ref = entity_refs[tgui_selected_id]
+ var/datum/weakref/wref = entity_refs[tgui_selected_id]
+ var/ref = wref.resolve()
+ if(!ref)
+ to_chat(usr, SPAN_NOTICE("[tgui_selected_id] has invalid reference, deleting"))
+ entity_names -= tgui_selected_id
+ entity_refs -= tgui_selected_id
+ tgui_selected_id = ""
+ tgui_selected_type = ""
+ tgui_selected_name = ""
+ tgui_selected_refs = null
+ return
if(istype(ref, /mob/living))
var/mob/living/L = ref
if(L.client)
@@ -305,7 +337,7 @@
L.say(message)
/datum/entity_narrate/proc/narrate_tgui_atom(atom/A, message as text)
- message = sanitize(message)
+ message = encode_html_emphasis(sanitize(message))
if(tgui_narrate_mode && tgui_narrate_privacy)
A.visible_message("\The [A.name] [message]", range = 1)
else if(tgui_narrate_mode && !tgui_narrate_privacy)
diff --git a/code/modules/admin/verbs/event_triggers.dm b/code/modules/admin/verbs/event_triggers.dm
new file mode 100644
index 0000000000..332e6a7d62
--- /dev/null
+++ b/code/modules/admin/verbs/event_triggers.dm
@@ -0,0 +1,128 @@
+/*
+Eventkit verb to be used to spawn the obj/effect/landmarks defined under code\game\objects\effects\landmarks_events.dm
+*/
+/client/proc/manage_event_triggers()
+ set name = "Manage Event Triggers"
+ set desc = "Open dialogue to create or delete narration/notification triggers"
+ set category = "EventKit"
+
+ if(!check_rights(R_FUN)) return
+
+
+
+ var/mode = tgui_input_list(src, "What do you wish to do?", "Manage Event Triggers", \
+ list(
+ "Create Notification Trigger",
+ "Create Narration Trigger",
+ "Manage Personal Triggers",
+ "Manage Other's Triggers",
+ "Cancel"
+ ), "Cancel" )
+ if(!mode || mode == "Cancel") return
+
+ feedback_add_details("admin_verb","EventTriggerManage")
+ switch(mode)
+
+ if("Create Notification Trigger")
+ var/obj/effect/landmark/event_trigger/ET = new /obj/effect/landmark/event_trigger(usr.loc)
+ ET.set_vars(src)
+
+ if("Create Narration Trigger")
+
+ var/obj/effect/landmark/event_trigger/auto_narrate/AN = new /obj/effect/landmark/event_trigger/auto_narrate(usr.loc)
+ AN.set_vars(src)
+
+ if("Manage Personal Triggers")
+ var/personal_list = event_triggers[src.ckey]
+ if(!LAZYLEN(personal_list))
+ to_chat(src, SPAN_NOTICE("You don't have any landmarks to manage!"))
+ return
+ personal_list |= list("Cancel", "Delete All")
+ var/choice = tgui_input_list(src, "Select a landmark to choose between teleporting to it or deleting it, select delete all to clear them.", \
+ "Manage Personal Triggers", personal_list)
+ if(!choice || choice == "Cancel")
+ return
+ if(choice == "Delete All")
+ var/confirm = tgui_alert(src, "ARE YOU SURE? THERE IS NO GOING BACK", "CONFIRM", list("Go Back", "Delete all my event triggers"), autofocus = FALSE)
+ if(confirm == "Go Back")
+ return
+ for(var/obj/effect/landmark/event_trigger/ET in personal_list)
+ ET.delete_me = TRUE
+ qdel(ET)
+ else if(istype(choice, /obj/effect/landmark/event_trigger))
+ var/obj/effect/landmark/event_trigger/ET = choice
+ var/decision = tgui_alert(src, "Teleport to Landmark or Delete it?", "Manage [ET.name]", list("Teleport", "Delete"), autofocus = FALSE)
+ if(decision == "Teleport")
+ var/mob/M = usr
+ if(isobserver(M))
+ var/confirm_teleport = tgui_alert(src, "You're not a ghost! Admin-ghost?", "You're not a ghost", \
+ list("Cancel", "Teleport me with my character"))
+ if(confirm_teleport == "Cancel")
+ return
+ M.forceMove(get_turf(ET))
+ if(decision == "Delete")
+ var/confirm = tgui_alert(src, "ARE YOU SURE? THERE IS NO GOING BACK FROM DELETING [ET.name]", "CONFIRM", list("Go Back", "Delete it!"), autofocus = FALSE)
+ if(confirm == "Go Back")
+ return
+ ET.delete_me = TRUE
+ qdel(ET)
+ if("Manage Other's Triggers")
+ var/other_ckey = sanitize(tgui_input_text(src, "input trigger owner's ckey", "CKEY", ""))
+ var/others_list = event_triggers[other_ckey]
+ if(!LAZYLEN(others_list))
+ to_chat(src, SPAN_NOTICE("[other_ckey] doesn't have any landmarks to manage!"))
+ return
+ others_list |= list("Cancel", "Delete All")
+ var/choice = tgui_input_list(src, "Select a landmark to choose between teleporting to it or deleting it, select delete all to clear them.", \
+ "Manage Personal Triggers", others_list)
+ if(!choice || choice == "Cancel")
+ return
+ if(choice == "Delete All")
+ if(other_ckey && GLOB.directory[other_ckey])
+ var/client/C = GLOB.directory[other_ckey]
+ var/mob/M = C.statobj
+ if(M.client && M.client.inactivity < 30 MINUTES)
+ if(tgui_alert(src, "[M] has only been inactive for [M.client.inactivity / (1 MINUTE)] minutes.\n \
+ If you want to delete their event triggers, ask them in asay or discord to do it themselves or wait 30 minutes. \n \
+ Only proceed if you are absolutely certain.", "Force Delete", list("Confirm", "Cancel")) == "Confirm")
+ for(var/obj/effect/landmark/event_trigger/ET in others_list)
+ ET.delete_me = TRUE
+ qdel(ET)
+ log_and_message_admins("[src.ckey] deleted all of [other_ckey]'s event triggers while [other_ckey] was active")
+ return
+ var/confirm = tgui_alert(src, "ARE YOU SURE? THERE IS NO GOING BACK", "CONFIRM", list("Go Back", "Delete all my event triggers"), autofocus = FALSE)
+ if(confirm == "Go Back")
+ return
+ for(var/obj/effect/landmark/event_trigger/ET in others_list)
+ ET.delete_me = TRUE
+ qdel(ET)
+ log_and_message_admins("[src.ckey] deleted all of [other_ckey]'s event triggers. [other_ckey] was either inactive or disconnected at this time.")
+ else if(istype(choice, /obj/effect/landmark/event_trigger))
+ var/obj/effect/landmark/event_trigger/ET = choice
+ var/decision = tgui_alert(src, "Teleport to Landmark or Delete it?", "Manage [ET]", list("Teleport", "Delete"), autofocus = FALSE)
+ if(decision == "Teleport")
+ var/mob/M = usr
+ if(isobserver(M))
+ var/confirm_teleport = tgui_alert(src, "You're not a ghost! Admin-ghost?", "You're not a ghost", \
+ list("Cancel", "Teleport me with my character"))
+ if(confirm_teleport == "Cancel")
+ return
+ M.forceMove(get_turf(ET))
+ if(decision == "Delete")
+ if(other_ckey && GLOB.directory[other_ckey])
+ var/client/C = GLOB.directory[other_ckey]
+ var/mob/M = C.statobj
+ if(M.client && M.client.inactivity < 30 MINUTES)
+ if(tgui_alert(src, "[M] has only been inactive for [M.client.inactivity / (1 MINUTE)] minutes.\n \
+ If you want to delete their event triggers, ask them in asay or discord to do it themselves or wait 30 minutes. \n \
+ Only proceed if you are absolutely certain.", "Force Delete", list("Confirm", "Cancel")) == "Confirm")
+ ET.delete_me = TRUE
+ qdel(ET)
+ log_and_message_admins("[src.ckey] tried to delete event trigger [ET.name] while [other_ckey] is active.")
+ return
+ var/confirm = tgui_alert(src, "ARE YOU SURE? THERE IS NO GOING BACK FROM DELETING [ET.name]", "CONFIRM", list("Go Back", "Delete it!"), autofocus = FALSE)
+ if(confirm == "Go Back")
+ return
+ ET.delete_me = TRUE
+ qdel(ET)
+ log_and_message_admins("[src.ckey] tried to deleted event trigger [ET.name], [other_ckey] is either disconnected or inactive.")
diff --git a/code/modules/admin/view_variables/helpers.dm b/code/modules/admin/view_variables/helpers.dm
index bad05eb138..a678271e88 100644
--- a/code/modules/admin/view_variables/helpers.dm
+++ b/code/modules/admin/view_variables/helpers.dm
@@ -38,6 +38,7 @@
+
@@ -48,6 +49,7 @@
+
diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm
index 6554626777..8a3f8811c2 100644
--- a/code/modules/admin/view_variables/topic.dm
+++ b/code/modules/admin/view_variables/topic.dm
@@ -92,6 +92,44 @@
src.admin_give_modifier(M)
href_list["datumrefresh"] = href_list["give_modifier"]
+ else if(href_list["give_wound_internal"])
+ if(!check_rights(R_ADMIN|R_FUN|R_DEBUG|R_EVENT))
+ return
+
+ var/mob/living/carbon/human/H = locate(href_list["give_wound_internal"])
+ if(!istype(H))
+ to_chat(usr, span_notice("This can only be used on instances of type /mob/living/carbon/human"))
+ return
+
+ var/severity = tgui_input_number(usr, "How much damage should the bleeding internal wound cause? \
+ Bleed timer directly correlates with this. 0 cancels. Input is rounded to nearest integer.",
+ "Wound Severity", 0, min_value = 0, round_value = TRUE )
+ if(!severity) return
+
+ var/obj/item/organ/external/chosen_organ = tgui_input_list(usr, "Choose an external organ to inflict IB on!", "Organ Choice", H.organs)
+ if(!chosen_organ || !istype(chosen_organ))
+ to_chat(usr, span_notice("The chosen organ is of inappropriate type or no longer exists."))
+ return
+
+ var/datum/wound/internal_bleeding/I = new /datum/wound/internal_bleeding(severity)
+ if(!I || !istype(I))
+ to_chat(usr, span_notice("Could not initialize internal wound"))
+ log_debug("[usr] attempted to create an internal bleeding wound on [H]'s [chosen_organ] of [severity] damage \
+ and wound initialization failed")
+
+ chosen_organ.wounds += I
+ chosen_organ.update_wounds()
+ chosen_organ.update_damages()
+ H.bad_external_organs += chosen_organ
+ H.handle_organs()
+
+ if(H.client)
+ H.custom_pain("You feel a throbbing pain inside your [chosen_organ]", severity, force=TRUE)
+ log_and_message_admins("created an Internal Bleeding wound on [H.ckey]'s mob [H] on [chosen_organ] of [severity] damage", usr)
+
+ href_list["datumrefresh"] = href_list["give_wound_internal"]
+
+
else if(href_list["give_disease2"])
if(!check_rights(R_ADMIN|R_FUN|R_EVENT)) return
@@ -157,6 +195,29 @@
if(usr.client)
usr.client.cmd_assume_direct_control(M)
+ else if(href_list["give_ai"])
+ if(!check_rights(0)) return
+
+ var/mob/M = locate(href_list["give_ai"])
+ if(!istype(M, /mob/living))
+ to_chat(usr, span_notice("This can only be used on instances of type /mob/living"))
+ return
+ var/mob/living/L = M
+ if(L.client || L.teleop)
+ to_chat(usr, span_warning("This cannot be used on player mobs!"))
+ return
+
+ if(L.ai_holder) //Cleaning up the original ai
+ var/ai_holder_old = L.ai_holder
+ L.ai_holder = null
+ qdel(ai_holder_old) //Only way I could make #TESTING - Unable to be GC'd to stop. del() logs show it works.
+ L.ai_holder_type = tgui_input_list(usr, "Choose AI holder", "AI Type", typesof(/datum/ai_holder/))
+ L.initialize_ai_holder()
+ L.faction = sanitize(tgui_input_text(usr, "Please input AI faction", "AI faction", "neutral"))
+ L.a_intent = tgui_input_list(usr, "Please choose AI intent", "AI intent", list(I_HURT, I_HELP))
+ if(tgui_alert(usr, "Make mob wake up? This is needed for carbon mobs.", "Wake mob?", list("Yes", "No")) == "Yes")
+ L.AdjustSleeping(-100)
+
else if(href_list["make_skeleton"])
if(!check_rights(R_FUN)) return
diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm
index 307d3870b2..78e85bfa3e 100644
--- a/code/modules/ai/ai_holder.dm
+++ b/code/modules/ai/ai_holder.dm
@@ -15,12 +15,8 @@
var/ai_holder_type = null // Which ai_holder datum to give to the mob when initialized. If null, nothing happens.
/mob/living/Initialize()
- if(ai_holder_type)
- ai_holder = new ai_holder_type(src)
- if(istype(src, /mob/living/carbon/human))
- var/mob/living/carbon/human/H = src
- H.hud_used = new /datum/hud(H)
- H.create_mob_hud(H.hud_used)
+ if(!ai_holder)
+ initialize_ai_holder()
return ..()
/mob/living/Destroy()
@@ -37,6 +33,22 @@
ai_holder.manage_processing(AI_PROCESSING)
return ..()
+//Extracted from mob/living/Initialize() so that we may call it at any time after a mob was created
+/mob/living/proc/initialize_ai_holder()
+ if(ai_holder) //Making double sure we clean up and properly GC the original ai_holder
+ var/old_holder = ai_holder
+ ai_holder = null
+ qdel(old_holder)
+ if(ai_holder_type)
+ ai_holder = new ai_holder_type(src)
+ if(!ai_holder)
+ log_debug("[src] could not initialize ai_holder of type [ai_holder_type]")
+ return
+ if(istype(src, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = src
+ H.hud_used = new /datum/hud(H)
+ H.create_mob_hud(H.hud_used)
+
/datum/ai_holder
var/mob/living/holder = null // The mob this datum is going to control.
var/stance = STANCE_IDLE // Determines if the mob should be doing a specific thing, e.g. attacking, following, standing around, etc.
@@ -447,7 +459,7 @@
if(speak_chance) // In the long loop since otherwise it wont shut up.
handle_idle_speaking()
- if(hostile)
+ if(hostile || vore_hostile)
ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE)
find_target()
diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm
index ef301ccf71..835bd36fe9 100644
--- a/code/modules/ai/ai_holder_targeting.dm
+++ b/code/modules/ai/ai_holder_targeting.dm
@@ -6,6 +6,9 @@
var/mauling = FALSE // Attacks unconscious mobs
var/unconscious_vore = FALSE //VOREStation Add - allows a mob to go for unconcious targets IF their vore prefs align
var/handle_corpse = FALSE // Allows AI to acknowledge corpses (e.g. nurse spiders)
+ var/vore_hostile = FALSE // The same as hostile, but with vore pref checks
+ var/micro_hunt = FALSE // Will target mobs at or under the micro_hunt_size size, requires vore_hostile to be true
+ var/micro_hunt_size = 0.25
var/atom/movable/target = null // The thing (mob or object) we're trying to kill.
var/atom/movable/preferred_target = null// If set, and if given the chance, we will always prefer to target this over other options.
@@ -312,3 +315,14 @@
/datum/ai_holder/proc/lose_taunt()
ai_log("lose_taunt() : Resetting preferred_target.", AI_LOG_INFO)
preferred_target = null
+
+/datum/ai_holder/proc/vore_check(mob/living/L)
+ if(!holder.vore_selected) //We probably don't have a belly so don't even try
+ return FALSE
+ if(!isliving(L)) //We only want mob/living
+ return FALSE
+ if(!L.devourable || !L.allowmobvore) //Check their prefs
+ return FALSE
+ if(micro_hunt && !(L.get_effective_size(TRUE) <= micro_hunt_size)) //Are they small enough to get?
+ return FALSE
+ return TRUE // Let's go!
diff --git a/code/modules/ai/ai_holder_targeting_vr.dm b/code/modules/ai/ai_holder_targeting_vr.dm
index 4c7ef85403..0828e1b468 100644
--- a/code/modules/ai/ai_holder_targeting_vr.dm
+++ b/code/modules/ai/ai_holder_targeting_vr.dm
@@ -3,3 +3,50 @@
return FALSE
return ..()
+
+/datum/ai_holder/simple_mob/vore
+ hostile = FALSE
+ retaliate = TRUE
+ vore_hostile = TRUE
+ forgive_resting = TRUE
+ cooperative = FALSE
+
+/datum/ai_holder/simple_mob/vore/micro_hunter
+ micro_hunt = TRUE
+ micro_hunt_size = 0.8
+
+/datum/ai_holder/simple_mob/vore/hostile
+ hostile = TRUE
+
+/datum/ai_holder/simple_mob/vore/find_target(list/possible_targets, has_targets_list)
+ if(!vore_hostile)
+ return ..()
+ if(!isanimal(holder)) //Only simplemobs have the vars we need
+ return ..()
+ var/mob/living/simple_mob/H = holder
+ if(H.vore_fullness >= H.vore_capacity) //Don't beat people up if we're full
+ return ..()
+ ai_log("find_target() : Entered.", AI_LOG_TRACE)
+
+ . = list()
+ if(!has_targets_list)
+ possible_targets = list_targets()
+ var/list/valid_mobs = list()
+ for(var/mob/living/possible_target in possible_targets)
+ if(!can_attack(possible_target))
+ continue
+ . |= possible_target
+ if(!isliving(possible_target))
+ continue
+ if(vore_check(possible_target))
+ valid_mobs |= possible_target
+
+ var/new_target
+ if(valid_mobs.len)
+ new_target = pick(valid_mobs)
+ else if(hostile)
+ new_target = pick(.)
+ if(!new_target)
+ return null
+ give_target(new_target)
+ return new_target
diff --git a/code/modules/awaymissions/redgate.dm b/code/modules/awaymissions/redgate.dm
index b9757e9aac..340fbd7421 100644
--- a/code/modules/awaymissions/redgate.dm
+++ b/code/modules/awaymissions/redgate.dm
@@ -9,7 +9,14 @@
pixel_x = -16
var/obj/structure/redgate/target
- var/list/exceptions = list(/obj/structure/ore_box) //made it a var so that GMs or map makers can selectively allow things to pass through
+ var/secret = FALSE //If either end of the redgate has this enabled, ghosts will not be able to click to teleport
+ var/list/exceptions = list(
+ /obj/structure/ore_box
+ ) //made it a var so that GMs or map makers can selectively allow things to pass through
+ var/list/restrictions = list(
+ /mob/living/simple_mob/vore/overmap/stardog,
+ /mob/living/simple_mob/vore/bigdragon
+ ) //There are some things we don't want to come through no matter what.
/obj/structure/redgate/Destroy()
if(target)
@@ -27,6 +34,10 @@
keycheck = FALSE //we'll allow it
else
return
+
+ if(M.type in restrictions) //Some stuff we don't want to bring EVEN IF it has a key.
+ return
+
if(keycheck) //exceptions probably won't have a ckey
if(!M.ckey) //We only want players, no bringing the weird stuff on the other side back
return
@@ -34,13 +45,22 @@
if(!target)
toggle_portal()
- var/turf/place = get_turf(target)
- var/possible_turfs = place.AdjacentTurfs()
- if(isemptylist(possible_turfs))
+ var/turf/ourturf = find_our_turf(M) //Find the turf on the opposite side of the target
+ if(!ourturf.check_density(TRUE,TRUE)) //Make sure there isn't a wall there
+ M.unbuckle_all_mobs(TRUE)
+ M.stop_pulling()
+ playsound(src,'sound/effects/ominous-hum-2.ogg', 100,1)
+ M.forceMove(ourturf) //Let's just do forcemove, I don't really want people teleporting to weird places if they have bluespace stuff
+ else
to_chat(M, "Something blocks your way.")
- return
- var/turf/temptarg = pick(possible_turfs)
- do_safe_teleport(M, temptarg, 0)
+
+/obj/structure/redgate/proc/find_our_turf(var/atom/movable/AM) //This finds the turf on the opposite side of the target gate from where you are
+ var/offset_x = x - AM.x //used for more smooth teleporting
+ var/offset_y = y - AM.y
+
+ var/turf/temptarg = locate((target.x + offset_x),(target.y + offset_y),target.z)
+
+ return temptarg
/obj/structure/redgate/proc/toggle_portal()
if(target)
@@ -71,8 +91,10 @@
/obj/structure/redgate/attack_ghost(var/mob/observer/dead/user)
- if(target && user?.client?.holder)
- user.forceMove(get_turf(target))
+
+ if(target)
+ if(!(secret || target.secret) || user?.client?.holder)
+ user.forceMove(get_turf(target))
else
return ..()
@@ -139,3 +161,47 @@
As soon as you read this, get yourself out of here and come find us. We left you enough money to make the trip in the usual spot. We'll be in the registry once we arrive. We're waiting for you.
Yours, Medley"}
+
+/area/redgate/hotsprings
+ name = "hotsprings"
+ requires_power = 0
+
+/area/redgate/hotsprings/outdoors
+ name = "snowfields"
+ icon_state = "hotsprings_outside"
+
+/area/redgate/hotsprings/redgate
+ name = "redgate facility"
+ icon_state = "hotsprings_redgate"
+
+/area/redgate/hotsprings/westcave
+ name = "hotspring caves"
+ icon_state = "hotsprings_westcave"
+
+/area/redgate/hotsprings/eastcave
+ name = "icy caverns"
+ icon_state = "hotsprings_eastcave"
+
+/area/redgate/hotsprings/house
+ name = "snowy cabin"
+ icon_state = "hotsprings_house"
+
+/area/redgate/hotsprings/house/dorm1
+ name = "hotsprings dorm 1"
+ icon_state = "hotsprings_dorm1"
+
+/area/redgate/hotsprings/house/dorm2
+ name = "hotsprings dorm 2"
+ icon_state = "hotsprings_dorm2"
+
+/area/redgate/hotsprings/house/hotspringhouse
+ name = "small cabin"
+ icon_state = "hotsprings_hotspringhouse"
+
+/area/redgate/hotsprings/house/lovercave
+ name = "cosy cave"
+ icon_state = "hotsprings_lovercave"
+
+/area/redgate/hotsprings/house/succcave
+ name = "tiny cave"
+ icon_state = "hotsprings_succcave"
\ No newline at end of file
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index 8c624ff369..ed652ad8ea 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -263,7 +263,7 @@
if(alert)
for(var/client/X in GLOB.admins)
if(X.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping))
- X << 'sound/voice/bcriminal.ogg'
+ X << 'sound/effects/tones/newplayerping.ogg'
window_flash(X)
//VOREStation Edit end.
diff --git a/code/modules/client/preference_setup/loadout/loadout_gloves.dm b/code/modules/client/preference_setup/loadout/loadout_gloves.dm
index b3ea8d1b69..811d0740f3 100644
--- a/code/modules/client/preference_setup/loadout/loadout_gloves.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_gloves.dm
@@ -58,8 +58,18 @@
/datum/gear/gloves/fingerless
display_name = "fingerless gloves"
+ description = "A pair of gloves that don't actually cover the fingers. Available in classic black or recolourable white."
path = /obj/item/clothing/gloves/fingerless
+/datum/gear/gloves/fingerless/New()
+ ..()
+ var/list/selector_uniforms = list(
+ "black"=/obj/item/clothing/gloves/fingerless,
+ "recolourable white"=/obj/item/clothing/gloves/fingerless_recolourable
+ )
+ gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms))
+ gear_tweaks += gear_tweak_free_color_choice
+
/datum/gear/gloves/ring
display_name = "ring selection"
description = "Choose from a number of rings."
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index 7019d70528..0a5622752c 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -53,7 +53,7 @@
var/ooc_style = "everyone"
if(holder && !holder.fakekey)
ooc_style = "elevated"
- //VOREStation Block Edit Start
+
if(holder.rights & R_EVENT) //Retired Admins
ooc_style = "event_manager"
if(holder.rights & R_ADMIN && !(holder.rights & R_BAN)) //Game Masters
@@ -62,7 +62,7 @@
ooc_style = "developer"
if(holder.rights & R_ADMIN && holder.rights & R_BAN) //Admins
ooc_style = "admin"
- //VOREStation Block Edit End
+
for(var/client/target in GLOB.clients)
if(target.is_preference_enabled(/datum/client_preference/show_ooc))
@@ -149,12 +149,12 @@
display_name = holder.fakekey
if(mob.stat != DEAD)
display_name = mob.name
- //VOREStation Add - Resleeving shenanigan prevention
+
if(ishuman(mob))
var/mob/living/carbon/human/H = mob
if(H.original_player && H.original_player != H.ckey) //In a body not their own
display_name = "[H.mind.name] (as [H.name])"
- //VOREStation Add End
+
// Everyone in normal viewing range of the LOOC
for(var/mob/viewer in m_viewers)
@@ -177,12 +177,12 @@
if(target in GLOB.admins)
admin_stuff += "/([key])"
- to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " [display_name][admin_stuff]: ")
+ to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " [display_name][admin_stuff]: ")
for(var/client/target in r_receivers)
var/admin_stuff = "/([key])([admin_jump_link(mob, target.holder)])"
- to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " (R)[display_name][admin_stuff]: ")
+ to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " (R)[display_name][admin_stuff]: ")
/mob/proc/get_looc_source()
return src
diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm
index 007fdd7528..efcd364922 100644
--- a/code/modules/clothing/gloves/color.dm
+++ b/code/modules/clothing/gloves/color.dm
@@ -90,4 +90,10 @@
desc = "A pair of gloves that don't actually cover the fingers."
name = "fingerless gloves"
icon_state = "fingerlessgloves"
+ fingerprint_chance = 100
+
+/obj/item/clothing/gloves/fingerless_recolourable
+ desc = "A pair of gloves that don't actually cover the fingers."
+ name = "fingerless gloves"
+ icon_state = "fingerlessgloves_rc"
fingerprint_chance = 100
\ No newline at end of file
diff --git a/code/modules/economy/mint.dm b/code/modules/economy/mint.dm
index db50578fe7..07bc1d22a7 100644
--- a/code/modules/economy/mint.dm
+++ b/code/modules/economy/mint.dm
@@ -1,210 +1,49 @@
/**********************Mint**************************/
/obj/machinery/mineral/mint
name = "Coin press"
+ desc = "A relatively crude hand-operated coin press that turns sheets (or ingots, as the case may be) of materials into fresh coins. They're probably not going to be considered legal tender in most polities, but you might fool a vending machine with one..?
It looks like it'll accept silver, gold, diamond, iron, solid phoron, and uranium."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "coinpress0"
density = TRUE
anchored = TRUE
- var/obj/machinery/mineral/input = null
- var/obj/machinery/mineral/output = null
- var/amt_silver = 0 //amount of silver
- var/amt_gold = 0 //amount of gold
- var/amt_diamond = 0
- var/amt_iron = 0
- var/amt_phoron = 0
- var/amt_uranium = 0
- var/newCoins = 0 //how many coins the machine made in it's last load
- var/processing = 0
- var/chosen = MAT_STEEL //which material will be used to make coins
- var/coinsToProduce = 10
+ var/coinsToProduce = 6 //how many coins do we make per sheet? a sheet is 2000 units whilst a coin is 250, and some material should be lost in the process
+ var/list/validMats = list("silver", "gold", "diamond", "iron", "phoron", "uranium") //what's valid stuff to make coins out of?
-
-/obj/machinery/mineral/mint/Initialize()
- . = ..()
- for (var/dir in cardinal)
- input = locate(/obj/machinery/mineral/input, get_step(src, dir))
- if(input)
- break
- for (var/dir in cardinal)
- output = locate(/obj/machinery/mineral/output, get_step(src, dir))
- if(output)
- break
-
-/obj/machinery/mineral/mint/process()
- if(!input)
- return
-
- var/obj/item/stack/O = locate(/obj/item/stack, input.loc)
- if(!O)
- return
-
- var/processed = 1
- switch(O.get_material_name())
- if("gold")
- amt_gold += 100 * O.get_amount()
- if("silver")
- amt_silver += 100 * O.get_amount()
- if("diamond")
- amt_diamond += 100 * O.get_amount()
- if("phoron")
- amt_phoron += 100 * O.get_amount()
- if("uranium")
- amt_uranium += 100 * O.get_amount()
- if(MAT_STEEL)
- amt_iron += 100 * O.get_amount()
- else
- processed = 0
- if(processed)
- qdel(O)
-
-/obj/machinery/mineral/mint/attack_hand(user as mob)
-
- var/dat = "Coin Press
"
-
- if (!input)
- dat += text("input connection status: ")
- dat += text("NOT CONNECTED
")
- if (!output)
- dat += text("
output connection status: ")
- dat += text("NOT CONNECTED
")
-
- dat += text("
Gold inserted: [amt_gold] ")
- if (chosen == "gold")
- dat += text("chosen")
- else
- dat += text("Choose")
- dat += text("
Silver inserted: [amt_silver] ")
- if (chosen == "silver")
- dat += text("chosen")
- else
- dat += text("Choose")
- dat += text("
Iron inserted: [amt_iron] ")
- if (chosen == MAT_STEEL)
- dat += text("chosen")
- else
- dat += text("Choose")
- dat += text("
Diamond inserted: [amt_diamond] ")
- if (chosen == "diamond")
- dat += text("chosen")
- else
- dat += text("Choose")
- dat += text("
Phoron inserted: [amt_phoron] ")
- if (chosen == "phoron")
- dat += text("chosen")
- else
- dat += text("Choose")
- dat += text("
Uranium inserted: [amt_uranium] ")
- if (chosen == "uranium")
- dat += text("chosen")
- else
- dat += text("Choose")
-
- dat += text("
Will produce [coinsToProduce] [chosen] coins if enough materials are available.
")
- //dat += text("The dial which controls the number of conins to produce seems to be stuck. A technician has already been dispatched to fix this.")
- dat += text("-10 ")
- dat += text("-5 ")
- dat += text("-1 ")
- dat += text("+1 ")
- dat += text("+5 ")
- dat += text("+10 ")
-
- dat += text("
In total this machine produced [newCoins] coins.")
- dat += text("
Make coins")
- user << browse("[dat]", "window=mint")
-
-/obj/machinery/mineral/mint/Topic(href, href_list)
- if(..())
- return 1
- usr.set_machine(src)
- src.add_fingerprint(usr)
- if(processing==1)
- to_chat(usr, "The machine is processing.")
- return
- if(href_list["choose"])
- chosen = href_list["choose"]
- if(href_list["chooseAmt"])
- coinsToProduce = between(0, coinsToProduce + text2num(href_list["chooseAmt"]), 1000)
- if(href_list["makeCoins"])
- var/temp_coins = coinsToProduce
- if (src.output)
- processing = 1;
+/obj/machinery/mineral/mint/attackby(obj/item/stack/material/M as obj, mob/user as mob)
+ if(M.default_type in validMats)
+ user.visible_message("[user] starts to feed a sheet of [M.default_type] into \the [src].")
+ while(M.amount > 0)
icon_state = "coinpress1"
- var/obj/item/weapon/moneybag/M
- switch(chosen)
- if(MAT_STEEL)
- while(amt_iron > 0 && coinsToProduce > 0)
- if (locate(/obj/item/weapon/moneybag,output.loc))
- M = locate(/obj/item/weapon/moneybag,output.loc)
- else
- M = new/obj/item/weapon/moneybag(output.loc)
- new/obj/item/weapon/coin/iron(M)
- amt_iron -= 20
- coinsToProduce--
- newCoins++
- src.updateUsrDialog()
- sleep(5);
- if("gold")
- while(amt_gold > 0 && coinsToProduce > 0)
- if (locate(/obj/item/weapon/moneybag,output.loc))
- M = locate(/obj/item/weapon/moneybag,output.loc)
- else
- M = new/obj/item/weapon/moneybag(output.loc)
- new /obj/item/weapon/coin/gold(M)
- amt_gold -= 20
- coinsToProduce--
- newCoins++
- src.updateUsrDialog()
- sleep(5);
- if("silver")
- while(amt_silver > 0 && coinsToProduce > 0)
- if (locate(/obj/item/weapon/moneybag,output.loc))
- M = locate(/obj/item/weapon/moneybag,output.loc)
- else
- M = new/obj/item/weapon/moneybag(output.loc)
- new /obj/item/weapon/coin/silver(M)
- amt_silver -= 20
- coinsToProduce--
- newCoins++
- src.updateUsrDialog()
- sleep(5);
- if("diamond")
- while(amt_diamond > 0 && coinsToProduce > 0)
- if (locate(/obj/item/weapon/moneybag,output.loc))
- M = locate(/obj/item/weapon/moneybag,output.loc)
- else
- M = new/obj/item/weapon/moneybag(output.loc)
- new /obj/item/weapon/coin/diamond(M)
- amt_diamond -= 20
- coinsToProduce--
- newCoins++
- src.updateUsrDialog()
- sleep(5);
- if("phoron")
- while(amt_phoron > 0 && coinsToProduce > 0)
- if (locate(/obj/item/weapon/moneybag,output.loc))
- M = locate(/obj/item/weapon/moneybag,output.loc)
- else
- M = new/obj/item/weapon/moneybag(output.loc)
- new /obj/item/weapon/coin/phoron(M)
- amt_phoron -= 20
- coinsToProduce--
- newCoins++
- src.updateUsrDialog()
- sleep(5);
- if("uranium")
- while(amt_uranium > 0 && coinsToProduce > 0)
- if (locate(/obj/item/weapon/moneybag,output.loc))
- M = locate(/obj/item/weapon/moneybag,output.loc)
- else
- M = new/obj/item/weapon/moneybag(output.loc)
- new /obj/item/weapon/coin/uranium(M)
- amt_uranium -= 20
- coinsToProduce--
- newCoins++
- src.updateUsrDialog()
- sleep(5)
- icon_state = "coinpress0"
- processing = 0;
- coinsToProduce = temp_coins
- src.updateUsrDialog()
- return
\ No newline at end of file
+ if(do_after(user, 2 SECONDS, src))
+ M.amount--
+ if(M.default_type == "silver")
+ while(coinsToProduce-- > 0)
+ new /obj/item/weapon/coin/silver(user.loc)
+ else if(M.default_type == "gold")
+ while(coinsToProduce-- > 0)
+ new /obj/item/weapon/coin/gold(user.loc)
+ else if(M.default_type == "diamond")
+ while(coinsToProduce-- > 0)
+ new /obj/item/weapon/coin/diamond(user.loc)
+ else if(M.default_type == "iron")
+ while(coinsToProduce-- > 0)
+ new /obj/item/weapon/coin/iron(user.loc)
+ else if(M.default_type == "phoron")
+ while(coinsToProduce-- > 0)
+ new /obj/item/weapon/coin/phoron(user.loc)
+ else if(M.default_type == "uranium")
+ while(coinsToProduce-- > 0)
+ new /obj/item/weapon/coin/uranium(user.loc)
+ src.visible_message("\The [src] rattles and dispenses several [M.default_type] coins!")
+ coinsToProduce = initial(coinsToProduce)
+ if(M.amount == 0)
+ icon_state = "coinpress0"
+ qdel(M) //clean it up just to be sure
+ src.visible_message("\The [src] has run out of usable materials.")
+ break
+ else
+ to_chat(usr,"\The [src] is hand-operated and requires your full attention!")
+ icon_state = "coinpress0"
+ break
+ else
+ src.visible_message("\The [src] doesn't look like it'll accept that material.")
\ No newline at end of file
diff --git a/code/modules/eventkit/gm_interfaces/mob_spawner.dm b/code/modules/eventkit/gm_interfaces/mob_spawner.dm
index 568875065f..e5b9b4180c 100644
--- a/code/modules/eventkit/gm_interfaces/mob_spawner.dm
+++ b/code/modules/eventkit/gm_interfaces/mob_spawner.dm
@@ -2,6 +2,13 @@
// The path of the mob to be spawned
var/path
+ //The ai type path to be assigned to the mob
+ var/use_custom_ai = FALSE
+ var/ai_type = ""
+ var/faction = ""
+ var/intent = ""
+ var/new_path = TRUE //Sets default ai vars based on path. Tracked explicitly because tgui_act wouldn't make it work, used in tgui_data thusly
+
// Defines if the location of the spawned mob should be bound of the users position
var/loc_lock = FALSE
@@ -28,26 +35,40 @@
data["initial_y"] = usr.y;
data["initial_z"] = usr.z;
+
return data
/datum/eventkit/mob_spawner/tgui_data(mob/user)
var/list/data = list()
- data["loc_lock"] = loc_lock;
+ data["loc_lock"] = loc_lock
if(loc_lock)
- data["loc_x"] = usr.x;
- data["loc_y"] = usr.y;
- data["loc_z"] = usr.z;
+ data["loc_x"] = usr.x
+ data["loc_y"] = usr.y
+ data["loc_z"] = usr.z
data["path"] = path;
+ data["use_custom_ai"] = use_custom_ai
+
if(path)
- var/mob/M = new path();
+ var/mob/M = new path()
if(M)
- data["default_path_name"] = M.name;
- data["default_desc"] = M.desc;
- data["default_flavor_text"] = M.flavor_text;
- qdel(M);
+ data["default_path_name"] = M.name
+ data["default_desc"] = M.desc
+ data["default_flavor_text"] = M.flavor_text
+ if(new_path && istype(M, /mob/living))
+ var/mob/living/L = M
+ ai_type = (L.ai_holder_type ? L.ai_holder_type : /datum/ai_holder/simple_mob/inert)
+ faction = (L.faction ? L.faction : "neutral")
+ intent = (L.a_intent ? L.a_intent : I_HELP)
+ new_path = FALSE
+ qdel(L)
+ qdel(M)
+ data["ai_type"] = ai_type
+ data["faction"] = faction
+ data["intent"] = intent
+
return data
@@ -63,7 +84,20 @@
var/newPath = tgui_input_list(usr, "Please select the new path of the mob you want to spawn.", items = choices)
path = newPath
-
+ new_path = TRUE
+ return TRUE
+ if("toggle_custom_ai")
+ use_custom_ai = !use_custom_ai
+ return TRUE
+ if("set_faction")
+ faction = sanitize(tgui_input_text(usr, "Please input your mobs' faction", "Faction", (faction ? faction : "neutral")))
+ return TRUE
+ if("set_intent")
+ intent = tgui_input_list(usr, "Please select preferred intent", "Select Intent", list(I_HELP, I_HURT), (intent ? intent : I_HELP))
+ return TRUE
+ if("set_ai_path")
+ ai_type = tgui_input_list(usr, "Select AI path. Not all subtypes are compatible!", "AI type", \
+ typesof(/datum/ai_holder/), (ai_type ? ai_type : /datum/ai_holder/simple_mob/inert))
return TRUE
if("loc_lock")
loc_lock = !loc_lock
@@ -99,6 +133,18 @@
M.name = sanitize(name)
M.desc = sanitize(params["desc"])
M.flavor_text = sanitize(params["flavor_text"])
+ if(use_custom_ai)
+ if(istype(M, /mob/living))
+ var/mob/living/L = M
+ L.ai_holder_type = ai_type
+ L.faction = faction
+ L.a_intent = intent
+ L.initialize_ai_holder()
+ L.AdjustSleeping(-100)
+ else
+ to_chat(usr, span_notice("You can only set AI for subtypes of mob/living!"))
+
+
/*
WIP: Radius around selected coords
diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm
index 5f7640b321..10b52ed41c 100644
--- a/code/modules/events/carp_migration.dm
+++ b/code/modules/events/carp_migration.dm
@@ -49,6 +49,8 @@
// Okay we did *not* have any landmarks, so lets do our best!
var/i = 1
while (i <= num_groups)
+ if(!affecting_z.len)
+ return
var/Z = pick(affecting_z)
var/group_size = rand(group_size_min, group_size_max)
var/turf/map_center = locate(round(world.maxx/2), round(world.maxy/2), Z)
diff --git a/code/modules/events/gnat_migration.dm b/code/modules/events/gnat_migration.dm
index 8d0428eab8..e8b7696cb6 100644
--- a/code/modules/events/gnat_migration.dm
+++ b/code/modules/events/gnat_migration.dm
@@ -49,6 +49,8 @@
// Okay we did *not* have any landmarks, so lets do our best!
var/i = 1
while (i <= num_groups)
+ if(!affecting_z.len)
+ return
var/Z = pick(affecting_z)
var/group_size = rand(group_size_min, group_size_max)
var/turf/map_center = locate(round(world.maxx/2), round(world.maxy/2), Z)
diff --git a/code/modules/events/jellyfish_migration.dm b/code/modules/events/jellyfish_migration.dm
index 0a0f639451..aecf726739 100644
--- a/code/modules/events/jellyfish_migration.dm
+++ b/code/modules/events/jellyfish_migration.dm
@@ -49,6 +49,8 @@
// Okay we did *not* have any landmarks, so lets do our best!
var/i = 1
while (i <= num_groups)
+ if(!affecting_z.len)
+ return
var/Z = pick(affecting_z)
var/group_size = rand(group_size_min, group_size_max)
var/turf/map_center = locate(round(world.maxx/2), round(world.maxy/2), Z)
diff --git a/code/modules/events/meteors.dm b/code/modules/events/meteors.dm
index 0e4ec62b74..12cbdb770c 100644
--- a/code/modules/events/meteors.dm
+++ b/code/modules/events/meteors.dm
@@ -45,6 +45,8 @@
/datum/event/meteor_wave/proc/send_wave()
var/pick_side = prob(80) ? start_side : (prob(50) ? turn(start_side, 90) : turn(start_side, -90))
+ if(!affecting_z.len)
+ return
spawn() spawn_meteors(get_wave_size(), get_meteors(), pick_side, pick(affecting_z))
next_meteor += rand(next_meteor_lower, next_meteor_upper) / severity
waves--
@@ -135,7 +137,7 @@
. = round(. * 0.5)
if(speed > SHIP_SPEED_FAST) //Sanic stahp
. *= 2
-
+
//Smol ship evasion
if(victim.vessel_size < SHIP_SIZE_LARGE && speed < SHIP_SPEED_FAST)
var/skill_needed = SKILL_PROF
diff --git a/code/modules/events/ray_migration.dm b/code/modules/events/ray_migration.dm
index 559f77169b..94674c7522 100644
--- a/code/modules/events/ray_migration.dm
+++ b/code/modules/events/ray_migration.dm
@@ -49,6 +49,8 @@
// Okay we did *not* have any landmarks, so lets do our best!
var/i = 1
while (i <= num_groups)
+ if(!affecting_z.len)
+ return
var/Z = pick(affecting_z)
var/group_size = rand(group_size_min, group_size_max)
var/turf/map_center = locate(round(world.maxx/2), round(world.maxy/2), Z)
diff --git a/code/modules/events/shark_migration.dm b/code/modules/events/shark_migration.dm
index dc3d0d9438..fd5d152f44 100644
--- a/code/modules/events/shark_migration.dm
+++ b/code/modules/events/shark_migration.dm
@@ -49,6 +49,8 @@
// Okay we did *not* have any landmarks, so lets do our best!
var/i = 1
while (i <= num_groups)
+ if(!affecting_z.len)
+ return
var/Z = pick(affecting_z)
var/group_size = rand(group_size_min, group_size_max)
var/turf/map_center = locate(round(world.maxx/2), round(world.maxy/2), Z)
diff --git a/code/modules/events/spacefish_migration.dm b/code/modules/events/spacefish_migration.dm
index 710946417e..e25ae3a3cb 100644
--- a/code/modules/events/spacefish_migration.dm
+++ b/code/modules/events/spacefish_migration.dm
@@ -64,6 +64,8 @@
// Okay we did *not* have any landmarks, so lets do our best!
var/i = 1
while (i <= num_groups)
+ if(!affecting_z.len)
+ return
var/Z = pick(affecting_z)
var/group_size = rand(group_size_min, group_size_max)
var/turf/map_center = locate(round(world.maxx/2), round(world.maxy/2), Z)
diff --git a/code/modules/examine/examine.dm b/code/modules/examine/examine.dm
index c33ecae6b9..6643e677d6 100644
--- a/code/modules/examine/examine.dm
+++ b/code/modules/examine/examine.dm
@@ -65,15 +65,34 @@
var/description_holders = client.description_holders
stat(null,"[description_holders["icon"]] [description_holders["name"]]") //The name, written in big letters.
stat(null,"[description_holders["desc"]]") //the default examine text.
+
+
+ var/color_i = "#084B8A"
+ var/color_f = "#298A08"
+ var/color_a = "#8A0808"
+/*
+ The infowindow colours are set in code\modules\vchat\js\vchat.js file
+ Unfortunately, I cannot think of a way to do this elegantly where there's this central define that we can easily track.
+ As of 2023/08/05 13:10, the lightmode colour for vchat tabBackgroundColor is "none", this is also defined in interface\skin.dmf .
+ The darkmode colour for vchat tabBackgroundColor is "#272727".
+ Since it's possible that one day we'll have option to modify the user's preferred tabBackgroundColor
+ I will assume the lightmode colour will be left untouched - therefore, we are checking for none.
+*/
+ if(!(winget(src, "infowindow", "background-color") == "none"))
+ color_i = "#709ec9d8"
+ color_f = "#76d357"
+ color_a = "#c94d4d"
+
+
if(description_holders["info"])
- stat(null,"[description_holders["info"]]") //Blue, informative text.
+ stat(null,"[description_holders["info"]]") //Blue, informative text.
if(description_holders["interactions"])
for(var/line in description_holders["interactions"])
- stat(null, "[line]")
+ stat(null, "[line]")
if(description_holders["fluff"])
- stat(null,"[description_holders["fluff"]]") //Yellow, fluff-related text.
+ stat(null,"[description_holders["fluff"]]") //Yellow, fluff-related text.
if(description_holders["antag"])
- stat(null,"[description_holders["antag"]]") //Red, malicious antag-related text
+ stat(null,"[description_holders["antag"]]") //Red, malicious antag-related text
//override examinate verb to update description holders when things are examined
//mob verbs are faster than object verbs. See http://www.byond.com/forum/?post=1326139&page=2#comment8198716 for why this isn't atom/verb/examine()
@@ -100,3 +119,79 @@
if(client)
var/is_antag = ((mind && mind.special_role) || isobserver(src)) //ghosts don't have minds
client.update_description_holders(A, is_antag)
+
+
+/mob/verb/mob_examine()
+ set name = "Mob Examine"
+ set desc = "Allows one to examine mobs they can see, even from inside of bellies and objects."
+ set category = "IC"
+ set popup_menu = FALSE
+
+ if((is_blind(src) || src.stat) && !isobserver(src))
+ to_chat(src, "Something is there but you can't see it.")
+ return 1
+ var/list/E = list()
+ if(isAI(src))
+ var/mob/living/silicon/ai/my_ai = src
+ for(var/e in my_ai.all_eyes)
+ var/turf/my_turf = get_turf(e)
+ var/foundcam = FALSE
+ for(var/obj/cam in view(world.view, my_turf))
+ if(istype(cam, /obj/machinery/camera))
+ var/obj/machinery/camera/mycam = cam
+ if(!mycam.stat)
+ foundcam = TRUE
+ if(!foundcam)
+ continue
+ for(var/atom/M in view(world.view, my_turf))
+ if(M == src || istype(M, /mob/observer))
+ continue
+ if(ismob(M) && !M.invisibility)
+ if(src && src == M)
+ var/list/results = src.examine(src)
+ if(!results || !results.len)
+ results = list("You were unable to examine that. Tell a developer!")
+ to_chat(src, jointext(results, "
"))
+ update_examine_panel(src)
+ return
+ else
+ E |= M
+ if(E.len == 0)
+ return
+ else
+ var/my_turf = get_turf(src)
+ for(var/atom/M in view(world.view, my_turf))
+ if(ismob(M) && M != src && !istype(M, /mob/observer) && !M.invisibility)
+ E |= M
+ for(var/turf/T in view(world.view, my_turf))
+ if(!isopenspace(T))
+ continue
+ var/turf/checked = T
+ var/keepgoing = TRUE
+ while(keepgoing)
+ var/checking = GetBelow(checked)
+ for(var/atom/m in checking)
+ if(ismob(m) && !istype(m, /mob/observer) && !m.invisibility)
+ E |= m
+ checked = checking
+ if(!isopenspace(checked))
+ keepgoing = FALSE
+
+ if(E.len == 0)
+ to_chat(src, SPAN_NOTICE("There are no mobs to examine."))
+ return
+ var/atom/B = null
+ if(E.len == 1)
+ B = pick(E)
+ else
+ B = tgui_input_list(src, "What would you like to examine?", "Examine", E)
+ if(!B)
+ return
+ if(!isbelly(loc) && !istype(loc, /obj/item/weapon/holder) && !isAI(src))
+ if(B.z == src.z)
+ face_atom(B)
+ var/list/results = B.examine(src)
+ if(!results || !results.len)
+ results = list("You were unable to examine that. Tell a developer!")
+ to_chat(src, jointext(results, "
"))
+ update_examine_panel(B)
diff --git a/code/modules/examine/examine_vr.dm b/code/modules/examine/examine_vr.dm
deleted file mode 100644
index f198da0edb..0000000000
--- a/code/modules/examine/examine_vr.dm
+++ /dev/null
@@ -1,74 +0,0 @@
-/mob/verb/mob_examine()
- set name = "Mob Examine"
- set desc = "Allows one to examine mobs they can see, even from inside of bellies and objects."
- set category = "IC"
- set popup_menu = FALSE
-
- if((is_blind(src) || src.stat) && !isobserver(src))
- to_chat(src, "Something is there but you can't see it.")
- return 1
- var/list/E = list()
- if(isAI(src))
- var/mob/living/silicon/ai/my_ai = src
- for(var/e in my_ai.all_eyes)
- var/turf/my_turf = get_turf(e)
- var/foundcam = FALSE
- for(var/obj/cam in view(world.view, my_turf))
- if(istype(cam, /obj/machinery/camera))
- var/obj/machinery/camera/mycam = cam
- if(!mycam.stat)
- foundcam = TRUE
- if(!foundcam)
- continue
- for(var/atom/M in view(world.view, my_turf))
- if(M == src || istype(M, /mob/observer))
- continue
- if(ismob(M) && !M.invisibility)
- if(src && src == M)
- var/list/results = src.examine(src)
- if(!results || !results.len)
- results = list("You were unable to examine that. Tell a developer!")
- to_chat(src, jointext(results, "
"))
- update_examine_panel(src)
- return
- else
- E |= M
- if(E.len == 0)
- return
- else
- var/my_turf = get_turf(src)
- for(var/atom/M in view(world.view, my_turf))
- if(ismob(M) && M != src && !istype(M, /mob/observer) && !M.invisibility)
- E |= M
- for(var/turf/T in view(world.view, my_turf))
- if(!isopenspace(T))
- continue
- var/turf/checked = T
- var/keepgoing = TRUE
- while(keepgoing)
- var/checking = GetBelow(checked)
- for(var/atom/m in checking)
- if(ismob(m) && !istype(m, /mob/observer) && !m.invisibility)
- E |= m
- checked = checking
- if(!isopenspace(checked))
- keepgoing = FALSE
-
- if(E.len == 0)
- to_chat(src, SPAN_NOTICE("There are no mobs to examine."))
- return
- var/atom/B = null
- if(E.len == 1)
- B = pick(E)
- else
- B = tgui_input_list(src, "What would you like to examine?", "Examine", E)
- if(!B)
- return
- if(!isbelly(loc) && !istype(loc, /obj/item/weapon/holder) && !isAI(src))
- if(B.z == src.z)
- face_atom(B)
- var/list/results = B.examine(src)
- if(!results || !results.len)
- results = list("You were unable to examine that. Tell a developer!")
- to_chat(src, jointext(results, "
"))
- update_examine_panel(B)
\ No newline at end of file
diff --git a/code/modules/materials/materials/_materials.dm b/code/modules/materials/materials/_materials.dm
index 05046ba85a..6ce2fab16c 100644
--- a/code/modules/materials/materials/_materials.dm
+++ b/code/modules/materials/materials/_materials.dm
@@ -352,6 +352,7 @@ var/list/name_to_material
// If is_brittle() returns true, these are only good for a single strike.
recipes = list(
new /datum/stack_recipe("[display_name] baseball bat", /obj/item/weapon/material/twohanded/baseballbat, 10, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", pass_stack_color = TRUE),
+ new /datum/stack_recipe("[display_name] staff", /obj/item/weapon/material/twohanded/staff, 10, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", pass_stack_color = TRUE),
new /datum/stack_recipe("[display_name] ashtray", /obj/item/weapon/material/ashtray, 2, one_per_turf = 1, on_floor = 1, supplied_material = "[name]", pass_stack_color = TRUE),
new /datum/stack_recipe("[display_name] spoon", /obj/item/weapon/material/kitchen/utensil/spoon/plastic, 1, on_floor = 1, supplied_material = "[name]", pass_stack_color = TRUE),
new /datum/stack_recipe("[display_name] armor plate", /obj/item/weapon/material/armor_plating, 1, time = 20, on_floor = 1, supplied_material = "[name]", pass_stack_color = TRUE),
diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm
index f58c741ded..4918df39f0 100644
--- a/code/modules/mob/living/living_movement.dm
+++ b/code/modules/mob/living/living_movement.dm
@@ -276,6 +276,8 @@ default behaviour is:
is_shifted = FALSE
pixel_x = default_pixel_x
pixel_y = default_pixel_y
+ layer = MOB_LAYER
+ plane = MOB_PLANE
// End VOREstation edit
if(pulling) // we were pulling a thing and didn't lose it during our move.
diff --git a/code/modules/mob/living/simple_mob/overmap_mob_vr.dm b/code/modules/mob/living/simple_mob/overmap_mob_vr.dm
index 8a998d48ea..dd4fc73631 100644
--- a/code/modules/mob/living/simple_mob/overmap_mob_vr.dm
+++ b/code/modules/mob/living/simple_mob/overmap_mob_vr.dm
@@ -84,13 +84,60 @@
name = "DONT SPAWN ME"
desc = "I'm a bad person I'm sorry"
+ maxHealth = 100000
+ health = 100000
+ movement_cooldown = 10
+
+ see_in_dark = 10
+
faction = "overmap"
low_priority = FALSE
devourable = FALSE
digestable = FALSE
+
+ harm_intent_damage = 1
+ melee_damage_lower = 50
+ melee_damage_upper = 100
+ attack_sharp = FALSE
+
+ 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 = 900
+
+ mob_size = MOB_HUGE
+
+ loot_list = list(/obj/random/underdark/uncertain)
+
+ armor = list(
+ "melee" = 1000,
+ "bullet" = 1000,
+ "laser" = 1000,
+ "energy" = 1000,
+ "bomb" = 1000,
+ "bio" = 1000,
+ "rad" = 1000)
+
+ armor_soak = list(
+ "melee" = 1000,
+ "bullet" = 1000,
+ "laser" = 1000,
+ "energy" = 1000,
+ "bomb" = 1000,
+ "bio" = 1000,
+ "rad" = 1000
+ )
+
var/scanner_desc
var/obj/effect/overmap/visitable/simplemob/child_om_marker
var/om_child_type
+ var/shipvore = FALSE //Enable this to allow the mob to eat spaceships by dragging them onto its sprite.
/mob/living/simple_mob/vore/overmap/New(mapload, new_child)
if(new_child)
@@ -99,7 +146,7 @@
/mob/living/simple_mob/vore/overmap/Initialize()
. = ..()
- if(!om_child_type && !om_child_type)
+ if(!om_child_type)
log_and_message_admins("An improperly configured OM mob tried to spawn, and was deleted.")
return INITIALIZE_HINT_QDEL
if(!child_om_marker)
@@ -109,3 +156,66 @@
/mob/living/simple_mob/vore/overmap/Destroy()
qdel_null(child_om_marker)
return ..()
+
+//SHIP
+
+/obj/effect/overmap/visitable/ship/simplemob
+ name = "unknown ship"
+ icon = 'icons/obj/overmap.dmi'
+ icon_state = "ship"
+ scannable = TRUE
+ known = FALSE
+ in_space = FALSE //Just cuz we don't want people getting here via map edge transitions normally.
+ unknown_name = "unknown ship"
+ unknown_state = "ship"
+
+ var/mob/living/simple_mob/vore/overmap/parent_mob_type
+ var/mob/living/simple_mob/vore/overmap/parent
+
+/obj/effect/overmap/visitable/ship/simplemob/New(newloc, new_parent)
+ if(new_parent)
+ parent = new_parent
+ return ..()
+
+/obj/effect/overmap/visitable/ship/simplemob/Initialize()
+ . = ..()
+ if(!parent_mob_type && !parent)
+ log_and_message_admins("An improperly configured OM mob event tried to spawn, and was deleted.")
+ return INITIALIZE_HINT_QDEL
+ if(!parent)
+ var/mob/living/simple_mob/vore/overmap/P = new parent_mob_type(loc, src)
+ parent = P
+ om_mob_event_setup()
+
+/obj/effect/overmap/visitable/ship/simplemob/proc/om_mob_event_setup()
+ scanner_desc = parent.scanner_desc
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_parent_moved))
+ skybox_pixel_x = rand(-100,100)
+ if(known)
+ name = initial(parent.name)
+ icon = initial(parent.icon)
+ icon_state = initial(parent.icon_state)
+ color = initial(parent.color)
+ desc = initial(parent.desc)
+
+/obj/effect/overmap/visitable/ship/simplemob/Destroy()
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+ qdel_null(parent)
+ return ..()
+
+/obj/effect/overmap/visitable/ship/simplemob/get_scan_data(mob/user)
+ if(!known)
+ known = TRUE
+ name = initial(parent.name)
+ icon = initial(parent.icon)
+ icon_state = initial(parent.icon_state)
+ color = initial(parent.color)
+ desc = initial(parent.desc)
+
+ var/dat = {"\[b\]Scan conducted at\[/b\]: [stationtime2text()] [stationdate2text()]\n\[b\]Grid coordinates\[/b\]: [x],[y]\n\n[scanner_desc]"}
+
+ return dat
+
+/obj/effect/overmap/visitable/ship/simplemob/proc/on_parent_moved(atom/movable/source, OldLoc, Dir, Forced)
+ forceMove(parent.loc)
+ set_dir(parent.dir)
diff --git a/code/modules/mob/living/simple_mob/simple_mob_vr.dm b/code/modules/mob/living/simple_mob/simple_mob_vr.dm
index 098ac1023e..e72e7aa160 100644
--- a/code/modules/mob/living/simple_mob/simple_mob_vr.dm
+++ b/code/modules/mob/living/simple_mob/simple_mob_vr.dm
@@ -268,6 +268,8 @@
/mob/living/simple_mob/proc/tryBumpNom(var/mob/tmob)
//returns TRUE if we actually start an attempt to bumpnom, FALSE if checks fail or the random bump nom chance fails
if(istype(tmob) && will_eat(tmob) && !istype(tmob, type) && prob(vore_bump_chance) && !ckey) //check if they decide to eat. Includes sanity check to prevent cannibalism.
+ if(!faction_bump_vore && faction == tmob.faction)
+ return FALSE
if(tmob.canmove && prob(vore_pounce_chance)) //if they'd pounce for other noms, pounce for these too, otherwise still try and eat them if they hold still
tmob.Weaken(5)
tmob.visible_message("\The [src] [vore_bump_emote] \the [tmob]!!")
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/spacewhale.dm b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/spacewhale.dm
index 71402e113b..65bd18c973 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/spacewhale.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/spacewhale.dm
@@ -19,56 +19,13 @@
om_child_type = /obj/effect/overmap/visitable/simplemob/spacewhale
- maxHealth = 100000
- health = 100000
- movement_cooldown = 10
-
- see_in_dark = 10
-
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "punches"
-
- harm_intent_damage = 1
- melee_damage_lower = 50
- melee_damage_upper = 100
- attack_sharp = FALSE
attacktext = list("chomped", "bashed", "monched", "bumped")
ai_holder_type = /datum/ai_holder/simple_mob/melee/spacewhale
- 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 = 900
-
- loot_list = list(/obj/random/underdark/uncertain)
-
- armor = list(
- "melee" = 1000,
- "bullet" = 1000,
- "laser" = 1000,
- "energy" = 1000,
- "bomb" = 1000,
- "bio" = 1000,
- "rad" = 1000)
-
- armor_soak = list(
- "melee" = 1000,
- "bullet" = 1000,
- "laser" = 1000,
- "energy" = 1000,
- "bomb" = 1000,
- "bio" = 1000,
- "rad" = 1000
- )
-
speak_emote = list("rumbles")
say_list_type = /datum/say_list/spacewhale
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/stardog.dm b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/stardog.dm
new file mode 100644
index 0000000000..e7e0e9ecca
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/stardog.dm
@@ -0,0 +1,1737 @@
+//I'm sorry for this file, no one should have to deal with this
+//I am in a pain trance and coding is the only thing that can distract me
+//I am a dwarf in a fey mood, but what I make will not be a masterwork, woe
+
+/datum/category_item/catalogue/fauna/stardog
+ name = "Alien Wildlife - Star Dog"
+ desc = "I appears to be a canine of some sort, though absolutely massive in scale and surrounded in radical redspace energies!"
+ value = CATALOGUER_REWARD_SUPERHARD
+
+/mob/living/simple_mob/vore/overmap/stardog
+ name = "dog"
+ desc = "It is a relatively ordinary looking canine mutt! It radiates mischief and otherworldly energy..."
+ tt_desc = "E Canis lupus stellarus"
+
+ scanner_desc = "I appears to be a canine of some sort, though absolutely massive in scale and surrounded in radical redspace energies!"
+ catalogue_data = list(/datum/category_item/catalogue/fauna/stardog)
+
+ icon = 'icons/mob/vore.dmi'
+ icon_state = "woof"
+ icon_living = "woof"
+ icon_dead = "woof_dead"
+ icon_rest = "woof_rest"
+
+ om_child_type = /obj/effect/overmap/visitable/ship/simplemob/stardog
+
+ response_help = "pets"
+ response_disarm = "rudely paps"
+ response_harm = "punches"
+
+ attacktext = list("nipped", "chomped", "bullied", "gnaws on")
+ attack_sound = 'sound/voice/bork.ogg'
+ friendly = list("snoofs", "nuzzles", "ruffs happily at", "smooshes on")
+
+ ai_holder_type = /datum/ai_holder/simple_mob/woof/stardog
+
+ has_langs = list(LANGUAGE_ANIMAL, LANGUAGE_CANILUNZT, LANGUAGE_GALCOM)
+ say_list_type = /datum/say_list/softdog
+ swallowTime = 0.1 SECONDS
+
+ loot_list = list(/obj/random/underdark/uncertain)
+
+ armor = list(
+ "melee" = 1000,
+ "bullet" = 1000,
+ "laser" = 1000,
+ "energy" = 1000,
+ "bomb" = 1000,
+ "bio" = 1000,
+ "rad" = 1000)
+
+ armor_soak = list(
+ "melee" = 1000,
+ "bullet" = 1000,
+ "laser" = 1000,
+ "energy" = 1000,
+ "bomb" = 1000,
+ "bio" = 1000,
+ "rad" = 1000
+ )
+
+ movement_cooldown = 5
+ copy_prefs_to_mob = FALSE
+ player_msg = "The dog accepts you into itself, allowing you to dictate what will happen. The dog occasionally thinks unknowable thoughts, though you can understand some of its needs and desires. The dog shares its experience with you. You can navigate space, 'transition' to certain locations, and you can dine upon some of the space weather. The dog doesn't seem to know how any of this works exactly, this is just how things are for the dog, they come as naturally to the dog as blinking."
+
+ var/affinity = 0
+ var/obj/structure/control_pod/control_node = null
+ var/admin_override = FALSE //If true, makes affinity and nutrition irrelevant.
+ var/list/weather_areas = list() //We'll call a proc on these areas when we eat, don't worry!
+
+/mob/living/simple_mob/vore/overmap/stardog/Login()
+ . = ..()
+ verbs -= /mob/living/simple_mob/proc/set_name
+ verbs -= /mob/living/simple_mob/proc/set_desc
+
+/mob/living/simple_mob/vore/overmap/stardog/attack_hand(mob/living/user)
+ if(!(user.pickup_pref && user.pickup_active))
+ return ..()
+ var/list/possible_targets = list()
+
+ for(var/mob/living/player in player_list)
+ if(!(player.z in child_om_marker.map_z))
+ continue
+ if(!(isliving(player) && istype(player.loc,/turf/simulated/floor/outdoors/fur) && player.client))
+ continue
+ if(player.resizable && player.pickup_pref)
+ possible_targets |= player
+
+ if(!possible_targets.len)
+ return ..()
+ user.visible_message("\The [user] reaches for something in \the [src]'s fur...","You look through \the [src]'s fur...")
+ var/mob/living/that_one = tgui_input_list(user, "Select a mob:", "Select a mob to grab!", possible_targets)
+ if(!that_one)
+ return ..()
+ to_chat(that_one, "\The [user]'s hand reaches toward you!!!")
+ if(!do_after(user, 3 SECONDS, src))
+ return ..()
+ if(!istype(that_one.loc,/turf/simulated/floor/outdoors/fur))
+ to_chat(user, "\The [that_one] got away...")
+ to_chat(that_one, "You got away!")
+ return
+ var/prev_size = that_one.size_multiplier
+ that_one.resize(RESIZE_TINY, ignore_prefs = TRUE)
+ if(!that_one.attempt_to_scoop(user, ignore_size = TRUE))
+ that_one.resize(prev_size, ignore_prefs = TRUE)
+ return ..()
+
+/mob/living/simple_mob/vore/overmap/stardog/Life()
+ . = ..()
+ if(admin_override)
+ affinity = 9999
+ nutrition = 9999
+ if(devourable) //This will cause problems probably so please do not eat the dog
+ devourable = FALSE
+ digestable = FALSE
+ if(ckey && control_node)
+ if(nutrition <= 200)
+ adjust_affinity(-10)
+ else if(nutrition < 500)
+ adjust_affinity(-3)
+ else
+ adjust_affinity(-1)
+ if(!affinity)
+ control_node.eject()
+ if(!ckey && resting)
+ lay_down()
+
+ if(istype(loc, /turf/unsimulated/map))
+ if(!invisibility)
+ invisibility = INVISIBILITY_ABSTRACT
+ child_om_marker.invisibility = 0
+ ai_holder.base_wander_delay = 50
+ ai_holder.wander_delay = 1
+ melee_damage_lower = 50
+ melee_damage_upper = 100
+ mob_size = MOB_HUGE
+ child_om_marker.set_light(5, 1, "#ff8df5")
+ movement_cooldown = 5
+
+ else if(invisibility)
+ invisibility = 0
+ child_om_marker.invisibility = INVISIBILITY_ABSTRACT
+ ai_holder.base_wander_delay = 5
+ ai_holder.wander_delay = 1
+ melee_damage_lower = 1
+ melee_damage_upper = 5
+ mob_size = MOB_SMALL
+ child_om_marker.set_light(0)
+ movement_cooldown = 0
+
+/mob/living/simple_mob/vore/overmap/stardog/perform_the_nom(mob/living/user, mob/living/prey, mob/living/pred, obj/belly/belly, delay)
+ to_chat(src, "You can't do that.") //The dog can move back and forth between the overmap.
+ return //If it can do normal vore mechanics, it can carry players to the OM,
+ //and release them there. I think that's probably a bad idea.
+
+/mob/living/simple_mob/vore/overmap/stardog/Initialize()
+ . = ..()
+ child_om_marker.set_light(5, 1, "#ff8df5")
+
+/mob/living/simple_mob/vore/overmap/stardog/Destroy()
+ if(control_node)
+ control_node.host = null
+ control_node = null
+ for(var/anything in weather_areas)
+ weather_areas -= anything
+ return ..()
+
+/mob/living/simple_mob/vore/overmap/stardog/Stat()
+ ..()
+ if(statpanel("Status"))
+ stat(null, "Affinity: [round(affinity)]")
+
+/mob/living/simple_mob/vore/overmap/stardog/start_pulling(var/atom/movable/AM)
+ if(!istype(loc, /turf/unsimulated/map)) //Don't pull stuff on the overmap
+ ..()
+
+/mob/living/simple_mob/vore/overmap/stardog/proc/adjust_affinity(amount)
+ if(amount > 0)
+ var/multiplier = nutrition / 250
+ affinity += (amount * multiplier)
+ if(amount < 0)
+ affinity += amount
+ if(affinity <= 0)
+ affinity = 0
+ if(affinity > 1000)
+ affinity = 1000
+
+/mob/living/simple_mob/vore/overmap/stardog/verb/eject()
+ set name = "Eject"
+ set desc = "Stop controlling the dog and return to your own body."
+ set category = "Abilities"
+
+ control_node.eject()
+
+/mob/living/simple_mob/vore/overmap/stardog/verb/eat_space_weather()
+ set name = "Eat Space Weather"
+ set desc = "Eat carp or rocks!"
+ set category = "Abilities"
+
+ var/obj/effect/overmap/event/E
+ var/nut = 0
+ var/aff = 0
+ var/mob = FALSE
+ var/ore = 0
+ var/tre = 0
+ var/msg = "REPLACE ME"
+ var/heal = FALSE
+ var/delet = TRUE
+
+ for(var/obj/effect/overmap/event/e in loc)
+ if(istype(e, /obj/effect/overmap/event/carp))
+ E = e
+ nut = 250
+ aff = -50
+ mob = TRUE
+ var/list/msglist = list(
+ "You lap up \the [E]. They're pretty filling, but you don't really like the taste...",
+ "You lap up \the [E]. You can feel them wiggle all the way down... They don't taste very good, but you feel energized afterward.",
+ "You lap up \the [E]. They flee away from you, attempting to scatter in all directions, but you're faster! They leave an unpleasant taste on your tongue, but your belly doesn't seem to mind them."
+ )
+ msg = pick(msglist)
+ else if(istype(e, /obj/effect/overmap/event/dust))
+ E = e
+ aff = -100
+ tre = 15
+ ore = 25
+ var/list/msglist = list(
+ "You lap up \the [E]. The dust clings to your mouth and throat!!! You cough and splutter unhappily! It is literally space dirt, and it tastes like it!",
+ "You lap up \the [E]. The bitter taste of the dust sticks to your tongue and takes a lot of work to get off! It's really frustrating!",
+ "You lap up \the [E]. Not only does it taste horrible and feel worse going down, some of it gets in your eyes!"
+ )
+ msg = pick(msglist)
+ else if(istype(e, /obj/effect/overmap/event/meteor))
+ E = e
+ aff = -200
+ tre = 5
+ ore = 100
+ var/list/msglist = list(
+ "You lap up \the [E]. The rocks roll down your gullet haphazardly. Some of them knock together and clatter their way down, while others turn to powder. Some of them even have some pretty sharp edges that don't feel very nice! They certainly don't taste very nice, and they weight heavily inside of your belly...",
+ "You lap up \the [E]. When they land inside you can feel the weight of them settle in. They make your insides kind of queasy...",
+ "You lap up \the [E]. They taste like rocks, and make you think of all the better things you could be eating..."
+ )
+
+ msg = pick(msglist)
+ else if(istype(e, /obj/effect/overmap/event/electric))
+ E = e
+ aff = 15
+ msg = "You try to eat \the [E], but you find that no matter how much of it you lick or homn upon, yet more remains! It makes your mouth tingle, and your fur stand on end! It's kind of fun, but it doesn't taste like anything, and you definitely don't feel any more full."
+ delet = FALSE
+ else if(istype(e, /obj/effect/overmap/event/ion))
+ E = e
+ aff = 20
+ msg = "When you approach \the [E], you find that the dog's will pulls away from your own a little bit. It seems to really like the shimmering clouds, and it feels really good to nestle up among them. Like taking a relaxing dip into a regenerative spring. Any aches and pains that the dog was experiencing seem to fade away, leaving it feeling refreshed!"
+ heal = TRUE
+ delet = FALSE
+ else
+ to_chat(src, "You can't eat \the [e].")
+ return
+
+ if(!E)
+ to_chat(src, "There isn't anything to eat here.")
+ return
+
+ to_chat(src, "You begin to eat \the [E]...")
+
+ if(!do_after(src, 20 SECONDS, E, exclusive = TRUE))
+ return
+ to_chat(src, "[msg]")
+ if(nut || aff)
+ adjust_nutrition(nut)
+ adjust_affinity(aff)
+ if(mob)
+ spawn_mob()
+ to_chat(src, "You can feel something moving inside of you...")
+ if(ore)
+ spawn_ore(ore)
+ if(tre)
+ spawn_treasure(tre)
+ if(heal)
+ adjustFireLoss(-999)
+ adjustBruteLoss(-999)
+ if(delet)
+ qdel(E)
+
+/mob/living/simple_mob/vore/overmap/stardog/proc/spawn_mob()
+ for(var/area/redgate/stardog/flesh_abyss/a in weather_areas)
+ if(istype(a, /area/redgate/stardog/flesh_abyss))
+ a.spawn_mob()
+/mob/living/simple_mob/vore/overmap/stardog/proc/spawn_ore(chance)
+ for(var/area/redgate/stardog/flesh_abyss/a in weather_areas)
+ if(istype(a, /area/redgate/stardog/flesh_abyss) && prob(chance))
+ a.spawn_ore()
+/mob/living/simple_mob/vore/overmap/stardog/proc/spawn_treasure(chance)
+ for(var/area/redgate/stardog/flesh_abyss/a in weather_areas)
+ if(istype(a, /area/redgate/stardog/flesh_abyss) && prob(chance))
+ a.spawn_treasure()
+
+/mob/living/simple_mob/vore/overmap/stardog/verb/transition() //Don't ask how it works. I don't know. I didn't think about it. I just thought it would be cool.
+ set name = "Transition"
+ set desc = "Attempt to go to the location you have arrived at, or return to space!"
+ set category = "Abilities"
+ if(nutrition <= 500)
+ to_chat(src, "You're too hungry...")
+ return
+ if(istype(loc, /turf/unsimulated/map))
+ var/list/destinations = list()
+ var/list/our_maps = list()
+ for(var/obj/effect/overmap/visitable/v in loc)
+ if(v == child_om_marker)
+ continue
+ if(!v.map_z.len)
+ continue
+ for(var/our_z in v.map_z)
+ our_maps |= our_z
+ if(!our_maps.len)
+ to_chat(src, "There is nowhere nearby to go to! You need to get closer to somewhere you can transition to before you can transition.")
+ return
+ for(var/obj/effect/landmark/l in landmarks_list)
+ if(l.z in our_maps)
+ if(istype(l,/obj/effect/landmark/stardog))
+ destinations |= l
+
+ if(!destinations.len)
+ to_chat(src, "There is nowhere nearby to land! You need to get closer to somewhere else that you can transition to before you can transition.")
+ return
+ for(var/obj/effect/landmark/stardog/l in destinations)
+ var/obj/effect/overmap/visitable/our_dest = tgui_input_list(src, "Where would you like to try to go?", "Transition", destinations, timeout = 15 SECONDS, strict_modern = TRUE)
+ if(!our_dest)
+ to_chat(src, "You decide not to transition.")
+ return
+ to_chat(src, "You begin to transition down to \the [our_dest], stay still...")
+ if(!do_after(src, 15 SECONDS, exclusive = TRUE))
+ to_chat(src, "You were interrupted.")
+ return
+ visible_message("\The [src] disappears!!!")
+ stop_pulling()
+ forceMove(get_turf(our_dest))
+ adjust_nutrition(-1000)
+ visible_message("\The [src] steps into the area as if from nowhere!")
+
+ else
+ to_chat(src, "You begin to transition back to space, stay still...")
+ if(!do_after(src, 15 SECONDS, exclusive = TRUE))
+ to_chat(src, "You were interrupted.")
+ return
+
+ visible_message("\The [src] disappears!!!")
+ stop_pulling()
+ forceMove(get_turf(get_overmap_sector(z)))
+ adjust_nutrition(-500)
+
+
+/obj/effect/overmap/visitable/ship/simplemob/stardog
+ icon = 'icons/obj/overmap.dmi'
+ icon_state = "ship"
+ skybox_icon = 'icons/skybox/anomaly.dmi'
+ skybox_icon_state = "space_dog"
+ skybox_pixel_x = 0
+ skybox_pixel_y = 0
+ glide_size = 2
+ parent_mob_type = /mob/living/simple_mob/vore/overmap/stardog
+ scanner_desc = "CONFIGURE ME"
+
+/datum/ai_holder/simple_mob/woof/stardog
+ hostile = FALSE
+ cooperative = TRUE
+ retaliate = TRUE
+ speak_chance = 1
+ wander = TRUE
+ wander_delay = 1
+ base_wander_delay = 50
+
+/turf/simulated/floor/outdoors/fur
+ name = "fur"
+ desc = "Thick, silky fur!"
+ icon = 'icons/turf/fur.dmi'
+ icon_state = "fur0"
+ edge_blending_priority = 4
+ initial_flooring = /decl/flooring/fur
+ can_dig = FALSE
+ turf_layers = list()
+ var/tree_chance = 25
+ var/tree_color = null
+ var/tree_type = /obj/structure/flora/tree/fur
+
+/turf/simulated/floor/outdoors/fur/attackby()
+ return
+
+/turf/simulated/floor/outdoors/fur/attack_hand(mob/user)
+ . = ..()
+ pet()
+
+/turf/simulated/floor/outdoors/fur/ex_act(severity)
+ return
+
+
+/turf/simulated/floor/outdoors/fur/Entered(atom/movable/AM, atom/oldloc)
+ . = ..()
+ if(ishuman(AM))
+ var/mob/living/carbon/human/L = AM
+ L.fur_submerge()
+
+/turf/simulated/floor/outdoors/fur/Exited(atom/movable/AM, atom/new_loc)
+ . = ..()
+ if(ishuman(AM))
+ var/mob/living/carbon/human/L = AM
+ L.fur_submerge()
+
+/mob/living/carbon/human/proc/fur_submerge()
+ if(QDESTROYING(src))
+ return
+
+ remove_layer(MOB_WATER_LAYER)
+
+ if(!istype(loc,/turf/simulated/floor/outdoors/fur) || lying)
+ return
+
+ var/atom/A = loc
+ var/image/I = image(icon = 'icons/turf/fur.dmi', icon_state = "submerged", layer = BODY_LAYER+MOB_WATER_LAYER)
+ I.color = A.color
+ overlays_standing[MOB_WATER_LAYER] = I
+
+ apply_layer(MOB_WATER_LAYER)
+
+/turf/simulated/floor/outdoors/fur/woof
+ color = "#c69c85"
+ tree_color = "#eeb698"
+
+/turf/simulated/floor/outdoors/fur/woof/no_trees
+ icon_state = "furX"
+ tree_chance = 0
+
+/turf/simulated/floor/outdoors/fur/Initialize()
+ . = ..()
+ if(tree_chance && prob(tree_chance) && !check_density())
+ var/obj/structure/flora/tree/tree = new tree_type(src)
+ if(tree_color)
+ tree.color = tree_color
+ else
+ tree.color = color
+
+/turf/simulated/floor/outdoors/fur/woof/wall
+ name = "dense fur"
+ desc = "Silky and soft, but too thick to pass or cut!"
+ color = "#92705d"
+ opacity = TRUE
+ tree_color = null
+ tree_chance = 100
+ tree_type = /obj/structure/flora/tree/fur/wall
+ outdoors = FALSE
+
+/turf/simulated/floor/outdoors/fur/verb/pet()
+ set name = "Pet Fur"
+ set desc = "Pet the fur!"
+ set category = "IC"
+ set src in oview(1)
+
+ usr.visible_message("\The [usr] pets \the [src].", "You pet \the [src].", runemessage = "pet pat...")
+ var/obj/effect/overmap/visitable/ship/simplemob/stardog/s = get_overmap_sector(z)
+
+ if(s && istype(s, /obj/effect/overmap/visitable/ship/simplemob/stardog))
+ var/mob/living/simple_mob/vore/overmap/stardog/m = s.parent
+ m.adjust_affinity(1)
+ if(m.affinity >= 10 && prob(5))
+ m.visible_message("\The [m]'s tail wags happily!")
+
+/turf/simulated/floor/outdoors/fur/verb/emote_beyond(message as message) //Now even the stars will know your sin.
+ set name = "Emote Beyond"
+ set desc = "Emote to those beyond the fur!"
+ set category = "IC"
+ set src in oview(1)
+
+ if(!isliving(usr))
+ return
+ var/mob/living/L = usr
+ if(L.client.prefs.muted & MUTE_IC)
+ to_chat(L, "You cannot speak in IC (muted).")
+ return
+ if (!message)
+ message = tgui_input_text(usr, "Type a message to emote.","Emote Beyond")
+ message = sanitize_or_reflect(message,L)
+ if (!message)
+ return
+ if (L.stat == DEAD)
+ return L.say_dead(message)
+ var/obj/effect/overmap/visitable/ship/simplemob/stardog/s = get_overmap_sector(z)
+ if(!s || !istype(s, /obj/effect/overmap/visitable/ship/simplemob/stardog))
+ return
+
+ var/mob/living/simple_mob/vore/overmap/stardog/m = s.parent
+
+ log_subtle(message,L)
+ message = "[L] [message]"
+ message = "(From the back of \the [m]) " + message
+ message = encode_html_emphasis(message)
+
+ var/undisplayed_message = "[L] does something too subtle for you to see."
+ var/list/vis = get_mobs_and_objs_in_view_fast(get_turf(m),1,2)
+ var/list/vis_mobs = vis["mobs"]
+ vis_mobs |= L
+ for(var/mob/M as anything in vis_mobs)
+ if(isnewplayer(M))
+ continue
+ if(isobserver(M) && !L.is_preference_enabled(/datum/client_preference/whisubtle_vis) && !M.client?.holder)
+ spawn(0)
+ M.show_message(undisplayed_message, 2)
+ else
+ spawn(0)
+ M.show_message(message, 2)
+ if(M.is_preference_enabled(/datum/client_preference/subtle_sounds))
+ M << sound('sound/talksounds/subtle_sound.ogg', volume = 50)
+
+/decl/flooring/fur
+ name = "fur"
+ desc = "Thick, silky fur!"
+ icon = 'icons/turf/fur.dmi'
+ icon_base = "fur"
+ has_base_range = 15
+
+ can_paint = TRUE
+
+ footstep_sounds = list("human" = list(
+ 'sound/effects/footstep/carpet1.ogg',
+ 'sound/effects/footstep/carpet2.ogg',
+ 'sound/effects/footstep/carpet3.ogg',
+ 'sound/effects/footstep/carpet4.ogg',
+ 'sound/effects/footstep/carpet5.ogg'))
+
+/obj/structure/flora/tree/fur
+ name = "tall fur"
+ desc = "Tall stalks of fur block your path! Someone needs a trim!"
+ icon = 'icons/obj/fur_tree.dmi'
+ icon_state = "tallfur1"
+ base_state = "tallfur"
+ opacity = TRUE
+ product = /obj/item/stack/material/fur
+ product_amount = 10
+ health = 100
+ max_health = 100
+ pixel_x = 0
+ pixel_y = 0
+ shake_animation_degrees = 2
+ sticks = FALSE
+ var/mob_chance = 5
+ var/static/list/mob_list = list( //Just, all the vore mobs. If some of the paths weren't shitty I would just put like `subtypesof(/mob/living/simple_mob/vore)` here. Maybe I'll fix that later, I am dying right now, I hope I will be remembered fondly when I die
+ /mob/living/simple_mob/vore/aggressive/corrupthound,
+ /mob/living/simple_mob/vore/aggressive/corrupthound/prettyboi,
+ /mob/living/simple_mob/vore/aggressive/deathclaw,
+ /mob/living/simple_mob/vore/aggressive/dino,
+ /mob/living/simple_mob/vore/aggressive/dragon,
+ /mob/living/simple_mob/vore/aggressive/frog,
+ /mob/living/simple_mob/vore/aggressive/giant_snake,
+ /mob/living/simple_mob/vore/aggressive/mimic,
+ /mob/living/simple_mob/vore/aggressive/panther,
+ /mob/living/simple_mob/vore/aggressive/rat,
+ /mob/living/simple_mob/vore/alienanimals/catslug,
+ /mob/living/simple_mob/vore/alienanimals/dustjumper,
+ /mob/living/simple_mob/vore/alienanimals/skeleton,
+ /mob/living/simple_mob/vore/alienanimals/space_jellyfish,
+ /mob/living/simple_mob/vore/alienanimals/startreader,
+ /mob/living/simple_mob/vore/alienanimals/succlet,
+ /mob/living/simple_mob/vore/alienanimals/teppi,
+ /mob/living/simple_mob/vore/alienanimals/teppi/baby,
+ /mob/living/simple_mob/vore/bee,
+ /mob/living/simple_mob/vore/bigdragon,
+ /mob/living/simple_mob/vore/bigdragon/friendly,
+ /mob/living/simple_mob/vore/catgirl,
+ /mob/living/simple_mob/vore/fennec,
+ /mob/living/simple_mob/vore/fennec/huge,
+ /mob/living/simple_mob/vore/fennix,
+ /mob/living/simple_mob/vore/greatwolf,
+ /mob/living/simple_mob/vore/hippo,
+ /mob/living/simple_mob/vore/horse,
+ /mob/living/simple_mob/vore/horse/big,
+ /mob/living/simple_mob/vore/jelly,
+ /mob/living/simple_mob/vore/lamia/random,
+ /mob/living/simple_mob/vore/leopardmander,
+ /mob/living/simple_mob/vore/oregrub,
+ /mob/living/simple_mob/vore/otie,
+ /mob/living/simple_mob/vore/otie/red,
+ /mob/living/simple_mob/vore/pakkun,
+ /mob/living/simple_mob/vore/rabbit,
+ /mob/living/simple_mob/vore/redpanda,
+ /mob/living/simple_mob/vore/sect_drone,
+ /mob/living/simple_mob/vore/sect_queen,
+ /mob/living/simple_mob/vore/sheep,
+ /mob/living/simple_mob/vore/solargrub,
+ /mob/living/simple_mob/vore/squirrel,
+ /mob/living/simple_mob/vore/squirrel/big,
+ /mob/living/simple_mob/vore/weretiger,
+ /mob/living/simple_mob/vore/wolf,
+ /mob/living/simple_mob/vore/wolf/direwolf,
+ /mob/living/simple_mob/vore/wolfgirl,
+ /mob/living/simple_mob/vore/woof
+ )
+
+/obj/structure/flora/tree/fur/choose_icon_state()
+ return "[base_state][rand(1, 2)]"
+
+/obj/structure/flora/tree/fur/attack_hand(mob/user)
+ return
+
+/obj/structure/flora/tree/fur/die()
+ if(product && product_amount)
+ var/obj/item/stack/material/fur/F = new product(get_turf(src), product_amount)
+ F.color = color
+ F.update_icon()
+ visible_message("\The [src] is felled!")
+ if(prob(mob_chance))
+ if(!mob_list.len)
+ return
+ var/ourmob = pickweight(mob_list)
+ var/mob/living/simple_mob/s = new ourmob(get_turf(src))
+ visible_message("\The [s] tumbles out of \the [src]!")
+ s.ai_holder.hostile = FALSE
+ s.ai_holder.retaliate = TRUE
+ s.ghostjoin = TRUE
+ s.ghostjoin_icon()
+
+ var/obj/effect/overmap/visitable/ship/simplemob/stardog/s = get_overmap_sector(z)
+ if(s && istype(s,/obj/effect/overmap/visitable/ship/simplemob/stardog))
+ var/mob/living/simple_mob/vore/overmap/stardog/dog = s.parent
+ dog.adjust_affinity(15)
+
+ qdel(src)
+
+/obj/structure/flora/tree/fur/wall
+ name = "dense fur"
+ desc = "Silky and soft, but too thick to pass or cut!"
+
+/obj/structure/flora/tree/fur/wall/attackby(obj/item/weapon/W, mob/living/user)
+ return
+
+/area/redgate/stardog
+ name = "dog"
+/area/redgate/stardog/flesh_abyss
+ name = "flesh abyss"
+ icon_state = "redblatri"
+ forced_ambience = list('sound/vore/stomach_loop.ogg', 'sound/vore/sunesound/prey/loop.ogg')
+ floracountmax = 0
+ valid_flora = list(
+ /obj/structure/outcrop/coal = 10,
+ /obj/structure/outcrop/diamond = 1,
+ /obj/structure/outcrop/gold = 3,
+ /obj/structure/outcrop/iron = 10,
+ /obj/structure/outcrop/lead = 6,
+ /obj/structure/outcrop/phoron = 10,
+ /obj/structure/outcrop/platinum = 5,
+ /obj/structure/outcrop/silver = 8,
+ /obj/structure/outcrop/uranium = 3,
+ /obj/random/outcrop = 5
+ )
+
+ semirandom = TRUE
+ semirandom_groups = 1
+ semirandom_group_min = 5
+ semirandom_group_max = 15
+ mob_intent = "retaliate"
+ valid_mobs = list(
+ list(
+ /mob/living/simple_mob/vore/vore_hostile/abyss_lurker = 100,
+ /mob/living/simple_mob/vore/vore_hostile/leaper = 100,
+ /mob/living/simple_mob/vore/vore_hostile/gelatinous_cube = 10
+ )
+ )
+
+ var/mob_chance = 10
+ var/treasure_chance = 50
+ var/list/valid_treasure = list(
+ /obj/item/weapon/cell/infinite = 5,
+ /obj/item/weapon/cell/device/weapon/recharge/alien = 5,
+ /obj/item/device/nif/authentic = 1,
+ /obj/item/toy/bosunwhistle = 50,
+ /obj/random/mouseray = 50,
+ /obj/item/weapon/gun/energy/mouseray/metamorphosis/advanced/random = 10,
+ /obj/item/weapon/gun/energy/mouseray/metamorphosis/advanced = 5,
+ /obj/item/clothing/mask/gas/voice = 25,
+ /obj/item/device/perfect_tele = 15,
+ /obj/item/weapon/gun/energy/sizegun = 50,
+ /obj/item/device/slow_sizegun = 50,
+ /obj/item/capture_crystal/master = 5,
+ /obj/item/capture_crystal/ultra = 15,
+ /obj/item/capture_crystal/great = 25,
+ /obj/item/capture_crystal/random = 50,
+ /obj/random/pizzabox = 10, //The dog intercepted your pizza voucher delivery, what a scamp
+ /obj/item/weapon/bluespace_harpoon = 15,
+ /obj/random/awayloot = 5,
+ /obj/random/cash = 15,
+ /obj/random/cash/big = 10,
+ /obj/random/cash/huge = 5,
+ /obj/random/maintenance/clean = 10,
+ /obj/random/maintenance/misc = 10
+ )
+ no_comms = TRUE
+ ghostjoin = TRUE
+ sound_env = SOUND_ENVIRONMENT_CAVE
+ var/treasuremax = 3
+ var/spawnstuff = TRUE
+ var/include_enzyme = FALSE
+
+/area/redgate/stardog/flesh_abyss/EvalValidSpawnTurfs()
+ for(var/turf/simulated/floor/F in src)
+ if(istype(F, /turf/simulated/floor/flesh))
+ valid_spawn_turfs |= F
+
+ if(include_enzyme)
+ if(istype(F, /turf/simulated/floor/water/digestive_enzymes))
+ valid_spawn_turfs |= F
+
+/area/redgate/stardog/flesh_abyss/spawn_flora_on_turf()
+ if(!spawnstuff)
+ return
+ if(!valid_flora.len)
+ to_world_log("[src] does not have a set valid flora list!")
+ return TRUE
+
+ var/obj/F
+ var/turf/Turf
+ var/howmany = rand(0,floracountmax)
+ for(var/floracount = 1 to howmany)
+ F = pickweight(valid_flora)
+ Turf = pick(valid_spawn_turfs)
+ if(!Turf.check_density())
+ new F(Turf)
+
+/area/redgate/stardog/flesh_abyss/spawn_mob_on_turf()
+ if(!spawnstuff)
+ return
+ if(!valid_mobs.len)
+ to_world_log("[src] does not have a set valid mobs list!")
+ return TRUE
+
+ var/mob/M
+ var/turf/Turf
+ if(semirandom)
+ for(var/groupscount = 1 to (semirandom_groups))
+ var/ourgroup = pickweight(valid_mobs)
+ var/goodnum = rand(semirandom_group_min, semirandom_group_max)
+ for(var/mobscount = 1 to (goodnum))
+ M = pickweight(ourgroup)
+ Turf = pick(valid_spawn_turfs)
+ if(!Turf.check_density())
+ var/mob/ourmob = new M(Turf)
+ adjust_mob(ourmob)
+ else
+ for(var/mobscount = 1 to mobcountmax)
+ M = pickweight(valid_mobs)
+ Turf = pick(valid_spawn_turfs)
+ if(!Turf.check_density())
+ var/mob/ourmob = new M(Turf)
+ adjust_mob(ourmob)
+
+/area/redgate/stardog/flesh_abyss/proc/spawn_mob()
+ if(!spawnstuff)
+ return
+ if(!valid_mobs.len)
+ to_world_log("[src] does not have a set valid mobs list!")
+ return
+
+ if(!prob(mob_chance))
+ return
+ var/mob/M
+ var/turf/Turf
+ var/goodnum = rand(semirandom_group_min, semirandom_group_max)
+ for(var/mobscount = 1 to goodnum)
+ M = pickweight(pickweight(valid_mobs))
+ Turf = pick(valid_spawn_turfs)
+ if(!Turf.check_density())
+ var/mob/ourmob = new M(Turf)
+ adjust_mob(ourmob)
+
+/area/redgate/stardog/flesh_abyss/proc/spawn_ore()
+ if(!spawnstuff)
+ return
+ if(!valid_flora.len)
+ to_world_log("[src] does not have a set valid flora list!")
+ return
+
+ var/obj/F
+ var/turf/Turf
+ var/howmany = rand(1,floracountmax)
+ for(var/ore = 1 to howmany)
+ F = pickweight(valid_flora)
+ Turf = pick(valid_spawn_turfs)
+ if(!Turf.check_density())
+ new F(Turf)
+
+/area/redgate/stardog/flesh_abyss/proc/spawn_treasure()
+ if(!spawnstuff)
+ return
+ if(treasure_chance <= 0)
+ return
+ if(!valid_treasure.len)
+ to_world_log("[src] does not have a set valid treasure list!")
+ return
+
+ var/obj/F
+ var/turf/Turf
+ var/howmany = rand(1,treasuremax)
+ for(var/treasure = 1 to howmany)
+ if(prob(treasure_chance))
+ continue
+ F = pickweight(valid_treasure)
+ Turf = pick(valid_spawn_turfs)
+ if(!Turf.check_density())
+ new F(Turf)
+
+/area/redgate/stardog/flesh_abyss/no_spawn
+ icon_state = "blublacir"
+ semirandom_groups = 0
+ semirandom_group_min = 0
+ semirandom_group_max = 0
+
+ valid_mobs = null
+ spawnstuff = FALSE
+
+/area/redgate/stardog/flesh_abyss/digestive_tract
+ icon_state = "greblacir"
+ semirandom_groups = 1
+ semirandom_group_min = 1
+ semirandom_group_max = 10
+ include_enzyme = TRUE
+ valid_mobs = list(
+ list(
+ /mob/living/simple_mob/vore/vore_hostile/abyss_lurker = 10,
+ /mob/living/simple_mob/vore/vore_hostile/leaper = 20,
+ /mob/living/simple_mob/vore/vore_hostile/gelatinous_cube = 100
+ )
+ )
+ ghostjoin = TRUE
+
+/area/redgate/stardog/flesh_abyss/stomach
+ floracountmax = 3
+ valid_flora = list(
+ /obj/structure/outcrop/coal = 10,
+ /obj/structure/outcrop/diamond = 1,
+ /obj/structure/outcrop/gold = 3,
+ /obj/structure/outcrop/iron = 10,
+ /obj/structure/outcrop/lead = 6,
+ /obj/structure/outcrop/phoron = 10,
+ /obj/structure/outcrop/platinum = 5,
+ /obj/structure/outcrop/silver = 8,
+ /obj/structure/outcrop/uranium = 3,
+ /obj/random/outcrop = 5
+ )
+ semirandom = FALSE
+ semirandom_groups = 1
+ semirandom_group_min = 1
+ semirandom_group_max = 3
+ valid_mobs = list(
+ list(
+ /mob/living/simple_mob/animal/space/carp/event = 100,
+ /mob/living/simple_mob/animal/space/carp/large = 25,
+ /mob/living/simple_mob/animal/space/carp/large/huge = 5,
+ /mob/living/simple_mob/vore/alienanimals/space_jellyfish = 100
+ )
+ )
+ mob_chance = 10
+ treasure_chance = 25
+ treasuremax = 1
+ spawnstuff = TRUE
+ ghostjoin = FALSE
+
+/area/redgate/stardog/flesh_abyss/s_int
+ floracountmax = 1
+ valid_flora = list(
+ /obj/structure/outcrop/coal = 5,
+ /obj/structure/outcrop/diamond = 2,
+ /obj/structure/outcrop/gold = 3,
+ /obj/structure/outcrop/iron = 7,
+ /obj/structure/outcrop/lead = 3,
+ /obj/structure/outcrop/phoron = 5,
+ /obj/structure/outcrop/platinum = 5,
+ /obj/structure/outcrop/silver = 8,
+ /obj/structure/outcrop/uranium = 3,
+ /obj/random/outcrop = 5
+ )
+ semirandom = FALSE
+ semirandom_groups = 1
+ semirandom_group_min = 1
+ semirandom_group_max = 3
+ valid_mobs = list(
+ list(
+ /mob/living/simple_mob/animal/space/carp/event = 100,
+ /mob/living/simple_mob/animal/space/carp/large = 25,
+ /mob/living/simple_mob/animal/space/carp/large/huge = 5,
+ /mob/living/simple_mob/vore/alienanimals/space_jellyfish = 100
+ )
+ )
+ mob_chance = 5
+ treasure_chance = 33
+ treasuremax = 5
+ spawnstuff = TRUE
+ ghostjoin = FALSE
+
+/area/redgate/stardog/flesh_abyss/l_int
+ floracountmax = 5
+ valid_flora = list(
+ /obj/structure/outcrop/diamond = 3,
+ /obj/structure/outcrop/gold = 3,
+ /obj/structure/outcrop/iron = 5,
+ /obj/structure/outcrop/phoron = 1,
+ /obj/structure/outcrop/platinum = 5,
+ /obj/structure/outcrop/silver = 8,
+ /obj/structure/outcrop/uranium = 3,
+ /obj/random/outcrop = 1
+ )
+ semirandom = FALSE
+ semirandom_groups = 1
+ semirandom_group_min = 1
+ semirandom_group_max = 1
+ valid_mobs = list(
+ list(
+ /mob/living/simple_mob/animal/space/carp/event = 100,
+ /mob/living/simple_mob/animal/space/carp/large = 25,
+ /mob/living/simple_mob/animal/space/carp/large/huge = 5,
+ /mob/living/simple_mob/vore/alienanimals/space_jellyfish = 100
+ )
+ )
+ mob_chance = 5
+ treasure_chance = 50
+ treasuremax = 5
+ spawnstuff = TRUE
+ ghostjoin = FALSE
+
+/area/redgate/stardog/flesh_abyss/node
+ enter_message = "Radical energy hangs as a haze in the air. It's much less hot here than other places within the dog, but the air is thick with alien whispers and desires that you can hardly comprehend."
+ icon_state = "yelwhisqu"
+ requires_power = 0
+ spawnstuff = FALSE
+
+/area/redgate/stardog/flesh_abyss/play_ambience(var/mob/living/L, initial = TRUE)
+ if(!L.is_preference_enabled(/datum/client_preference/digestion_noises))
+ return
+ ..()
+
+/area/redgate/stardog/lounge
+ name = "redgate lounge"
+ icon_state = "redwhisqu"
+ requires_power = 0
+ ambience = list(
+ 'sound/ambience/star_dog/dougcockpit.ogg',
+ 'sound/ambience/otherworldly/otherworldly1.ogg',
+ 'sound/ambience/otherworldly/otherworldly2.ogg',
+ 'sound/ambience/otherworldly/otherworldly3.ogg',
+ 'sound/ambience/boy.ogg',
+ 'sound/ambience/expoutpost/expoutpost1.ogg',
+ 'sound/ambience/expoutpost/expoutpost2.ogg',
+ 'sound/ambience/expoutpost/expoutpost3.ogg',
+ 'sound/ambience/expoutpost/expoutpost4.ogg',
+ 'sound/ambience/tech_ruins/tech_ruins1.ogg',
+ 'sound/ambience/tech_ruins/tech_ruins2.ogg',
+ 'sound/ambience/tech_ruins/tech_ruins3.ogg',
+ 'sound/ambience/signal.ogg'
+ )
+
+/area/redgate/stardog/outside
+ name = "star dog"
+ icon_state = "redblacir"
+ semirandom = TRUE
+ ghostjoin = TRUE
+ ambience = list(
+ 'sound/ambience/star_dog/dark-cold-main-menu-loop-mild-mountain-sickness-marb7e.ogg',
+ 'sound/ambience/star_dog/ominous-ambience.ogg',
+ 'sound/ambience/star_dog/long_awoo.ogg',
+ 'sound/ambience/star_dog/woof.ogg',
+ 'sound/ambience/star_dog/woof2.ogg'
+ )
+ sound_env = SOUND_ENVIRONMENT_DIZZY
+
+ valid_mobs = list( //Dog map spawns the dogs. It's not hard to understand!
+ list(
+ /mob/living/simple_mob/vore/woof
+ ) = 100,
+ list(
+ /mob/living/simple_mob/vore/wolf,
+ /mob/living/simple_mob/vore/wolf/direwolf,
+ /mob/living/simple_mob/vore/greatwolf
+ ) = 50,
+ list(
+ /mob/living/simple_mob/vore/otie,
+ /mob/living/simple_mob/vore/otie/friendly/chubby,
+ /mob/living/simple_mob/vore/otie/red,
+ /mob/living/simple_mob/vore/otie/red/chubby
+ ) = 50,
+ list(
+ /mob/living/simple_mob/animal/passive/dog/corgi,
+ /mob/living/simple_mob/animal/passive/dog/brittany,
+ /mob/living/simple_mob/animal/passive/dog/bullterrier,
+ /mob/living/simple_mob/animal/passive/dog/tamaskan
+ ) = 1,
+ list(
+ /mob/living/simple_mob/animal/space/carp = 100,
+ /mob/living/simple_mob/animal/space/carp/large = 25,
+ /mob/living/simple_mob/animal/space/carp/large/huge = 10,
+ /mob/living/simple_mob/animal/space/bats = 5,
+ /mob/living/simple_mob/animal/space/bear = 5,
+ /mob/living/simple_mob/animal/space/gnat = 5,
+ /mob/living/simple_mob/animal/space/ray = 5,
+ /mob/living/simple_mob/animal/space/shark = 5
+ ),
+ list( //The succlets can come too I guess lol
+ /mob/living/simple_mob/vore/alienanimals/succlet = 50,
+ /mob/living/simple_mob/vore/alienanimals/succlet/dark = 50,
+ /mob/living/simple_mob/vore/alienanimals/succlet/moss = 50,
+ /mob/living/simple_mob/vore/alienanimals/succlet/poison = 10,
+ /mob/living/simple_mob/vore/alienanimals/succlet/big = 10,
+ /mob/living/simple_mob/vore/alienanimals/succlet/king = 1
+ ) = 10
+ )
+ semirandom_groups = 5
+ semirandom_group_min = 1
+ semirandom_group_max = 10
+ mob_intent = "retaliate"
+
+/obj/structure/control_pod //god someone is going to try to fuck with this, everyone is going to be angry, I'm so sorry
+ name = "node"
+ desc = "A smooth node of nerves and flesh. It seems almost to radiate whispers of alien thought and emotion."
+ icon = 'icons/obj/flesh_machines.dmi'
+ icon_state = "control_node0"
+
+ density = TRUE
+ anchored = TRUE
+ pixel_x = -16
+ pixel_y = -10
+ unacidable = TRUE
+
+ var/mob/living/simple_mob/vore/overmap/stardog/host
+ var/mob/living/controller
+
+/obj/structure/control_pod/Initialize(mapload)
+ . = ..()
+ set_up()
+
+/obj/structure/control_pod/proc/set_up()
+ var/obj/effect/overmap/visitable/ship/simplemob/stardog/s = get_overmap_sector(z)
+ if(istype(s,/obj/effect/overmap/visitable/ship/simplemob/stardog))
+ var/mob/living/simple_mob/vore/overmap/stardog/dog = s.parent
+ if(!dog.control_node)
+ host = dog
+ dog.control_node = src
+
+/obj/structure/control_pod/Destroy()
+ if(host)
+ host.control_node = null
+ host = null
+ return ..()
+
+/obj/structure/control_pod/attack_hand(mob/living/user)
+ . = ..()
+ if(!host)
+ set_up()
+ if(!host)
+ to_chat(user, "It doesn't respond...")
+ return
+ control(user)
+
+/obj/structure/control_pod/proc/control(mob/living/user)
+ if(!host.affinity) //take care of my dog
+ to_chat(user, "As you press your hand to \the [src], it resists your advance... A sense of longing ripples through your mind...")
+ return
+ if(controller) //busy
+ to_chat(user, "You can see \the [controller] inside! Tendrils of nerves seem to have attached themselves to \the [controller]! There's no room for you right now!")
+ return
+ user.visible_message("\The [user] reaches out to touch \the [src]...","You reach out to touch \the [src]...")
+ if(!do_after(user, 10 SECONDS, src, exclusive = TRUE))
+ user.visible_message("\The [user] pulls back from \the [src].","You pull back from \the [src].")
+ return
+ if(controller) //got busy while you were waiting, get rekt
+ to_chat(user, "You can see \the [controller] inside! Tendrils of nerves seem to have attached themselves to \the [controller]! There's no room for you right now!")
+ return
+ controller = user
+ visible_message("\The [src] accepts \the [controller], submerging them beneath the surface of the flesh!")
+ user.stop_pulling()
+ user.forceMove(src)
+ host.ckey = user.ckey
+ log_admin("[host.ckey] has taken contol of \the [host].")
+ icon_state = "control_node1"
+ plane = ABOVE_MOB_PLANE
+ set_light(5, 0.75, "#f94bff")
+
+/obj/structure/control_pod/proc/eject()
+ to_chat(host, "You feel your control over \the [host] slip away from you!")
+ controller.forceMove(get_turf(src))
+ controller.ckey = host.ckey
+ visible_message("\The [controller] is ejected from \the [src], tumbling free!")
+ log_admin("[controller.ckey] is no longer controlling [host], they have been returned to their body, [controller].")
+ icon_state = "control_node0"
+ plane = OBJ_PLANE
+ set_light(0)
+ var/our_x = rand(-5,5) + x
+ var/our_y = rand(-5,5) + y
+
+ var/turf/throwtarg = locate(our_x, our_y, z) //teehee
+ spawn(0)
+ playsound(src, 'sound/vore/schlorp.ogg', vol = 100, vary = FALSE, volume_channel = VOLUME_CHANNEL_VORE)
+ controller.throw_at(throwtarg, 10, 1)
+ controller = null
+
+/obj/effect/landmark/stardog //I didn't know how else to decide where the dog will land
+ name = "stardog landing"
+ icon = 'icons/obj/landmark_vr.dmi'
+ icon_state = "transition"
+
+/obj/effect/landmark/stardog/Initialize()
+ . = ..()
+ var/area/a = get_area(src)
+ name = a.name
+
+/obj/effect/landmark/area_gatherer
+ name = "stardog area gatherer"
+/obj/effect/landmark/area_gatherer/Initialize()
+ . = ..()
+ LateInitialize()
+
+/obj/effect/landmark/area_gatherer/LateInitialize() //I am very afraid
+ var/obj/effect/overmap/visitable/ship/simplemob/stardog/s = get_overmap_sector(z)
+ var/mob/living/simple_mob/vore/overmap/stardog/dog = s.parent
+ dog.weather_areas |= get_area(src)
+ for(var/thing in dog.weather_areas)
+ qdel(src)
+
+/obj/machinery/computer/ship/navigation/telescreen/dog_eye
+ name = "visual nexus"
+ desc = "A glowing bundle of nerves across which you can see what the dog sees."
+ icon = 'icons/obj/flesh_machines.dmi'
+ icon_state = "screen_eye"
+ pixel_x = -16
+ pixel_y = -16
+ clicksound = 'sound/vore/squish1.ogg'
+
+/obj/machinery/computer/ship/navigation/telescreen/dog_eye/attackby(I, user)
+ return
+
+/obj/machinery/computer/ship/navigation/telescreen/dog_eye/update_icon()
+ . = ..()
+ icon_state = "screen_eye"
+
+/obj/machinery/computer/ship/navigation/verb/emote_beyond(message as message) //I could have put this into any other file but right here will do
+ set name = "Emote Beyond"
+ set desc = "Emote to those beyond the ship!"
+ set category = "IC"
+ set src in oview(7)
+
+ if(!isliving(usr))
+ return
+ var/mob/living/L = usr
+ if(L.client.prefs.muted & MUTE_IC)
+ to_chat(L, "You cannot speak in IC (muted).")
+ return
+ if (!message)
+ message = tgui_input_text(usr, "Type a message to emote.","Emote Beyond")
+ message = sanitize_or_reflect(message,L)
+ if (!message)
+ return
+ if (L.stat == DEAD)
+ return L.say_dead(message)
+ var/obj/effect/overmap/visitable/ship/s = get_overmap_sector(z)
+ if(!s || !istype(s, /obj/effect/overmap/visitable/ship))
+ to_chat(L, "You can't do that here.")
+ return
+
+ log_subtle(message,L)
+ message = "[L] [message]"
+ message = "(From within \the [s]) " + message
+ message = encode_html_emphasis(message)
+
+ var/undisplayed_message = "[L] does something too subtle for you to see."
+ var/list/vis = get_mobs_and_objs_in_view_fast(get_turf(s),1,2)
+ var/list/vis_mobs = vis["mobs"]
+ vis_mobs |= L
+ for(var/mob/M as anything in vis_mobs)
+ if(isnewplayer(M))
+ continue
+ if(isobserver(M) && !L.is_preference_enabled(/datum/client_preference/whisubtle_vis) && !M.client?.holder)
+ spawn(0)
+ M.show_message(undisplayed_message, 2)
+ else
+ spawn(0)
+ M.show_message(message, 2)
+ if(M.is_preference_enabled(/datum/client_preference/subtle_sounds))
+ M << sound('sound/talksounds/subtle_sound.ogg', volume = 50)
+
+/area/redgate/stardog/eyes
+
+ name = "eye"
+ icon_state = "bluwhicir"
+
+ var/list/our_eyes = list()
+
+
+/area/redgate/stardog/eyes/Entered(mob/M)
+ . = ..()
+ consider_eyes()
+
+/area/redgate/stardog/eyes/Exited(atom/movable/AM, newLoc)
+ . = ..()
+ consider_eyes()
+
+/area/redgate/stardog/eyes/proc/consider_eyes() //CONSIDER THEM PLEASE
+ var/close = FALSE
+ var/list/check = get_area_turfs(/area/redgate/stardog/eyes)
+ for(var/turf/t in check)
+ for(var/thing in t.contents)
+ if(istype(thing, /obj/effect/dog_eye)) //We can have eyes in our eyes, it's fine
+ continue
+ if(isobserver(thing)) //Ghosts aren't real
+ continue
+ if(isobj(thing) || ismob(thing))
+ close = TRUE //AAAAAAAAAAAAAAAUUUUUUUUGHHHHHHHHH ITS IN MY EYES HELP
+
+ for(var/obj/effect/dog_eye/e in our_eyes)
+ if(close)
+ e.icon_state = "eye_closed" // u . u
+ else
+ e.icon_state = "eye_open" // * w *
+
+/obj/effect/dog_eye
+ name = "eye"
+ desc = "It's peeking!"
+ icon = 'icons/obj/flesh_machines.dmi'
+ icon_state = "eye_open"
+ anchored = TRUE
+
+ pixel_x = -16
+
+/obj/effect/dog_nose
+ name = "nose"
+ desc = "Good for sniffin' with!"
+ icon = 'icons/obj/flesh_machines.dmi'
+ icon_state = "nose"
+ anchored = TRUE
+
+/obj/effect/dog_nose/attack_hand(mob/living/user)
+ . = ..()
+ user.visible_message("\The [user] boops the snoot.","You boop the snoot.",runemessage = "boop")
+
+/obj/effect/dog_nose/Crossed(atom/movable/AM as mob|obj)
+ . = ..()
+ sneef(AM)
+
+/obj/effect/dog_nose/proc/sneef(mob/living/L)
+ if(!isliving(L))
+ return
+ if(L.client)
+ to_chat(L, "A hot breath rushes up from under your feet, before the air rushes back down into the dog's nose as the dog sniffs you! SNEEF SNEEF!!!")
+
+/obj/effect/dog_eye/Initialize()
+ . = ..()
+ var/area/redgate/stardog/eyes/e = get_area(src)
+ if(istype(e,/area/redgate/stardog/eyes))
+ e.our_eyes |= src
+
+/obj/effect/dog_teleporter //look, I could have just used a bump teleporter, and I don't have an excuse, also everyone is going to be angry but it hurts too much for me to care right now, hopefully I will finish this before I start caring
+ name = "mouth"
+ desc = "It's waiting to accept treats!"
+ icon = 'icons/obj/flesh_machines.dmi'
+ icon_state = "mouth"
+ invisibility = 0
+ anchored = TRUE
+ pixel_x = -16
+ var/id = "mouth_a" //same id will be linked
+ var/static/list/dog_teleporters = list() //List of all the teleporters
+ var/reciever = FALSE //If true, doesn't teleport, only recieves
+ var/obj/effect/dog_teleporter/target //Target for teleporting to, automatically set by id
+ var/throw_through = TRUE //When moved the mob/obj will be thrown south
+ var/teleport_sound = 'sound/vore/schlorp.ogg' //The sound that plays when we use the teleporter. Respects vore sound preferences.
+ var/teleport_message = ""
+ var/check_keys = FALSE
+ var/check_prefs = TRUE
+
+/obj/effect/dog_teleporter/Initialize()
+ . = ..()
+ dog_teleporters |= src
+ do_setup()
+ if(icon_state == "exit_b") //♪♫Blinded by the light♪♫
+ set_light(5, 1, "#ffffff")
+
+/obj/effect/dog_teleporter/proc/do_setup()
+ if(target)
+ return
+ for(var/obj/effect/dog_teleporter/T in dog_teleporters)
+ if(!istype(T,/obj/effect/dog_teleporter))
+ dog_teleporters -= T
+ continue
+ if(id == T.id)
+ if(T == src)
+ continue
+ target = T
+ if(!T.target)
+ T.target = src
+
+/obj/effect/dog_teleporter/Crossed(atom/movable/AM as mob|obj) //I am ashamed to admit how long it took to get this to do anything
+ . = ..()
+ lets_go(AM)
+
+/obj/effect/dog_teleporter/attack_hand(mob/living/user)
+ . = ..()
+ lets_go(user)
+
+/obj/effect/dog_teleporter/attack_generic(mob/user)
+ . = ..()
+ lets_go(user)
+
+/obj/effect/dog_teleporter/proc/lets_go(atom/movable/AM as mob|obj) //Wahoo! Here we go!
+ if(reciever)
+ return
+ if(!target)
+ do_setup()
+ if(!target)
+ return
+ var/mob/living/L = null
+ if(isliving(AM))
+ L = AM
+ if(check_prefs && (!L.devourable || !L.allowmobvore))
+ return
+ if(check_keys && !L.ckey)
+ return
+ L.stop_pulling()
+ L.Weaken(3)
+ GLOB.prey_eaten_roundstat++
+ if(target.reciever) //We don't have to worry
+ AM.unbuckle_all_mobs(TRUE)
+ AM.forceMove(get_turf(target))
+ extra(AM)
+ return
+ var/turf/place = locate(target.x, (target.y - 1), target.z) //If the target is also a teleporter, let's pick a place to set them down next to the target.
+ //Setting them ON the target will probably make an infinite loop, and that seems lame.
+ AM.unbuckle_all_mobs(TRUE)
+ AM.forceMove(place)
+ extra(AM)
+
+/obj/effect/dog_teleporter/proc/extra(atom/movable/AM as mob|obj)
+ var/go = FALSE
+ if(isobserver(AM))
+ return
+ playsound(src, teleport_sound, vol = 100, vary = 1, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE)
+ playsound(target, teleport_sound, vol = 100, vary = 1, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE)
+ if(isliving(AM))
+ var/mob/living/L = AM
+ if(teleport_message && L.client)
+ to_chat(src, "[teleport_message]")
+ go = TRUE
+ if(isobj(AM))
+ go = FALSE
+
+ if(!go)
+ return
+
+ visible_message("\The [AM] passes through \the [src]!")
+ if(throw_through) //We will throw the target to the south!
+ var/turf/throwtarg = locate(target.x, (target.y - 5), target.z)
+ spawn(0)
+ AM.throw_at(throwtarg, 10, 1) //reverbfart.ogg
+
+/obj/effect/dog_teleporter/food_gobbler
+ teleport_sound = 'sound/vore/gulp.ogg'
+ teleport_message = "The thundering drum of the dog's heart beat throbs all around you, while the sweltering heat of its body soaks into you. It's soft and wet as a symphony of gurgles and glorps fills the steamy air!"
+
+/obj/effect/dog_teleporter/food_gobbler/Crossed(atom/movable/AM)
+
+ if(istype(AM, /obj/item/weapon/reagent_containers/food))
+ gobble_food(AM)
+ else return ..()
+
+/obj/effect/dog_teleporter/food_gobbler/proc/gobble_food(obj/item/I)
+ if(!isitem(I))
+ return
+ var/obj/effect/overmap/visitable/ship/simplemob/stardog/s = get_overmap_sector(z)
+ if(s && istype(s,/obj/effect/overmap/visitable/ship/simplemob/stardog))
+ if(!s.parent)
+ return
+ var/mob/living/simple_mob/vore/overmap/stardog/dog = s.parent
+ dog.adjust_nutrition(I.reagents.total_volume)
+ dog.adjust_affinity(25)
+ playsound(src, teleport_sound, vol = 100, vary = 1, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE)
+ visible_message("The dog gobbles up \the [I]!")
+ if(dog.client)
+ to_chat(dog, "[I.thrower ? "\The [I.thrower]" : "Someone"] feeds \the [I] to you!")
+ qdel(I)
+ GLOB.items_digested_roundstat++
+
+/obj/effect/dog_teleporter/reciever
+ name = "exit"
+ desc = "It's too tight to go in there!"
+ icon_state = "exita"
+ pixel_y = -16
+ reciever = TRUE
+
+/obj/effect/dog_teleporter/reciever/invisible
+ invisibility = INVISIBILITY_ABSTRACT
+ reciever = TRUE
+ id = "mouth_a"
+
+/obj/effect/dog_teleporter/reciever/invisible/mouth_return
+ invisibility = INVISIBILITY_ABSTRACT
+ reciever = TRUE
+ id = "mouth_b"
+
+/obj/effect/dog_teleporter/exit
+ name = "exit"
+ desc = "You can see the light at the end of the tunnel!"
+ icon_state = "exit_b"
+ id = "exit"
+ pixel_x = -16
+ pixel_y = -16
+ check_keys = TRUE
+ check_prefs = FALSE //We don't have to worry about it on the way out
+
+/obj/effect/dog_teleporter/mouth_return
+ name = "light"
+ desc = "You can see the light shining in from above!"
+ icon_state = "exit_b"
+ id = "mouth_b"
+ pixel_x = -16
+ pixel_y = -16
+ check_keys = TRUE
+ check_prefs = FALSE //We don't have to worry about it on the way out
+
+/obj/effect/dog_teleporter/reciever/exit //tee hee
+ name = "exit"
+ desc = "It's too tight to go in there!"
+ icon_state = "exit"
+ id = "exit"
+ pixel_x = -16
+ pixel_y = -16
+ layer = ABOVE_TURF_LAYER
+ plane = TURF_PLANE
+
+/turf/simulated/floor/water/digestive_enzymes //I'm sorry - Medical is going to be really angry. I hope people don't go ';HELP, HELP IN THE FLESH ABYSS!!!' but I know they will
+ name = "digestive enzymes"
+ desc = "A body of some kind of green fluid. It seems shallow enough to walk through, if needed."
+ icon = 'icons/turf/stomach_vr.dmi'
+ icon_state = "composite"
+ water_icon = 'icons/turf/stomach_vr.dmi'
+ water_state = "enzyme_shallow"
+ under_state = "flesh_floor"
+
+ reagent_type = "Sulphuric acid" //why not
+ outdoors = FALSE
+ var/mob/living/simple_mob/vore/overmap/stardog/linked_mob
+ var/mobstuff = TRUE //if false, we don't care about dogs, and that's terrible
+ var/we_process = FALSE //don't start another process while you're processing, idiot
+
+/turf/simulated/floor/water/digestive_enzymes/Entered(atom/movable/AM)
+ if(digest_stuff(AM) && !we_process)
+ START_PROCESSING(SSturfs, src)
+ we_process = TRUE
+
+/turf/simulated/floor/water/digestive_enzymes/hitby(atom/movable/AM)
+ if(digest_stuff(AM) && !we_process)
+ START_PROCESSING(SSturfs, src)
+ we_process = TRUE
+
+/turf/simulated/floor/water/digestive_enzymes/process()
+ if(!digest_stuff())
+ we_process = FALSE
+ return PROCESS_KILL
+
+/turf/simulated/floor/water/digestive_enzymes/proc/can_digest(atom/movable/AM as mob|obj)
+ . = FALSE
+ if(AM.loc != src)
+ return FALSE
+ if(isitem(AM))
+ var/obj/item/I = AM
+ if(I.unacidable || I.throwing || I.is_incorporeal())
+ return FALSE
+ var/food = FALSE
+ if(istype(I,/obj/item/weapon/reagent_containers/food))
+ food = TRUE
+ if(prob(95)) //Give people a chance to pick them up
+ return TRUE
+ I.visible_message("\The [I] sizzles...")
+ var/yum = I.digest_act() //Glorp
+ if(istype(I , /obj/item/weapon/card))
+ yum = 0 //No, IDs do not have infinite nutrition, thank you
+ if(mobstuff && linked_mob && yum)
+ if(food)
+ yum += 50
+ linked_mob.adjust_nutrition(yum)
+ return TRUE
+ if(isliving(AM))
+ var/mob/living/L = AM
+ if(L.unacidable || !L.digestable || L.buckled || L.hovering || L.throwing || L.is_incorporeal())
+ return FALSE
+ if(ishuman(L))
+ var/mob/living/carbon/human/H = L
+ if(!H.pl_suit_protected())
+ return TRUE
+ if(H.resting && !H.pl_head_protected())
+ return TRUE
+ else return TRUE
+
+/turf/simulated/floor/water/digestive_enzymes/proc/digest_stuff(atom/movable/AM) //I'm so sorry
+ . = FALSE
+
+ var/damage = 1
+ if(mobstuff && !linked_mob) //You might be wondering how we got here. It all started when I decided that I would make a vore level and make some of the turfs affect some mob somewhere in the world. So I used some convenient tools that people who are actually smart made, to make this horrible abomination.
+ var/obj/effect/overmap/visitable/ship/simplemob/stardog/s = get_overmap_sector(z)
+ if(s && istype(s,/obj/effect/overmap/visitable/ship/simplemob/stardog))
+ linked_mob = s.parent //dogge
+
+ if(linked_mob) //Please for the love of all that is good, make all this mob shit its own proc, future me
+ damage += clamp(((500 - linked_mob.nutrition) / 100), 1 , 5)
+ var/list/stuff = list()
+ for(var/thing in src)
+ if(can_digest(thing))
+ stuff |= thing
+ if(!stuff.len)
+ return FALSE
+ var/thing = pick(stuff) //We only think about one thing at a time, otherwise things get wacky
+ . = TRUE
+ if(ishuman(thing))
+ var/mob/living/carbon/human/H = thing
+ if(!H)
+ return
+ visible_message(runemessage = "blub...")
+ if(H.stat == DEAD)
+ H.unacidable = TRUE //Don't touch this one again, we're gonna delete it in a second
+ H.release_vore_contents()
+ for(var/obj/item/W in H)
+ if(istype(W, /obj/item/organ/internal/mmi_holder/posibrain))
+ var/obj/item/organ/internal/mmi_holder/MMI = W
+ MMI.removed()
+ if(istype(W, /obj/item/weapon/implant/backup) || istype(W, /obj/item/device/nif) || istype(W, /obj/item/organ))
+ continue
+ H.drop_from_inventory(W)
+ if(linked_mob)
+ var/how_much = H.mob_size + H.nutrition
+ if(!H.ckey)
+ how_much = how_much / 10 //Braindead mobs are worth less
+ linked_mob.adjust_nutrition(how_much)
+ H.mind?.vore_death = TRUE
+ GLOB.prey_digested_roundstat++
+ spawn(0)
+ qdel(H) //glorp
+ return
+ if(linked_mob)
+ H.burn_skin(damage)
+ if(linked_mob)
+ var/how_much = (damage * H.size_multiplier) * H.get_digestion_nutrition_modifier() * linked_mob.get_digestion_efficiency_modifier()
+ if(!H.ckey)
+ how_much = how_much / 10 //Braindead mobs are worth less
+ linked_mob.adjust_nutrition(how_much)
+ else if (isliving(thing))
+ var/mob/living/L = thing
+ if(!L)
+ return
+ visible_message(runemessage = "blub...")
+ if(L.stat == DEAD)
+ L.unacidable = TRUE //Don't touch this one again, we're gonna delete it in a second
+ L.release_vore_contents()
+ if(linked_mob)
+ var/how_much = L.mob_size + L.nutrition
+ if(!L.ckey)
+ how_much = how_much / 10 //Braindead mobs are worth less
+ linked_mob.adjust_nutrition(how_much)
+ qdel(L) //gloop
+ return
+ L.adjustFireLoss(damage)
+ if(linked_mob)
+ var/how_much = (damage * L.size_multiplier) * L.get_digestion_nutrition_modifier() * linked_mob.get_digestion_efficiency_modifier()
+ if(!L.ckey)
+ how_much = how_much / 10 //Braindead mobs are worth less
+ linked_mob.adjust_nutrition(how_much)
+
+/turf/simulated/floor/flesh/mover
+ icon_state = "flesh_floor_mover"
+ var/movechance = 5
+ var/we_process = FALSE
+ var/move_dir = 2
+
+/turf/simulated/floor/flesh/mover/Initialize(mapload)
+ . = ..()
+ move_dir = dir
+ dir = SOUTH
+
+/turf/simulated/floor/flesh/mover/Entered(atom/movable/AM)
+ if(!we_process)
+ START_PROCESSING(SSturfs, src)
+
+/turf/simulated/floor/flesh/mover/hitby(atom/movable/AM)
+ if(!we_process)
+ START_PROCESSING(SSturfs, src)
+
+/turf/simulated/floor/flesh/mover/process() //Mostly stolen from conveyor2.dm
+ if(movechance <= 0)
+ we_process = FALSE
+ return PROCESS_KILL
+ we_process = TRUE
+ if(!prob(movechance)) //Let's kind of control the speed that this happens at
+ return
+ var/items_moved = 0
+ for(var/atom/movable/A in contents)
+ if(A.anchored)
+ continue
+ if(!isitem(A) && !isliving(A))
+ continue
+ if(A.loc != src) //Don't move things that aren't here
+ continue
+ step(A,move_dir)
+ items_moved++
+
+ if(items_moved >= 10)
+ break
+
+ if(!items_moved) //If we didn't move anything let's shut it down
+ we_process = FALSE
+ return PROCESS_KILL
+
+/obj/structure/auto_flesh_door //It's like a simple door, but it opens and closes automatically now and then!
+ name = "flesh valve"
+ density = TRUE
+ opacity = TRUE
+ anchored = TRUE
+ can_atmos_pass = ATMOS_PASS_DENSITY
+
+ icon = 'icons/turf/stomach_vr.dmi'
+ icon_state = "fleshdoor"
+
+ var/state = 0 //closed, 1 == open
+ var/isSwitchingStates = 0
+ var/countdown = 0
+ var/knock_sound = 'sound/effects/attackblob.ogg'
+ var/list/open_sounds = list(
+ 'sound/vore/sunesound/prey/squish_01.ogg',
+ 'sound/vore/sunesound/prey/squish_02.ogg',
+ 'sound/vore/sunesound/prey/squish_03.ogg',
+ 'sound/vore/sunesound/prey/squish_04.ogg',
+ 'sound/vore/sunesound/prey/stomachmove.ogg'
+ )
+ var/faction = "macrobacteria"
+
+/obj/structure/auto_flesh_door/Initialize()
+ . = ..()
+ countdown = rand(50,250)
+ START_PROCESSING(SSobj, src)
+ update_icon()
+
+/obj/structure/auto_flesh_door/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ update_nearby_tiles()
+ return ..()
+
+/obj/structure/auto_flesh_door/process()
+ if(countdown <= 0)
+ SwitchState()
+ else
+ countdown --
+ if(!state)
+ for(var/mob/living/L in src.loc.contents)
+ if(isliving(L))
+ L.Weaken(3)
+ if(prob(5))
+ to_chat(L, "\The [src] throbs heavily around you...")
+
+/obj/structure/auto_flesh_door/attack_generic(mob/user, damage, attack_verb)
+ . = ..()
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ if(!Adjacent(user))
+ return
+ else if(user.faction == faction)
+ SwitchState()
+ else if(user.a_intent == I_HELP)
+ visible_message("[user] knocks on \the [src].", "Someone knocks on \the [src].")
+ playsound(src, knock_sound, 50, 0, 3)
+ countdown -= 10
+ else
+ visible_message("[user] hammers on \the [src]!", "Someone hammers loudly on \the [src]!")
+ playsound(src, knock_sound, 50, 0, 3)
+ countdown -= 25
+
+/obj/structure/auto_flesh_door/attack_hand(mob/user as mob)
+ . = ..()
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ if(!Adjacent(user))
+ return
+ else if(user.faction == faction)
+ SwitchState()
+ else if(user.a_intent == I_HELP)
+ visible_message("[user] knocks on \the [src].", "Someone knocks on \the [src].")
+ playsound(src, knock_sound, 50, 0, 3)
+ countdown -= 10
+ else
+ visible_message("[user] hammers on \the [src]!", "Someone hammers loudly on \the [src]!")
+ playsound(src, knock_sound, 50, 0, 3)
+ countdown -= 25
+
+/obj/structure/auto_flesh_door/CanPass(atom/movable/mover, turf/target)
+ return !density
+
+/obj/structure/auto_flesh_door/proc/SwitchState()
+ if(state)
+ Close()
+ else
+ Open()
+
+/obj/structure/auto_flesh_door/proc/Open()
+ isSwitchingStates = 1
+ var/oursound = pick(open_sounds)
+ playsound(src, oursound, 100, 1, preference = /datum/client_preference/digestion_noises , volume_channel = VOLUME_CHANNEL_VORE)
+ flick("flesh-opening",src)
+ sleep(8)
+ density = FALSE
+ set_opacity(0)
+ state = 1
+ update_icon()
+ isSwitchingStates = 0
+ update_nearby_tiles()
+ countdown = rand(10,20)
+ layer = OBJ_LAYER
+ plane = OBJ_PLANE
+
+/obj/structure/auto_flesh_door/proc/Close()
+ isSwitchingStates = 1
+ var/oursound = pick(open_sounds)
+ playsound(src, oursound, 100, 1, preference = /datum/client_preference/digestion_noises , volume_channel = VOLUME_CHANNEL_VORE)
+ flick("flesh-closing",src)
+ sleep(8)
+ density = TRUE
+ set_opacity(1)
+ state = 0
+ update_icon()
+ isSwitchingStates = 0
+ update_nearby_tiles()
+ countdown = rand(50,250)
+ layer = ABOVE_MOB_LAYER
+ plane = ABOVE_MOB_PLANE
+ for(var/mob/living/L in src.loc.contents)
+ if(isliving(L))
+ L.Weaken(3)
+ L.visible_message("\The [src] closes up on \the [L]!","The weight of \the [src] closes in on you, squeezing you on all sides so tightly that you can hardly move! It throbs against you as the way is sealed, with you stuck in the middle!!!")
+
+/obj/structure/auto_flesh_door/update_icon()
+ if(state)
+ icon_state = "flesh-open"
+ else
+ icon_state = "flesh-closed"
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/teppi.dm b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/teppi.dm
index d1613829e3..a1ebe4c214 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/teppi.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/teppi.dm
@@ -124,7 +124,7 @@ GLOBAL_VAR_INIT(teppi_count, 0) // How mant teppi DO we have?
attack_sound = 'sound/voice/teppi/roar.ogg' // make a better one idiot
friendly = list("snoofs", "nuzzles", "nibbles", "smooshes on")
- ai_holder_type = /datum/ai_holder/simple_mob/teppi
+ ai_holder_type = /datum/ai_holder/simple_mob/vore/micro_hunter
mob_size = MOB_LARGE
@@ -160,6 +160,10 @@ GLOBAL_VAR_INIT(teppi_count, 0) // How mant teppi DO we have?
vore_default_contamination_flavor = "Wet"
vore_default_contamination_color = "grey"
vore_default_item_mode = IM_DIGEST
+ vore_bump_chance = 5
+ vore_pounce_chance = 35
+ vore_pounce_falloff = 0
+ vore_standing_too = TRUE
/mob/living/simple_mob/vore/alienanimals/teppi/init_vore()
..()
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm b/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm
index 76db60732b..caab987a8e 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm
@@ -5,12 +5,18 @@
/mob/living/simple_mob
var/nameset
var/limit_renames = TRUE
+ var/copy_prefs_to_mob = TRUE
/mob/living/simple_mob/Login()
. = ..()
verbs |= /mob/living/simple_mob/proc/set_name
verbs |= /mob/living/simple_mob/proc/set_desc
+ if(copy_prefs_to_mob)
+ login_prefs()
+
+/mob/living/proc/login_prefs()
+
ooc_notes = client.prefs.metadata
digestable = client.prefs_vr.digestable
devourable = client.prefs_vr.devourable
@@ -37,7 +43,6 @@
step_mechanics_pref = client.prefs_vr.step_mechanics_pref
pickup_pref = client.prefs_vr.pickup_pref
-
/mob/living/simple_mob/proc/set_name()
set name = "Set Name"
set desc = "Sets your mobs name. You only get to do this once."
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/vore_hostile.dm b/code/modules/mob/living/simple_mob/subtypes/vore/vore_hostile.dm
new file mode 100644
index 0000000000..0d76550158
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/vore_hostile.dm
@@ -0,0 +1,333 @@
+//Mobs who's primary purpose is to go eat people who have their vore prefs turned on. They're retaliate mobs to everyone else.
+/mob/living/simple_mob/vore/vore_hostile
+ name = "peeb"
+ desc = "REPLACE ME"
+ ai_holder_type = /datum/ai_holder/simple_mob/vore
+
+/////ABYSS LURKER/////
+
+/datum/category_item/catalogue/fauna/abyss_lurker
+ name = "Alien Wildlife - Abyss Lurker"
+ desc = "Halitha Norotanis is a species of endobiotic life form that feeds primarily off of parasites, invaders, and other foreign bodies to its host. It responds to sound and touch, attacking and engulfing anything it deems to be a threat. It hardly makes any noise as it moves in the darkness and it will hunt down and investigate sound as it lurks. It doesn't seem to have any eyes or ears, but the golden antennae on its head seem to ripple in the direction of sounds."
+ value = CATALOGUER_REWARD_EASY
+
+/mob/living/simple_mob/vore/vore_hostile/abyss_lurker
+ name = "abyss lurker"
+ desc = "A pale mass of heaving flesh that gropes around in the gloom. It doesn't appear to have any eyes."
+ tt_desc = "Halitha Norotanis"
+ icon = 'icons/mob/alienanimals_x64.dmi'
+ icon_state = "abyss_lurker"
+ icon_living = "abyss_lurker"
+ icon_dead = "abyss_lurker-dead"
+ icon_rest = "abyss_lurker"
+ vis_height = 64
+
+ faction = "macrobacteria"
+ maxHealth = 600
+ health = 600
+ movement_cooldown = 3
+
+ harm_intent_damage = 1
+ melee_damage_lower = 1
+ melee_damage_upper = 1
+
+ meat_amount = 5
+ meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
+ catalogue_data = list(/datum/category_item/catalogue/fauna/abyss_lurker)
+
+ see_in_dark = 8
+
+ pixel_x = -16
+ default_pixel_x = -16
+
+
+ mob_size = MOB_LARGE
+ mob_bump_flag = HEAVY
+ mob_swap_flags = HEAVY
+ mob_push_flags = HEAVY
+ mob_size = MOB_LARGE
+
+ attacktext = list("flashes", "slaps", "smothers", "grapples")
+ attack_sound = 'sound/effects/attackblob.ogg'
+
+ ai_holder_type = /datum/ai_holder/simple_mob/say_aggro
+
+ swallowTime = 2 SECONDS
+ vore_active = 1
+ vore_capacity = 1
+ vore_bump_chance = 50
+ vore_bump_emote = "begins to absorb"
+ vore_ignores_undigestable = 0
+ vore_default_mode = DM_SELECT
+ vore_icons = null
+ vore_stomach_name = "Stomach"
+ vore_default_item_mode = IM_DIGEST
+ vore_pounce_chance = 50
+ vore_pounce_cooldown = 10
+ vore_pounce_successrate = 75
+ vore_pounce_falloff = 0
+ vore_pounce_maxhealth = 100
+ vore_standing_too = TRUE
+ unacidable = TRUE
+
+/mob/living/simple_mob/vore/vore_hostile/abyss_lurker/init_vore()
+ ..()
+ var/obj/belly/B = vore_selected
+ B.name = "interior"
+ B.desc = "It's hot and overwhelmingly tight! The interior of the pale creature groans with the effort of squeezing you. Everything is hot and churning and eager to grind and smother you in thick fluids. The weight of the creature's body pressing in at you makes it hard to move at all, while you are squeezed to the very core of the creature! There seems almost not to even be an organ for this so much as the creature has folded around you, trying to incorporate your matter into its body with vigor!"
+ B.mode_flags = DM_FLAG_THICKBELLY | DM_FLAG_NUMBING
+ B.belly_fullscreen = "yet_another_tumby"
+ B.digest_brute = 3
+ B.digest_burn = 2
+ B.digestchance = 0
+ B.absorbchance = 0
+ B.escapechance = 25
+ B.escape_stun = 5
+
+/mob/living/simple_mob/vore/vore_hostile/abyss_lurker/attack_hand(mob/living/user)
+
+ if(client || !user.client || !ai_holder || !isliving(user))
+ return ..()
+ if(!user.devourable || !user.allowmobvore || !user.can_be_drop_prey)
+ return ..()
+ ai_holder.give_target(user, TRUE)
+ ai_holder.track_target_position()
+ ai_holder.set_stance(STANCE_FIGHT)
+
+/datum/ai_holder/simple_mob/say_aggro
+ hostile = FALSE
+ forgive_resting = TRUE
+ cooperative = FALSE
+
+/datum/ai_holder/simple_mob/say_aggro/on_hear_say(mob/living/speaker, message)
+ . = ..()
+ if(holder.client || !speaker.client)
+ return
+ if(!speaker.devourable || !speaker.allowmobvore || !speaker.can_be_drop_prey)
+ return
+ if(speaker.z != holder.z)
+ return
+ give_target(speaker, TRUE)
+ track_target_position()
+ set_stance(STANCE_FIGHT)
+
+/////Leaper/////
+
+/datum/category_item/catalogue/fauna/leaper
+ name = "Alien Wildlife - Abyss Leaper"
+ desc = "Halitha Tannerack is a species of endobiotic life form that feeds primarily off of parasites, invaders, and other foreign bodies to its host. It pounces upon threats using its powerful legs to leap great distances. It has expressed higher thinking, though it seems not to act entirely in its own interest, serving its host organism's well-being before any other priorities. This means that it is capable of picking its targets, and will not attack indiscriminately. It chooses only to attack those it senses to be a threat to its host, though what exactly it qualifies to be a threat has not been fully explored. This creature radiates a strange energy from within, and while this energy has been studied, it is very poorly understood."
+ value = CATALOGUER_REWARD_EASY
+
+/mob/living/simple_mob/vore/vore_hostile/leaper
+ name = "abyss leaper"
+ desc = "A tall, pale creature with blood red markings. It has powerful legs and long dexterous tentacles. Its eyes and tentacles appear to glow from within with some unknown energy."
+ tt_desc = "Halitha Tannerack"
+ icon = 'icons/mob/alienanimals_x64.dmi'
+ icon_state = "filter"
+ icon_living = "filter"
+ icon_dead = "filter-dead"
+ icon_rest = "filter"
+ vis_height = 64
+
+ faction = "macrobacteria"
+ maxHealth = 600
+ health = 600
+
+ harm_intent_damage = 1
+ melee_damage_lower = 1
+ melee_damage_upper = 1
+
+ movement_cooldown = 1
+ meat_amount = 5
+ meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
+ catalogue_data = list(/datum/category_item/catalogue/fauna/leaper)
+
+ see_in_dark = 8
+
+ pixel_x = -16
+ default_pixel_x = -16
+
+ mob_size = MOB_LARGE
+ mob_bump_flag = HEAVY
+ mob_swap_flags = HEAVY
+ mob_push_flags = HEAVY
+ mob_size = MOB_LARGE
+
+ has_eye_glow = TRUE
+
+ attacktext = list("pushes", "slaps", "whips", "grapples")
+ attack_sound = 'sound/effects/attackblob.ogg'
+
+ ai_holder_type = /datum/ai_holder/simple_mob/vore
+
+ swallowTime = 1 SECONDS
+ vore_active = 1
+ vore_capacity = 2
+ vore_bump_chance = 75
+ vore_bump_emote = "grabs ahold of"
+ vore_ignores_undigestable = 0
+ vore_default_mode = DM_SELECT
+ vore_icons = SA_ICON_LIVING
+ vore_stomach_name = "stomach"
+ vore_default_item_mode = IM_DIGEST
+ vore_pounce_chance = 75
+ vore_pounce_cooldown = 10
+ vore_pounce_successrate = 75
+ vore_pounce_falloff = 0
+ vore_pounce_maxhealth = 100
+ vore_standing_too = TRUE
+ can_be_drop_prey = FALSE
+ can_be_drop_pred = TRUE
+ throw_vore = TRUE
+ unacidable = TRUE
+
+ special_attack_min_range = 2
+ special_attack_max_range = 6
+ special_attack_cooldown = 10 SECONDS
+
+ var/leap_warmup = 1 SECOND // How long the leap telegraphing is.
+ var/leap_sound = 'sound/weapons/spiderlunge.ogg'
+
+/mob/living/simple_mob/vore/vore_hostile/leaper/init_vore()
+ ..()
+ var/obj/belly/B = vore_selected
+ B.name = "stomach"
+ B.desc = "The flesh of the tall creature's stomach folds over you in doughy waves, squeezing you into the tightest shape it can manage with idle flexes churning down on you. Your limbs often find themselves lost between folds and tugged this way or that, held in a skin tight press that is not painful, but is hard to pull away from. You can see a strange, glittering pink and purple light glimmering through the flesh of the monster all around you, like your very own sea of stars. The walls rush in to fill all the space, squeezing you from head to toe no matter how you might wiggle, the weight of the semi-transparent interior flesh keeping you neatly secured deep inside while wringing the fight out of you."
+ B.mode_flags = DM_FLAG_THICKBELLY | DM_FLAG_NUMBING
+ B.belly_fullscreen = "yet_another_tumby"
+ B.digest_brute = 2
+ B.digest_burn = 2
+ B.digestchance = 0
+ B.absorbchance = 0
+ B.escapechance = 25
+ B.colorization_enabled = TRUE
+ B.belly_fullscreen_color = "#591579"
+ B.escape_stun = 3
+
+// The leaping attack.
+/mob/living/simple_mob/vore/vore_hostile/leaper/do_special_attack(atom/A) //Mostly copied from hunter.dm
+ set waitfor = FALSE
+ if(!isliving(A))
+ return FALSE
+ var/mob/living/L = A
+ if(!L.devourable || !L.allowmobvore || !L.can_be_drop_prey || !L.throw_vore || L.unacidable)
+ return FALSE
+
+ set_AI_busy(TRUE)
+ visible_message(span("warning","\The [src]'s eyes flash ominously!"))
+ to_chat(L, span("danger","\The [src] focuses on you!"))
+ // Telegraph, since getting stunned suddenly feels bad.
+ do_windup_animation(A, leap_warmup)
+ sleep(leap_warmup) // For the telegraphing.
+
+ if(L.z != z) //Make sure you haven't disappeared to somewhere we can't go
+ set_AI_busy(FALSE)
+ return FALSE
+
+ // Do the actual leap.
+ status_flags |= LEAPING // Lets us pass over everything.
+ visible_message(span("critical","\The [src] leaps at \the [L]!"))
+ throw_at(get_step(L, get_turf(src)), special_attack_max_range+1, 1, src)
+ playsound(src, leap_sound, 75, 1)
+
+ sleep(5) // For the throw to complete. It won't hold up the AI ticker due to waitfor being false.
+
+ if(status_flags & LEAPING)
+ status_flags &= ~LEAPING // Revert special passage ability.
+
+ set_AI_busy(FALSE)
+ if(Adjacent(L)) //We leapt at them but we didn't manage to hit them, let's see if we're next to them
+ L.Weaken(2) //get knocked down, idiot
+
+/////Gelatinous Cube/////
+
+/datum/category_item/catalogue/fauna/gelatinous_cube
+ name = "Alien Wildlife - Gelatinous Cube"
+ desc = "Macrocollagen Vulgaris is a species of slow moving slime. Debate still rages over whether or not it is actually even alive. It is most commonly found in the shape of a cube, while its colors can vary wildly depending upon what it has ingested. The cube is comprised of an extremely thick gel like substance that is highly corrosive, anything caught inside without appropriate protection will only last a few moments. This slime does move around, which is the primary argument for classifying it as alive, but it seems to wander randomly toward sources of nutrients. It seems perfectly indifferent to what it can ingest, and will simply ingest everything, from dirt, to people."
+ value = CATALOGUER_REWARD_EASY
+
+/mob/living/simple_mob/vore/vore_hostile/gelatinous_cube
+ name = "gelatinous cube"
+ desc = "A cube of corrosive slime. It seems to slide around very slowly. You're not sure if it's actually moving under its own power, or if it is just sliding around haphazardly. It is somewhat transparent, and you can see clouds of still processing materials inside as they break down."
+ tt_desc = "Macrocollagen Vulgaris"
+ icon = 'icons/mob/alienanimals_x64.dmi'
+ icon_state = "cube"
+ icon_living = "cube"
+ icon_dead = " "
+ icon_rest = "cube"
+ vis_height = 64
+
+ faction = "macrobacteria"
+ maxHealth = 500
+ health = 500
+
+ harm_intent_damage = 1
+ melee_damage_lower = 1
+ melee_damage_upper = 1
+
+ movement_cooldown = 50
+ meat_amount = 0
+ meat_type = null
+ catalogue_data = list(/datum/category_item/catalogue/fauna/gelatinous_cube)
+
+ see_in_dark = 8
+
+ pixel_x = -16
+ default_pixel_x = -16
+
+ mob_size = MOB_LARGE
+ mob_bump_flag = HUMAN
+ mob_swap_flags = HEAVY
+ mob_push_flags = HEAVY
+ mob_size = MOB_LARGE
+
+ attacktext = list("splashes against", "slaps", "smothers", "engulfs")
+ attack_sound = 'sound/effects/attackblob.ogg'
+
+ ai_holder_type = /datum/ai_holder/simple_mob/vore
+
+ swallowTime = 0 SECONDS
+ vore_active = 1
+ vore_capacity = 1
+ vore_bump_chance = 100
+ vore_bump_emote = "begins to absorb"
+ vore_ignores_undigestable = 0
+ vore_default_mode = DM_SELECT
+ vore_icons = SA_ICON_LIVING
+ vore_stomach_name = "interior"
+ vore_default_item_mode = IM_DIGEST
+ vore_pounce_chance = 50
+ vore_pounce_cooldown = 10
+ vore_pounce_successrate = 75
+ vore_pounce_falloff = 0
+ vore_pounce_maxhealth = 100
+ vore_standing_too = TRUE
+ unacidable = TRUE
+
+/mob/living/simple_mob/vore/vore_hostile/gelatinous_cube/init_vore()
+ ..()
+ var/obj/belly/B = vore_selected
+ B.name = "interior"
+ B.desc = "An incredibly thick oozing slime surrounds you, filling in all the space around your form! It's hard to catch a breath here as the jiggling gel that makes up the body of the creature swiftly fills in the hole you made in its surface by entering. The gel is semi-transparent, and you can see your surroundings though its surface, and similarly you can be seen floating in the gel from the outside. When the cube moves, your whole body is wobbled along with it. There are clouds of still processing material floating all around you as the corrosive substance works on breaking everything down."
+ B.mode_flags = DM_FLAG_NUMBING
+ B.belly_fullscreen = "yet_another_tumby"
+ B.colorization_enabled = TRUE
+ B.belly_fullscreen_color = color
+ B.digest_brute = 2
+ B.digest_burn = 10
+ B.digest_oxy = 12
+ B.digestchance = 0
+ B.absorbchance = 0
+ B.escapechance = 10
+ B.escapetime = 10 SECONDS
+ B.selective_preference = DM_DIGEST
+ B.escape_stun = 3
+
+/mob/living/simple_mob/vore/vore_hostile/gelatinous_cube/Initialize()
+ . = ..()
+ color = random_color(TRUE)
+
+/mob/living/simple_mob/vore/vore_hostile/gelatinous_cube/death()
+ . = ..()
+
+ qdel(src)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 219a11db30..cd94acf4da 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1090,6 +1090,33 @@
if(pixel_x <= (default_pixel_x + 16))
pixel_x++
is_shifted = TRUE
+
+/mob/verb/planeup()
+ set hidden = TRUE
+ if(!canface())
+ return FALSE
+ if(plane >= MOB_PLANE + 3) //Don't bother going too high!
+ return
+ if(layer == MOB_LAYER) //Become higher
+ layer = ABOVE_MOB_LAYER
+ plane += 1 //Increase the plane
+ if(plane == MOB_PLANE) //Return to normal
+ layer = MOB_LAYER
+ is_shifted = TRUE
+
+/mob/verb/planedown()
+ set hidden = TRUE
+ if(!canface())
+ return FALSE
+ if(plane <= MOB_PLANE - 3) //Don't bother going too low!
+ return
+ if(layer == MOB_LAYER) //Become lower
+ layer = BELOW_MOB_LAYER
+ plane -= 1 //Decrease the plane
+ if(plane == MOB_PLANE) //Return to normal
+ layer = MOB_LAYER
+ is_shifted = TRUE
+
// End VOREstation edit
/mob/proc/adjustEarDamage()
diff --git a/code/modules/mob/mob_defines_vr.dm b/code/modules/mob/mob_defines_vr.dm
index 09ada3789b..619cfe5940 100644
--- a/code/modules/mob/mob_defines_vr.dm
+++ b/code/modules/mob/mob_defines_vr.dm
@@ -9,8 +9,9 @@
var/size_multiplier = 1 //multiplier for the mob's icon size
var/accumulated_rads = 0 // For radiation stuff.
+ var/faction_bump_vore = FALSE // Don't bump nom mobs of the same faction
/mob/drop_location()
if(temporary_form)
return temporary_form.drop_location()
- return ..()
\ No newline at end of file
+ return ..()
diff --git a/code/modules/overmap/events/event_handler.dm b/code/modules/overmap/events/event_handler.dm
index 2d9c2badda..b2782c2450 100644
--- a/code/modules/overmap/events/event_handler.dm
+++ b/code/modules/overmap/events/event_handler.dm
@@ -80,7 +80,7 @@ GLOBAL_DATUM_INIT(overmap_event_handler, /decl/overmap_event_handler, new)
E.startWhen = 0
E.endWhen = INFINITY
// TODO - Leshana - Note: event.setup() is called before these are set!
- E.affecting_z = ship.map_z
+ E.affecting_z = ship.map_z.Copy()
E.victim = ship
LAZYADD(ship_events[ship], E)
diff --git a/code/modules/overmap/ships/ship_vr.dm b/code/modules/overmap/ships/ship_vr.dm
index 97afe44e72..4eed0abd23 100644
--- a/code/modules/overmap/ships/ship_vr.dm
+++ b/code/modules/overmap/ships/ship_vr.dm
@@ -9,6 +9,10 @@
/obj/effect/overmap/visitable/ship/MouseDrop(atom/over)
if(!isliving(over) || !Adjacent(over) || !Adjacent(usr))
return
+ if(istype(over, /mob/living/simple_mob/vore/overmap))
+ var/mob/living/simple_mob/vore/overmap/sdog = over
+ if(!sdog.shipvore)
+ return
var/mob/living/L = over
var/confirm = tgui_alert(L, "You COULD eat this spaceship...", "Eat spaceship?", list("Eat it!", "No, thanks."))
if(confirm == "Eat it!")
@@ -28,14 +32,14 @@
/obj/effect/overmap/visitable/ship/hear_talk(mob/talker, list/message_pieces, verb)
. = ..()
-
+
var/list/listeners = get_people_in_ship()
for(var/mob/M as anything in listeners)
M.hear_say(message_pieces, verb, FALSE, talker)
/obj/effect/overmap/visitable/ship/show_message(msg, type, alt, alt_type)
. = ..()
-
+
var/list/listeners = get_people_in_ship()
for(var/mob/M as anything in listeners)
M.show_message(msg, type, alt, alt_type)
@@ -43,6 +47,6 @@
/obj/effect/overmap/visitable/ship/see_emote(source, message, m_type)
. = ..()
- var/list/listeners = get_people_in_ship()
+ var/list/listeners = get_people_in_ship()
for(var/mob/M as anything in listeners)
M.show_message(message, m_type)
diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm
index c0811a4575..1e16e3c453 100644
--- a/code/modules/paperwork/faxmachine.dm
+++ b/code/modules/paperwork/faxmachine.dm
@@ -1,16 +1,17 @@
var/list/obj/machinery/photocopier/faxmachine/allfaxes = list()
-var/list/admin_departments = list("[using_map.boss_name]", "Virgo-Prime Governmental Authority", "Virgo-Erigonne Job Boards", "Supply") // Vorestation Edit
+var/list/admin_departments = list("[using_map.boss_name]", "Virgo-Prime Governmental Authority", "Virgo-Erigonne Job Boards", "Supply")
var/list/alldepartments = list()
+var/global/last_fax_role_request
var/list/adminfaxes = list() //cache for faxes that have been sent to admins
/obj/machinery/photocopier/faxmachine
name = "fax machine"
- desc = "Sent papers and pictures far away! Or to your co-worker's office a few doors down."
+ desc = "Send papers and pictures far away! Or to your co-worker's office a few doors down."
icon = 'icons/obj/library.dmi'
icon_state = "fax"
insert_anim = "faxsend"
- req_one_access = list(access_lawyer, access_heads, access_armory, access_qm)
+ req_one_access = list()
use_power = USE_POWER_IDLE
idle_power_usage = 30
@@ -37,6 +38,109 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins
tgui_interact(user)
+/obj/machinery/photocopier/faxmachine/verb/remove_card()
+ set name = "Remove ID card"
+ set category = "Object"
+ set src in oview(1)
+
+ var/mob/living/L = usr
+
+ if(!L || !isturf(L.loc) || !isliving(L))
+ return
+ if(!ishuman(L) && !issilicon(L))
+ return
+ if(L.stat || L.restrained())
+ return
+ if(!scan)
+ to_chat(L, span_notice("There is no I.D card to remove!"))
+ return
+
+ scan.forceMove(loc)
+ if(ishuman(usr) && !usr.get_active_hand())
+ usr.put_in_hands(scan)
+ scan = null
+ authenticated = null
+
+/obj/machinery/photocopier/faxmachine/verb/request_roles()
+ set name = "Staff Request Form"
+ set category = "Object"
+ set src in oview(1)
+
+ var/mob/living/L = usr
+
+ if(!L || !isturf(L.loc) || !isliving(L))
+ return
+ if(!ishuman(L) && !issilicon(L))
+ return
+ if(L.stat || L.restrained())
+ return
+ if(last_fax_role_request && (world.time - last_fax_role_request < 5 MINUTES))
+ to_chat(L, "The global automated relays are still recalibrating. Try again later or relay your request in written form for processing.")
+ return
+
+ var/confirmation = tgui_alert(L, "Are you sure you want to send automated crew request?", "Confirmation", list("Yes", "No", "Cancel"))
+ if(confirmation != "Yes")
+ return
+
+ var/list/jobs = list()
+ for(var/datum/department/dept as anything in SSjob.get_all_department_datums())
+ if(!dept.assignable || dept.centcom_only)
+ continue
+ for(var/job in SSjob.get_job_titles_in_department(dept.name))
+ var/datum/job/J = SSjob.get_job(job)
+ if(J.requestable)
+ jobs |= job
+
+ var/role = tgui_input_list(L, "Pick the job to request.", "Job Request", jobs)
+ if(!role)
+ return
+
+ var/datum/job/job_to_request = SSjob.get_job(role)
+ var/reason = "Unspecified"
+ var/list/possible_reasons = list("Unspecified", "General duties", "Emergency situation")
+ possible_reasons += job_to_request.get_request_reasons()
+ reason = tgui_input_list(L, "Pick request reason.", "Request reason", possible_reasons)
+
+ var/final_conf = tgui_alert(L, "You are about to request [role]. Are you sure?", "Confirmation", list("Yes", "No", "Cancel"))
+ if(final_conf != "Yes")
+ return
+
+ var/datum/department/ping_dept = SSjob.get_ping_role(role)
+ if(!ping_dept)
+ to_chat(L, "Selected job cannot be requested for \[ERRORDEPTNOTFOUND] reason. Please report this to system administrator.")
+ return
+ var/message_color = "#FFFFFF"
+ var/ping_name = null
+ switch(ping_dept.name)
+ if(DEPARTMENT_COMMAND)
+ ping_name = "Command"
+ if(DEPARTMENT_SECURITY)
+ ping_name = "Security"
+ if(DEPARTMENT_ENGINEERING)
+ ping_name = "Engineering"
+ if(DEPARTMENT_MEDICAL)
+ ping_name = "Medical"
+ if(DEPARTMENT_RESEARCH)
+ ping_name = "Research"
+ if(DEPARTMENT_CARGO)
+ ping_name = "Supply"
+ if(DEPARTMENT_CIVILIAN)
+ ping_name = "Service"
+ if(DEPARTMENT_PLANET)
+ ping_name = "Expedition"
+ if(DEPARTMENT_SYNTHETIC)
+ ping_name = "Silicon"
+ //if(DEPARTMENT_TALON)
+ // ping_name = "Offmap"
+ if(!ping_name)
+ to_chat(L, "Selected job cannot be requested for \[ERRORUNKNOWNDEPT] reason. Please report this to system administrator.")
+ return
+ message_color = ping_dept.color
+
+ message_chat_rolerequest(message_color, ping_name, reason, role)
+ last_fax_role_request = world.time
+ to_chat(L, "Your request was transmitted.")
+
/obj/machinery/photocopier/faxmachine/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
@@ -50,6 +154,7 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins
data["rank"] = rank
data["isAI"] = isAI(user)
data["isRobot"] = isrobot(user)
+ data["adminDepartments"] = admin_departments
data["bossName"] = using_map.boss_name
data["copyItem"] = copyitem
@@ -107,6 +212,8 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins
usr.put_in_hands(copyitem)
to_chat(usr, "You take \the [copyitem] out of \the [src].")
copyitem = null
+ if("send_automated_staff_request")
+ request_roles()
if(!authenticated)
return
@@ -216,8 +323,8 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins
// Sadly, we can't use a switch statement here due to not using a constant value for the current map's centcom name.
if(destination == using_map.boss_name)
message_admins(sender, "[uppertext(using_map.boss_short)] FAX", rcvdcopy, "CentComFaxReply", "#006100")
- else if(destination == "Virgo-Prime Governmental Authority") // Vorestation Edit
- message_admins(sender, "VIRGO GOVERNMENT FAX", rcvdcopy, "CentComFaxReply", "#1F66A0") // Vorestation Edit
+ else if(destination == "Virgo-Prime Governmental Authority")
+ message_admins(sender, "VIRGO GOVERNMENT FAX", rcvdcopy, "CentComFaxReply", "#1F66A0")
else if(destination == "Supply")
message_admins(sender, "[uppertext(using_map.boss_short)] SUPPLY FAX", rcvdcopy, "CentComFaxReply", "#5F4519")
else
@@ -253,10 +360,8 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins
to_chat(C,msg)
C << 'sound/machines/printer.ogg'
- // VoreStation Edit Start
var/faxid = export_fax(sent)
- message_chat_admins(sender, faxname, sent, faxid, font_colour)
- // VoreStation Edit End
+ message_chat_admins(sender, faxname, sent, faxid, font_colour) //Sends to admin chat
// Webhooks don't parse the HTML on the paper, so we gotta strip them out so it's still readable.
var/summary = make_summary(sent)
@@ -277,3 +382,80 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins
"body" = summary
)
)
+
+/*
+ ##### ####
+ ##### Webhook Functionality ####
+ ##### ####
+*/
+
+/datum/configuration
+ var/chat_webhook_url = "" // URL of the webhook for sending announcements/faxes to discord chat.
+ var/chat_webhook_key = "" // Shared secret for authenticating to the chat webhook
+ var/fax_export_dir = "data/faxes" // Directory in which to write exported fax HTML files.
+
+
+/**
+ * Write the fax to disk as (potentially multiple) HTML files.
+ * If the fax is a paper_bundle, do so recursively for each page.
+ * returns a random unique faxid.
+ */
+/obj/machinery/photocopier/faxmachine/proc/export_fax(fax)
+ var faxid = "[num2text(world.realtime,12)]_[rand(10000)]"
+ if (istype(fax, /obj/item/weapon/paper))
+ var/obj/item/weapon/paper/P = fax
+ var/text = "
" \
+ + "[H.scribble ? "
" \
- + "[H.scribble ? "