Files
CHOMPStation2/code/game/objects/items/weapons/capture_crystal.dm
CHOMPStation2StaffMirrorBot a967fb3861 [MIRROR] Check for 516 byond:// hrefs (#9624)
Co-authored-by: ShadowLarkens <shadowlarkens@gmail.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
2024-12-13 00:55:48 +01:00

870 lines
40 KiB
Plaintext

/obj/item/capture_crystal
name = "capture crystal"
desc = "A silent, unassuming crystal in what appears to be some kind of steel housing."
icon = 'icons/obj/capture_crystal_vr.dmi'
icon_state = "inactive"
drop_sound = 'sound/items/drop/ring.ogg'
pickup_sound = 'sound/items/pickup/ring.ogg'
throwforce = 0
force = 0
actions_types = list(/datum/action/item_action/command)
w_class = ITEMSIZE_SMALL //CHOMPEdit
var/active = FALSE //Is it set up?
var/mob/living/owner //Reference to the owner
var/mob/living/bound_mob //Reference to our bound mob
var/spawn_mob_type //The kind of mob an inactive crystal will try to spawn when activated
var/activate_cooldown = 30 SECONDS //How long do we wait between unleashing and recalling
var/last_activate //Automatically set by things that try to move the bound mob or capture things
var/empty_icon = "empty"
var/full_icon = "full"
var/capture_chance_modifier = 1 //So we can have special subtypes with different capture rates!
/obj/item/capture_crystal/Initialize()
. = ..()
update_icon()
//Let's make sure we clean up our references and things if the crystal goes away (such as when it's digested)
/obj/item/capture_crystal/Destroy()
if(bound_mob)
if(bound_mob in contents)
unleash()
to_chat(bound_mob, span_notice("You feel like yourself again. You are no longer under the influence of \the [src]'s command."))
UnregisterSignal(bound_mob, COMSIG_PARENT_QDELETING)
bound_mob.capture_caught = FALSE
bound_mob = null
if(owner)
UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
owner = null
if(in_gut) // CHOMPedit start
UnregisterSignal(in_gut, COMSIG_PARENT_QDELETING)
UnregisterSignal(in_gut, COMSIG_BELLY_UPDATE_VORE_FX)
UnregisterSignal(in_gut, COMSIG_BELLY_UPDATE_PREY_LOOP)
in_gut = null // CHOMPedit end
return ..()
/obj/item/capture_crystal/examine(user)
. = ..()
if(user == owner && bound_mob)
. += span_notice("[bound_mob]'s crystal")
if(isanimal(bound_mob))
. += span_notice("[bound_mob.health / bound_mob.maxHealth * 100]%")
if(bound_mob.ooc_notes)
. += span_deptradio("OOC Notes:") + " <a href='byond://?src=\ref[bound_mob];ooc_notes=1'>\[View\]</a> - <a href='byond://?src=\ref[src];print_ooc_notes_to_chat=1'>\[Print\]</a>"
. += span_deptradio("<a href='byond://?src=\ref[bound_mob];vore_prefs=1'>\[Mechanical Vore Preferences\]</a>")
//Command! This lets the owner toggle hostile on AI controlled mobs, or send a silent command message to your bound mob, wherever they may be.
/obj/item/capture_crystal/ui_action_click(mob/user, actiontype)
if(!ismob(loc))
return
var/mob/living/M = src.loc
if(M != owner)
to_chat(M, span_notice("\The [src] emits an unpleasant tone... It does not respond to your command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(!bound_mob)
to_chat(M, span_notice("\The [src] emits an unpleasant tone... There is nothing to command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(isanimal(bound_mob) && !bound_mob.client)
if(!isnull(bound_mob.get_AI_stance()))
var/datum/ai_holder/AI = bound_mob.ai_holder
AI.hostile = !AI.hostile
if(!AI.hostile)
AI.set_stance(STANCE_IDLE)
to_chat(M, span_notice("\The [bound_mob] is now [AI.hostile ? "hostile" : "passive"]."))
log_admin("[key_name_admin(M)] set [bound_mob] to [AI.hostile].")
else if(bound_mob.client)
var/transmit_msg = tgui_input_text(user, "What is your command?", "Command")
if(length(transmit_msg) >= MAX_MESSAGE_LEN)
to_chat(M, span_danger("Your message was TOO LONG!:[transmit_msg]"))
return
transmit_msg = sanitize(transmit_msg, max_length = MAX_MESSAGE_LEN)
if(isnull(transmit_msg))
to_chat(M, span_notice("You decided against it."))
return
to_chat(bound_mob, span_notice("\The [owner] commands, '[transmit_msg]'"))
to_chat(M, span_notice("Your command has been transmitted, '[transmit_msg]'"))
log_admin("[key_name_admin(M)] sent the command, '[transmit_msg]' to [bound_mob].")
else
to_chat(M, span_notice("\The [src] emits an unpleasant tone... \The [bound_mob] is unresponsive."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
//Lets the owner get AI controlled bound mobs to follow them, or tells player controlled mobs to follow them.
/obj/item/capture_crystal/verb/follow_owner()
set name = "Toggle Follow"
set category = "Object"
set src in usr
if(!ismob(loc))
return
var/mob/living/M = src.loc
if(M != owner)
to_chat(M, span_notice("\The [src] emits an unpleasant tone... It does not respond to your command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(bound_mob.stat != CONSCIOUS)
to_chat(M, span_notice("\The [src] emits an unpleasant tone... \The [bound_mob] is not able to hear your command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(bound_mob.client)
to_chat(bound_mob, span_notice("\The [owner] wishes for you to follow them."))
else if(bound_mob in contents)
if(!bound_mob.ai_holder)
to_chat(M, span_notice("\The [src] emits an unpleasant tone... \The [bound_mob] is not able to follow your command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return
var/datum/ai_holder/AI = bound_mob.ai_holder
if(AI.leader)
to_chat(M, span_notice("\The [src] chimes~ \The [bound_mob] stopped following [AI.leader]."))
AI.lose_follow(AI.leader)
else
AI.set_follow(M)
to_chat(M, span_notice("\The [src] chimes~ \The [bound_mob] started following following [AI.leader]."))
else if(!(bound_mob in view(M)))
to_chat(M, span_notice("\The [src] emits an unpleasant tone... \The [bound_mob] is not able to hear your command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
if(!bound_mob.ai_holder)
to_chat(M, span_notice("\The [src] emits an unpleasant tone... \The [bound_mob] is not able to follow your command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return
var/datum/ai_holder/AI = bound_mob.ai_holder
if(AI.leader)
to_chat(M, span_notice("\The [src] chimes~ \The [bound_mob] stopped following [AI.leader]."))
AI.lose_follow(AI.leader)
else
AI.set_follow(M)
to_chat(M, span_notice("\The [src] chimes~ \The [bound_mob] started following following [AI.leader]."))
//Don't really want people 'haha funny' capturing and releasing one another willy nilly. So! If you wanna release someone, you gotta destroy the thingy.
//(Which is consistent with how it works with digestion anyway.)
/obj/item/capture_crystal/verb/destroy_crystal()
set name = "Destroy Crystal"
set category = "Object"
set src in usr
if(!ismob(loc))
return
var/mob/living/M = src.loc
if(M != owner)
to_chat(M, span_notice("\The [src] is too hard for you to break."))
else
M.visible_message("\The [M] crushes \the [src] into dust...", "\The [src] cracks and disintegrates in your hand.")
qdel(src)
//If you catch something/someone and want to give it to someone else though, that's fine.
/obj/item/capture_crystal/verb/release_ownership()
set name = "Release Ownership"
set category = "Object"
set src in usr
if(!ismob(loc))
return
var/mob/living/M = src.loc
if(M != owner)
to_chat(M, span_notice("\The [src] emits an unpleasant tone... It does not respond to your command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else
M.visible_message("\The [src] flickers in \the [M]'s hand and emits a little tone.", "\The [src] flickers in your hand and emits a little tone.")
playsound(src, 'sound/effects/capture-crystal-out.ogg', 75, 1, -1)
UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
owner = null
//Let's make inviting ghosts be an option you can do instead of an automatic thing!
/obj/item/capture_crystal/verb/invite_ghost()
set name = "Enhance (Toggle Ghost Join)"
set category = "Object"
set src in usr
if(!ismob(loc))
return
var/mob/living/U = src.loc
if(!bound_mob)
to_chat(U, span_notice("\The [src] emits an unpleasant tone... There is nothing to enhance."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return
else if(U != owner)
to_chat(U, span_notice("\The [src] emits an unpleasant tone... It does not respond to your command."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return
else if(bound_mob.client || !isanimal(bound_mob))
to_chat(U, span_notice("\The [src] emits an unpleasant tone... \The [bound_mob] is not eligable for enhancement."))
playsound(src, 'sound/effects/capture-crystal-problem.ogg', 75, 1, -1)
return //Need to type cast the mob so it can detect ghostjoin
var/mob/living/simple_mob/M = bound_mob
if(M.ghostjoin)
M.ghostjoin = FALSE
to_chat(U, span_notice("\The [bound_mob] is no longer eligable to be joined by ghosts."))
else if(tgui_alert(U, "Do you want to offer your [bound_mob] up to ghosts to play as? There is no way undo this once a ghost takes over.", "Invite ghosts?",list("No","Yes")) == "Yes")
M.ghostjoin = TRUE
to_chat(U, span_notice("\The [bound_mob] is now eligable to be joined by ghosts. It will need to be out of the crystal to be able to be joined."))
else
to_chat(U, span_notice("You decided against it."))
/obj/item/capture_crystal/update_icon()
. = ..()
if(spawn_mob_type)
icon_state = full_icon
else if(!bound_mob)
icon_state = "inactive"
else if(bound_mob in contents)
icon_state = full_icon
else
icon_state = empty_icon
if(!cooldown_check())
icon_state = "[icon_state]-busy"
spawn(activate_cooldown) //If it's busy then we want to wait a bit to fix the sprite after the cooldown is done.
update_icon()
/obj/item/capture_crystal/proc/cooldown_check()
if(world.time < last_activate + activate_cooldown)
return FALSE
else return TRUE
/obj/item/capture_crystal/attack(mob/living/M, mob/living/user)
if(bound_mob)
if(!bound_mob.devourable) //Don't eat if prefs are bad
return
if(user.zone_sel.selecting == "mouth") //Click while targetting the mouth and you eat/feed the stored mob to whoever you clicked on
if(bound_mob in contents)
user.visible_message("\The [user] moves \the [src] to [M]'s [M.vore_selected]...")
M.perform_the_nom(M, bound_mob, M, M.vore_selected)
else if(M == user) //You don't have a mob, you ponder the orb instead of trying to capture yourself
user.visible_message("\The [user] ponders \the [src]...", "You ponder \the [src]...")
else if (cooldown_check()) //Try to capture someone without throwing
user.visible_message("\The [user] taps \the [M] with \the [src].")
activate(user, M)
else
to_chat(user, span_notice("\The [src] emits an unpleasant tone... It is not ready yet."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
//Tries to unleash or recall your stored mob
/obj/item/capture_crystal/attack_self(mob/living/user)
if(bound_mob && !owner)
if(bound_mob == user)
to_chat(user, span_notice("\The [src] emits an unpleasant tone... It does not activate for you."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return
if(tgui_alert(user, "\The [src] hasn't got an owner. It has \the [bound_mob] registered to it. Would you like to claim this as yours?", "Claim ownership", list("No","Yes")) == "Yes")
owner = user
if(!cooldown_check())
to_chat(user, span_notice("\The [src] emits an unpleasant tone... It is not ready yet."))
if(bound_mob)
playsound(src, 'sound/effects/capture-crystal-problem.ogg', 75, 1, -1)
else
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(user == bound_mob) //You can't recall yourself
to_chat(user, span_notice("\The [src] emits an unpleasant tone... It does not activate for you."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(!active)
activate(user)
else
determine_action(user)
//Make it so the crystal knows if its mob references get deleted to make sure things get cleaned up
/obj/item/capture_crystal/proc/knowyoursignals(mob/living/M, mob/living/U)
RegisterSignal(M, COMSIG_PARENT_QDELETING, PROC_REF(mob_was_deleted), TRUE)
RegisterSignal(U, COMSIG_PARENT_QDELETING, PROC_REF(owner_was_deleted), TRUE)
//The basic capture command does most of the registration work.
/obj/item/capture_crystal/proc/capture(mob/living/M, mob/living/U)
if(!M.capture_crystal || M.capture_caught)
to_chat(U, span_warning("This creature is not suitable for capture."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return
knowyoursignals(M, U)
owner = U
if(isanimal(M))
var/mob/living/simple_mob/S = M
S.revivedby = U.name
if(!bound_mob)
bound_mob = M
bound_mob.capture_caught = TRUE
persist_storable = FALSE
desc = "A glowing crystal in what appears to be some kind of steel housing."
//Determines the capture chance! So you can't capture AI mobs if they're perfectly healthy and all that
/obj/item/capture_crystal/proc/capture_chance(mob/living/M, user)
if(capture_chance_modifier >= 100) //Master crystal always work
return 100
var/capture_chance = ((1 - (M.health / M.maxHealth)) * 100) //Inverted health percent! 100% = 0%
//So I don't know how this works but here's a kind of explanation
//Basic chance + ((Mob's max health - minimum calculated health) / (Max allowed health - Min allowed health)*(Chance at Max allowed health - Chance at minimum allowed health)
capture_chance += 35 + ((M.maxHealth - 5)/ (1000-5)*(-100 - 35))
//Basically! Mobs over 1000 max health will be unable to be caught without using status effects.
//Thanks Aronai!
var/effect_count = 0 //This will give you a smol chance to capture if you have applied status effects, even if the chance would ordinarily be <0
if(M.stat == UNCONSCIOUS)
capture_chance += 0.1
effect_count += 1
else if(M.stat == CONSCIOUS)
capture_chance *= 0.9
else
capture_chance = 0
if(M.weakened) //Haha you fall down
capture_chance += 0.1
effect_count += 1
if(M.stunned) //What's the matter???
capture_chance += 0.1
effect_count += 1
if(M.on_fire) //AAAAAAAA
capture_chance += 0.1
effect_count += 1
if(M.paralysis) //Oh noooo
capture_chance += 0.1
effect_count += 1
if(M.ai_holder.stance == STANCE_IDLE) //SNEAK ATTACK???
capture_chance += 0.1
effect_count += 1
capture_chance *= capture_chance_modifier
if(capture_chance <= 0)
capture_chance = 0 + effect_count
if(capture_chance <= 0)
capture_chance = 0
to_chat(user, span_notice("There's no chance... It needs to be weaker."))
last_activate = world.time
log_admin("[user] threw a capture crystal at [M] and got [capture_chance]% chance to catch.")
return capture_chance
//Handles checking relevent bans, preferences, and asking the player if they want to be caught
/obj/item/capture_crystal/proc/capture_player(mob/living/M, mob/living/U)
if(jobban_isbanned(M, JOB_GHOSTROLES))
to_chat(U, span_warning("This creature is not suitable for capture."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(!M.capture_crystal || M.capture_caught)
to_chat(U, span_warning("This creature is not suitable for capture."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(tgui_alert(M, "Would you like to be caught by in [src] by [U]? You will be bound to their will.", "Become Caught",list("No","Yes")) == "Yes")
if(tgui_alert(M, "Are you really sure? The only way to undo this is to OOC escape while you're in the crystal.", "Become Caught", list("No","Yes")) == "Yes")
log_admin("[key_name(M)] has agreed to become caught by [key_name(U)].")
capture(M, U)
recall(U)
return
to_chat(U, span_warning("This creature is too strong willed to be captured."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
//The clean up procs!
/obj/item/capture_crystal/proc/mob_was_deleted()
UnregisterSignal(bound_mob, COMSIG_PARENT_QDELETING)
UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
bound_mob.capture_caught = FALSE
bound_mob = null
owner = null
active = FALSE
persist_storable = TRUE
update_icon()
/obj/item/capture_crystal/proc/owner_was_deleted()
UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
owner = null
active = FALSE
update_icon()
//If the crystal hasn't been set up, it does this
/obj/item/capture_crystal/proc/activate(mob/living/user, target)
if(!cooldown_check()) //Are we ready to do things yet?
to_chat(thrower, span_notice("\The [src] clicks unsatisfyingly... It is not ready yet."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return
if(spawn_mob_type && !bound_mob) //We don't already have a mob, but we know what kind of mob we want
bound_mob = new spawn_mob_type(src) //Well let's spawn it then!
bound_mob.faction = user.faction
spawn_mob_type = null
capture(bound_mob, user)
if(bound_mob) //We have a mob! Let's finish setting up.
user.visible_message("\The [src] clicks, and then emits a small chime.", "\The [src] grows warm in your hand, something inside is awake.")
active = TRUE
if(!owner) //Do we have an owner? It's pretty unlikely that this would ever happen! But it happens, let's claim the crystal.
owner = user
if(isanimal(bound_mob))
var/mob/living/simple_mob/S = bound_mob
S.revivedby = user.name
determine_action(user, target)
return
else if(isliving(target)) //So we don't have a mob, let's try to claim one! Is the target a mob?
var/mob/living/M = target
last_activate = world.time
if(M.capture_caught) //Can't capture things that were already caught.
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
to_chat(user, span_notice("\The [src] clicks unsatisfyingly... \The [M] is already under someone else's control."))
return
else if(M.stat == DEAD) //Is it dead? We can't influence dead things.
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
to_chat(user, span_notice("\The [src] clicks unsatisfyingly... \The [M] is not in a state to be captured."))
return
else if(M.client) //Is it player controlled?
capture_player(M, user) //We have to do things a little differently if so.
return
else if(!isanimal(M)) //So it's not player controlled, but it's also not a simplemob?
to_chat(user, span_warning("This creature is not suitable for capture."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return
var/mob/living/simple_mob/S = M
if(!S.ai_holder) //We don't really want to capture simplemobs that don't have an AI
to_chat(user, span_warning("This creature is not suitable for capture."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(prob(capture_chance(S, user))) //OKAY! So we have an NPC simplemob with an AI, let's calculate its capture chance! It varies based on the mob's condition.
capture(S, user) //We did it! Woo! We capture it!
user.visible_message("\The [src] clicks, and then emits a small chime.", "Alright! \The [S] was caught!")
recall(user)
active = TRUE
else //Shoot, it didn't work and now it's mad!!!
S.ai_holder.go_wake()
S.ai_holder.give_target(user, urgent = TRUE)
user.visible_message("\The [src] bonks into \the [S], angering it!")
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
to_chat(user, span_notice("\The [src] clicks unsatisfyingly."))
update_icon()
return
//The target is not a mob, so let's not do anything.
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
to_chat(user, span_notice("\The [src] clicks unsatisfyingly."))
//We're using the crystal, but what will it do?
/obj/item/capture_crystal/proc/determine_action(mob/living/U, T)
if(!cooldown_check()) //Are we ready yet?
to_chat(thrower, span_notice("\The [src] clicks unsatisfyingly... It is not ready yet."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
return //No
if(bound_mob in contents) //Do we have our mob?
if(T)
unleash(U, T) //Yes, let's let it out!
else
unleash(U)
else if (bound_mob) //Do we HAVE a mob?
recall(U) //Yes, let's try to put it back in the crystal
else //No we don't have a mob, let's reset the crystal.
to_chat(U, span_notice("\The [src] clicks unsatisfyingly."))
active = FALSE
update_icon()
owner = null
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
//Let's try to call our mob back!
/obj/item/capture_crystal/proc/recall(mob/living/user)
if(bound_mob in view(user)) //We can only recall it if we can see it
var/turf/turfmemory = get_turf(bound_mob)
if(isanimal(bound_mob) && bound_mob.ai_holder)
var/mob/living/simple_mob/M = bound_mob
M.ai_holder.go_sleep() //AI doesn't need to think when it's in the crystal
bound_mob.forceMove(src)
last_activate = world.time
bound_mob.visible_message("\The [user]'s [src] flashes, disappearing [bound_mob] in an instant!!!", "\The [src] pulls you back into confinement in a flash of light!!!")
animate_action(turfmemory)
playsound(src, 'sound/effects/capture-crystal-in.ogg', 75, 1, -1)
update_icon()
else
to_chat(user, span_notice("\The [src] clicks and emits a small, unpleasant tone. \The [bound_mob] cannot be recalled."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
//Let's let our mob out!
/obj/item/capture_crystal/proc/unleash(mob/living/user, atom/target)
if(!user && !target) //We got thrown but we're not sure who did it, let's go to where the crystal is
var/drop_loc = get_turf(src)
if (drop_loc)
bound_mob.forceMove(drop_loc)
return
if(!target) //We know who wants to let us out, but they didn't say where, so let's drop us on them
bound_mob.forceMove(user.drop_location())
else //We got thrown! Let's go where we got thrown
bound_mob.forceMove(target.drop_location())
last_activate = world.time
if(isanimal(bound_mob))
var/mob/living/simple_mob/M = bound_mob
M.ai_holder.go_wake() //Okay it's time to do work, let's wake up!
bound_mob.faction = owner.faction //Let's make sure we aren't hostile to our owner or their friends
bound_mob.visible_message("\The [user]'s [src] flashes, \the [bound_mob] appears in an instant!!!", "The world around you rematerialize as you are unleashed from the [src] next to \the [user]. You feel a strong compulsion to enact \the [owner]'s will.")
animate_action(get_turf(bound_mob))
playsound(src, 'sound/effects/capture-crystal-out.ogg', 75, 1, -1)
update_icon()
//Let's make a flashy sparkle when someone appears or disappears!
/obj/item/capture_crystal/proc/animate_action(atom/thing)
var/image/coolanimation = image('icons/obj/capture_crystal_vr.dmi', null, "animation")
coolanimation.plane = PLANE_LIGHTING_ABOVE
thing.overlays += coolanimation
sleep(11)
thing.overlays -= coolanimation
//IF the crystal somehow ends up in a tummy and digesting with a bound mob who doesn't want to be eaten, let's move them to the ground
/obj/item/capture_crystal/digest_act(var/atom/movable/item_storage = null)
if(bound_mob) //CHOMPEdit
if(bound_mob in contents && !bound_mob.devourable)
bound_mob.forceMove(src.drop_location())
return ..()
//We got thrown! Let's figure out what to do
/obj/item/capture_crystal/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, datum/callback/callback)
. = ..()
if(target == bound_mob && thrower != bound_mob) //We got thrown at our bound mob (and weren't thrown by the bound mob) let's ignore the cooldown and just put them back in
recall(thrower)
else if(!cooldown_check()) //OTHERWISE let's obey the cooldown
to_chat(thrower, span_notice("\The [src] emits an soft tone... It is not ready yet."))
if(bound_mob)
playsound(src, 'sound/effects/capture-crystal-problem.ogg', 75, 1, -1)
else
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(!active) //The ball isn't set up, let's try to set it up.
if(isliving(target)) //We're hitting a mob, let's try to capture it.
sleep(10)
activate(thrower, target)
return
sleep(10)
activate(thrower, src)
else if(!bound_mob) //We hit something else, and we don't have a mob, so we can't really do anything!
to_chat(thrower, span_notice("\The [src] clicks unpleasantly..."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
else if(bound_mob in contents) //We have our mob! Let's try to let it out.
sleep(10)
unleash(thrower, src)
update_icon()
else //Our mob isn't here, we can't do anything.
to_chat(thrower, span_notice("\The [src] clicks unpleasantly..."))
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
/obj/item/capture_crystal/basic
/obj/item/capture_crystal/great
name = "great capture crystal"
capture_chance_modifier = 1.5
/obj/item/capture_crystal/ultra
name = "ultra capture crystal"
capture_chance_modifier = 2
/obj/item/capture_crystal/master
name = "master capture crystal"
capture_chance_modifier = 100
/obj/item/capture_crystal/cass
spawn_mob_type = /mob/living/simple_mob/vore/woof/cass
/obj/item/capture_crystal/adg
spawn_mob_type = /mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced
/obj/item/capture_crystal/bigdragon
spawn_mob_type = /mob/living/simple_mob/vore/bigdragon
/obj/item/capture_crystal/bigdragon/friendly
spawn_mob_type = /mob/living/simple_mob/vore/bigdragon/friendly
/obj/item/capture_crystal/teppi
spawn_mob_type = /mob/living/simple_mob/vore/alienanimals/teppi
/obj/item/capture_crystal/broodmother
spawn_mob_type = /mob/living/simple_mob/animal/giant_spider/broodmother
/obj/item/capture_crystal/skeleton
spawn_mob_type = /mob/living/simple_mob/vore/alienanimals/skeleton
/obj/item/capture_crystal/dustjumper
spawn_mob_type = /mob/living/simple_mob/vore/alienanimals/dustjumper
/obj/item/capture_crystal/random
var/static/list/possible_mob_types = list(
list(/mob/living/simple_mob/animal/goat),
list(
/mob/living/simple_mob/animal/passive/bird,
/mob/living/simple_mob/animal/passive/bird/azure_tit,
/mob/living/simple_mob/animal/passive/bird/black_bird,
/mob/living/simple_mob/animal/passive/bird/european_robin,
/mob/living/simple_mob/animal/passive/bird/goldcrest,
/mob/living/simple_mob/animal/passive/bird/ringneck_dove,
/mob/living/simple_mob/animal/passive/bird/parrot,
/mob/living/simple_mob/animal/passive/bird/parrot/black_headed_caique,
/mob/living/simple_mob/animal/passive/bird/parrot/budgerigar,
/mob/living/simple_mob/animal/passive/bird/parrot/budgerigar/blue,
/mob/living/simple_mob/animal/passive/bird/parrot/budgerigar/bluegreen,
/mob/living/simple_mob/animal/passive/bird/parrot/cockatiel,
/mob/living/simple_mob/animal/passive/bird/parrot/cockatiel/grey,
/mob/living/simple_mob/animal/passive/bird/parrot/cockatiel/white,
/mob/living/simple_mob/animal/passive/bird/parrot/cockatiel/yellowish,
/mob/living/simple_mob/animal/passive/bird/parrot/eclectus,
/mob/living/simple_mob/animal/passive/bird/parrot/grey_parrot,
/mob/living/simple_mob/animal/passive/bird/parrot/kea,
/mob/living/simple_mob/animal/passive/bird/parrot/pink_cockatoo,
/mob/living/simple_mob/animal/passive/bird/parrot/sulphur_cockatoo,
/mob/living/simple_mob/animal/passive/bird/parrot/white_caique,
/mob/living/simple_mob/animal/passive/bird/parrot/white_cockatoo
),
list(
/mob/living/simple_mob/animal/passive/cat,
/mob/living/simple_mob/animal/passive/cat/black
),
list(/mob/living/simple_mob/animal/passive/chick),
list(/mob/living/simple_mob/animal/passive/cow),
list(/mob/living/simple_mob/animal/passive/dog/brittany),
list(/mob/living/simple_mob/animal/passive/dog/corgi),
list(/mob/living/simple_mob/animal/passive/dog/tamaskan),
list(/mob/living/simple_mob/animal/passive/fox),
list(/mob/living/simple_mob/animal/passive/hare),
list(/mob/living/simple_mob/animal/passive/lizard),
list(/mob/living/simple_mob/animal/passive/mouse),
list(/mob/living/simple_mob/animal/passive/mouse/jerboa),
list(/mob/living/simple_mob/animal/passive/opossum),
list(/mob/living/simple_mob/animal/passive/pillbug),
list(/mob/living/simple_mob/animal/passive/snake),
list(/mob/living/simple_mob/animal/passive/tindalos),
list(/mob/living/simple_mob/animal/passive/yithian),
list(
/mob/living/simple_mob/vore/wolf,
/mob/living/simple_mob/vore/wolf/direwolf
),
list(/mob/living/simple_mob/vore/rabbit),
list(/mob/living/simple_mob/vore/redpanda),
list(/mob/living/simple_mob/vore/woof),
list(/mob/living/simple_mob/vore/fennec),
list(/mob/living/simple_mob/vore/fennix),
list(/mob/living/simple_mob/vore/hippo),
list(/mob/living/simple_mob/vore/horse),
list(/mob/living/simple_mob/vore/bee),
list(
/mob/living/simple_mob/animal/space/bear,
/mob/living/simple_mob/animal/space/bear/brown
),
list(
/mob/living/simple_mob/vore/otie/feral,
/mob/living/simple_mob/vore/otie/feral/chubby,
/mob/living/simple_mob/vore/otie/red,
/mob/living/simple_mob/vore/otie/red/chubby
),
list(/mob/living/simple_mob/animal/sif/diyaab),
list(/mob/living/simple_mob/animal/sif/duck),
list(/mob/living/simple_mob/animal/sif/frostfly),
list(
/mob/living/simple_mob/animal/sif/glitterfly =50,
/mob/living/simple_mob/animal/sif/glitterfly/rare = 1
),
list(
/mob/living/simple_mob/animal/sif/kururak = 10,
/mob/living/simple_mob/animal/sif/kururak/leader = 1,
/mob/living/simple_mob/animal/sif/kururak/hibernate = 2,
),
list(
/mob/living/simple_mob/animal/sif/sakimm = 10,
/mob/living/simple_mob/animal/sif/sakimm/intelligent = 1
),
list(/mob/living/simple_mob/animal/sif/savik) = 5,
list(
/mob/living/simple_mob/animal/sif/shantak = 10,
/mob/living/simple_mob/animal/sif/shantak/leader = 1
),
list(/mob/living/simple_mob/animal/sif/siffet),
list(/mob/living/simple_mob/animal/sif/tymisian),
list(
/mob/living/simple_mob/animal/giant_spider/electric = 5,
/mob/living/simple_mob/animal/giant_spider/frost = 5,
/mob/living/simple_mob/animal/giant_spider/hunter = 10,
/mob/living/simple_mob/animal/giant_spider/ion = 5,
/mob/living/simple_mob/animal/giant_spider/lurker = 10,
/mob/living/simple_mob/animal/giant_spider/pepper = 10,
/mob/living/simple_mob/animal/giant_spider/phorogenic = 10,
/mob/living/simple_mob/animal/giant_spider/thermic = 5,
/mob/living/simple_mob/animal/giant_spider/tunneler = 10,
/mob/living/simple_mob/animal/giant_spider/webslinger = 5,
/mob/living/simple_mob/animal/giant_spider/broodmother = 1),
list(
/mob/living/simple_mob/vore/wolf = 10,
/mob/living/simple_mob/vore/wolf/direwolf = 5,
/mob/living/simple_mob/vore/greatwolf = 1,
/mob/living/simple_mob/vore/greatwolf/black = 1,
/mob/living/simple_mob/vore/greatwolf/grey = 1
),
list(/mob/living/simple_mob/creature/strong),
list(/mob/living/simple_mob/faithless/strong),
list(/mob/living/simple_mob/animal/goat),
list(
/mob/living/simple_mob/animal/sif/shantak/leader = 1,
/mob/living/simple_mob/animal/sif/shantak = 10),
list(/mob/living/simple_mob/animal/sif/savik,),
list(/mob/living/simple_mob/animal/sif/hooligan_crab),
list(
/mob/living/simple_mob/animal/space/alien = 50,
/mob/living/simple_mob/animal/space/alien/drone = 40,
/mob/living/simple_mob/animal/space/alien/sentinel = 25,
/mob/living/simple_mob/animal/space/alien/sentinel/praetorian = 15,
/mob/living/simple_mob/animal/space/alien/queen = 10,
/mob/living/simple_mob/animal/space/alien/queen/empress = 5,
/mob/living/simple_mob/animal/space/alien/queen/empress/mother = 1
),
list(/mob/living/simple_mob/animal/space/bats/cult/strong),
list(
/mob/living/simple_mob/animal/space/bear,
/mob/living/simple_mob/animal/space/bear/brown
),
list(
/mob/living/simple_mob/animal/space/carp = 50,
/mob/living/simple_mob/animal/space/carp/large = 10,
/mob/living/simple_mob/animal/space/carp/large/huge = 5
),
list(/mob/living/simple_mob/animal/space/goose),
list(/mob/living/simple_mob/vore/jelly),
list(/mob/living/simple_mob/animal/space/tree),
list(
/mob/living/simple_mob/vore/aggressive/corrupthound = 10,
/mob/living/simple_mob/vore/aggressive/corrupthound/prettyboi = 1,
),
list(/mob/living/simple_mob/vore/aggressive/deathclaw),
list(/mob/living/simple_mob/vore/aggressive/dino),
list(/mob/living/simple_mob/vore/aggressive/dragon),
list(/mob/living/simple_mob/vore/aggressive/dragon/virgo3b),
list(/mob/living/simple_mob/vore/aggressive/frog),
list(/mob/living/simple_mob/vore/aggressive/giant_snake),
list(/mob/living/simple_mob/vore/aggressive/mimic),
list(/mob/living/simple_mob/vore/aggressive/panther),
list(/mob/living/simple_mob/vore/aggressive/rat),
list(/mob/living/simple_mob/vore/bee),
list(
/mob/living/simple_mob/vore/sect_drone = 10,
/mob/living/simple_mob/vore/sect_queen = 1
),
list(/mob/living/simple_mob/vore/solargrub),
list(
/mob/living/simple_mob/vore/oregrub = 5,
/mob/living/simple_mob/vore/oregrub/lava = 1
),
list(/mob/living/simple_mob/vore/catgirl),
list(/mob/living/simple_mob/vore/wolfgirl),
list(
/mob/living/simple_mob/vore/lamia,
/mob/living/simple_mob/vore/lamia/albino,
/mob/living/simple_mob/vore/lamia/albino/bra,
/mob/living/simple_mob/vore/lamia/albino/shirt,
/mob/living/simple_mob/vore/lamia/bra,
/mob/living/simple_mob/vore/lamia/cobra,
/mob/living/simple_mob/vore/lamia/cobra/bra,
/mob/living/simple_mob/vore/lamia/cobra/shirt,
/mob/living/simple_mob/vore/lamia/copper,
/mob/living/simple_mob/vore/lamia/copper/bra,
/mob/living/simple_mob/vore/lamia/copper/shirt,
/mob/living/simple_mob/vore/lamia/green,
/mob/living/simple_mob/vore/lamia/green/bra,
/mob/living/simple_mob/vore/lamia/green/shirt,
/mob/living/simple_mob/vore/lamia/zebra,
/mob/living/simple_mob/vore/lamia/zebra/bra,
/mob/living/simple_mob/vore/lamia/zebra/shirt
),
list(
/mob/living/simple_mob/humanoid/merc = 100,
/mob/living/simple_mob/humanoid/merc/melee/sword = 50,
/mob/living/simple_mob/humanoid/merc/ranged = 25,
/mob/living/simple_mob/humanoid/merc/ranged/grenadier = 1,
/mob/living/simple_mob/humanoid/merc/ranged/ionrifle = 10,
/mob/living/simple_mob/humanoid/merc/ranged/laser = 5,
/mob/living/simple_mob/humanoid/merc/ranged/rifle = 5,
/mob/living/simple_mob/humanoid/merc/ranged/smg = 5,
/mob/living/simple_mob/humanoid/merc/ranged/sniper = 1,
/mob/living/simple_mob/humanoid/merc/ranged/space = 10,
/mob/living/simple_mob/humanoid/merc/ranged/technician = 5
),
list(
/mob/living/simple_mob/humanoid/pirate = 3,
/mob/living/simple_mob/humanoid/pirate/ranged = 1
),
list(/mob/living/simple_mob/mechanical/combat_drone),
list(/mob/living/simple_mob/mechanical/corrupt_maint_drone),
list(
/mob/living/simple_mob/mechanical/hivebot = 100,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage = 20,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/backline = 10,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/basic = 20,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/dot = 5,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/ion = 20,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/laser = 10,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/rapid = 2,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege = 1,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/emp = 5,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/fragmentation = 1,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/radiation = 1,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong = 3,
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong/guard = 3,
/mob/living/simple_mob/mechanical/hivebot/support = 8,
/mob/living/simple_mob/mechanical/hivebot/support/commander = 5,
/mob/living/simple_mob/mechanical/hivebot/support/commander/autofollow = 10,
/mob/living/simple_mob/mechanical/hivebot/swarm = 20,
/mob/living/simple_mob/mechanical/hivebot/tank = 20,
/mob/living/simple_mob/mechanical/hivebot/tank/armored = 20,
/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_bullet = 20,
/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_laser = 20,
/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_melee = 20,
/mob/living/simple_mob/mechanical/hivebot/tank/meatshield = 20
),
list(/mob/living/simple_mob/mechanical/infectionbot),
list(/mob/living/simple_mob/mechanical/mining_drone),
list(/mob/living/simple_mob/mechanical/technomancer_golem),
list(
/mob/living/simple_mob/mechanical/viscerator,
/mob/living/simple_mob/mechanical/viscerator/piercing
),
list(/mob/living/simple_mob/mechanical/wahlem),
list(/mob/living/simple_mob/animal/passive/fox/syndicate),
list(/mob/living/simple_mob/animal/passive/fox),
list(/mob/living/simple_mob/vore/wolf/direwolf),
list(/mob/living/simple_mob/vore/jelly),
list(
/mob/living/simple_mob/vore/otie/feral,
/mob/living/simple_mob/vore/otie/feral/chubby,
/mob/living/simple_mob/vore/otie/red,
/mob/living/simple_mob/vore/otie/red/chubby
),
list(
/mob/living/simple_mob/shadekin/blue = 100,
/mob/living/simple_mob/shadekin/green = 50,
/mob/living/simple_mob/shadekin/orange = 20,
/mob/living/simple_mob/shadekin/purple = 60,
/mob/living/simple_mob/shadekin/red = 40,
/mob/living/simple_mob/shadekin/yellow = 1
),
list(
/mob/living/simple_mob/vore/aggressive/corrupthound,
/mob/living/simple_mob/vore/aggressive/corrupthound/prettyboi
),
list(/mob/living/simple_mob/vore/aggressive/deathclaw),
list(/mob/living/simple_mob/vore/aggressive/dino),
list(/mob/living/simple_mob/vore/aggressive/dragon),
list(/mob/living/simple_mob/vore/aggressive/dragon/virgo3b),
list(/mob/living/simple_mob/vore/aggressive/frog),
list(/mob/living/simple_mob/vore/aggressive/giant_snake),
list(/mob/living/simple_mob/vore/aggressive/mimic),
list(/mob/living/simple_mob/vore/aggressive/panther),
list(/mob/living/simple_mob/vore/aggressive/rat),
list(/mob/living/simple_mob/vore/bee),
list(/mob/living/simple_mob/vore/catgirl),
list(/mob/living/simple_mob/vore/cookiegirl),
list(/mob/living/simple_mob/vore/fennec),
list(/mob/living/simple_mob/vore/fennix),
list(/mob/living/simple_mob/vore/hippo),
list(/mob/living/simple_mob/vore/horse),
list(/mob/living/simple_mob/vore/oregrub),
list(/mob/living/simple_mob/vore/rabbit),
list(
/mob/living/simple_mob/vore/redpanda = 50,
/mob/living/simple_mob/vore/redpanda/fae = 1
),
list(
/mob/living/simple_mob/vore/sect_drone = 10,
/mob/living/simple_mob/vore/sect_queen = 1
),
list(/mob/living/simple_mob/vore/solargrub),
list(/mob/living/simple_mob/vore/woof),
list(/mob/living/simple_mob/vore/alienanimals/teppi),
list(/mob/living/simple_mob/vore/alienanimals/space_ghost),
list(/mob/living/simple_mob/vore/alienanimals/catslug),
list(/mob/living/simple_mob/vore/alienanimals/space_jellyfish),
list(/mob/living/simple_mob/vore/alienanimals/startreader),
list(/mob/living/simple_mob/vore/bigdragon),
list(
/mob/living/simple_mob/vore/leopardmander = 50,
/mob/living/simple_mob/vore/leopardmander/blue = 10,
/mob/living/simple_mob/vore/leopardmander/exotic = 1
),
list(/mob/living/simple_mob/vore/sheep),
list(/mob/living/simple_mob/vore/weretiger),
list(/mob/living/simple_mob/vore/alienanimals/skeleton),
list(/mob/living/simple_mob/vore/alienanimals/dustjumper),
list(/mob/living/simple_mob/vore/cryptdrake),
list(/mob/living/simple_mob/vore/stalker),
list(/mob/living/simple_mob/vore/horse/kelpie),
list(/mob/living/simple_mob/vore/scrubble),
list(/mob/living/simple_mob/vore/sonadile),
list(/mob/living/simple_mob/vore/devil)
)
/obj/item/capture_crystal/random/Initialize()
var/subchoice = pickweight(possible_mob_types) //Some of the lists have nested lists, so let's pick one of them
var/choice = pickweight(subchoice) //And then we'll pick something from whatever's left
spawn_mob_type = choice //Now when someone uses this, we'll spawn whatever we picked!
return ..()
/mob/living
var/capture_crystal = TRUE //If TRUE, the mob is capturable. Otherwise it isn't.
var/capture_caught = FALSE //If TRUE, the mob has already been caught, and so cannot be caught again.