Merge pull request #9294 from MistakeNot4892/xenofauna

Xenofauna Tech equipment update.
This commit is contained in:
Atermonera
2023-12-28 21:09:19 -08:00
committed by GitHub
29 changed files with 464 additions and 85 deletions

View File

@@ -16,18 +16,18 @@
if(!LAZYLEN(data))
to_chat(user, span("warning", "\The [src] is not interesting enough to catalogue."))
return FALSE
else
// Check if this has nothing new on it.
var/has_new_data = FALSE
for(var/t in data)
var/datum/category_item/catalogue/item = GLOB.catalogue_data.resolve_item(t)
if(!item.visible)
has_new_data = TRUE
break
if(!has_new_data)
to_chat(user, span("warning", "Scanning \the [src] would provide no new information."))
return FALSE
// Check if this has nothing new on it.
var/has_new_data = FALSE
for(var/t in data)
var/datum/category_item/catalogue/item = GLOB.catalogue_data.resolve_item(t)
if(!item.visible)
has_new_data = TRUE
break
if(!has_new_data)
to_chat(user, span("warning", "Scanning \the [src] would provide no new information."))
return FALSE
return TRUE
@@ -53,29 +53,25 @@
return catalogue_data
/mob/living/carbon/human/get_catalogue_data()
var/list/data = list()
// First, handle robot-ness.
var/beep_boop = get_FBP_type()
switch(beep_boop)
if(FBP_CYBORG)
data += /datum/category_item/catalogue/technology/cyborgs
LAZYADD(., /datum/category_item/catalogue/technology/cyborgs)
if(FBP_POSI)
data += /datum/category_item/catalogue/technology/positronics
LAZYADD(., /datum/category_item/catalogue/technology/positronics)
if(FBP_DRONE)
data += /datum/category_item/catalogue/technology/drone/drones
LAZYADD(., /datum/category_item/catalogue/technology/drone/drones)
// Now for species.
if(!(beep_boop in list(FBP_POSI, FBP_DRONE))) // Don't give the species entry if they are a posi or drone.
if(species && LAZYLEN(species.catalogue_data))
data += species.catalogue_data
return data
// Don't give the species entry if they are a posi or drone.
if(!(beep_boop in list(FBP_POSI, FBP_DRONE)) && species && LAZYLEN(species.catalogue_data))
LAZYADD(., species.catalogue_data)
/mob/living/silicon/robot/get_catalogue_data()
var/list/data = list()
switch(braintype)
if(BORG_BRAINTYPE_CYBORG)
data += /datum/category_item/catalogue/technology/cyborgs
LAZYADD(., /datum/category_item/catalogue/technology/cyborgs)
if(BORG_BRAINTYPE_POSI)
data += /datum/category_item/catalogue/technology/positronics
LAZYADD(., /datum/category_item/catalogue/technology/positronics)
if(BORG_BRAINTYPE_DRONE)
data += /datum/category_item/catalogue/technology/drone/drones
return data
LAZYADD(., /datum/category_item/catalogue/technology/drone/drones)

View File

@@ -193,6 +193,13 @@
siemens_coefficient = 0.9
armor = list(melee = 30, bullet = 20, laser = 20, energy = 20, bomb = 35, bio = 75, rad = 35)
/obj/item/clothing/head/hood/explorer/xenofauna
name = "xenofauna field hood"
desc = "A lightly armoured hood for surveying harsh environments."
icon_state = "xenofauna"
siemens_coefficient = 0.5
armor = list(melee = 20, bullet = 10, laser = 10, energy = 10, bomb = 25, bio = 90, rad = 60)
// Costumes
/obj/item/clothing/head/hood/carp_hood
name = "carp hood"

View File

@@ -425,5 +425,16 @@
/obj/item/material/knife,
/obj/item/tank,
/obj/item/radio,
/obj/item/pickaxe
)
/obj/item/pickaxe,
/obj/item/cataloguer,
/obj/item/specimen_tagger
)
/obj/item/clothing/suit/storage/hooded/explorer/xenofauna
name = "xenofauna field suit"
desc = "A lightly armoured suit for surveying harsh environments."
icon_state = "xenofauna"
item_state = "xenofauna"
siemens_coefficient = 0.5
armor = list(melee = 20, bullet = 10, laser = 10, energy = 10, bomb = 25, bio = 90, rad = 60) // Better bio/rad protection than explo, but less armour.
hoodtype = /obj/item/clothing/head/hood/explorer/xenofauna

View File

@@ -1210,12 +1210,17 @@ Uniforms and such
worn_state = "bathrobe"
/obj/item/clothing/under/explorer
desc = "A green uniform for operating in hazardous environments."
desc = "A grey and cyan uniform for working in the field."
name = "explorer's jumpsuit"
icon_state = "explorer"
/obj/item/clothing/under/xenofauna
desc = "A grey and purple uniform for working in the field."
name = "xenofauna technician's jumpsuit"
icon_state = "xenofauna"
/obj/item/clothing/under/explorer/armored
desc = "A green uniform for operating in hazardous environments. This one looks like it's been modified."
desc = "A grey and green uniform for operating in hazardous environments. This one looks like it's been modified."
armor = list(melee = 10, bullet = 10, laser = 10,energy = 10, bomb = 10, bio = 10, rad = 30)
armorsoak = list(melee = 5, bullet = 5, laser = 5,energy = 5, bomb = 5, bio = 5, rad = 0)

View File

@@ -9,6 +9,8 @@
var/entries_decay_at // Set in rounds. This controls when item messages start getting scrambled.
var/entry_decay_weight = 0.5
var/has_admin_data
var/area_restricted = TRUE
var/station_restricted = TRUE
/datum/persistent/New()
SetFilename()
@@ -71,14 +73,15 @@
/datum/persistent/proc/IsValidEntry(var/atom/entry)
if(!istype(entry))
return FALSE
if(GetEntryAge(entry) >= entries_expire_at)
if(!isnull(entries_expire_at) && GetEntryAge(entry) >= entries_expire_at)
return FALSE
var/turf/T = get_turf(entry)
if(!T || !(T.z in using_map.station_levels) )
return FALSE
var/area/A = get_area(T)
if(!A || (A.area_flags & AREA_FLAG_IS_NOT_PERSISTENT))
if(station_restricted && (!T || !(T.z in using_map.station_levels) ))
return FALSE
if(area_restricted)
var/area/A = get_area(T)
if(!A || (A.area_flags & AREA_FLAG_IS_NOT_PERSISTENT))
return FALSE
return TRUE
/datum/persistent/proc/GetEntryAge(var/atom/entry)

View File

@@ -0,0 +1,44 @@
// Currently unticked pending full implementation of the required data structures
// and tracking/load logic. Needs to account for DMMS load, critters spawning in POIs,
// critters spawning on station, etc. Fair bit of design work to consider.
/datum/persistent/specimen
name = "specimen"
entries_expire_at = 10
area_restricted = FALSE
station_restricted = FALSE
/datum/persistent/specimen/CreateEntryInstance(var/turf/creating, var/list/token)
new /obj/item/gps/specimen_tag(
creating,
token["age"]+1,
token["implanted_by"],
token["specimen_id"],
token["specimen_gender"],
token["physical_info"],
token["behavior_info"],
text2path(token["specimen_type"])
)
/datum/persistent/specimen/GetEntryAge(var/atom/entry)
var/obj/item/gps/specimen_tag/save_specimen = entry
if(!istype(save_specimen))
return 0
return save_specimen.age
/datum/persistent/specimen/IsValidEntry(atom/entry)
. = ..()
var/obj/item/gps/specimen_tag/save_specimen = entry
return istype(save_specimen) && save_specimen.has_been_implanted() && save_specimen.implanted_in.stat != DEAD
/datum/persistent/specimen/CompileEntry(var/atom/entry, var/write_file)
. = ..()
var/obj/item/gps/specimen_tag/save_specimen = entry
if(!istype(save_specimen))
return
save_specimen.update_from_animal()
LAZYADDASSOC(., "implanted_by", save_specimen.implanted_by)
LAZYADDASSOC(., "specimen_id", save_specimen.gps_tag)
LAZYADDASSOC(., "specimen_type", save_specimen.implanted_in.type)
LAZYADDASSOC(., "specimen_gender", save_specimen.gender)
LAZYADDASSOC(., "physical_info", save_specimen.physical_info)
LAZYADDASSOC(., "behavior_info", save_specimen.behavioral_info)

View File

@@ -0,0 +1,292 @@
// Device used to implant, remove or read specimen tags.
/* Notes on specimen tagger and expected flow:
* - Xenofauna player uses tagger (/obj/item/specimen_tagger) on appropriate critter (appropriate type, has cataloguer info).
* - Tag (/obj/item/gps/specimen_tag) is created and registered in the critter.
* - Xenofauna players can then track the tag via GPS to scan, remove, etc.
* - When persistent specimens are committed, tags will be loaded and assigned to mobs at world init.
*/
/obj/item/specimen_tagger
name = "specimen tagger"
desc = "A handheld device used to implant, remove and read xenofauna tracking tags from local specimens. Not for use on crewmembers."
icon = 'icons/obj/specimen_tagger.dmi'
icon_state = "tagger"
force = 0
item_flags = NOBLUDGEON
var/tag_id = "FAUNA0"
var/static/list/unsuitable_tag_types = list(
// Do not specimen tag your crewmates
/mob/living/carbon,
/mob/living/simple_mob/animal/sif/grafadreka/trained,
// Do not specimen tag pre-mapped critters
/mob/living/simple_mob/animal/passive/opossum/poppy,
/mob/living/simple_mob/animal/passive/dog/corgi/Ian,
/mob/living/simple_mob/animal/passive/cat/runtime
)
/obj/item/specimen_tagger/attack_self(mob/user)
var/new_tag = input("Please enter desired tag.", name, tag_id) as text
if(QDELETED(src) || QDELETED(user) || user.incapacitated() || loc != user)
return TRUE
new_tag = uppertext(copytext(sanitize(new_tag), 1, 11))
if(!length(new_tag))
return TRUE
tag_id = new_tag
to_chat(usr, "You set the tracker tag to '[tag_id]'.")
return TRUE
/obj/item/specimen_tagger/examine(mob/user)
. = ..()
. += "Use this on a living animal on help intent to read an existing tracker, grab intent to tag an animal with a tracker, and any other intent to remove an existing tracker."
/obj/item/specimen_tagger/attack(mob/living/M, mob/living/user, target_zone, attack_modifier)
switch(user.a_intent)
if(I_HELP)
try_read_tag(user, M)
if(I_GRAB)
try_implant_tag(user, M)
else
try_remove_tag(user, M)
return TRUE
/obj/item/specimen_tagger/proc/try_read_tag(var/mob/user, var/mob/living/target)
var/obj/item/gps/specimen_tag/xenotag = locate() in target
if(!istype(xenotag) || !xenotag.has_been_implanted())
to_chat(user, SPAN_WARNING("\The [target] has not been tagged."))
return FALSE
to_chat(user, "<b>Specimen data for [xenotag.gps_tag]:</b>")
to_chat(user, "<b>Species:</b> [target.real_name]")
to_chat(user, "<b>Tag duration:</b> [xenotag.age] shift\s")
to_chat(user, "<b>Tagged by:</b> [xenotag.implanted_by]")
to_chat(user, "<b>Physical notes:</b> [xenotag.physical_info]")
to_chat(user, "<b>Behavioral notes:</b> [xenotag.behavioral_info]")
return TRUE
/obj/item/specimen_tagger/proc/check_can_tag(var/mob/user, var/mob/living/target)
if(QDELETED(target) || !istype(target) || target.stat == DEAD || target.isSynthetic())
to_chat(user, SPAN_WARNING("Xenofauna specimens need to be living organic creatures."))
return FALSE
if(!LAZYLEN(target.get_catalogue_data()))
to_chat(user, SPAN_WARNING("There's no scientific reason to tag \the [target]."))
return FALSE
if(target.key || target.client || is_type_in_list(target, unsuitable_tag_types))
to_chat(user, SPAN_WARNING("\The [target] is not suitable for tagging."))
return FALSE
var/obj/item/gps/specimen_tag/xenotag = locate(/obj/item/gps/specimen_tag) in target
if(istype(xenotag) && xenotag.has_been_implanted())
to_chat(user, SPAN_WARNING("\The [target] has already been tagged."))
return FALSE
return TRUE
/obj/item/specimen_tagger/proc/try_implant_tag(var/mob/user, var/mob/living/target)
if(!check_can_tag(user, target))
return FALSE
user.visible_message(SPAN_NOTICE("\The [user] begins tagging \the [target] with \the [src]..."))
if(!do_after(user, 3 SECONDS, target) || !check_can_tag(user, target))
return FALSE
var/obj/item/gps/specimen_tag/xenotag = new
xenotag.SetTag(tag_id)
xenotag.implanted_by = user.real_name
if(user.mind)
var/user_title = user.mind.assigned_role || user.mind.role_alt_title
if(user_title)
xenotag.implanted_by = "[xenotag.implanted_by], [user_title]"
xenotag.implant(target)
user.visible_message(SPAN_NOTICE("\The [user] tags \the [target] with \a [xenotag]!"))
return TRUE
/obj/item/specimen_tagger/proc/can_remove_tag(var/mob/user, var/mob/living/target)
if(!istype(target))
to_chat(user, SPAN_WARNING("\The [target] is not a xenofauna specimen."))
return FALSE
var/obj/item/gps/specimen_tag/xenotag = locate() in target
if(!istype(xenotag) || !xenotag.has_been_implanted())
to_chat(user, SPAN_WARNING("\The [target] has not been tagged."))
return FALSE
return TRUE
/obj/item/specimen_tagger/proc/try_remove_tag(var/mob/user, var/mob/living/target)
if(!can_remove_tag(user, target))
return FALSE
var/obj/item/gps/specimen_tag/xenotag = locate() in target
if(!istype(xenotag))
return FALSE
user.visible_message(SPAN_NOTICE("\The [user] starts removing \the [xenotag] from \the [target] with \the [src]..."))
if(!do_after(user, 3 SECONDS, target) || !can_remove_tag(user, target))
return FALSE
if(!istype(xenotag))
return FALSE
qdel(xenotag)
user.visible_message(SPAN_NOTICE("\The [user] removes \the [xenotag] from \the [target]!"))
return TRUE
// Specimen tag itself.
/obj/item/gps/specimen_tag
name = "xenofauna tracker"
gps_tag = "FAUNA0"
icon = 'icons/obj/specimen_tag.dmi'
icon_state = "gps-tag"
w_class = ITEMSIZE_TINY
tag_category = "XENOFAUNA"
var/age = 0
var/mob/living/implanted_in
var/implanted_by
var/physical_info = "No notes recorded."
var/behavioral_info = "No notes recorded."
/obj/item/gps/specimen_tag/Initialize(mapload, _age, _implanted_by, _specimen_id, _specimen_gender, _physical_info, _behavioral_info, _specimen_type)
// If we have a specimen, set up our data.
if(_specimen_type)
var/mob/living/critter = new _specimen_type(get_turf(src))
implant(critter, TRUE)
if(_specimen_gender)
critter.gender = _specimen_gender
if(_age)
age = _age
if(_specimen_id)
gps_tag = _specimen_id
if(_physical_info)
physical_info = _physical_info
if(_behavioral_info)
behavioral_info = _behavioral_info
if(_implanted_by)
implanted_by = _implanted_by
. = ..()
if(!tracking)
toggletracking()
/obj/item/gps/specimen_tag/Destroy()
clear_implanted()
// TODO: persistent tags
// SSpersistence.forget_value(src, /datum/persistent/specimen)
. = ..()
/obj/item/gps/specimen_tag/Move()
. = ..()
if(implanted_in && loc != implanted_in)
clear_implanted()
// Specimen tags are just for tracking, they don't work as held GPS.
/obj/item/gps/specimen_tag/attack_hand(mob/living/user)
toggletracking(user)
return TRUE
/obj/item/gps/specimen_tag/check_visible_to_holder()
return FALSE
/obj/item/gps/specimen_tag/create_compass()
return
/obj/item/gps/specimen_tag/display_list()
return list()
/obj/item/gps/specimen_tag/proc/has_been_implanted()
return !QDELETED(implanted_in) && istype(implanted_in) && loc == implanted_in
/obj/item/gps/specimen_tag/proc/implant(var/mob/target, var/implanted_in_init = FALSE)
forceMove(target)
implanted_in = target
GLOB.destroyed_event.register(implanted_in, src, /obj/item/gps/specimen_tag/proc/clear_implanted)
// TODO: persistent tags
// SSpersistence.track_value(src, /datum/persistent/specimen)
if(!implanted_in_init)
generate_critter_info()
/obj/item/gps/specimen_tag/proc/generate_critter_info()
var/list/possible_physical_info
var/list/possible_behavioral_info
var/list/catalogue_data = implanted_in.get_catalogue_data()
for(var/catalogue_data_type in catalogue_data)
if(ispath(catalogue_data_type, /datum/category_item/catalogue/fauna))
var/datum/category_item/catalogue/fauna/fauna_data = GLOB.catalogue_data.resolve_item(catalogue_data_type)
var/notes = fauna_data.get_fauna_physical_notes()
if(!isnull(notes))
LAZYDISTINCTADD(possible_physical_info, notes)
notes = fauna_data.get_fauna_behavior_notes()
if(!isnull(notes))
LAZYDISTINCTADD(possible_behavioral_info, notes)
if(LAZYLEN(possible_physical_info))
physical_info = pick(possible_physical_info)
else
physical_info = "No notes recorded."
if(LAZYLEN(possible_behavioral_info))
behavioral_info = pick(possible_behavioral_info)
else
behavioral_info = "No notes recorded."
/obj/item/gps/specimen_tag/proc/clear_implanted()
if(implanted_in)
GLOB.destroyed_event.unregister(implanted_in, src)
implanted_in = null
/obj/item/gps/specimen_tag/proc/update_from_animal()
return
// Mob helpers/overrides.
/mob/living/examine(mob/user, infix, suffix)
. = ..()
var/obj/item/gps/specimen_tag/xenotag = locate() in src
if(istype(xenotag) && xenotag.has_been_implanted())
. += "\The [src] has been tagged with \a [xenotag]."
// Lists of strings used by the specimen tracking system to add flavour to
// specimens. Static lists in procs to allow overriding while also not putting
// a massive string list on every single cataloguer fauna entry.
/datum/category_item/catalogue/fauna/proc/get_fauna_physical_notes()
var/static/list/fauna_physical_notes = list(
"Perpetually smells like mold no matter what we do about it.",
"Perpetually smells like mildew no matter what we do about it.",
"Perpetually smells like sifsap no matter what we do about it.",
"Seems to always have an itch in the spot it can't reach.",
"Vocalizations are notably harsh and loud for the species.",
"Vocalizations are notably soft and quiet for the species.",
"Has a notch in its left ear.",
"Has a notch in its right ear.",
"Is missing its left ear.",
"Is missing its right ear.",
"Is missing its left eye.",
"Is missing its right eye.",
"Has had the very tip of its tail chewed off.",
"Might have trouble with its hearing.",
"Is more scar tissue than animal at this point.",
"Walks with a limp.",
"Has two differently colored eyes.",
"Might have a cold...",
"Is allergic to nuts.",
"Is allergic to berries.",
"Is allergic to dairy."
)
return fauna_physical_notes
/datum/category_item/catalogue/fauna/proc/get_fauna_behavior_notes()
var/static/list/fauna_behavior_notes = list(
"Likes rolling around in the moss with reckless abandon.",
"Enjoys munching on frostbelles the most, as a treat.",
"Enjoys munching on wabback the most, as a treat.",
"Enjoys munching on eyebulbs the most, as a treat.",
"Constantly sniffs around at everything new.",
"Seems super friendly! Probably won't bite. Probably.",
"Seems rather stand-offish. Mind the personal bubble.",
"Loves a good back scritch.",
"Loves a good head scritch.",
"Loves a good behind the ear scritch.",
"Seems to hate the world and everything in it.",
"Just tolerates being pet, but certainly doesn't enjoy it.",
"Often sticks its head into snowbanks to contemplate the state of things.",
"Enjoys singing along to songs only it can hear. Mostly just sounds like an animal wailing.",
"Would rather be fishing.",
"Partakes in many siestas.",
"Struggles with object permanence.",
"Is very picky about food; maybe it's a texture thing.",
"Is a sentient garbage disposal for anything even remotely edible.",
"Loves swimming and splashing around in water.",
"Sinks like a rock the moment it enters water.",
"Gets lost easily.",
"Enjoys soft things. It'd have a bed of plushies if it knew what a bed was.",
"Never seems to be in a rush to go anywhere...",
"Has gotta go fast at all times."
)
return fauna_behavior_notes

View File

@@ -59,6 +59,8 @@
var/move_delay = 1
var/fire_sound = null // This is handled by projectile.dm's fire_sound var now, but you can override the projectile's fire_sound with this one if you want to.
var/fire_sound_text = "gunshot"
var/fire_volume_silenced = 10
var/fire_volume = 50
var/fire_anim = null
var/recoil = 0 //screen shake
var/silenced = 0
@@ -648,9 +650,9 @@
return
if(silenced)
playsound(src, shot_sound, 10, 1)
playsound(src, shot_sound, fire_volume_silenced, 1)
else
playsound(src, shot_sound, 50, 1)
playsound(src, shot_sound, fire_volume, 1)
//Suicide handling.
/obj/item/gun/var/mouthshoot = 0 //To stop people from suiciding twice... >.>

View File

@@ -14,26 +14,30 @@
/obj/item/gun/energy/phasegun/tranq_rifle
name = "tranquilizer rifle"
desc = "A niche RayZar product designed for nonlethal animal control. A specialized emitter disrupts the nervous system of the target, eventually inducing sleep. Only rated for use on wildlife."
icon_state = "phaserifle"
item_state = "phaserifle"
wielded_item_state = "phaserifle-wielded"
icon_state = "tranqrifle"
item_state = "tranqrifle"
wielded_item_state = "tranqrifle-wielded"
w_class = ITEMSIZE_LARGE
slot_flags = SLOT_BACK
charge_cost = 140
projectile_type = /obj/item/projectile/energy/phase/tranq
accuracy = 15
one_handed_penalty = 30
fire_volume_silenced = 5
fire_volume = 15
/obj/item/gun/energy/phasegun/tranq_pistol
name = "tranquilizer pistol"
desc = "A niche RayZar product designed for nonlethal animal control. A specialized emitter disrupts the nervous system of the target, eventually inducing sleep. Only rated for use on wildlife."
icon_state = "phase"
item_state = "taser"
icon_state = "tranq"
item_state = "tranq"
w_class = ITEMSIZE_NORMAL
slot_flags = SLOT_BELT|SLOT_HOLSTER
charge_cost = 200
projectile_type = /obj/item/projectile/energy/phase/tranq/weak
one_handed_penalty = 0
fire_volume_silenced = 5
fire_volume = 15
/obj/item/gun/energy/phasegun/mounted
self_recharge = TRUE

View File

@@ -300,7 +300,9 @@
damage = 0
nodamage = TRUE
SA_bonus_damage = 0
icon_state = "flight"
hud_state = "laser_heat"
fire_sound = 'sound/weapons/dartgun.ogg'
var/tranq_duration = 30 SECONDS
var/tranq_delay = 10 SECONDS
var/tranq_delay_modifier = 0.7