#define pet_carrier_full(carrier) carrier.occupants.len >= carrier.max_occupants || carrier.occupant_weight >= carrier.max_occupant_weight
//Used to transport little animals without having to drag them across the station.
//Comes with a handy lock to prevent them from running off.
/obj/item/pet_carrier
name = "pet carrier"
desc = "A big white-and-blue pet carrier. Good for carrying meat to the chef cute animals around."
icon = 'icons/obj/pet_carrier.dmi'
icon_state = "pet_carrier_open"
item_state = "pet_carrier"
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
force = 5
attack_verb = list("bashed", "carried")
w_class = WEIGHT_CLASS_BULKY
throw_speed = 2
throw_range = 3
custom_materials = list(/datum/material/iron = 7500, /datum/material/glass = 100)
var/open = TRUE
var/locked = FALSE
var/list/occupants = list()
var/occupant_weight = 0
var/max_occupants = 3 //Hard-cap so you can't have infinite mice or something in one carrier
var/max_occupant_weight = MOB_SIZE_SMALL //This is calculated from the mob sizes of occupants
var/entrance_name = "door" //name of the entrance to the item
var/escape_time = 200 //how long it takes for mobs above small sizes to escape (for small sizes, its randomly 1.5 to 2x this)
var/load_time = 30 //how long it takes for mobs to be loaded into the pet carrier
var/has_lock_sprites = TRUE //whether to load the lock overlays or not
var/allows_hostiles = FALSE //does the pet carrier allow hostile entities to be held within it?
/obj/item/pet_carrier/Destroy()
if(occupants.len)
for(var/V in occupants)
remove_occupant(V)
return ..()
/obj/item/pet_carrier/Exited(atom/movable/occupant)
if(occupant in occupants && isliving(occupant))
var/mob/living/L = occupant
occupants -= occupant
occupant_weight -= L.mob_size
/obj/item/pet_carrier/handle_atom_del(atom/A)
if(A in occupants && isliving(A))
var/mob/living/L = A
occupants -= L
occupant_weight -= L.mob_size
..()
/obj/item/pet_carrier/examine(mob/user)
. = ..()
if(occupants.len)
for(var/V in occupants)
var/mob/living/L = V
. += "It has [L] inside."
else
. += "It has nothing inside."
if(user.canUseTopic(src))
. += "Activate it in your hand to [open ? "close" : "open"] its [entrance_name]."
if(!open)
. += "Alt-click to [locked ? "unlock" : "lock"] its [entrance_name]."
/obj/item/pet_carrier/attack_self(mob/living/user)
if(open)
to_chat(user, "You close [src]'s [entrance_name].")
playsound(user, 'sound/effects/bin_close.ogg', 50, TRUE)
open = FALSE
else
if(locked)
to_chat(user, "[src] is locked!")
return
to_chat(user, "You open [src]'s [entrance_name].")
playsound(user, 'sound/effects/bin_open.ogg', 50, TRUE)
open = TRUE
update_icon()
/obj/item/pet_carrier/AltClick(mob/living/user)
. = ..()
if(open || !user.canUseTopic(src, BE_CLOSE))
return
locked = !locked
to_chat(user, "You flip the lock switch [locked ? "down" : "up"].")
if(locked)
playsound(user, 'sound/machines/boltsdown.ogg', 30, TRUE)
else
playsound(user, 'sound/machines/boltsup.ogg', 30, TRUE)
update_icon()
return TRUE
/obj/item/pet_carrier/attack(mob/living/target, mob/living/user)
if(user.a_intent == INTENT_HARM)
return ..()
if(!open)
to_chat(user, "You need to open [src]'s [entrance_name]!")
return
if(target.mob_size > max_occupant_weight)
if(ishuman(target))
var/mob/living/carbon/human/H = target
if(iscatperson(H))
to_chat(user, "You'd need a lot of catnip and treats, plus maybe a laser pointer, for that to work.")
else
to_chat(user, "Humans, generally, do not fit into [name]s.")
else
to_chat(user, "You get the feeling [target] isn't meant for a [name].")
return
if(user == target)
to_chat(user, "Why would you ever do that?")
return
if(ishostile(target) && !allows_hostiles && target.move_resist < MOVE_FORCE_VERY_STRONG) //don't allow goliaths into pet carriers
to_chat(user, "You have a feeling you shouldn't keep this as a pet.")
load_occupant(user, target)
/obj/item/pet_carrier/relaymove(mob/living/user, direction)
if(open)
loc.visible_message("[user] climbs out of [src]!", \
"[user] jumps out of [src]!")
remove_occupant(user)
return
else if(!locked)
loc.visible_message("[user] pushes open the [entrance_name] to [src]!", \
"[user] pushes open the [entrance_name] of [src]!")
open = TRUE
update_icon()
return
else if(user.client)
container_resist(user)
/obj/item/pet_carrier/container_resist(mob/living/user)
//don't do the whole resist timer thing if it's open!
if(open)
loc.visible_message("[user] climbs out of [src]!", \
"[user] jumps out of [src]!")
remove_occupant(user)
return
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
if(user.mob_size <= MOB_SIZE_SMALL)
to_chat(user, "You begin to try escaping the [src] and start fumbling for the lock switch... (This will take some time.)")
to_chat(loc, "You see [user] attempting to unlock the [src]!")
if(!do_after(user, rand(escape_time * 1.5, escape_time * 2), target = user) || open || !locked || !(user in occupants))
return
loc.visible_message("[user] flips the lock switch on [src] by reaching through!", null, null, null, user)
to_chat(user, "Bingo! The lock pops open!")
locked = FALSE
playsound(src, 'sound/machines/boltsup.ogg', 30, TRUE)
update_icon()
else
loc.visible_message("[src] starts rattling as something pushes against the [entrance_name]!", null, null, null, user)
to_chat(user, "You start pushing out of [src]... (This will take about 20 seconds.)")
if(!do_after(user, escape_time, target = user) || open || !locked || !(user in occupants))
return
loc.visible_message("[user] shoves out of [src]!", null, null, null, user)
to_chat(user, "You shove open [src]'s [entrance_name] against the lock's resistance and fall out!")
locked = FALSE
open = TRUE
update_icon()
remove_occupant(user)
/obj/item/pet_carrier/update_icon_state()
if(open)
icon_state = initial(icon_state)
else
icon_state = "pet_carrier_[!occupants.len ? "closed" : "occupied"]"
/obj/item/pet_carrier/update_overlays()
. = ..()
if(!open && has_lock_sprites)
. += "[locked ? "" : "un"]locked"
/obj/item/pet_carrier/MouseDrop(atom/over_atom)
if(isopenturf(over_atom) && usr.canUseTopic(src, BE_CLOSE, ismonkey(usr)) && usr.Adjacent(over_atom) && open && occupants.len)
usr.visible_message("[usr] unloads [src].", \
"You unload [src] onto [over_atom].")
for(var/V in occupants)
remove_occupant(V, over_atom)
else
return ..()
/obj/item/pet_carrier/proc/load_occupant(mob/living/user, mob/living/target)
if(pet_carrier_full(src))
to_chat(user, "[src] is already carrying too much!")
return
user.visible_message("[user] starts loading [target] into [src].", \
"You start loading [target] into [src]...", null, null, target)
to_chat(target, "[user] starts loading you into [user.p_their()] [name]!")
if(!do_mob(user, target, load_time))
return
if(target in occupants)
return
if(pet_carrier_full(src)) //Run the checks again, just in case
to_chat(user, "[src] is already carrying too much!")
return
user.visible_message("[user] loads [target] into [src]!", \
"You load [target] into [src].", null, null, target)
to_chat(target, "[user] loads you into [user.p_their()] [name]!")
add_occupant(target)
/obj/item/pet_carrier/proc/add_occupant(mob/living/occupant)
if(occupant in occupants || !istype(occupant))
return
occupant.forceMove(src)
occupants += occupant
occupant_weight += occupant.mob_size
/obj/item/pet_carrier/proc/remove_occupant(mob/living/occupant, turf/new_turf)
if(!(occupant in occupants) || !istype(occupant))
return
occupant.forceMove(new_turf ? new_turf : get_turf(src))
occupants -= occupant
occupant_weight -= occupant.mob_size
occupant.setDir(SOUTH)
//bluespace jar, a reskin of the pet carrier that can fit people and smashes when thrown
/obj/item/pet_carrier/bluespace
name = "bluespace jar"
desc = "A jar, that seems to be bigger on the inside, somehow allowing lifeforms to fit through its narrow entrance."
open = FALSE //starts closed so it looks better on menus
icon_state = "bluespace_jar"
item_state = "bluespace_jar"
lefthand_file = ""
righthand_file = ""
max_occupant_weight = MOB_SIZE_HUMAN //can fit people, like a bluespace bodybag!
load_time = 40 //loading things into a jar takes longer than a regular pet carrier
entrance_name = "lid"
w_class = WEIGHT_CLASS_SMALL //it's a jar
throw_speed = 3
throw_range = 7
max_occupants = 1 //far less than a regular carrier or bluespace bodybag, because it can be thrown to release the contents
allows_hostiles = TRUE //can fit hostile creatures, with the move resist restrictions in place, this means they still cannot take things like legions/goliaths/etc regardless
has_lock_sprites = FALSE //jar doesn't show the regular lock overlay
custom_materials = list(/datum/material/glass = 1000, /datum/material/bluespace = 600)
escape_time = 10 //half the time of a bluespace bodybag
var/datum/gas_mixture/occupant_gas_supply
/obj/item/pet_carrier/bluespace/update_icon_state()
if(open)
icon_state = "bluespace_jar_open"
else
icon_state = "bluespace_jar"
/obj/item/pet_carrier/bluespace/throw_impact()
. = ..()
//delete the item upon impact, releasing the creature inside (this is handled by its deletion)
if(occupants.len)
loc.visible_message("The bluespace jar smashes, releasing [occupants[1]]!")
playsound(src, "shatter", 70, 1)
qdel(src)
/obj/item/pet_carrier/bluespace/add_occupant(mob/living/occupant) //update the gas supply as required, this acts like magical internals
. = ..()
if(!occupant_gas_supply)
occupant_gas_supply = new
if(isanimal(occupant))
var/mob/living/simple_animal/animal = occupant
occupant_gas_supply.set_temperature(animal.minbodytemp) //simple animals only care about temperature when their turf isnt a location
else
if(ishuman(occupant)) //humans require resistance to cold/heat and living in no air while inside, and lose this when outside
ADD_TRAIT(occupant, TRAIT_RESISTCOLD, "bluespace_container_cold_resist")
ADD_TRAIT(occupant, TRAIT_RESISTHEAT, "bluespace_container_heat_resist")
ADD_TRAIT(occupant, TRAIT_NOBREATH, "bluespace_container_no_breath")
ADD_TRAIT(occupant, TRAIT_RESISTHIGHPRESSURE, "bluespace_container_resist_high_pressure")
ADD_TRAIT(occupant, TRAIT_RESISTLOWPRESSURE, "bluespace_container_resist_low_pressure")
/obj/item/pet_carrier/bluespace/remove_occupant(mob/living/occupant)
. = ..()
if(ishuman(occupant))
REMOVE_TRAIT(occupant, TRAIT_RESISTCOLD, "bluespace_container_cold_resist")
REMOVE_TRAIT(occupant, TRAIT_RESISTHEAT, "bluespace_container_heat_resist")
REMOVE_TRAIT(occupant, TRAIT_NOBREATH, "bluespace_container_no_breath")
REMOVE_TRAIT(occupant, TRAIT_RESISTHIGHPRESSURE, "bluespace_container_resist_high_pressure")
REMOVE_TRAIT(occupant, TRAIT_RESISTLOWPRESSURE, "bluespace_container_resist_low_pressure")
/obj/item/pet_carrier/bluespace/return_air()
if(!occupant_gas_supply)
occupant_gas_supply = new
return occupant_gas_supply
#undef pet_carrier_full