Files
Ben10Omintrix 7763db34a1 Basic beepskies and some ED209 additions (#95783)
## About The Pull Request
this PR refactors beepskies and ED209s into basic mobs. ive also added a
few features for the ed209 because i cant keep my hands to myself:

ED bots can now wear hats! a few hats give them some new unique
dialogue. Putting a cowboy's hat on an ED will have it acting like a
town Sherrif

EDs are now rideable when emagged. theyll still be shooting people while
ridden. youll also have the ability to charge at people full throttle
running them over

ive also added a new Nukie variant, purchasable from the uplink. These
versions come fitted with an LMG and will patrol the station and shoot
all walks of life. If emagged, theyll blow up people with (weaker)
rockets (im still working on balancing this). they are rideable but i
highly suggest against it; youll be caught in the crossfire. (i
understand if ud prefer i atomize this feature)

## Why It's Good For The Game
gives some personality to EDs, as well as updating all these bots to the
cool new ai system

## Changelog
🆑
refactor: beepskies, and ed209s have been refactored. please report any
bugs
add: ed209s can now wear hats! some hats give them new dialogue
add: emagged ed209s are now rideable! 
add: added evil versions of the ed209s to the syndie uplink
/🆑

---------

Co-authored-by: san7890 <the@san7890.com>
2026-04-25 00:25:28 +00:00

264 lines
10 KiB
Plaintext

/mob/living/basic/bot/secbot
name = "\improper Securitron"
desc = "A little security robot. He looks less than thrilled."
icon = 'icons/mob/silicon/aibots.dmi'
icon_state = "secbot"
base_icon_state = "secbot"
light_color = "#f56275"
light_power = 0.8
gender = MALE
density = FALSE
anchored = FALSE
health = 25
maxHealth = 25
damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, STAMINA = 0, OXY = 0)
pass_flags = PASSMOB | PASSFLAPS
combat_mode = TRUE
can_buckle_to = FALSE
req_one_access = list(ACCESS_SECURITY)
radio_key = /obj/item/encryptionkey/secbot //AI Priv + Security
radio_channel = RADIO_CHANNEL_SECURITY //Security channel
bot_type = SEC_BOT
bot_mode_flags = ~BOT_MODE_CAN_BE_SAPIENT
data_hud_type = TRAIT_SECURITY_HUD
hackables = "target identification systems"
path_image_color = COLOR_RED
possessed_message = "You are a securitron! Guard the station to the best of your ability!"
additional_access = /datum/id_trim/job/detective
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 1.2, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 3.2)
ai_controller = /datum/ai_controller/basic_controller/bot/secbot
///Whether this secbot is considered 'commissioned' and given the trait on Initialize.
var/commissioned = FALSE
///The type of baton this Secbot will use
var/baton_type = /obj/item/melee/baton/security
///The weapon (from baton_type) that will be used to make arrests.
var/obj/item/weapon
///The threat level of the BOT, will arrest anyone at threatlevel 4 or above
var/threatlevel = 0
///Flags SecBOTs use on what to check on targets when arresting, and whether they should announce it to security/handcuff their target
/// Look at the security_mode_flags bitfield for more information on what's togglable here.
var/security_mode_flags = SECBOT_DECLARE_ARRESTS | SECBOT_CHECK_RECORDS | SECBOT_HANDCUFF_TARGET
/// On arrest, charges the violator this much.
/// If they don't have that much in their account, they will get beaten instead
var/price_arrest = 0
/// Charged each time the violator is stunned on detain
var/price_detain = 0
///The department the secbot will deposit collected money into
var/payment_department = ACCOUNT_SEC
///what sound we play when stunning
var/stun_sound = 'sound/items/weapons/egloves.ogg'
///The type of cuffs we use on criminals after making arrests
var/cuff_type = /obj/item/restraints/handcuffs/cable/zipties/used
/mob/living/basic/bot/secbot/Initialize(mapload)
. = ..()
weapon = new baton_type(src)
update_appearance(UPDATE_ICON)
if(commissioned)
ADD_TRAIT(src, TRAIT_COMMISSIONED, INNATE_TRAIT)
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
AddElement(/datum/element/connect_loc, loc_connections)
AddComponent(/datum/component/security_vision, judgement_criteria = NONE, update_judgement_criteria = CALLBACK(src, PROC_REF(judgement_criteria)))
add_arrest_component()
/mob/living/basic/bot/secbot/Destroy()
QDEL_NULL(weapon)
return ..()
/mob/living/basic/bot/secbot/update_icon_state()
if(mode == BOT_HUNT)
icon_state = "[base_icon_state]-c"
return ..()
/mob/living/basic/bot/secbot/turn_off()
..()
update_bot_mode(new_mode = BOT_IDLE)
/mob/living/basic/bot/secbot/on_saboteur(datum/source, disrupt_duration)
. = ..()
if(!(security_mode_flags & SECBOT_SABOTEUR_AFFECTED))
security_mode_flags |= SECBOT_SABOTEUR_AFFECTED
addtimer(CALLBACK(src, PROC_REF(remove_saboteur_effect)), disrupt_duration)
return TRUE
/mob/living/basic/bot/secbot/proc/remove_saboteur_effect()
security_mode_flags &= ~SECBOT_SABOTEUR_AFFECTED
/mob/living/basic/bot/secbot/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)//shocks only make him angry
if(speed >= initial(speed) + 3)
return
speed += 3
addtimer(VARSET_CALLBACK(src, speed, speed - 3), 6 SECONDS)
playsound(src, 'sound/machines/defib/defib_zap.ogg', 50)
visible_message(span_warning("[src] shakes and speeds up!"))
/mob/living/basic/bot/secbot/Exited(atom/movable/gone, direction)
. = ..()
if(gone == weapon)
weapon = null
update_appearance()
// Variables sent to TGUI
/mob/living/basic/bot/secbot/ui_data(mob/user)
var/list/data = ..()
if(!(bot_access_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user))
data["custom_controls"]["check_id"] = security_mode_flags & SECBOT_CHECK_IDS
data["custom_controls"]["check_weapons"] = security_mode_flags & SECBOT_CHECK_WEAPONS
data["custom_controls"]["check_warrants"] = security_mode_flags & SECBOT_CHECK_RECORDS
data["custom_controls"]["handcuff_targets"] = security_mode_flags & SECBOT_HANDCUFF_TARGET
data["custom_controls"]["arrest_alert"] = security_mode_flags & SECBOT_DECLARE_ARRESTS
return data
// Actions received from TGUI
/mob/living/basic/bot/secbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
var/mob/user = ui.user
if(. || (bot_access_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(user)))
return
switch(action)
if("check_id")
security_mode_flags ^= SECBOT_CHECK_IDS
return TRUE
if("check_weapons")
security_mode_flags ^= SECBOT_CHECK_WEAPONS
return TRUE
if("check_warrants")
security_mode_flags ^= SECBOT_CHECK_RECORDS
return TRUE
if("handcuff_targets")
security_mode_flags ^= SECBOT_HANDCUFF_TARGET
return TRUE
if("arrest_alert")
security_mode_flags ^= SECBOT_DECLARE_ARRESTS
return TRUE
/mob/living/basic/bot/secbot/attack_hand(mob/living/carbon/human/user, list/modifiers)
// Turns an oversight into a feature. Beepsky will now announce when pacifists taunt him over sec comms.
if(HAS_TRAIT(user, TRAIT_PACIFISM))
user.visible_message(span_notice("[user] taunts [src], daring [p_them()] to give chase!"), \
span_notice("You taunt [src], daring [p_them()] to chase you!"), span_hear("You hear someone shout a daring taunt!"), DEFAULT_MESSAGE_RANGE, user)
speak("Taunted by pacifist scumbag [RUNECHAT_BOLD("[user]")] in [get_area(src)].", radio_channel)
// Interrupt the attack chain. We've already handled this scenario for pacifists.
return
return ..()
/mob/living/basic/bot/secbot/proc/retrieve_emag_message()
audible_message(span_danger("[src] buzzes oddly!"))
/mob/living/basic/bot/secbot/emag_act(mob/user, obj/item/card/emag/emag_card)
. = ..()
if(!(bot_access_flags & BOT_COVER_EMAGGED))
return
if(user)
balloon_alert(user, "target assessment circuits shorted")
retrieve_emag_message()
security_mode_flags &= ~SECBOT_DECLARE_ARRESTS
update_appearance()
return TRUE
/mob/living/basic/bot/secbot/proc/post_arrest(mob/living/carbon/current_target)
playsound(src, SFX_LAW, 50, FALSE)
/mob/living/basic/bot/secbot/proc/post_stun(mob/living/carbon/current_target, harm = FALSE)
flick("[base_icon_state]-c", src)
var/threat = 5 || ai_controller.blackboard[BB_CURRENT_CRIMINAL_ASSESSMENT]
if(security_mode_flags & SECBOT_DECLARE_ARRESTS)
var/area/location = get_area(src)
speak("[security_mode_flags & SECBOT_HANDCUFF_TARGET ? "Arresting" : "Detaining"] level [threat] scumbag [RUNECHAT_BOLD("[current_target]")] in [location].", radio_channel)
payment_check(current_target)
update_bot_mode(new_mode = BOT_PREP_ARREST)
/mob/living/basic/bot/secbot/explode()
var/atom/drop_location = drop_location()
retrieve_secbot_drops(drop_location)
new /obj/effect/decal/cleanable/blood/oil(loc)
return ..()
/mob/living/basic/bot/secbot/proc/retrieve_secbot_drops(atom/drop_location)
var/obj/item/bot_assembly/secbot/secbot_assembly = new(drop_location)
secbot_assembly.build_step = ASSEMBLY_FIRST_STEP
secbot_assembly.add_overlay("hs_hole")
secbot_assembly.created_name = name
new /obj/item/assembly/prox_sensor(drop_location)
drop_part(weapon, drop_location)
/mob/living/basic/bot/secbot/proc/on_entered(datum/source, atom/movable/to_be_tripped)
SIGNAL_HANDLER
var/mob/living/possible_target = ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(!has_gravity() || !ismob(to_be_tripped) || !possible_target)
return
var/mob/living/carbon/tripped_mob = to_be_tripped
if(istype(tripped_mob) && !in_range(src, possible_target))
knockOver(tripped_mob)
/// Returns true if the current target is unable to pay to be detained/arrested
/mob/living/basic/bot/secbot/proc/payment_check(mob/living/carbon/human/human_target)
var/fair_market_price = (security_mode_flags & SECBOT_HANDCUFF_TARGET) ? price_arrest : price_detain
if(fair_market_price <= 0)
return FALSE
if(!ishuman(human_target))
return FALSE
var/obj/item/card/id/target_id = human_target.get_idcard()
if(!target_id)
say("Unable to pay fine: No ID card found.")
return TRUE
var/datum/bank_account/insurance = target_id.registered_account
if(!insurance)
say("Unable to pay fine: No bank account found.")
return TRUE
if(!insurance.adjust_money(-fair_market_price, "Securitron fine"))
say("Unable to pay fine: Not enough funds in account.")
return TRUE
SSeconomy.get_dep_account(payment_department)?.adjust_money(fair_market_price)
say("Fine paid: Thank you for your compliance. Your account been charged [fair_market_price] [MONEY_NAME].")
return FALSE
/mob/living/basic/bot/secbot/generate_speak_list()
var/static/list/secbot_lines = list(
BEEPSKY_VOICED_CRIMINAL_DETECTED = 'sound/mobs/non-humanoids/beepsky/criminal.ogg',
BEEPSKY_VOICED_FREEZE = 'sound/mobs/non-humanoids/beepsky/freeze.ogg',
BEEPSKY_VOICED_JUSTICE = 'sound/mobs/non-humanoids/beepsky/justice.ogg',
BEEPSKY_VOICED_YOUR_MOVE = 'sound/mobs/non-humanoids/beepsky/creep.ogg',
BEEPSKY_VOICED_I_AM_THE_LAW = 'sound/mobs/non-humanoids/beepsky/iamthelaw.ogg',
BEEPSKY_VOICED_SECURE_DAY = 'sound/mobs/non-humanoids/beepsky/secureday.ogg',
)
return secbot_lines
/mob/living/basic/bot/secbot/proc/judgement_criteria()
var/final = FALSE
if(bot_access_flags & BOT_COVER_EMAGGED)
final |= JUDGE_EMAGGED
if(security_mode_flags & SECBOT_CHECK_IDS)
final |= JUDGE_IDCHECK
if(security_mode_flags & SECBOT_CHECK_RECORDS)
final |= JUDGE_RECORDCHECK
if(security_mode_flags & SECBOT_CHECK_WEAPONS)
final |= JUDGE_WEAPONCHECK
if(security_mode_flags & SECBOT_SABOTEUR_AFFECTED)
final |= JUDGE_CHILLOUT
return final
/mob/living/basic/bot/secbot/proc/add_arrest_component()
AddComponent(/datum/component/stun_n_cuff,\
stun_sound = stun_sound,\
post_stun_callback = CALLBACK(src, PROC_REF(post_stun)),\
post_arrest_callback = CALLBACK(src, PROC_REF(post_arrest)),\
handcuff_type = cuff_type,\
)