Files
Bubberstation/code/modules/modular_computers/file_system/programs/virtual_pet.dm

623 lines
22 KiB
Plaintext

GLOBAL_LIST_EMPTY(global_pet_updates)
GLOBAL_LIST_EMPTY(virtual_pets_list)
#define MAX_UPDATE_LENGTH 50
#define PET_MAX_LEVEL 3
#define PET_MAX_STEPS_RECORD 50000
#define PET_EAT_BONUS 500
#define PET_CLEAN_BONUS 250
#define PET_PLAYMATE_BONUS 500
#define PET_STATE_HUNGRY "hungry"
#define PET_STATE_ASLEEP "asleep"
#define PET_STATE_HAPPY "happy"
#define PET_STATE_NEUTRAL "neutral"
/datum/computer_file/program/virtual_pet
filename = "virtualpet"
filedesc = "Virtual Pet"
downloader_category = PROGRAM_CATEGORY_GAMES
extended_desc = "Download your very own Orbie today!"
program_open_overlay = "generic"
program_flags = PROGRAM_ON_NTNET_STORE
size = 3
tgui_id = "NtosVirtualPet"
program_icon = "paw"
can_run_on_flags = PROGRAM_PDA
detomatix_resistance = DETOMATIX_RESIST_MALUS
///how many steps have we walked
var/steps_counter = 0
///the pet hologram
var/mob/living/pet
///the type of our pet
var/pet_type = /mob/living/basic/orbie
///our current happiness
var/happiness = 0
///our max happiness
var/max_happiness = 1750
///our current level
var/level = 1
///required exp to get to next level
var/to_next_level = 1000
///how much exp we currently have
var/current_level_progress = 0
///our current hunger
var/hunger = 0
///maximum hunger threshold
var/max_hunger = 500
///pet icon for each state
var/static/list/pet_state_icons = list(
PET_STATE_HUNGRY = list("icon" = 'icons/ui/virtualpet/pet_state.dmi', "icon_state" = "pet_hungry"),
PET_STATE_HAPPY = list("icon" = 'icons/ui/virtualpet/pet_state.dmi', "icon_state" = "pet_happy"),
PET_STATE_ASLEEP = list("icon" = 'icons/ui/virtualpet/pet_state.dmi', "icon_state" = "pet_asleep"),
PET_STATE_NEUTRAL = list("icon" = 'icons/ui/virtualpet/pet_state.dmi', "icon_state" = "pet_neutral"),
)
///hat options and what level they will be unlocked at
var/static/list/hat_selections = list(
/obj/item/clothing/head/hats/tophat = 1,
/obj/item/clothing/head/fedora = 1,
/obj/item/clothing/head/soft/fishing_hat = 1,
/obj/item/cigarette/dart = 1,
/obj/item/clothing/head/hats/bowler = 2,
/obj/item/clothing/head/hats/warden/police = 2,
/obj/item/clothing/head/wizard/tape = 2,
/obj/item/clothing/head/utility/hardhat/cakehat/energycake = 2,
/obj/item/clothing/head/cowboy/bounty = 2,
/obj/item/clothing/head/hats/warden/red = 3,
/obj/item/clothing/head/hats/caphat = 3,
/obj/item/clothing/head/costume/crown/fancy = 3,
)
///hat options that are locked behind achievements
var/static/list/cheevo_hats = list(
/obj/item/clothing/head/soft/fishing_hat = /datum/award/achievement/skill/legendary_fisher,
/obj/item/cigarette/dart = /datum/award/achievement/misc/cigarettes,
/obj/item/clothing/head/wizard/tape = /datum/award/achievement/misc/grand_ritual_finale,
/obj/item/clothing/head/utility/hardhat/cakehat/energycake = /datum/award/achievement/misc/cayenne_disk,
/obj/item/clothing/head/cowboy/bounty = /datum/award/achievement/misc/hot_damn,
/obj/item/clothing/head/costume/crown/fancy = /datum/award/achievement/misc/debt_extinguished,
)
///A list of hats that override the hat offsets and transform variable
var/static/list/special_hat_placement = list(
/obj/item/cigarette/dart = list(
"west" = list(2,-1),
"east" = list(-2,-1),
"north" = list(0,0),
"south" = list(0, -3),
"transform" = list(1, 1),
),
)
///hologram hat we have selected for our pet
var/list/selected_hat = list()
///manage hat offsets for when we turn directions
var/static/list/hat_offsets = list(
"west" = list(0,1),
"east" = list(0,1),
"north" = list(1,1),
"south" = list(1,1),
)
///area we have picked as dropoff location for petfeed
var/area/selected_area
///possible colors our pet can have
var/static/list/possible_colors= list(
"white" = null, //default color state
"light blue" = "#c3ecf3",
"light green" = "#b1ffe8",
)
///areas we wont drop the chocolate in
var/static/list/restricted_areas = typecacheof(list(
/area/station/security,
/area/station/command,
/area/station/ai_monitored,
/area/station/maintenance,
/area/station/solars,
))
///our profile picture
var/icon/profile_picture
///cooldown till we can reroll the pet feed dropzone
COOLDOWN_DECLARE(area_reroll)
///cooldown till our pet gains happiness again from being cleaned
COOLDOWN_DECLARE(on_clean_cooldown)
///cooldown till we can release/recall our pet
COOLDOWN_DECLARE(summon_cooldown)
///cooldown till we can alter our pet's appearance again
COOLDOWN_DECLARE(alter_appearance_cooldown)
/datum/computer_file/program/virtual_pet/on_install()
. = ..()
profile_picture = getFlatIcon(image(icon = 'icons/ui/virtualpet/pet_state.dmi', icon_state = "pet_preview"))
GLOB.virtual_pets_list += src
pet = new pet_type(computer)
pet.forceMove(computer)
pet.AddComponent(/datum/component/leash, computer, 9, force_teleport_out_effect = /obj/effect/temp_visual/guardian/phase/out)
RegisterSignal(pet, COMSIG_QDELETING, PROC_REF(remove_pet))
RegisterSignal(pet, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_overlays_updated)) //hologramic hat management
RegisterSignal(pet, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_change_dir))
RegisterSignal(pet, COMSIG_MOVABLE_MOVED, PROC_REF(after_pet_move))
RegisterSignal(pet, COMSIG_MOB_ATE, PROC_REF(after_pet_eat)) // WE ATEEE
RegisterSignal(pet, COMSIG_ATOM_PRE_CLEAN, PROC_REF(pet_pre_clean))
RegisterSignal(pet, COMSIG_LIVING_DEATH, PROC_REF(on_death))
RegisterSignal(pet, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(post_cleaned))
RegisterSignal(pet, COMSIG_AI_BLACKBOARD_KEY_SET(BB_NEARBY_PLAYMATE), PROC_REF(on_playmate_find))
RegisterSignal(computer, COMSIG_ATOM_ENTERED, PROC_REF(on_pet_entered))
RegisterSignal(computer, COMSIG_ATOM_EXITED, PROC_REF(on_pet_exit))
/datum/computer_file/program/virtual_pet/Destroy()
GLOB.virtual_pets_list -= src
if(!QDELETED(pet))
QDEL_NULL(pet)
STOP_PROCESSING(SSprocessing, src)
return ..()
/datum/computer_file/program/virtual_pet/proc/on_death(datum/source)
SIGNAL_HANDLER
pet.forceMove(computer)
/datum/computer_file/program/virtual_pet/proc/on_message_receive(datum/source, sender_title, inbound_message, photo_message)
SIGNAL_HANDLER
var/message_to_display = "[sender_title] has sent you a message [photo_message ? "with a photo attached" : ""]: [inbound_message]!"
pet.ai_controller?.set_blackboard_key(BB_LAST_RECEIVED_MESSAGE, message_to_display)
/datum/computer_file/program/virtual_pet/proc/pet_pre_clean(atom/source, mob/user)
SIGNAL_HANDLER
if(!COOLDOWN_FINISHED(src, on_clean_cooldown))
source.balloon_alert(user, "already clean!")
return COMSIG_ATOM_CANCEL_CLEAN
/datum/computer_file/program/virtual_pet/proc/on_playmate_find(datum/source)
SIGNAL_HANDLER
happiness = min(happiness + PET_PLAYMATE_BONUS, max_happiness)
START_PROCESSING(SSprocessing, src)
/datum/computer_file/program/virtual_pet/proc/post_cleaned(mob/source, mob/user)
SIGNAL_HANDLER
. = NONE
source.spin(spintime = 2 SECONDS, speed = 1) //celebrate!
happiness = min(happiness + PET_CLEAN_BONUS, max_happiness)
COOLDOWN_START(src, on_clean_cooldown, 1 MINUTES)
START_PROCESSING(SSprocessing, src)
. |= COMPONENT_CLEANED|COMPONENT_CLEANED_GAIN_XP
///manage the pet's hat offsets when he changes direction
/datum/computer_file/program/virtual_pet/proc/on_change_dir(datum/source, old_dir, new_dir)
SIGNAL_HANDLER
if(!length(selected_hat))
return
set_hat_offsets(new_dir)
/datum/computer_file/program/virtual_pet/proc/on_photo_captured(datum/source, atom/target, atom/user, datum/picture/photo)
SIGNAL_HANDLER
if(isnull(photo))
return
computer.store_file(new /datum/computer_file/picture(photo))
/datum/computer_file/program/virtual_pet/proc/set_hat_offsets(new_dir)
var/direction_text = dir2text(new_dir)
var/hat_type = selected_hat["type"]
var/list/offsets_list = special_hat_placement[hat_type]?[direction_text] || hat_offsets[direction_text]
var/mutable_appearance/hat_appearance = selected_hat["appearance"]
hat_appearance.pixel_w = offsets_list[1]
hat_appearance.pixel_z = offsets_list[2] + selected_hat["worn_offset"]
pet.update_appearance(UPDATE_OVERLAYS)
///give our pet his hologram hat
/datum/computer_file/program/virtual_pet/proc/on_overlays_updated(atom/source, list/overlays)
SIGNAL_HANDLER
if(!length(selected_hat))
return
overlays += selected_hat["appearance"]
/datum/computer_file/program/virtual_pet/proc/alter_profile_picture()
var/image/pet_preview = image(icon = 'icons/ui/virtualpet/pet_state.dmi', icon_state = "pet_preview")
if(pet.cached_color_filter)
pet_preview.color = apply_matrix_to_color(COLOR_WHITE, pet.cached_color_filter["color"], pet.cached_color_filter["space"] || COLORSPACE_RGB)
else if (pet.color)
pet_preview.color = pet.color
if(length(selected_hat))
var/mutable_appearance/our_selected_hat = selected_hat["appearance"]
var/mutable_appearance/hat_preview = mutable_appearance(our_selected_hat.icon, our_selected_hat.icon_state, appearance_flags = RESET_COLOR|KEEP_APART)
hat_preview.pixel_z = -9 + selected_hat["worn_offset"]
var/list/spec_hat = special_hat_placement[selected_hat["type"]]?["south"]
if(spec_hat)
hat_preview.pixel_w += spec_hat[1]
hat_preview.pixel_z += spec_hat[2]
pet_preview.add_overlay(hat_preview)
profile_picture = getFlatIcon(pet_preview, no_anim = TRUE)
COOLDOWN_START(src, alter_appearance_cooldown, 10 SECONDS)
///decrease the pet's hunger after it eats
/datum/computer_file/program/virtual_pet/proc/after_pet_eat(datum/source)
SIGNAL_HANDLER
hunger = min(hunger + PET_EAT_BONUS, max_hunger)
happiness = min(happiness + PET_EAT_BONUS, max_happiness)
START_PROCESSING(SSprocessing, src)
///start processing if we enter the pda and need healing
/datum/computer_file/program/virtual_pet/proc/on_pet_entered(atom/movable/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
if(arrived != pet)
return
ADD_TRAIT(pet, TRAIT_AI_PAUSED, REF(src))
if((datum_flags & DF_ISPROCESSING))
return
if(pet.health < pet.maxHealth) //if we're in the pda, heal up
START_PROCESSING(SSprocessing, src)
/datum/computer_file/program/virtual_pet/proc/on_pet_exit(atom/movable/source, atom/movable/exited)
SIGNAL_HANDLER
if(exited != pet)
return
REMOVE_TRAIT(pet, TRAIT_AI_PAUSED, REF(src))
if((datum_flags & DF_ISPROCESSING))
return
if(hunger > 0 || happiness > 0) //if were outside the pda, we become hungry and happiness decreases
START_PROCESSING(SSprocessing, src)
/datum/computer_file/program/virtual_pet/process()
if(pet.loc == computer)
if(pet.health >= pet.maxHealth)
return PROCESS_KILL
if(pet.stat == DEAD)
pet.revive(ADMIN_HEAL_ALL)
pet.heal_overall_damage(5)
return
if(hunger > 0)
hunger--
if(happiness > 0)
happiness--
if(hunger <=0 && happiness <= 0)
return PROCESS_KILL
/datum/computer_file/program/virtual_pet/proc/after_pet_move(atom/movable/movable, atom/old_loc)
SIGNAL_HANDLER
if(!isturf(pet.loc) || !isturf(old_loc))
return
steps_counter = min(steps_counter + 1, PET_MAX_STEPS_RECORD)
increment_exp()
if(steps_counter % 2000 == 0) //every 2000 steps, announce the milestone to the world!
announce_global_updates(message = "has walked [steps_counter] steps!")
/datum/computer_file/program/virtual_pet/proc/increment_exp()
var/modifier = 1
var/hunger_happiness = hunger + happiness
var/max_hunger_happiness = max_hunger + max_happiness
switch(hunger_happiness / max_hunger_happiness)
if(0.8 to 1)
modifier = 3
if(0.5 to 0.8)
modifier = 2
current_level_progress = min(current_level_progress + modifier, to_next_level)
if(current_level_progress >= to_next_level)
handle_level_up()
/datum/computer_file/program/virtual_pet/proc/handle_level_up()
current_level_progress = 0
level++
grant_level_abilities()
pet.ai_controller?.set_blackboard_key(BB_VIRTUAL_PET_LEVEL, level)
playsound(computer.loc, 'sound/mobs/non-humanoids/orbie/orbie_level_up.ogg', 50)
to_next_level += (level**2) + 500
SEND_SIGNAL(pet, COMSIG_VIRTUAL_PET_LEVEL_UP, level) //its a signal so different path types of virtual pets can handle leveling up differently
announce_global_updates(message = "has reached level [level]!")
/datum/computer_file/program/virtual_pet/proc/grant_level_abilities()
switch(level)
if(2)
RegisterSignal(computer, COMSIG_COMPUTER_RECEIVED_MESSAGE, PROC_REF(on_message_receive)) // we will now read out PDA messages
var/datum/action/cooldown/mob_cooldown/lights/lights = new(pet)
lights.Grant(pet)
pet.ai_controller?.set_blackboard_key(BB_LIGHTS_ABILITY, lights)
if(3)
var/datum/action/cooldown/mob_cooldown/capture_photo/photo_ability = new(pet)
photo_ability.Grant(pet)
pet.ai_controller?.set_blackboard_key(BB_PHOTO_ABILITY, photo_ability)
RegisterSignal(photo_ability.ability_camera, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_photo_captured))
/datum/computer_file/program/virtual_pet/proc/announce_global_updates(message)
if(isnull(message))
return
var/list/message_to_announce = list(
"name" = pet.name,
"pet_picture" = icon2base64(profile_picture),
"message" = message,
"likers" = list(REF(src))
)
if(length(GLOB.global_pet_updates) >= MAX_UPDATE_LENGTH)
GLOB.global_pet_updates.Cut(1,2)
GLOB.global_pet_updates += list(message_to_announce)
playsound(computer.loc, 'sound/mobs/non-humanoids/orbie/orbie_notification_sound.ogg', 50)
/datum/computer_file/program/virtual_pet/proc/remove_pet(datum/source)
SIGNAL_HANDLER
pet = null
if(QDELETED(src))
return
computer.remove_file(src) //all is lost we no longer have a reason to exist
/datum/computer_file/program/virtual_pet/kill_program(mob/user)
if(pet && pet.loc != computer)
pet.forceMove(computer) //recall the hologram back to the pda
STOP_PROCESSING(SSprocessing, src)
return ..()
/datum/computer_file/program/virtual_pet/proc/get_pet_state()
if(isnull(pet))
return
if(pet.loc == computer)
return PET_STATE_ASLEEP
if(happiness/max_happiness > 0.8)
return PET_STATE_HAPPY
if(hunger/max_hunger < 0.5)
return PET_STATE_HUNGRY
return PET_STATE_NEUTRAL
/datum/computer_file/program/virtual_pet/ui_data(mob/user)
var/list/data = list()
var/obj/item/hat_type = selected_hat?["type"]
data["currently_summoned"] = (pet.loc != computer)
data["selected_area"] = (selected_area ? selected_area.name : "No location set")
data["pet_state"] = get_pet_state()
data["hunger"] = hunger
data["maximum_hunger"] = max_hunger
data["pet_hat"] = (hat_type ? initial(hat_type.name) : "none")
data["can_reroll"] = COOLDOWN_FINISHED(src, area_reroll)
data["can_summon"] = COOLDOWN_FINISHED(src, summon_cooldown)
data["can_alter_appearance"] = COOLDOWN_FINISHED(src, alter_appearance_cooldown)
data["pet_name"] = pet.name
data["steps_counter"] = steps_counter
data["in_dropzone"] = (istype(get_area(computer), selected_area))
data["pet_area"] = (pet.loc != computer ? get_area_name(pet) : "Sleeping in PDA")
data["current_exp"] = current_level_progress
data["required_exp"] = to_next_level
data["happiness"] = happiness
data["maximum_happiness"] = max_happiness
data["level"] = level
data["pet_color"] = ""
var/color_value = LAZYACCESS(pet.atom_colours, FIXED_COLOUR_PRIORITY)
for(var/index in possible_colors)
if(possible_colors[index] == color_value)
data["pet_color"] = index
break
data["pet_gender"] = pet.gender
data["pet_updates"] = list()
for(var/i in length(GLOB.global_pet_updates) to 1 step -1)
var/list/update = GLOB.global_pet_updates[i]
if(isnull(update))
continue
data["pet_updates"] += list(list(
"update_id" = i,
"update_name" = update["name"],
"update_picture" = update["pet_picture"],
"update_message" = update["message"],
"update_likers" = length(update["likers"]),
"update_already_liked" = ((REF(src)) in update["likers"]),
))
data["all_pets"] = list()
for(var/datum/computer_file/program/virtual_pet/program as anything in GLOB.virtual_pets_list)
data["all_pets"] += list(list(
"other_pet_name" = program.pet.name,
"other_pet_picture" = icon2base64(program.profile_picture),
))
return data
/datum/computer_file/program/virtual_pet/ui_static_data(mob/user)
var/list/data = list()
data["pet_state_icons"] = list()
for(var/list_index in pet_state_icons)
var/list/sprite_location = pet_state_icons[list_index]
data["pet_state_icons"] += list(list(
"name" = list_index,
"icon" = icon2base64(getFlatIcon(image(icon = sprite_location["icon"], icon_state = sprite_location["icon_state"]), no_anim=TRUE))
))
data["hat_selections"] = list(list(
"hat_id" = null,
"hat_name" = "none",
))
for(var/type_index in hat_selections)
if(level >= hat_selections[type_index])
var/obj/item/hat = type_index
var/hat_name = initial(hat.name)
if(length(SSachievements.achievements)) // The Achievements subsystem is active.
var/datum/award/required_cheevo = cheevo_hats[hat]
if(required_cheevo && !user.client.get_award_status(required_cheevo))
hat_name = "LOCKED"
data["hat_selections"] += list(list(
"hat_id" = type_index,
"hat_name" = hat_name,
))
data["possible_colors"] = list()
for(var/color in possible_colors)
data["possible_colors"] += list(list(
"color_name" = color,
"color_value" = possible_colors[color],
))
var/static/list/possible_emotes = list(
/datum/emote/flip,
/datum/emote/jump,
/datum/emote/living/shiver,
/datum/emote/spin,
/datum/emote/silicon/beep,
)
data["possible_emotes"] = list("none")
for(var/datum/emote/target_emote as anything in possible_emotes)
data["possible_emotes"] += target_emote.key
data["preview_icon"] = icon2base64(profile_picture)
return data
/datum/computer_file/program/virtual_pet/ui_act(action, params, datum/tgui/ui)
. = ..()
switch(action)
if("summon_pet")
if(!COOLDOWN_FINISHED(src, summon_cooldown))
return TRUE
if(pet.loc == computer)
release_pet(ui.user)
else
recall_pet(ui.user)
COOLDOWN_START(src, summon_cooldown, 10 SECONDS)
if("apply_customization")
if(!COOLDOWN_FINISHED(src, alter_appearance_cooldown))
return TRUE
var/obj/item/chosen_type = text2path(params["chosen_hat"])
if(isnull(chosen_type))
selected_hat.Cut()
else if(hat_selections[chosen_type])
var/datum/award/required_cheevo = cheevo_hats[chosen_type]
if(length(SSachievements.achievements) && required_cheevo && !ui.user.client.get_award_status(required_cheevo))
to_chat(ui.user, span_info("This customization requires the \"[span_bold(initial(required_cheevo.name))]\ achievement to be unlocked."))
else
selected_hat["type"] = chosen_type
var/state_to_use = initial(chosen_type.worn_icon_state) || initial(chosen_type.icon_state)
var/mutable_appearance/selected_hat_appearance = mutable_appearance(initial(chosen_type.worn_icon), state_to_use, appearance_flags = RESET_COLOR|KEEP_APART)
selected_hat["worn_offset"] = initial(chosen_type.worn_y_offset)
var/list/scale_list = special_hat_placement[chosen_type]?["scale"]
if(scale_list)
selected_hat_appearance.transform = selected_hat_appearance.transform.Scale(scale_list[1], scale_list[2])
else
selected_hat_appearance.transform = selected_hat_appearance.transform.Scale(0.8, 1)
selected_hat["appearance"] = selected_hat_appearance
set_hat_offsets(pet.dir)
var/chosen_color = params["chosen_color"]
if(isnull(chosen_color))
pet.remove_atom_colour(FIXED_COLOUR_PRIORITY)
else
pet.add_atom_colour(chosen_color, FIXED_COLOUR_PRIORITY)
var/input_name = sanitize_name(params["chosen_name"], allow_numbers = TRUE)
pet.name = (input_name ? input_name : initial(pet.name))
new /obj/effect/temp_visual/guardian/phase(pet.loc)
switch(params["chosen_gender"])
if("male")
pet.gender = MALE
if("female")
pet.gender = FEMALE
if("neuter")
pet.gender = NEUTER
pet.update_appearance()
alter_profile_picture()
update_static_data(ui.user, ui)
if("get_feed_location")
generate_petfeed_area()
if("drop_feed")
drop_feed()
if("like_update")
var/index = params["update_reference"]
var/list/update_message = GLOB.global_pet_updates[index]
if(isnull(update_message))
return TRUE
var/our_reference = REF(src)
if(our_reference in update_message["likers"])
update_message["likers"] -= our_reference
else
update_message["likers"] += our_reference
if("teach_tricks")
var/trick_name = params["trick_name"]
var/list/trick_sequence = params["tricks"]
if(isnull(pet.ai_controller))
return TRUE
if(!isnull(trick_name))
pet.ai_controller.set_blackboard_key(BB_TRICK_NAME, trick_name)
for (var/trick_move in trick_sequence)
if (!length(GLOB.emote_list[LOWER_TEXT(trick_move)]))
trick_sequence -= trick_move
pet.ai_controller.override_blackboard_key(BB_TRICK_SEQUENCE, trick_sequence)
playsound(computer.loc, 'sound/mobs/non-humanoids/orbie/orbie_trick_learned.ogg', 50)
return TRUE
/datum/computer_file/program/virtual_pet/proc/generate_petfeed_area()
if(!COOLDOWN_FINISHED(src, area_reroll))
return
var/list/filter_area_list = typecache_filter_list(GLOB.the_station_areas, restricted_areas)
var/list/target_area_list = GLOB.the_station_areas.Copy() - filter_area_list
if(!length(target_area_list))
return
selected_area = pick(target_area_list)
COOLDOWN_START(src, area_reroll, 2 MINUTES)
/datum/computer_file/program/virtual_pet/proc/drop_feed()
if(!istype(get_area(computer), selected_area))
return
announce_global_updates(message = "has found a chocolate at [selected_area.name]")
selected_area = null
var/obj/item/food/virtual_chocolate/chocolate = new(get_turf(computer))
chocolate.fade_into_nothing(life_time = 30 SECONDS) //we cant maintain its existence for too long!
/datum/computer_file/program/virtual_pet/proc/recall_pet(mob/living/friend)
animate(pet, transform = matrix().Scale(0.3, 0.3), time = 1.5 SECONDS)
addtimer(CALLBACK(pet, TYPE_PROC_REF(/atom/movable, forceMove), computer), 1.5 SECONDS)
SEND_SIGNAL(pet, COMSIG_VIRTUAL_PET_RECALLED, friend)
/datum/computer_file/program/virtual_pet/proc/release_pet(mob/living/our_user)
var/turf/drop_zone
var/list/turfs_list = get_adjacent_open_turfs(computer.drop_location())
for(var/turf/possible_turf as anything in turfs_list)
if(possible_turf.is_blocked_turf())
continue
drop_zone = possible_turf
break
var/turf/final_turf = isnull(drop_zone) ? computer.drop_location() : drop_zone
pet.befriend(our_user) //befriend whoever set us out
animate(pet, transform = matrix(), time = 1.5 SECONDS)
pet.forceMove(final_turf)
playsound(computer.loc, 'sound/mobs/non-humanoids/orbie/orbie_send_out.ogg', 20)
SEND_SIGNAL(pet, COMSIG_VIRTUAL_PET_SUMMONED, our_user)
new /obj/effect/temp_visual/guardian/phase(pet.loc)
#undef PET_MAX_LEVEL
#undef PET_MAX_STEPS_RECORD
#undef PET_EAT_BONUS
#undef PET_CLEAN_BONUS
#undef PET_PLAYMATE_BONUS
#undef PET_STATE_HUNGRY
#undef PET_STATE_ASLEEP
#undef PET_STATE_HAPPY
#undef PET_STATE_NEUTRAL
#undef MAX_UPDATE_LENGTH