mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 06:00:16 +01:00
Merge branch 'master' of github.com:tgstation/tgstation into upstream-2026-06-23
This commit is contained in:
@@ -82,4 +82,4 @@
|
||||
to_chat(src,
|
||||
html = "[link] [message]",
|
||||
avoid_highlighting = speaker == src)
|
||||
return TRUE
|
||||
return HEAR_HEARD | HEAR_UNDERSTOOD
|
||||
|
||||
@@ -302,7 +302,7 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
|
||||
/mob/eye,
|
||||
/mob/living/basic/boss,
|
||||
/mob/living/basic/regal_rat,
|
||||
/mob/living/simple_animal/bot,
|
||||
/mob/living/basic/bot,
|
||||
/mob/living/simple_animal/hostile/megafauna,
|
||||
))
|
||||
if(!is_type_in_typecache(potential_mob_poi, mob_allowed_typecache) && !potential_mob_poi.GetComponent(/datum/component/deadchat_control))
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
initial_language_holder = /datum/language_holder/empty
|
||||
can_buckle_to = FALSE
|
||||
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 0, OXY = 1)
|
||||
pull_force = 0
|
||||
pull_force = MOVE_FORCE_NONE
|
||||
/// Size of cloud produced from a dying spore
|
||||
var/death_cloud_size = BLOBMOB_CLOUD_NONE
|
||||
var/loot = /obj/item/food/spore_sack
|
||||
|
||||
@@ -12,16 +12,17 @@ Difficulty: Medium
|
||||
/mob/living/basic/boss/blood_drunk_miner
|
||||
name = "blood-drunk miner"
|
||||
desc = "A miner destined to wander forever, engaged in an endless hunt."
|
||||
health = 900
|
||||
maxHealth = 900
|
||||
health = 1300
|
||||
maxHealth = 1300
|
||||
icon_state = "miner"
|
||||
icon_living = "miner"
|
||||
base_icon_state = "miner"
|
||||
icon = 'icons/mob/simple/broadMobs.dmi'
|
||||
health_doll_icon = "miner"
|
||||
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_SPECIAL|MOB_MINING
|
||||
light_color = COLOR_LIGHT_GRAYISH_RED
|
||||
speak_emote = list("roars")
|
||||
speed = 3
|
||||
speed = 2.5
|
||||
pixel_x = -16
|
||||
base_pixel_x = -16
|
||||
basic_mob_flags = DEL_ON_DEATH
|
||||
@@ -43,7 +44,7 @@ Difficulty: Medium
|
||||
victor_memory_type = /datum/memory/megafauna_slayer
|
||||
|
||||
crusher_loot = list(/obj/item/crusher_trophy/miner_eye, /obj/item/knife/hunting/wildhunter)
|
||||
regular_loot = list(/obj/item/melee/cleaving_saw, /obj/item/gun/energy/recharge/kinetic_accelerator)
|
||||
regular_loot = list(/obj/item/melee/cleaving_saw, /obj/item/gun/energy/recharge/kinetic_accelerator/bdm)
|
||||
|
||||
/// Their little saw
|
||||
var/obj/item/melee/cleaving_saw/miner/miner_saw
|
||||
@@ -87,9 +88,9 @@ Difficulty: Medium
|
||||
|
||||
/// Returns a list of innate actions for the blood-drunk miner.
|
||||
/mob/living/basic/boss/blood_drunk_miner/proc/get_innate_actions()
|
||||
var/static/list/innate_abilities = list(
|
||||
/datum/action/cooldown/mob_cooldown/dash = BB_BDM_DASH_ABILITY,
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator = BB_BDM_KINETIC_ACCELERATOR_ABILITY,
|
||||
var/list/innate_abilities = list(
|
||||
/datum/action/cooldown/mob_cooldown/charge/basic_charge/blood_drunk_miner = BB_BDM_DASH_ABILITY,
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/kinetic_accelerator = BB_BDM_KINETIC_ACCELERATOR_ABILITY,
|
||||
/datum/action/cooldown/mob_cooldown/dash_attack = BB_BDM_DASH_ATTACK_ABILITY,
|
||||
/datum/action/cooldown/mob_cooldown/transform_weapon = BB_BDM_TRANSFORM_WEAPON_ABILITY,
|
||||
)
|
||||
@@ -101,6 +102,13 @@ Difficulty: Medium
|
||||
|
||||
INVOKE_ASYNC(ai_controller.blackboard[BB_BDM_TRANSFORM_WEAPON_ABILITY], TYPE_PROC_REF(/datum/action, Trigger), src, NONE)
|
||||
|
||||
/mob/living/basic/boss/blood_drunk_miner/proc/transform_saw()
|
||||
miner_saw.attack_self(src)
|
||||
var/saw_open = HAS_TRAIT(miner_saw, TRAIT_TRANSFORM_ACTIVE)
|
||||
rapid_melee_hits = saw_open ? 3 : 5
|
||||
icon_state = "[base_icon_state][saw_open ? "_transformed":""]"
|
||||
icon_living = "[base_icon_state][saw_open ? "_transformed":""]"
|
||||
|
||||
/mob/living/basic/boss/blood_drunk_miner/ex_act(severity, target)
|
||||
var/datum/action/cooldown/mob_cooldown/dash_ability = ai_controller.blackboard[BB_BDM_DASH_ABILITY]
|
||||
if(dash_ability.Trigger(target = target))
|
||||
@@ -145,28 +153,58 @@ Difficulty: Medium
|
||||
/// Namely, we just use the miner saw to rapidly hit the target multiple times
|
||||
/mob/living/basic/boss/blood_drunk_miner/proc/attack_override(mob/living/source, atom/target, proximity, modifiers)
|
||||
SIGNAL_HANDLER
|
||||
if(!istype(target, /mob/living))
|
||||
|
||||
if(!isliving(target))
|
||||
return
|
||||
|
||||
var/mob/living/victim = target
|
||||
if(should_devour(target))
|
||||
devour(target)
|
||||
if(should_devour(victim))
|
||||
devour(victim)
|
||||
return COMPONENT_HOSTILE_NO_ATTACK
|
||||
|
||||
do_chain_attack(victim, modifiers)
|
||||
return COMPONENT_HOSTILE_NO_ATTACK
|
||||
|
||||
/mob/living/basic/boss/blood_drunk_miner/proc/do_chain_attack(mob/living/victim, modifiers, sequence_hit = 1)
|
||||
if (!Adjacent(victim))
|
||||
post_attack_effects(victim, modifiers)
|
||||
return
|
||||
|
||||
changeNext_move(CLICK_CD_MELEE)
|
||||
victim.visible_message(
|
||||
span_danger("[src] slashes at [victim] with [p_their()] cleaving saw!"),
|
||||
span_userdanger("You are slashed at by [src]'s cleaving saw!"),
|
||||
)
|
||||
|
||||
var/datum/callback/melee_callback = CALLBACK(miner_saw, TYPE_PROC_REF(/obj/item/melee/cleaving_saw/miner, melee_attack_chain), src, victim, modifiers)
|
||||
var/delay = 0.2 SECONDS
|
||||
for(var/i in 1 to rapid_melee_hits)
|
||||
addtimer(melee_callback, (i - 1) * delay)
|
||||
var/delay = HAS_TRAIT(miner_saw, TRAIT_TRANSFORM_ACTIVE) ? 0.5 SECONDS : 0.3 SECONDS
|
||||
apply_status_effect(/datum/status_effect/saw_slashes_slowdown, delay)
|
||||
INVOKE_ASYNC(miner_saw, TYPE_PROC_REF(/obj/item/melee/cleaving_saw/miner, melee_attack_chain), src, victim, modifiers)
|
||||
|
||||
post_attack_effects(victim, modifiers)
|
||||
if (sequence_hit >= rapid_melee_hits)
|
||||
post_attack_effects(victim, modifiers)
|
||||
return
|
||||
|
||||
return COMPONENT_HOSTILE_NO_ATTACK
|
||||
addtimer(CALLBACK(src, PROC_REF(do_chain_attack), victim, modifiers, sequence_hit + 1), delay)
|
||||
|
||||
/datum/status_effect/saw_slashes_slowdown
|
||||
id = "saw_slashes_slowdown"
|
||||
alert_type = null
|
||||
status_type = STATUS_EFFECT_REFRESH
|
||||
|
||||
/datum/status_effect/saw_slashes_slowdown/on_creation(mob/living/new_owner, new_duration)
|
||||
duration = new_duration
|
||||
return ..()
|
||||
|
||||
/datum/status_effect/saw_slashes_slowdown/refresh(effect, new_duration)
|
||||
duration = new_duration
|
||||
|
||||
/datum/status_effect/saw_slashes_slowdown/on_apply()
|
||||
. = ..()
|
||||
owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/saw_slashes_slowdown)
|
||||
|
||||
/datum/status_effect/saw_slashes_slowdown/on_remove()
|
||||
. = ..()
|
||||
owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/saw_slashes_slowdown)
|
||||
|
||||
/// Hook for potential additional behaviors after attacking
|
||||
/mob/living/basic/boss/blood_drunk_miner/proc/post_attack_effects(mob/living/victim, list/modifiers)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/datum/action/cooldown/mob_cooldown/charge/basic_charge/blood_drunk_miner
|
||||
cooldown_time = 1.5 SECONDS
|
||||
charge_delay = 0.1 SECONDS
|
||||
shake_duration = 0.2 SECONDS // A bit longer so he shakes during the dash too
|
||||
charge_distance = 6
|
||||
// Don't stun ourselves or the target
|
||||
recoil_duration = -1
|
||||
knockdown_duration = -1
|
||||
destroy_objects = FALSE
|
||||
charge_damage = 0
|
||||
charge_speed = 0.3
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/charge/basic_charge/blood_drunk_miner/hit_target(atom/movable/source, atom/target, damage_dealt)
|
||||
. = ..()
|
||||
if(!isbasicmob(source) || !isliving(target))
|
||||
return
|
||||
var/mob/living/basic/basic_source = source
|
||||
basic_source.melee_attack(target, ignore_cooldown = TRUE)
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/transform_weapon
|
||||
name = "Transform Weapon"
|
||||
button_icon = 'icons/obj/mining_zones/artefacts.dmi'
|
||||
button_icon_state = "cleaving_saw"
|
||||
desc = "Transform weapon into a different state."
|
||||
cooldown_time = 5 SECONDS
|
||||
shared_cooldown = MOB_SHARED_COOLDOWN_2
|
||||
/// The max possible cooldown, cooldown is random between the default cooldown time and this
|
||||
var/max_cooldown_time = 10 SECONDS
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/transform_weapon/Activate(atom/target_atom)
|
||||
disable_cooldown_actions()
|
||||
do_transform()
|
||||
StartCooldown(rand(cooldown_time, max_cooldown_time), 0)
|
||||
enable_cooldown_actions()
|
||||
return TRUE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/transform_weapon/proc/do_transform()
|
||||
if(!istype(owner, /mob/living/basic/boss/blood_drunk_miner))
|
||||
return
|
||||
var/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner = owner
|
||||
blood_drunk_miner.transform_saw()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/kinetic_accelerator
|
||||
name = "Fire Kinetic Accelerator"
|
||||
desc = "Fires a kinetic accelerator projectile at the target."
|
||||
button_icon = 'icons/obj/weapons/guns/energy.dmi'
|
||||
button_icon_state = "kineticgun"
|
||||
cooldown_time = 1.5 SECONDS
|
||||
projectile_type = /obj/projectile/kinetic/miner
|
||||
projectile_sound = 'sound/items/weapons/kinetic_accel.ogg'
|
||||
shot_count = 3
|
||||
shot_delay = 0.15 SECONDS
|
||||
default_projectile_spread = 10
|
||||
can_move = FALSE
|
||||
/// Delay for the alert
|
||||
var/alert_delay = 0.5 SECONDS
|
||||
/// Delay before we start shooting during which we cannot move
|
||||
var/prefire_delay = 0.2 SECONDS
|
||||
/// Delay before the user can move or act again after firing
|
||||
var/reload_delay = 0.1 SECONDS
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/kinetic_accelerator/Activate(atom/target_atom)
|
||||
owner.visible_message(span_danger("[owner] fires the proto-kinetic accelerator!"))
|
||||
owner.face_atom(target_atom)
|
||||
owner.do_alert_animation(alert_delay + (shot_count - 1) * shot_delay)
|
||||
disable_cooldown_actions()
|
||||
if (alert_delay > prefire_delay) // As to delay movement blocking
|
||||
SLEEP_CHECK_DEATH(alert_delay - prefire_delay, owner)
|
||||
return ..()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/kinetic_accelerator/attack_sequence(mob/living/firer, atom/target)
|
||||
SLEEP_CHECK_DEATH(prefire_delay, firer)
|
||||
. = ..()
|
||||
sleep(reload_delay)
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/kinetic_accelerator/shoot_projectile(atom/origin, atom/target, set_angle, mob/firer, projectile_spread, speed_multiplier, override_projectile_type, override_homing)
|
||||
. = ..()
|
||||
new /obj/effect/temp_visual/dir_setting/firing_effect(get_turf(firer), firer.dir)
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/kinetic_accelerator/long_burst
|
||||
shot_count = 5
|
||||
shot_delay = 0.1 SECONDS
|
||||
@@ -12,7 +12,7 @@
|
||||
BB_BDM_RANGED_ATTACK_COOLDOWN = 0,
|
||||
)
|
||||
|
||||
movement_delay = 0.3 SECONDS
|
||||
movement_delay = 0.25 SECONDS
|
||||
ai_movement = /datum/ai_movement/basic_avoidance
|
||||
idle_behavior = /datum/idle_behavior/idle_random_walk
|
||||
planning_subtrees = list(
|
||||
@@ -63,4 +63,4 @@
|
||||
return TRUE
|
||||
|
||||
/datum/ai_controller/blood_drunk_miner/doom
|
||||
movement_delay = 0.8 SECONDS
|
||||
movement_delay = 0.5 SECONDS
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
/// A slightly nerfed saw as the normal one is much too murdery.
|
||||
/obj/item/melee/cleaving_saw/miner
|
||||
force = 6
|
||||
open_force = 10
|
||||
|
||||
/obj/item/melee/cleaving_saw/miner/attack(mob/living/target, mob/living/carbon/human/user)
|
||||
target.add_stun_absorption(source = "miner", duration = 1 SECONDS, priority = INFINITY)
|
||||
return ..()
|
||||
force = 8
|
||||
open_force = 12
|
||||
|
||||
/obj/projectile/kinetic/miner
|
||||
damage = 20
|
||||
speed = 1.1
|
||||
damage = 18
|
||||
icon_state = "ka_tracer"
|
||||
range = 4
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#define HUNTER_DASH_PROBABILITY 12
|
||||
|
||||
/// heals slightly on melee hits
|
||||
/// Heals slightly on melee hits
|
||||
/mob/living/basic/boss/blood_drunk_miner/guidance
|
||||
|
||||
/mob/living/basic/boss/blood_drunk_miner/guidance/attack_override(mob/living/source, atom/target, proximity, modifiers)
|
||||
@@ -19,10 +19,11 @@
|
||||
if(!isnull(dash_attack))
|
||||
INVOKE_ASYNC(dash_attack, TYPE_PROC_REF(/datum/action, Trigger), src, NONE, victim)
|
||||
|
||||
/// Slow but constantly dashes and has longer barrages
|
||||
/mob/living/basic/boss/blood_drunk_miner/doom
|
||||
name = "hostile-environment miner"
|
||||
desc = "A miner destined to hop across dimensions for all eternity, hunting anomalous creatures."
|
||||
speed = 8
|
||||
speed = 5
|
||||
ranged_attack_cooldown_duration = 0.8 SECONDS
|
||||
ai_controller = /datum/ai_controller/blood_drunk_miner/doom
|
||||
|
||||
@@ -32,4 +33,17 @@
|
||||
if(!isnull(dash_ability))
|
||||
dash_ability.cooldown_time = 0.8 SECONDS
|
||||
|
||||
var/datum/action/cooldown/dash_attack = ai_controller.blackboard[BB_BDM_DASH_ATTACK_ABILITY]
|
||||
if(!isnull(dash_attack))
|
||||
dash_attack.cooldown_time = 4 SECONDS
|
||||
|
||||
/mob/living/basic/boss/blood_drunk_miner/doom/get_innate_actions()
|
||||
var/list/innate_abilities = list(
|
||||
/datum/action/cooldown/mob_cooldown/charge/basic_charge/blood_drunk_miner = BB_BDM_DASH_ABILITY,
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/kinetic_accelerator/long_burst = BB_BDM_KINETIC_ACCELERATOR_ABILITY,
|
||||
/datum/action/cooldown/mob_cooldown/dash_attack = BB_BDM_DASH_ATTACK_ABILITY,
|
||||
/datum/action/cooldown/mob_cooldown/transform_weapon = BB_BDM_TRANSFORM_WEAPON_ABILITY,
|
||||
)
|
||||
return innate_abilities
|
||||
|
||||
#undef HUNTER_DASH_PROBABILITY
|
||||
|
||||
@@ -196,7 +196,7 @@ GLOBAL_LIST_INIT(command_strings, list(
|
||||
/mob/living/basic/bot/proc/get_emagged_message()
|
||||
return get_policy(ROLE_EMAGGED_BOT) || "You are a malfunctioning bot! Disrupt everyone and cause chaos!"
|
||||
|
||||
/mob/living/basic/bot/proc/turn_on()
|
||||
/mob/living/basic/bot/proc/turn_on(mob/user)
|
||||
if(stat == DEAD)
|
||||
return FALSE
|
||||
set_mode_flags(bot_mode_flags | BOT_MODE_ON)
|
||||
|
||||
@@ -474,6 +474,9 @@
|
||||
|
||||
/mob/living/basic/bot/medbot/nukie/Initialize(mapload, new_skin)
|
||||
. = ..()
|
||||
var/datum/action/minimap/nuclear/tacmap_action = new
|
||||
tacmap_action.Grant(src)
|
||||
add_minimap_blip(src, MINIMAP_NUKEOP_BLIP, "mediborg")
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_DISARMED, PROC_REF(nuke_disarm))
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_ARMED, PROC_REF(nuke_arm))
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_DETONATING, PROC_REF(nuke_detonate))
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/mob/living/basic/bot/mulebot
|
||||
name = "\improper MULEbot"
|
||||
desc = "A Multiple Utility Load Effector bot."
|
||||
icon = 'icons/mob/silicon/aibots.dmi'
|
||||
icon_state = "mulebot0"
|
||||
base_icon_state = "mulebot"
|
||||
|
||||
light_color = "#ffcc99"
|
||||
light_power = 0.8
|
||||
|
||||
health = 50
|
||||
maxHealth = 50
|
||||
|
||||
damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, STAMINA = 0, OXY = 0)
|
||||
density = TRUE
|
||||
mob_size = MOB_SIZE_LARGE
|
||||
move_resist = MOVE_FORCE_STRONG
|
||||
animate_movement = SLIDE_STEPS
|
||||
speed = 3
|
||||
|
||||
combat_mode = TRUE
|
||||
|
||||
buckle_lying = 0
|
||||
buckle_prevents_pull = TRUE // No pulling loaded shit
|
||||
|
||||
bot_mode_flags = ~BOT_MODE_ROUNDSTART_POSSESSION
|
||||
req_one_access = list(ACCESS_ROBOTICS, ACCESS_CARGO)
|
||||
radio_key = /obj/item/encryptionkey/headset_cargo
|
||||
radio_channel = RADIO_CHANNEL_SUPPLY
|
||||
pass_flags = PASSFLAPS
|
||||
bot_type = MULE_BOT
|
||||
|
||||
additional_access = /datum/id_trim/job/cargo_technician
|
||||
path_image_color = "#7F5200"
|
||||
hud_type = /datum/hud/living/mulebot
|
||||
|
||||
hackables = "obstacle detection circuits"
|
||||
possessed_message = "You are a MULEbot! Do your best to make sure that packages get to their destination!"
|
||||
ai_controller = /datum/ai_controller/basic_controller/bot/mulebot
|
||||
|
||||
/// unique identifier in case there are multiple mulebots.
|
||||
var/id
|
||||
|
||||
/// what we're transporting
|
||||
var/atom/movable/load
|
||||
/// who's riding us
|
||||
var/mob/living/passenger
|
||||
|
||||
///flags of mulebot mode
|
||||
var/mulebot_delivery_flags = MULEBOT_RETURN_MODE | MULEBOT_AUTO_PICKUP_MODE | MULEBOT_REPORT_DELIVERY_MODE
|
||||
|
||||
///Internal Powercell
|
||||
var/obj/item/stock_parts/power_store/cell
|
||||
///How much power we use when we move.
|
||||
var/cell_move_power_usage = 0.0005 * STANDARD_CELL_CHARGE
|
||||
///The amount of steps we should take until we rest for a time.
|
||||
var/num_steps = 0
|
||||
|
||||
///The chance to be deleted and replaced by a different mule
|
||||
var/replacement_chance = 0.666
|
||||
///home destination, only used by mappers.
|
||||
var/home_destination = ""
|
||||
|
||||
/mob/living/basic/bot/mulebot/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
if(prob(replacement_chance) && mapload)
|
||||
new /mob/living/basic/bot/mulebot/paranormal(loc)
|
||||
return INITIALIZE_HINT_QDEL
|
||||
|
||||
set_wires(new /datum/wires/mulebot(src))
|
||||
var/obj/item/stock_parts/power_store/cell/upgraded/new_cell = new(src)
|
||||
assign_cell(new_cell)
|
||||
ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, "")
|
||||
AddElement(/datum/element/ridable, /datum/component/riding/creature/mulebot)
|
||||
ADD_TRAIT(src, TRAIT_NOMOBSWAP, INNATE_TRAIT)
|
||||
add_traits(list(TRAIT_NOMOBSWAP, TRAIT_COMBAT_MODE_LOCK), INNATE_TRAIT)
|
||||
RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_pre_move))
|
||||
|
||||
set_id(suffix || assign_random_name())
|
||||
suffix = null
|
||||
if(name == "\improper MULEbot")
|
||||
name = "\improper MULEbot [id]"
|
||||
set_home(get_turf(src))
|
||||
ai_controller.update_able_to_run()
|
||||
update_appearance()
|
||||
|
||||
/mob/living/basic/bot/mulebot/Destroy()
|
||||
UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
|
||||
unload()
|
||||
QDEL_NULL(cell)
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/bot/mulebot/proc/assign_cell(atom/new_cell)
|
||||
cell = new_cell
|
||||
var/atom/movable/screen/mob_charge/charge_hud = hud_used?.screen_objects[HUD_MULEBOT_CHARGE]
|
||||
charge_hud?.update_battery_overlay(new_cell)
|
||||
charge_hud?.calculate_charge()
|
||||
|
||||
|
||||
/mob/living/basic/bot/mulebot/attack_hand(mob/living/carbon/human/user, list/modifiers)
|
||||
if(bot_access_flags & BOT_COVER_MAINTS_OPEN && !HAS_AI_ACCESS(user))
|
||||
wires.interact(user)
|
||||
return
|
||||
if(wires.is_cut(WIRE_RX) && HAS_AI_ACCESS(user))
|
||||
return
|
||||
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/bot/mulebot/examine(mob/user)
|
||||
. = ..()
|
||||
if(bot_access_flags & BOT_COVER_MAINTS_OPEN)
|
||||
if(cell)
|
||||
. += span_notice("It has \a [cell] installed.")
|
||||
. += span_info("You can use a <b>crowbar</b> to remove it.")
|
||||
else
|
||||
. += span_notice("It has an empty compartment where a <b>power cell</b> can be installed.")
|
||||
if(load) //observer check is so we don't show the name of the ghost that's sitting on it to prevent metagaming who's ded.
|
||||
. += span_notice("\A [isobserver(load) ? "ghostly figure" : load] is on its load platform.")
|
||||
|
||||
/mob/living/basic/bot/mulebot/get_cell()
|
||||
return cell
|
||||
|
||||
/mob/living/basic/bot/mulebot/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE)
|
||||
if(!can_unarmed_attack())
|
||||
return
|
||||
if(isturf(target) && isturf(loc) && loc.Adjacent(target) && load)
|
||||
unload(get_dir(loc, target))
|
||||
else
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/bot/mulebot/turn_on(mob/user)
|
||||
if(bot_access_flags & BOT_COVER_MAINTS_OPEN)
|
||||
if(user)
|
||||
to_chat(user, span_warning("[src]'s maintenance panel is open!"))
|
||||
return FALSE
|
||||
if(!has_power())
|
||||
if(user)
|
||||
to_chat(user, span_warning("[src] has no power!"))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/bot/mulebot/update_icon_state() //if you change the icon_state names, please make sure to update /datum/wires/mulebot/on_pulse() as well. <3
|
||||
. = ..()
|
||||
icon_state = "[base_icon_state][(bot_mode_flags & BOT_MODE_ON) ? wires?.is_cut(WIRE_AVOIDANCE) : "0"]"
|
||||
|
||||
/mob/living/basic/bot/mulebot/update_overlays()
|
||||
. = ..()
|
||||
if(bot_access_flags & BOT_COVER_MAINTS_OPEN)
|
||||
. += "[base_icon_state]-hatch"
|
||||
if(isnull(load) || ismob(load)) //mob offsets and such are handled by the riding component / buckling
|
||||
return
|
||||
var/mutable_appearance/load_overlay = mutable_appearance(load.icon, load.icon_state, layer + 0.01)
|
||||
load_overlay.pixel_y = initial(load.pixel_y) + 11
|
||||
. += load_overlay
|
||||
|
||||
/mob/living/basic/bot/mulebot/proc/handle_buzzing(datum/move_loop/has_target/jps/frustrations/source, frustration_counter)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
update_bot_mode(new_mode = BOT_BLOCKED)
|
||||
var/buzz_mode = frustration_counter >= source.maximum_frustrations ? MULEBOT_MOOD_ANNOYED : MULEBOT_MOOD_SIGH
|
||||
buzz(buzz_mode)
|
||||
|
||||
/mob/living/basic/bot/mulebot/handle_loop_movement(atom/movable/source, atom/oldloc, dir, forced) //incase we start moving again after being previously blocked, update our mode
|
||||
. = ..()
|
||||
if(mode != BOT_BLOCKED)
|
||||
return
|
||||
var/obj/machinery/navbeacon/beacon = ai_controller.current_movement_target
|
||||
if(!istype(beacon))
|
||||
return
|
||||
var/intended_mode = beacon.location == ai_controller.blackboard[BB_MULEBOT_HOME_BEACON] ? BOT_GO_HOME : BOT_DELIVER
|
||||
update_bot_mode(new_mode = intended_mode)
|
||||
|
||||
///Noises that mulebots make
|
||||
/mob/living/basic/bot/mulebot/proc/buzz(type)
|
||||
switch(type)
|
||||
if(MULEBOT_MOOD_SIGH)
|
||||
audible_message(span_hear("[src] makes a sighing buzz."))
|
||||
playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 50, FALSE)
|
||||
if(MULEBOT_MOOD_ANNOYED)
|
||||
audible_message(span_hear("[src] makes an annoyed buzzing sound."))
|
||||
playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, FALSE)
|
||||
if(MULEBOT_MOOD_DELIGHT)
|
||||
audible_message(span_hear("[src] makes a delighted ping!"))
|
||||
playsound(src, 'sound/machines/ping.ogg', 50, FALSE)
|
||||
if(MULEBOT_MOOD_CHIME)
|
||||
audible_message(span_hear("[src] makes a chiming sound!"))
|
||||
playsound(src, 'sound/machines/chime.ogg', 50, FALSE)
|
||||
flick("[base_icon_state]1", src)
|
||||
|
||||
/// returns true if the bot is fully powered.
|
||||
/mob/living/basic/bot/mulebot/proc/has_power()
|
||||
return cell && cell.charge > 0 && (!wires.is_cut(WIRE_POWER1) && !wires.is_cut(WIRE_POWER2))
|
||||
@@ -0,0 +1,145 @@
|
||||
/datum/ai_controller/basic_controller/bot/mulebot
|
||||
blackboard = list(
|
||||
BB_SALUTE_MESSAGES = list(
|
||||
"blinks its light in appreciation towards",
|
||||
)
|
||||
)
|
||||
ai_movement = /datum/ai_movement/jps/bot/mulebot
|
||||
max_target_distance = AI_MULEBOT_PATH_LENGTH
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/respond_to_summon,
|
||||
/datum/ai_planning_subtree/salute_authority,
|
||||
/datum/ai_planning_subtree/attempt_delivery,
|
||||
/datum/ai_planning_subtree/find_delivery_beacon,
|
||||
)
|
||||
reset_keys = list(
|
||||
BB_BOT_SUMMON_TARGET,
|
||||
BB_MULEBOT_DESTINATION_BEACON,
|
||||
BB_MULEBOT_TRAVEL_TARGET,
|
||||
)
|
||||
|
||||
/datum/ai_controller/basic_controller/bot/mulebot/get_able_to_run()
|
||||
var/mob/living/basic/bot/mulebot/bot_pawn = pawn
|
||||
if(!bot_pawn.has_power())
|
||||
return AI_UNABLE_TO_RUN
|
||||
return ..()
|
||||
|
||||
/datum/ai_controller/basic_controller/bot/mulebot/setup_able_to_run()
|
||||
. = ..()
|
||||
var/mob/living/basic/bot/my_bot = pawn
|
||||
var/static/list/wire_signals = list(
|
||||
COMSIG_MEND_WIRE(WIRE_POWER1), //this framework is insane
|
||||
COMSIG_MEND_WIRE(WIRE_POWER2),
|
||||
COMSIG_CUT_WIRE(WIRE_POWER1),
|
||||
COMSIG_CUT_WIRE(WIRE_POWER2),
|
||||
)
|
||||
RegisterSignals(my_bot.wires, wire_signals, PROC_REF(update_able_to_run))
|
||||
var/static/list/content_signals = list(
|
||||
COMSIG_ATOM_ENTERED,
|
||||
COMSIG_ATOM_EXITED,
|
||||
)
|
||||
RegisterSignals(my_bot, content_signals, PROC_REF(update_able_to_run))
|
||||
|
||||
/datum/ai_planning_subtree/find_delivery_beacon
|
||||
///what behavior do we use to seek beacons
|
||||
var/find_beacon_behaviour = /datum/ai_behavior/find_delivery_beacon
|
||||
|
||||
/datum/ai_planning_subtree/find_delivery_beacon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn
|
||||
if(bot_pawn.wires.is_cut(WIRE_BEACON))
|
||||
return
|
||||
|
||||
if(!controller.blackboard_key_exists(BB_MULEBOT_TRAVEL_TARGET))
|
||||
controller.queue_behavior(find_beacon_behaviour, BB_MULEBOT_TRAVEL_TARGET)
|
||||
|
||||
/datum/ai_behavior/find_delivery_beacon
|
||||
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
||||
|
||||
/datum/ai_behavior/find_delivery_beacon/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn
|
||||
var/atom/delivery_beacon
|
||||
|
||||
var/beacon_tag = null
|
||||
|
||||
switch(bot_pawn.mode)
|
||||
if(BOT_DELIVER)
|
||||
beacon_tag = controller.blackboard[BB_MULEBOT_DESTINATION_BEACON]
|
||||
if(BOT_GO_HOME)
|
||||
beacon_tag = controller.blackboard[BB_MULEBOT_HOME_BEACON]
|
||||
else
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
for(var/obj/machinery/navbeacon/beacon as anything in GLOB.deliverybeacons)
|
||||
if(beacon.location == beacon_tag)
|
||||
delivery_beacon = beacon
|
||||
break
|
||||
|
||||
if(isnull(delivery_beacon))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
controller.set_blackboard_key(BB_MULEBOT_TRAVEL_TARGET, delivery_beacon)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/travel_towards/delivery_beacon
|
||||
new_movement_type = /datum/ai_movement/jps/bot/mulebot
|
||||
|
||||
/datum/ai_planning_subtree/attempt_delivery
|
||||
///behavior we use to unload crates
|
||||
var/delivery_behaviour = /datum/ai_behavior/handle_delivery
|
||||
|
||||
/datum/ai_planning_subtree/attempt_delivery/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if(!controller.blackboard_key_exists(BB_MULEBOT_TRAVEL_TARGET))
|
||||
return
|
||||
|
||||
controller.queue_behavior(delivery_behaviour, BB_MULEBOT_TRAVEL_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
/datum/ai_behavior/handle_delivery
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
|
||||
|
||||
/datum/ai_behavior/handle_delivery/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/handle_delivery/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
var/obj/machinery/navbeacon/beacon = controller.blackboard[target_key]
|
||||
var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn
|
||||
|
||||
var/load_direction = beacon.codes[NAVBEACON_DELIVERY_DIRECTION] // this will be the load/unload dir
|
||||
if(!load_direction)
|
||||
load_direction = beacon.dir // fallback
|
||||
|
||||
load_direction = text2num(load_direction)
|
||||
|
||||
if(bot_pawn.load)
|
||||
if(bot_pawn.mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE)
|
||||
bot_pawn.radio_channel = RADIO_CHANNEL_SUPPLY //Supply channel
|
||||
bot_pawn.buzz(MULEBOT_MOOD_CHIME)
|
||||
bot_pawn.speak("Destination [RUNECHAT_BOLD("[beacon.location]")] reached. Unloading [bot_pawn.load].", bot_pawn.radio_channel)
|
||||
bot_pawn.unload(load_direction)
|
||||
|
||||
else
|
||||
if(bot_pawn.mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE) // find a crate
|
||||
var/atom/movable/atom_to_pick_up
|
||||
if(bot_pawn.wires.is_cut(WIRE_LOADCHECK)) // if hacked, load first unanchored thing we find
|
||||
for(var/atom/movable/target_atom in get_step(bot_pawn.loc, load_direction))
|
||||
if(!target_atom.anchored)
|
||||
atom_to_pick_up = target_atom
|
||||
break
|
||||
else // otherwise, look for crates only
|
||||
atom_to_pick_up = locate(/obj/structure/closet/crate) in get_step(bot_pawn.loc, load_direction)
|
||||
if(atom_to_pick_up?.Adjacent(bot_pawn))
|
||||
bot_pawn.load(atom_to_pick_up)
|
||||
if(bot_pawn.mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE)
|
||||
bot_pawn.speak("Now loading [bot_pawn.load] at [RUNECHAT_BOLD("[get_area_name(bot_pawn)]")].", bot_pawn.radio_channel)
|
||||
|
||||
if((bot_pawn.mulebot_delivery_flags & MULEBOT_RETURN_MODE) && controller.blackboard[BB_MULEBOT_HOME_BEACON] && controller.blackboard[BB_MULEBOT_HOME_BEACON] != beacon.location)
|
||||
bot_pawn.update_bot_mode(new_mode = BOT_GO_HOME)
|
||||
controller.clear_blackboard_key(BB_MULEBOT_TRAVEL_TARGET)
|
||||
else
|
||||
bot_pawn.bot_reset() // otherwise go idle
|
||||
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
@@ -0,0 +1,116 @@
|
||||
/mob/living/basic/bot/mulebot/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "Mule", name)
|
||||
ui.open()
|
||||
|
||||
/mob/living/basic/bot/mulebot/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["powerStatus"] = bot_mode_flags & BOT_MODE_ON
|
||||
data["locked"] = bot_access_flags & BOT_COVER_LOCKED
|
||||
data["siliconUser"] = HAS_SILICON_ACCESS(user)
|
||||
data["mode"] = mode ? "[mode]" : "Ready"
|
||||
data["modeStatus"] = ""
|
||||
switch(mode)
|
||||
if(BOT_IDLE, BOT_DELIVER, BOT_GO_HOME)
|
||||
data["modeStatus"] = "good"
|
||||
if(BOT_BLOCKED, BOT_NAV, BOT_WAIT_FOR_NAV)
|
||||
data["modeStatus"] = "average"
|
||||
if(BOT_NO_ROUTE)
|
||||
data["modeStatus"] = "bad"
|
||||
data["load"] = get_load_name()
|
||||
data["destination"] = ai_controller.blackboard[BB_MULEBOT_DESTINATION_BEACON]
|
||||
data["homeDestination"] = ai_controller.blackboard[BB_MULEBOT_HOME_BEACON]
|
||||
data["destinationsList"] = GLOB.deliverybeacontags
|
||||
data["cellPercent"] = cell?.percent()
|
||||
data["autoReturn"] = mulebot_delivery_flags & MULEBOT_RETURN_MODE
|
||||
data["autoPickup"] = mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE
|
||||
data["reportDelivery"] = mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE
|
||||
data["botId"] = id
|
||||
data["allowPossession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT
|
||||
data["possessionEnabled"] = can_be_possessed
|
||||
data["paiInserted"] = !!paicard
|
||||
return data
|
||||
|
||||
/mob/living/basic/bot/mulebot/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
|
||||
|
||||
bot_control(action, user, params)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/bot/mulebot/bot_control(command, mob/user, list/params = list(), pda = FALSE)
|
||||
if(pda && wires.is_cut(WIRE_RX)) // MULE wireless is controlled by wires.
|
||||
return
|
||||
|
||||
switch(command)
|
||||
if("stop")
|
||||
if(mode != BOT_IDLE)
|
||||
bot_reset()
|
||||
if("go")
|
||||
if(mode == BOT_IDLE)
|
||||
start()
|
||||
if("home")
|
||||
if(mode == BOT_IDLE || mode == BOT_DELIVER)
|
||||
start_home()
|
||||
if("destination")
|
||||
var/new_dest
|
||||
if(pda)
|
||||
new_dest = tgui_input_list(user, "Enter Destination", "Mulebot Settings", GLOB.deliverybeacontags, ai_controller.blackboard[BB_MULEBOT_DESTINATION_BEACON])
|
||||
else
|
||||
new_dest = params["value"]
|
||||
if(new_dest)
|
||||
set_destination(new_dest)
|
||||
if("setid")
|
||||
var/new_id = tgui_input_text(user, "Enter ID", "ID Assignment", id, max_length = MAX_NAME_LEN)
|
||||
if(new_id)
|
||||
set_id(new_id)
|
||||
name = "\improper MULEbot [new_id]"
|
||||
if("sethome")
|
||||
var/new_home = tgui_input_list(user, "Enter Home", "Mulebot Settings", GLOB.deliverybeacontags, ai_controller.blackboard[BB_MULEBOT_HOME_BEACON])
|
||||
if(new_home)
|
||||
ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, new_home)
|
||||
if("unload")
|
||||
if(load && mode != BOT_HUNT)
|
||||
unload()
|
||||
if("autoret")
|
||||
mulebot_delivery_flags ^= MULEBOT_RETURN_MODE
|
||||
if("autopick")
|
||||
mulebot_delivery_flags ^= MULEBOT_AUTO_PICKUP_MODE
|
||||
if("report")
|
||||
mulebot_delivery_flags ^= MULEBOT_REPORT_DELIVERY_MODE
|
||||
|
||||
/mob/living/basic/bot/mulebot/proc/start()
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return
|
||||
if(ai_controller.blackboard[BB_MULEBOT_DESTINATION_BEACON] == ai_controller.blackboard[BB_MULEBOT_HOME_BEACON])
|
||||
update_bot_mode(new_mode = BOT_GO_HOME)
|
||||
else
|
||||
update_bot_mode(new_mode = BOT_DELIVER)
|
||||
|
||||
/mob/living/basic/bot/mulebot/proc/start_home()
|
||||
set_destination(ai_controller.blackboard[BB_MULEBOT_HOME_BEACON])
|
||||
update_bot_mode(new_mode = BOT_GO_HOME)
|
||||
|
||||
/mob/living/basic/bot/mulebot/proc/set_destination(new_destination)
|
||||
ai_controller.set_blackboard_key(BB_MULEBOT_DESTINATION_BEACON, new_destination)
|
||||
|
||||
/mob/living/basic/bot/mulebot/proc/set_home(turf/home_loc)
|
||||
if(home_destination)
|
||||
ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, home_destination)
|
||||
home_destination = null
|
||||
if(!istype(home_loc))
|
||||
CRASH("MULEbot [id] was requested to set a home location to [home_loc ? "an invalid home loc ([home_loc.type])" : "null"]")
|
||||
|
||||
var/obj/machinery/navbeacon/home_beacon = locate() in home_loc
|
||||
if(isnull(home_beacon))
|
||||
ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, "")
|
||||
return
|
||||
ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, home_beacon.location)
|
||||
log_transport("[id]: MULEbot successfuly set home location to ID [home_beacon.location] at [home_beacon.x], [home_beacon.y], [home_beacon.z]")
|
||||
|
||||
///Sets the new ID of the mulebot
|
||||
/mob/living/basic/bot/mulebot/proc/set_id(new_id)
|
||||
id = new_id
|
||||
@@ -0,0 +1,117 @@
|
||||
/mob/living/basic/bot/mulebot/execute_resist()
|
||||
. = ..()
|
||||
if(load)
|
||||
unload()
|
||||
|
||||
/mob/living/basic/bot/mulebot/Exited(atom/movable/gone, direction)
|
||||
. = ..()
|
||||
if(gone == load)
|
||||
unload()
|
||||
if(gone == cell)
|
||||
turn_off()
|
||||
assign_cell()
|
||||
cell = null
|
||||
set_cell_hud()
|
||||
|
||||
/mob/living/basic/bot/mulebot/Entered(obj/item/stock_parts/power_store/cell/arrived, atom/old_loc, list/atom/old_locs)
|
||||
. = ..()
|
||||
if(istype(arrived) && isnull(cell))
|
||||
assign_cell(arrived)
|
||||
|
||||
// mousedrop a crate to load the bot
|
||||
// can load anything if hacked
|
||||
/mob/living/basic/bot/mulebot/mouse_drop_receive(atom/movable/atom_to_load, mob/user, params)
|
||||
if(!isliving(user))
|
||||
return
|
||||
|
||||
if(!istype(atom_to_load) || isdead(atom_to_load) || iseyemob(atom_to_load) || istype(atom_to_load, /obj/effect/dummy/phased_mob))
|
||||
return
|
||||
|
||||
load(atom_to_load)
|
||||
|
||||
/mob/living/basic/bot/mulebot/post_unbuckle_mob(mob/living/M)
|
||||
load = null
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/bot/mulebot/relaymove(mob/living/user, direction)
|
||||
if(user.incapacitated)
|
||||
return
|
||||
if(load == user)
|
||||
unload()
|
||||
|
||||
/mob/living/basic/bot/mulebot/remove_air(amount) //To prevent riders suffocating
|
||||
return loc ? loc.remove_air(amount) : null
|
||||
|
||||
/// Called to load an atom on the mulebot, which is usually a crate, unless if hacked
|
||||
/mob/living/basic/bot/mulebot/proc/load(atom/movable/atom_to_load)
|
||||
if(load || atom_to_load.anchored)
|
||||
return
|
||||
|
||||
if(!isturf(atom_to_load.loc)) //To prevent the loading from stuff from someone's inventory or screen icons.
|
||||
return
|
||||
|
||||
var/obj/structure/closet/crate/crate = atom_to_load
|
||||
if(!istype(crate))
|
||||
if(!wires.is_cut(WIRE_LOADCHECK))
|
||||
buzz(MULEBOT_MOOD_SIGH)
|
||||
return // if not hacked, only allow crates to be loaded
|
||||
crate = null
|
||||
|
||||
if(crate || isobj(atom_to_load))
|
||||
var/obj/object_to_load = atom_to_load
|
||||
if(object_to_load.has_buckled_mobs() || (locate(/mob) in atom_to_load)) //can't load non crates objects with mobs buckled to it or inside it.
|
||||
buzz(MULEBOT_MOOD_SIGH)
|
||||
return
|
||||
|
||||
if(crate)
|
||||
crate.close() //make sure the crate is closed
|
||||
|
||||
object_to_load.forceMove(src)
|
||||
|
||||
else if(isliving(atom_to_load))
|
||||
if(!load_mob(atom_to_load)) //forceMove() is handled in buckling
|
||||
return
|
||||
|
||||
load = atom_to_load
|
||||
update_appearance()
|
||||
|
||||
///resolves the name to display for the loaded mob. primarily needed for the paranormal subtype since we don't want to show the name of ghosts riding it.
|
||||
/mob/living/basic/bot/mulebot/proc/get_load_name()
|
||||
return load ? load.name : null
|
||||
|
||||
///Loads a mob onto the mulebot
|
||||
/mob/living/basic/bot/mulebot/proc/load_mob(mob/living/mob_to_load)
|
||||
can_buckle = TRUE
|
||||
if(buckle_mob(mob_to_load))
|
||||
passenger = mob_to_load
|
||||
load = mob_to_load
|
||||
can_buckle = FALSE
|
||||
return TRUE
|
||||
|
||||
// called to unload the bot
|
||||
// argument is optional direction to unload
|
||||
// if zero or null, unload at bot's location
|
||||
/mob/living/basic/bot/mulebot/proc/unload(dirn)
|
||||
if(QDELETED(load))
|
||||
if(load) //if our thing was qdel'd, there's likely a leftover reference. just clear it and remove the overlay. we'll let the bot keep moving around to prevent it abruptly stopping somewhere.
|
||||
load = null
|
||||
update_appearance()
|
||||
return
|
||||
|
||||
update_bot_mode(new_mode = BOT_IDLE)
|
||||
|
||||
var/atom/movable/cached_load = load //cache the load since unbuckling mobs clears the var.
|
||||
|
||||
unbuckle_all_mobs()
|
||||
|
||||
if(load) //don't have to do any of this for mobs.
|
||||
cached_load.forceMove(loc)
|
||||
cached_load.pixel_y = initial(cached_load.pixel_y)
|
||||
cached_load.layer = initial(cached_load.layer)
|
||||
SET_PLANE_EXPLICIT(cached_load, initial(cached_load.plane), src)
|
||||
load = null
|
||||
|
||||
if(dirn) //move the thing to the delivery point.
|
||||
cached_load.Move(get_step(loc,dirn), dirn)
|
||||
|
||||
update_appearance()
|
||||
@@ -0,0 +1,74 @@
|
||||
/mob/living/basic/bot/mulebot/proc/set_cell_hud()
|
||||
if(!has_power())
|
||||
set_hud_image_state(DIAG_BATT_HUD, "hudnobatt")
|
||||
return
|
||||
|
||||
var/atom/movable/screen/mob_charge/charge_hud = hud_used?.screen_objects[HUD_MULEBOT_CHARGE]
|
||||
charge_hud?.calculate_charge()
|
||||
set_hud_image_state(DIAG_BATT_HUD, "hudbatt[RoundDiagBar(cell.charge/cell.maxcharge)]")
|
||||
|
||||
/atom/movable/screen/mob_charge
|
||||
icon = 'icons/obj/machines/cell_charger.dmi'
|
||||
icon_state = "ccharger"
|
||||
screen_loc = ui_stamina
|
||||
///used to find the overlay for charger icon
|
||||
var/current_charge_level = 4
|
||||
///dynamic, based on what cell our nulebot's using
|
||||
var/image/battery_overlay
|
||||
///maptext that displays charge in numbers
|
||||
var/image/charge_overlay
|
||||
///is there a mouse on us
|
||||
var/hovering = FALSE
|
||||
|
||||
/atom/movable/screen/mob_charge/proc/update_battery_overlay(atom/target_battery)
|
||||
var/obj/item/stock_parts/power_store/cell/my_cell = target_battery || (locate() in get_mob())
|
||||
if(isnull(my_cell))
|
||||
battery_overlay = null
|
||||
else
|
||||
battery_overlay = image(icon = my_cell.icon, icon_state = my_cell.icon_state, loc = src, layer = src.layer + 0.1)
|
||||
update_appearance(UPDATE_ICON)
|
||||
|
||||
/atom/movable/screen/mob_charge/proc/calculate_charge()
|
||||
var/obj/item/stock_parts/power_store/cell/my_battery = locate() in get_mob()
|
||||
var/charge_value = isnull(my_battery) ? 0 : round(my_battery.charge/my_battery.maxcharge * 100 , 1)
|
||||
current_charge_level = round(charge_value * 4 / 100)
|
||||
charge_overlay.maptext = MAPTEXT("<div align='center' valign='middle' style='position:relative'>[charge_value]%</div>")
|
||||
update_appearance(UPDATE_ICON)
|
||||
|
||||
/atom/movable/screen/mob_charge/New(loc, ...)
|
||||
. = ..()
|
||||
charge_overlay = image(loc = src, layer = src.layer+0.2, pixel_y = -5)
|
||||
update_battery_overlay()
|
||||
|
||||
/atom/movable/screen/mob_charge/Destroy()
|
||||
charge_overlay = null
|
||||
battery_overlay = null
|
||||
return ..()
|
||||
|
||||
/atom/movable/screen/mob_charge/update_overlays()
|
||||
. = ..()
|
||||
. += mutable_appearance(icon, "ccharger-o[current_charge_level]")
|
||||
if(battery_overlay)
|
||||
. |= battery_overlay
|
||||
if(hovering)
|
||||
. |= charge_overlay
|
||||
|
||||
/atom/movable/screen/mob_charge/MouseEntered(location,control,params)
|
||||
if(usr != get_mob())
|
||||
return
|
||||
. = ..()
|
||||
hovering = TRUE
|
||||
calculate_charge()
|
||||
|
||||
/atom/movable/screen/mob_charge/MouseExited(location, control, params)
|
||||
if(usr != get_mob())
|
||||
return
|
||||
. = ..()
|
||||
hovering = FALSE
|
||||
update_appearance(UPDATE_ICON)
|
||||
|
||||
/datum/hud/living/mulebot
|
||||
|
||||
/datum/hud/living/mulebot/initialize_screen_objects()
|
||||
. = ..()
|
||||
add_screen_object(/atom/movable/screen/mob_charge, HUD_MULEBOT_CHARGE, HUD_GROUP_INFO)
|
||||
@@ -0,0 +1,72 @@
|
||||
/mob/living/basic/bot/mulebot/MobBump(mob/bumped_mob) // called when the bot bumps into a mob
|
||||
if(mind || !isliving(bumped_mob)) //if there's a sentience controlling the bot, they aren't allowed to harm folks.
|
||||
return ..()
|
||||
var/mob/living/bumped_living = bumped_mob
|
||||
if(wires.is_cut(WIRE_AVOIDANCE)) // usually just bumps, but if the avoidance wire is cut, knocks them over.
|
||||
if(iscyborg(bumped_living))
|
||||
visible_message(span_danger("[src] bumps into [bumped_living]!"))
|
||||
else if(bumped_living.Knockdown(8 SECONDS))
|
||||
log_combat(src, bumped_living, "knocked down")
|
||||
visible_message(span_danger("[src] knocks over [bumped_living]!"))
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/bot/mulebot/on_bot_movement(atom/movable/source, atom/oldloc, dir, forced)
|
||||
cell?.use(cell_move_power_usage)
|
||||
set_cell_hud()
|
||||
|
||||
if(has_gravity())
|
||||
for(var/mob/living/carbon/human/future_pancake in loc)
|
||||
if(future_pancake.body_position == LYING_DOWN)
|
||||
run_over(future_pancake)
|
||||
|
||||
return ..()
|
||||
|
||||
///Checks if the bot is on or if it has charge
|
||||
/mob/living/basic/bot/mulebot/proc/on_pre_move()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return COMPONENT_MOB_BOT_BLOCK_PRE_STEP
|
||||
|
||||
if((cell && (cell.charge < cell_move_power_usage)) || !has_power())
|
||||
turn_off()
|
||||
return COMPONENT_MOB_BOT_BLOCK_PRE_STEP
|
||||
|
||||
// when mulebot is in the same loc
|
||||
/mob/living/basic/bot/mulebot/proc/run_over(mob/living/carbon/human/crushed)
|
||||
if (!(bot_access_flags & BOT_COVER_EMAGGED) && !wires.is_cut(WIRE_AVOIDANCE))
|
||||
if (!has_status_effect(/datum/status_effect/careful_driving))
|
||||
crushed.visible_message(span_notice("[src] slows down to avoid crushing [crushed]."))
|
||||
apply_status_effect(/datum/status_effect/careful_driving)
|
||||
return // Player mules must be emagged before they can trample
|
||||
|
||||
log_combat(src, crushed, "run over", addition = "(DAMTYPE: [uppertext(BRUTE)])")
|
||||
crushed.visible_message(
|
||||
span_danger("[src] drives over [crushed]!"),
|
||||
span_userdanger("[src] drives over you!"),
|
||||
)
|
||||
|
||||
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
|
||||
|
||||
var/damage = rand(5, 15)
|
||||
var/static/list/zone_damages = list(
|
||||
BODY_ZONE_HEAD = 2,
|
||||
BODY_ZONE_CHEST = 2,
|
||||
BODY_ZONE_L_LEG = 0.5,
|
||||
BODY_ZONE_R_LEG = 0.5,
|
||||
BODY_ZONE_L_ARM = 0.5,
|
||||
BODY_ZONE_R_ARM = 0.5,
|
||||
)
|
||||
for(var/body_zone in zone_damages)
|
||||
crushed.apply_damage(zone_damages[body_zone] * damage, BRUTE, body_zone, run_armor_check(body_zone, MELEE))
|
||||
|
||||
add_mob_blood(crushed)
|
||||
|
||||
var/turf/below_us = get_turf(src)
|
||||
below_us.add_mob_blood(crushed)
|
||||
|
||||
AddComponent(/datum/component/blood_walk, \
|
||||
blood_type = /obj/effect/decal/cleanable/blood/tracks, \
|
||||
target_dir_change = TRUE, \
|
||||
transfer_blood_dna = TRUE, \
|
||||
max_blood = 4)
|
||||
@@ -0,0 +1,61 @@
|
||||
/mob/living/basic/bot/mulebot/paranormal
|
||||
name = "\improper GHOULbot"
|
||||
desc = "A rather ghastly looking... Multiple Utility Load Effector bot? It only seems to accept paranormal forces, and for this reason is fucking useless."
|
||||
icon_state = "paranormalmulebot0"
|
||||
base_icon_state = "paranormalmulebot"
|
||||
///avoid the utterly miniscule chance of infinite looping
|
||||
replacement_chance = 0
|
||||
|
||||
/mob/living/basic/bot/mulebot/paranormal/update_overlays()
|
||||
. = ..()
|
||||
if(!isobserver(load))
|
||||
return
|
||||
var/mutable_appearance/ghost_overlay = mutable_appearance('icons/mob/simple/mob.dmi', "ghost", layer + 0.01) //use a generic ghost icon, otherwise you can metagame who's dead if they have a custom ghost set
|
||||
ghost_overlay.pixel_y = 12
|
||||
. += ghost_overlay
|
||||
|
||||
/mob/living/basic/bot/mulebot/paranormal/get_load_name() //Don't reveal the name of ghosts so we can't metagame who died and all that.
|
||||
. = ..()
|
||||
if(. && isobserver(load))
|
||||
return "Unknown"
|
||||
|
||||
/mob/living/basic/bot/mulebot/paranormal/load(atom/movable/movable_atom)
|
||||
if(load || movable_atom.anchored)
|
||||
return
|
||||
|
||||
if(!isturf(movable_atom.loc)) //To prevent the loading from stuff from someone's inventory or screen icons.
|
||||
return
|
||||
|
||||
if(isobserver(movable_atom))
|
||||
visible_message(span_warning("A ghostly figure appears on [src]!"))
|
||||
movable_atom.forceMove(src)
|
||||
RegisterSignal(movable_atom, COMSIG_MOVABLE_MOVED, PROC_REF(ghost_moved))
|
||||
|
||||
else if(!wires.is_cut(WIRE_LOADCHECK))
|
||||
buzz(MULEBOT_MOOD_SIGH)
|
||||
return // if not hacked, only allow ghosts to be loaded
|
||||
|
||||
else if(isobj(movable_atom))
|
||||
if(movable_atom.has_buckled_mobs() || (locate(/mob) in movable_atom)) //can't load non crates objects with mobs buckled to it or inside it.
|
||||
buzz(MULEBOT_MOOD_SIGH)
|
||||
return
|
||||
|
||||
if(istype(movable_atom, /obj/structure/closet/crate))
|
||||
var/obj/structure/closet/crate/crate = movable_atom
|
||||
crate.close() //make sure it's closed
|
||||
|
||||
movable_atom.forceMove(src)
|
||||
|
||||
else if(isliving(movable_atom) && !load_mob(movable_atom))
|
||||
return
|
||||
|
||||
load = movable_atom
|
||||
update_bot_mode(new_mode = BOT_IDLE)
|
||||
update_appearance()
|
||||
|
||||
///Handles the ghosts moving out from the mule
|
||||
/mob/living/basic/bot/mulebot/paranormal/proc/ghost_moved()
|
||||
SIGNAL_HANDLER
|
||||
visible_message(span_notice("The ghostly figure vanishes..."))
|
||||
UnregisterSignal(load, COMSIG_MOVABLE_MOVED)
|
||||
unload()
|
||||
@@ -0,0 +1,46 @@
|
||||
/mob/living/basic/bot/mulebot/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
. = ..()
|
||||
update_appearance()
|
||||
|
||||
/mob/living/basic/bot/mulebot/crowbar_act(mob/living/user, obj/item/tool)
|
||||
if(!(bot_access_flags & BOT_COVER_MAINTS_OPEN) || user.combat_mode)
|
||||
return
|
||||
if(!cell)
|
||||
to_chat(user, span_warning("[src] doesn't have a power cell!"))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
cell.add_fingerprint(user)
|
||||
user.visible_message(
|
||||
span_notice("[user] crowbars [cell] out from [src]."),
|
||||
span_notice("You pry [cell] out of [src]."),
|
||||
)
|
||||
if(Adjacent(user) && !issilicon(user))
|
||||
user.put_in_hands(cell)
|
||||
else
|
||||
cell.forceMove(drop_location())
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/mob/living/basic/bot/mulebot/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
||||
if(istype(tool, /obj/item/stock_parts/power_store/cell) && (bot_access_flags & BOT_COVER_MAINTS_OPEN))
|
||||
if(cell)
|
||||
to_chat(user, span_warning("[src] already has a power cell!"))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if(!user.transferItemToLoc(tool, src))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
user.visible_message(
|
||||
span_notice("[user] inserts \a [cell] into [src]."),
|
||||
span_notice("You insert [cell] into [src]."),
|
||||
)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
if(is_wire_tool(tool) && (bot_access_flags & BOT_COVER_MAINTS_OPEN))
|
||||
attack_hand(user)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
return ..()
|
||||
|
||||
|
||||
/mob/living/basic/bot/mulebot/emag_act(mob/user, obj/item/card/emag/emag_card)
|
||||
. = ..()
|
||||
if(!(bot_access_flags & BOT_COVER_EMAGGED))
|
||||
return
|
||||
flick("[base_icon_state]-emagged", src)
|
||||
playsound(src, SFX_SPARKS, 100, FALSE, SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
return TRUE
|
||||
@@ -250,7 +250,7 @@
|
||||
var/obj/item/stack/rods/new_rods = new()
|
||||
new_rods.forceMove(src)
|
||||
|
||||
/mob/living/basic/bot/repairbot/turn_on()
|
||||
/mob/living/basic/bot/repairbot/turn_on(mob/user)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
|
||||
/// Picks a random toxin and assigns it to the bee
|
||||
/mob/living/basic/bee/proc/assign_random_toxin_reagent()
|
||||
assign_reagent(get_random_reagent_id(whitelist = subtypesof(/datum/reagent/toxin)))
|
||||
assign_reagent(GLOB.chemical_reagents_list[get_random_reagent_id(whitelist = subtypesof(/datum/reagent/toxin))])
|
||||
|
||||
/mob/living/basic/bee/mutate()
|
||||
. = ..()
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// The taxonomy of polar vs space bear left to imagination of the reader
|
||||
/mob/living/basic/mining/polarbear
|
||||
name = "polar bear"
|
||||
desc = "An aggressive animal that defends its territory with incredible power. These beasts don't run from their enemies."
|
||||
icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi'
|
||||
icon_state = "polarbear"
|
||||
icon_living = "polarbear"
|
||||
icon_dead = "polarbear_dead"
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST|MOB_MINING
|
||||
|
||||
friendly_verb_continuous = "growls at"
|
||||
friendly_verb_simple = "growl at"
|
||||
response_help_continuous = "pets"
|
||||
response_help_simple = "pet"
|
||||
response_disarm_continuous = "gently pushes aside"
|
||||
response_disarm_simple = "gently push aside"
|
||||
attack_verb_continuous = "claws"
|
||||
attack_verb_simple = "claw"
|
||||
attack_sound = 'sound/items/weapons/bladeslice.ogg'
|
||||
attack_vis_effect = ATTACK_EFFECT_CLAW
|
||||
|
||||
habitable_atmos = null
|
||||
speed = 2
|
||||
maxHealth = 300
|
||||
health = 300
|
||||
obj_damage = 40
|
||||
melee_damage_lower = 25
|
||||
melee_damage_upper = 25
|
||||
wound_bonus = -5
|
||||
exposed_wound_bonus = 10
|
||||
sharpness = SHARP_EDGED
|
||||
|
||||
move_force = MOVE_FORCE_VERY_STRONG
|
||||
move_resist = MOVE_FORCE_VERY_STRONG
|
||||
pull_force = MOVE_FORCE_VERY_STRONG
|
||||
butcher_results = list(/obj/item/food/meat/slab/bear = 3, /obj/item/stack/sheet/bone = 2)
|
||||
guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/goliath_hide/polar_bear_hide = 1)
|
||||
crusher_loot = /obj/item/crusher_trophy/bear_paw
|
||||
|
||||
ai_controller = /datum/ai_controller/basic_controller/polar
|
||||
|
||||
/mob/living/basic/mining/polarbear/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
add_traits(list(TRAIT_SPACEWALK, TRAIT_SWIMMER, TRAIT_FENCE_CLIMBER, TRAIT_SNOWSTORM_IMMUNE), INNATE_TRAIT)
|
||||
AddElement(/datum/element/ai_retaliate)
|
||||
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BEAR, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
|
||||
AddElement(/datum/element/change_force_on_death, move_force = MOVE_FORCE_DEFAULT, move_resist = MOVE_RESIST_DEFAULT, pull_force = PULL_FORCE_DEFAULT)
|
||||
AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_CLAW)
|
||||
|
||||
/mob/living/basic/mining/polarbear/lesser
|
||||
name = "magic polar bear"
|
||||
desc = "It seems sentient somehow."
|
||||
faction = list(FACTION_NEUTRAL)
|
||||
|
||||
/datum/ai_controller/basic_controller/polar
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
|
||||
BB_AGGRO_RANGE = 2,
|
||||
)
|
||||
|
||||
ai_movement = /datum/ai_movement/basic_avoidance
|
||||
idle_behavior = /datum/idle_behavior/idle_random_walk
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/enrage,
|
||||
/datum/ai_planning_subtree/escape_captivity,
|
||||
/datum/ai_planning_subtree/target_retaliate,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
/datum/ai_planning_subtree/random_speech/bear,
|
||||
)
|
||||
@@ -52,7 +52,7 @@ GLOBAL_LIST_INIT(tendrils, list())
|
||||
AddComponent(/datum/component/ai_target_timer)
|
||||
AddComponent(/datum/component/gps, "Eerie Signal")
|
||||
AddComponent(/datum/component/basic_mob_attack_telegraph, display_telegraph_overlay = FALSE, telegraph_duration = 0.4 SECONDS)
|
||||
AddComponent(/datum/component/regenerator, regeneration_delay = 30 SECONDS, brute_per_second = 20)
|
||||
AddComponent(/datum/component/regenerator, regeneration_delay = 30 SECONDS, brute_per_second = 20, outline_colour = COLOR_CULT_RED)
|
||||
add_traits(list(TRAIT_BACKSTAB_IMMUNE, TRAIT_IMMOBILIZED), INNATE_TRAIT)
|
||||
|
||||
var/static/list/abilities = list(
|
||||
|
||||
@@ -270,7 +270,8 @@
|
||||
///Handler for COMSIG_MOB_RETRIEVE_ACCESS
|
||||
/mob/living/basic/pet/dog/corgi/proc/retrieve_access(mob/accessor, list/player_access)
|
||||
SIGNAL_HANDLER
|
||||
player_access += access_card.GetAccess()
|
||||
if(access_card)
|
||||
player_access += access_card.GetAccess()
|
||||
|
||||
///Handles updating any existing overlays for the corgi (such as fashion items) when it changes how it appears, as in, dead or alive.
|
||||
/mob/living/basic/pet/dog/corgi/proc/on_appearance_change()
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
pass_flags = PASSMOB
|
||||
move_force = 0
|
||||
move_resist = 0
|
||||
pull_force = 0
|
||||
pull_force = MOVE_FORCE_NONE
|
||||
minimum_survivable_temperature = TCMB
|
||||
maximum_survivable_temperature = INFINITY
|
||||
death_message = "fades out of existence!"
|
||||
|
||||
@@ -252,6 +252,8 @@
|
||||
var/datum/callback/got_disk = CALLBACK(src, PROC_REF(got_disk))
|
||||
var/datum/callback/display_disk = CALLBACK(src, PROC_REF(display_disk))
|
||||
AddComponent(/datum/component/nuclear_bomb_operator, got_disk, display_disk)
|
||||
var/obj/item/implant/implanter = SSwardrobe.provide_type(/obj/item/implant/tacmap/nuclear/cayenne, src)
|
||||
implanter.implant(src, null, TRUE)
|
||||
|
||||
/mob/living/basic/carp/pet/cayenne/apply_colour()
|
||||
if (prob(RARE_CAYENNE_CHANCE))
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
minimum_survivable_temperature = 0
|
||||
maximum_survivable_temperature = 1500
|
||||
obj_damage = 0
|
||||
pull_force = 0
|
||||
pull_force = MOVE_FORCE_NONE
|
||||
environment_smash = ENVIRONMENT_SMASH_NONE
|
||||
|
||||
ai_controller = /datum/ai_controller/basic_controller/lightgeist
|
||||
|
||||
@@ -175,12 +175,6 @@
|
||||
|
||||
// A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you.
|
||||
/datum/action/cooldown/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster)
|
||||
for(var/mob/living/simple_animal/bot/bot in victim)
|
||||
if(!(bot.bot_cover_flags & BOT_COVER_EMAGGED))
|
||||
new /obj/effect/temp_visual/revenant(bot.loc)
|
||||
bot.bot_cover_flags &= ~BOT_COVER_LOCKED
|
||||
bot.bot_cover_flags |= BOT_COVER_MAINTS_OPEN
|
||||
bot.emag_act(caster)
|
||||
for(var/mob/living/basic/bot/bot in victim)
|
||||
if(!(bot.bot_access_flags & BOT_COVER_EMAGGED))
|
||||
new /obj/effect/temp_visual/revenant(bot.loc)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
ai_controller = /datum/ai_controller/robot_customer
|
||||
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 0, OXY = 1)
|
||||
voice_filter = "alimiter=0.9,acompressor=threshold=0.2:ratio=20:attack=10:release=50:makeup=2,highpass=f=1000"
|
||||
|
||||
/// The clothes that we draw on this tourist.
|
||||
var/clothes_set = "amerifat_clothes"
|
||||
@@ -42,7 +43,8 @@
|
||||
|
||||
add_traits(list(TRAIT_NOMOBSWAP, TRAIT_NO_TELEPORT, TRAIT_STRONG_GRABBER), INNATE_TRAIT) // never suffer a bitch to fuck with you
|
||||
AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE)
|
||||
|
||||
if(SStts.tts_enabled)
|
||||
voice = pick(strings("robot_voices.json", "[customer_data.type]", "config"))
|
||||
ai_controller.set_blackboard_key(BB_CUSTOMER_CUSTOMERINFO, customer_info)
|
||||
ai_controller.set_blackboard_key(BB_CUSTOMER_ATTENDING_VENUE, attending_venue)
|
||||
ai_controller.set_blackboard_key(BB_CUSTOMER_PATIENCE, customer_info.total_patience)
|
||||
|
||||
@@ -65,15 +65,15 @@
|
||||
/mob/living/basic/stoat/kit = 100 // Placeholder until we get proper baby stoats
|
||||
)
|
||||
AddComponent(\
|
||||
/datum/component/breed,\
|
||||
can_breed_with = typecacheof(list(/mob/living/basic/stoat)),\
|
||||
baby_paths = baby_paths,\
|
||||
/datum/component/breed,\
|
||||
can_breed_with = typecacheof(list(/mob/living/basic/stoat)),\
|
||||
baby_paths = baby_paths,\
|
||||
)
|
||||
|
||||
/mob/living/basic/stoat/kit
|
||||
name = "\improper stoat kit"
|
||||
real_name = "stoat"
|
||||
desc = "They're a stoat kit!"
|
||||
desc = "An apex predator, but friend-shaped, and tiny..."
|
||||
icon_state = "kit_stoat"
|
||||
icon_living = "kit_stoat"
|
||||
icon_dead = "kit_stoat_dead"
|
||||
@@ -82,3 +82,15 @@
|
||||
ai_controller = /datum/ai_controller/basic_controller/stoat/kit
|
||||
mob_size = MOB_SIZE_SMALL
|
||||
can_breed = FALSE
|
||||
|
||||
/mob/living/basic/stoat/kit/Initialize(mapload)
|
||||
. = ..()
|
||||
AddComponent(\
|
||||
/datum/component/growth_and_differentiation,\
|
||||
growth_time = 20 MINUTES,\
|
||||
growth_path = /mob/living/basic/stoat,\
|
||||
growth_probability = 100,\
|
||||
lower_growth_value = 0.5,\
|
||||
upper_growth_value = 1,\
|
||||
signals_to_kill_on = list(COMSIG_MOB_CLIENT_LOGIN),\
|
||||
)
|
||||
|
||||
@@ -94,6 +94,8 @@
|
||||
return destined_path
|
||||
|
||||
/mob/living/basic/turtle/process(seconds_per_tick)
|
||||
if(HAS_TRAIT(src, TRAIT_STASIS))
|
||||
return
|
||||
if(isnull(reagents) || !length(reagents.reagent_list)) //if we have no reagents, default to our highest destined path
|
||||
set_plant_growth(retrieve_destined_path(), 0.5)
|
||||
return
|
||||
|
||||
@@ -709,7 +709,7 @@
|
||||
var/changed_something = FALSE
|
||||
var/obj/item/organ/new_organ = pick(GLOB.bioscrambler_valid_organs)
|
||||
var/obj/item/organ/replaced = get_organ_slot(initial(new_organ.slot))
|
||||
if (!replaced || ORGAN_CAN_BE_BIOSCRAMBLED(replaced))
|
||||
if ((!replaced || ORGAN_CAN_BE_BIOSCRAMBLED(replaced)) && get_bodypart(deprecise_zone(new_organ.zone)))
|
||||
changed_something = TRUE
|
||||
new_organ = new new_organ()
|
||||
new_organ.replace_into(src)
|
||||
|
||||
@@ -273,17 +273,25 @@
|
||||
hands += I.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE, bodyshape = bodyshape)
|
||||
return hands
|
||||
|
||||
/mob/living/carbon/proc/get_fire_icon_state(stacks, on_fire)
|
||||
return "[dna?.species.fire_overlay || "human"]_[stacks > MOB_BIG_FIRE_STACK_THRESHOLD ? "big_fire" : "small_fire"]"
|
||||
|
||||
/mob/living/carbon/get_fire_overlay(stacks, on_fire)
|
||||
var/fire_icon = "[dna?.species.fire_overlay || "human"]_[stacks > MOB_BIG_FIRE_STACK_THRESHOLD ? "big_fire" : "small_fire"]"
|
||||
var/fire_icon = get_fire_icon_state(stacks, on_fire)
|
||||
var/list/overrides = list()
|
||||
SEND_SIGNAL(src, COMSIG_CARBON_GET_FIRE_OVERLAY, stacks, on_fire, fire_icon, overrides)
|
||||
if (length(overrides))
|
||||
return overrides[1]
|
||||
|
||||
if(!GLOB.fire_appearances[fire_icon])
|
||||
GLOB.fire_appearances[fire_icon] = mutable_appearance(
|
||||
'icons/mob/effects/onfire.dmi',
|
||||
fire_icon,
|
||||
-HIGHEST_LAYER,
|
||||
appearance_flags = RESET_COLOR|KEEP_APART,
|
||||
)
|
||||
if(GLOB.fire_appearances[fire_icon])
|
||||
return GLOB.fire_appearances[fire_icon]
|
||||
|
||||
GLOB.fire_appearances[fire_icon] = mutable_appearance(
|
||||
'icons/mob/effects/onfire.dmi',
|
||||
fire_icon,
|
||||
-HIGHEST_LAYER,
|
||||
appearance_flags = RESET_COLOR|KEEP_APART,
|
||||
)
|
||||
return GLOB.fire_appearances[fire_icon]
|
||||
|
||||
/mob/living/carbon/update_damage_overlays()
|
||||
@@ -341,14 +349,8 @@
|
||||
if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
|
||||
return
|
||||
|
||||
if(client && hud_used?.inv_slots[TOBITSHIFT(ITEM_SLOT_MASK) + 1])
|
||||
var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_MASK) + 1]
|
||||
inv.update_appearance()
|
||||
|
||||
if(wear_mask)
|
||||
if(!(obscured_slots & HIDEMASK))
|
||||
overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/clothing/mask.dmi', bodyshape = bodyshape)
|
||||
update_hud_wear_mask(wear_mask)
|
||||
if(wear_mask && !(obscured_slots & HIDEMASK))
|
||||
overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/clothing/mask.dmi', bodyshape = bodyshape)
|
||||
|
||||
apply_overlay(FACEMASK_LAYER)
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
qdel(organ)
|
||||
|
||||
/mob/living/carbon/spread_bodyparts(drop_bitflags=NONE)
|
||||
/mob/living/carbon/spread_bodyparts(drop_bitflags = NONE, gibbed = FALSE)
|
||||
for(var/obj/item/bodypart/part as anything in get_bodyparts())
|
||||
if(part.body_zone == BODY_ZONE_CHEST)
|
||||
continue // never drop this
|
||||
@@ -84,12 +84,16 @@
|
||||
for(var/obj/item/organ/leftover in part)
|
||||
leftover_organs += leftover
|
||||
|
||||
part.drop_limb(TRUE)
|
||||
part.drop_limb(special = gibbed, dismembered = !gibbed)
|
||||
part.throw_at(get_edge_target_turf(src, pick(GLOB.alldirs)), rand(1,3), 5)
|
||||
// any organs that weren't throw out already about need to follow the bodypart out
|
||||
for(var/obj/item/organ/leftover as anything in leftover_organs)
|
||||
leftover.Remove(src, TRUE)
|
||||
leftover.bodypart_insert(part)
|
||||
// depending on whether gibbed flag was set changes how the organs are removed,
|
||||
// so just let's be... very careful here and double check everything
|
||||
if(leftover.owner == src)
|
||||
leftover.Remove(src, gibbed)
|
||||
if(leftover.loc != part)
|
||||
leftover.bodypart_insert(part)
|
||||
|
||||
/mob/living/carbon/set_suicide(suicide_state) //you thought that box trick was pretty clever, didn't you? well now hardmode is on, boyo.
|
||||
. = ..()
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
message = "screeches!"
|
||||
message_mime = "screeches silently."
|
||||
emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
|
||||
specific_emote_audio_cooldown = 10 SECONDS
|
||||
manual_specific_emote_audio_cooldown = 10 SECONDS
|
||||
vary = FALSE
|
||||
|
||||
/datum/emote/living/carbon/human/screech/get_sound(mob/living/carbon/human/user)
|
||||
|
||||
@@ -1133,7 +1133,7 @@
|
||||
|
||||
. = ..()
|
||||
if(use_random_name)
|
||||
fully_replace_character_name(real_name, generate_random_mob_name())
|
||||
fully_replace_character_name(newname = generate_random_mob_name())
|
||||
|
||||
///Proc used to make monkey roles able to function like crew, but not be able to shift into humans easily.
|
||||
/mob/living/carbon/human/proc/crewlike_monkify()
|
||||
|
||||
@@ -64,7 +64,6 @@ There are several things that need to be remembered:
|
||||
update_pockets()
|
||||
update_worn_neck()
|
||||
update_transform()
|
||||
update_mutations_overlay()
|
||||
update_damage_overlays()
|
||||
// These are done via parent call update_body(), keeping them here for clarity
|
||||
// update_hair()
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
// INFECTIOUS UNIQUE
|
||||
TRAIT_STABLEHEART, // Replacement for noblood. Infectious zombies can bleed but don't need their heart.
|
||||
TRAIT_STABLELIVER, // Not necessary but for consistency with above
|
||||
TRAIT_APATHETIC, // They don't have the brains for mood.
|
||||
)
|
||||
|
||||
// Infectious zombies have slow legs
|
||||
@@ -134,6 +135,7 @@
|
||||
new_zombie.set_combat_mode(TRUE)
|
||||
// Needs to be added after combat mode is set
|
||||
ADD_TRAIT(new_zombie, TRAIT_COMBAT_MODE_LOCK, SPECIES_TRAIT)
|
||||
new_zombie.physiology.stamina_mod *= 0.33 //Zombie stam resist
|
||||
|
||||
// Deal with the source of this zombie corruption
|
||||
// Infection organ needs to be handled separately from mutant_organs
|
||||
@@ -162,6 +164,7 @@
|
||||
REMOVE_TRAIT(was_zombie, TRAIT_COMBAT_MODE_LOCK, SPECIES_TRAIT)
|
||||
qdel(was_zombie.GetComponent(/datum/component/mutant_hands))
|
||||
qdel(was_zombie.GetComponent(/datum/component/regenerator))
|
||||
was_zombie.physiology.stamina_mod /= 0.33
|
||||
|
||||
/datum/species/zombie/infectious/check_roundstart_eligible()
|
||||
return FALSE
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
spill_organs(drop_bitflags)
|
||||
|
||||
if(drop_bitflags & DROP_BODYPARTS)
|
||||
spread_bodyparts(drop_bitflags)
|
||||
spread_bodyparts(drop_bitflags, gibbed = TRUE)
|
||||
|
||||
// failsafe for if we fuck up and leave our brain behind. (other organs are replaceable so we can ignore them.)
|
||||
var/obj/item/organ/brain/brain = get_organ_slot(ORGAN_SLOT_BRAIN)
|
||||
@@ -83,7 +83,7 @@
|
||||
* drop_bitflags: (see code/__DEFINES/blood.dm)
|
||||
* * DROP_BRAIN - Detaches the head from the mob and launches it away from the body
|
||||
**/
|
||||
/mob/living/proc/spread_bodyparts(drop_bitflags=NONE)
|
||||
/mob/living/proc/spread_bodyparts(drop_bitflags = NONE, gibbed = FALSE)
|
||||
return
|
||||
|
||||
/// Length of the animation in dust_animation.dmi
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
message = "laughs."
|
||||
message_mime = "laughs silently!"
|
||||
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
|
||||
specific_emote_audio_cooldown = 8 SECONDS
|
||||
manual_specific_emote_audio_cooldown = 8 SECONDS
|
||||
vary = TRUE
|
||||
|
||||
/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE , intentional, params)
|
||||
@@ -419,7 +419,9 @@
|
||||
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
|
||||
mob_type_blacklist_typecache = list(/mob/living/brain)
|
||||
sound_wall_ignore = TRUE
|
||||
specific_emote_audio_cooldown = 10 SECONDS
|
||||
use_sound_tokens = TRUE
|
||||
manual_specific_emote_audio_cooldown = 10 SECONDS
|
||||
forced_specific_emote_audio_cooldown = 4 SECONDS
|
||||
vary = TRUE
|
||||
|
||||
/datum/emote/living/scream/run_emote(mob/user, params, type_override, intentional = FALSE)
|
||||
|
||||
@@ -186,11 +186,13 @@
|
||||
mobility_flags &= ~(MOBILITY_PULL)
|
||||
if(pulling)
|
||||
stop_pulling()
|
||||
pull_force_change()
|
||||
|
||||
/// Called when [TRAIT_PULL_BLOCKED] is removed from the mob.
|
||||
/mob/living/proc/on_pull_blocked_trait_loss(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
mobility_flags |= MOBILITY_PULL
|
||||
pull_force_change()
|
||||
|
||||
|
||||
/// Called when [TRAIT_INCAPACITATED] is added to the mob.
|
||||
|
||||
@@ -395,8 +395,9 @@
|
||||
now_pushing = FALSE
|
||||
|
||||
/mob/living/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE)
|
||||
if(!AM || !src)
|
||||
if(!src)
|
||||
return FALSE
|
||||
ASSERT(ismovable(AM), "[src] attempted to pull [AM ? "[AM], a nonmovable atom" : "a null object"]")
|
||||
if(!(AM.can_be_pulled(src, force)))
|
||||
return FALSE
|
||||
if(throwing || !(mobility_flags & MOBILITY_PULL))
|
||||
@@ -1022,10 +1023,11 @@
|
||||
if(heal_flags & HEAL_STAM)
|
||||
set_stamina_loss(0, updating_stamina = FALSE, forced = TRUE)
|
||||
|
||||
// I don't really care to keep this under a flag
|
||||
set_nutrition(NUTRITION_LEVEL_FED + 50)
|
||||
overeatduration = 0
|
||||
satiety = 0
|
||||
// Only aheals really do this right now, so this flag should be fine for the time being
|
||||
if(heal_flags & HEAL_ADMIN)
|
||||
set_nutrition(NUTRITION_LEVEL_FED + 50)
|
||||
overeatduration = 0
|
||||
satiety = 0
|
||||
|
||||
// These should be tracked by status effects
|
||||
losebreath = 0
|
||||
@@ -2170,10 +2172,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
|
||||
update_transform(var_value/current_size)
|
||||
. = TRUE
|
||||
if(NAMEOF(src, pull_force))
|
||||
if(var_value == 0) //no more pulling
|
||||
remove_verb(src, /mob/living/verb/pulled)
|
||||
else
|
||||
add_verb(src, /mob/living/verb/pulled)
|
||||
set_pull_force(var_value)
|
||||
. = TRUE
|
||||
|
||||
|
||||
@@ -3122,3 +3121,15 @@ GLOBAL_LIST_EMPTY(fire_appearances)
|
||||
if(HAS_TRAIT(src, TRAIT_ANALGESIA) && !force)
|
||||
return
|
||||
INVOKE_ASYNC(src, PROC_REF(emote), "scream")
|
||||
|
||||
/mob/living/proc/set_pull_force(new_pull_force)
|
||||
if(pull_force == new_pull_force)
|
||||
return
|
||||
pull_force = new_pull_force
|
||||
pull_force_change()
|
||||
|
||||
/mob/living/proc/pull_force_change()
|
||||
if(!pull_force || HAS_TRAIT(src, TRAIT_PULL_BLOCKED))
|
||||
remove_verb(src, /mob/living/verb/pulled)
|
||||
else
|
||||
add_verb(src, /mob/living/verb/pulled)
|
||||
|
||||
@@ -235,6 +235,23 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
|
||||
if(client?.autopunctuation) //BUBBER EDIT ADDITION: AUTOPUNCTUATION PREFERENCE CHECK
|
||||
message = autopunct_bare(message)
|
||||
// SKYRAT EDIT ADDITION END
|
||||
var/identifier = "invalid"
|
||||
var/tts_message_to_use = tts_message || message
|
||||
|
||||
|
||||
if(SStts.tts_enabled && voice && !message_mods[MODE_CUSTOM_SAY_ERASE_INPUT] && !HAS_TRAIT(src, TRAIT_SIGN_LANG) && !HAS_TRAIT(src, TRAIT_UNKNOWN_VOICE))
|
||||
var/list/filter = list()
|
||||
var/list/special_filter = list()
|
||||
if(length(voice_filter) > 0)
|
||||
filter += voice_filter
|
||||
|
||||
if(length(tts_filter) > 0)
|
||||
filter += tts_filter.Join(",")
|
||||
|
||||
var/shell_scrubbed_input = tts_speech_filter(html_decode(tts_message_to_use))
|
||||
identifier = "[sha1(get_tts_voice(filter, special_filter) + filter.Join(",") + num2text(pitch) + special_filter.Join("|") + shell_scrubbed_input + blip_base + num2text(blip_number))].[world.time]"
|
||||
message_mods[MODE_TTS_IDENTIFIER] = identifier
|
||||
|
||||
//This is before anything that sends say a radio message, and after all important message type modifications, so you can scumb in alien chat or something
|
||||
if(saymode && (saymode.handle_message(src, message, spans, language, message_mods) & SAYMODE_MESSAGE_HANDLED))
|
||||
return
|
||||
@@ -360,7 +377,12 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
|
||||
message = deaf_message
|
||||
|
||||
var/show_message_success = show_message(message, MSG_VISUAL, deaf_message, deaf_type, avoid_highlight)
|
||||
return understood && show_message_success
|
||||
if(show_message_success && understood)
|
||||
return HEAR_HEARD | HEAR_UNDERSTOOD
|
||||
else if (show_message_success && !understood)
|
||||
return HEAR_HEARD
|
||||
else
|
||||
return FALSE
|
||||
|
||||
if(speaker != src)
|
||||
if(!radio_freq) //These checks have to be separate, else people talking on the radio will make "You can't hear yourself!" appear when hearing people over the radio while deaf.
|
||||
@@ -380,7 +402,12 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
|
||||
// Recompose message for AI hrefs, language incomprehension.
|
||||
message = compose_message(speaker, message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, spans, message_mods)
|
||||
var/show_message_success = show_message(message, MSG_AUDIBLE, deaf_message, deaf_type, avoid_highlight)
|
||||
return understood && show_message_success
|
||||
if(show_message_success && understood)
|
||||
return HEAR_HEARD | HEAR_UNDERSTOOD
|
||||
else if (show_message_success && !understood)
|
||||
return HEAR_HEARD
|
||||
else
|
||||
return FALSE
|
||||
|
||||
/mob/living/send_speech(message_raw, message_range = 6, obj/source = src, bubble_type = bubble_icon, list/spans, datum/language/message_language = null, list/message_mods = list(), forced = null, tts_message, list/tts_filter)
|
||||
var/whisper_range = 0
|
||||
@@ -417,44 +444,27 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
|
||||
continue
|
||||
listening |= player_mob
|
||||
|
||||
var/tts_message_to_use = tts_message || message_raw
|
||||
|
||||
// this signal ignores whispers or language translations (only used by beetlejuice component)
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message_raw)
|
||||
|
||||
var/list/listened = list()
|
||||
for(var/atom/movable/listening_movable as anything in listening)
|
||||
if(!listening_movable)
|
||||
stack_trace("somehow theres a null returned from get_hearers_in_view() in send_speech!")
|
||||
continue
|
||||
|
||||
if(listening_movable.Hear(src, message_language, message_raw, null, null, null, spans, message_mods, message_range))
|
||||
if(listening_movable.Hear(src, message_language, message_raw, null, null, null, spans, message_mods, message_range) & HEAR_HEARD)
|
||||
listened += listening_movable
|
||||
|
||||
//speech bubble
|
||||
var/list/speech_bubble_recipients = list()
|
||||
var/found_client = FALSE
|
||||
var/talk_icon_state = say_test(message_raw)
|
||||
for(var/mob/M in listening)
|
||||
if(M.client)
|
||||
if(!M.client.prefs.read_preference(/datum/preference/toggle/enable_runechat) || (SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES)))
|
||||
speech_bubble_recipients.Add(M.client)
|
||||
found_client = TRUE
|
||||
if(SStts.tts_enabled && voice && found_client && !message_mods[MODE_CUSTOM_SAY_ERASE_INPUT] && !HAS_TRAIT(src, TRAIT_SIGN_LANG) && !HAS_TRAIT(src, TRAIT_UNKNOWN_VOICE))
|
||||
var/tts_message_to_use = tts_message
|
||||
if(!tts_message_to_use)
|
||||
tts_message_to_use = message_raw
|
||||
|
||||
var/list/filter = list()
|
||||
var/list/special_filter = list()
|
||||
if(length(voice_filter) > 0)
|
||||
filter += voice_filter
|
||||
|
||||
if(length(tts_filter) > 0)
|
||||
filter += tts_filter.Join(",")
|
||||
|
||||
var/voice_to_use = get_tts_voice(filter, special_filter)
|
||||
if (!CONFIG_GET(flag/tts_no_whisper) || (CONFIG_GET(flag/tts_no_whisper) && !message_mods[WHISPER_MODE]))
|
||||
INVOKE_ASYNC(SStts, TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), src, html_decode(tts_message_to_use), message_language, voice_to_use, filter.Join(","), listened, message_range = message_range, pitch = pitch, special_filters = special_filter.Join("|"))
|
||||
|
||||
do_tts_message(tts_message_to_use, message_language, message_mods, tts_filter, listened)
|
||||
var/image/say_popup = image('icons/mob/effects/talk.dmi', src, "[bubble_type][talk_icon_state]", FLY_LAYER)
|
||||
SET_PLANE_EXPLICIT(say_popup, ABOVE_GAME_PLANE, src)
|
||||
say_popup.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
|
||||
@@ -462,7 +472,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
|
||||
LAZYADD(update_on_z, say_popup)
|
||||
addtimer(CALLBACK(src, PROC_REF(clear_saypopup), say_popup), 3.5 SECONDS)
|
||||
|
||||
/mob/living/proc/get_tts_voice(list/filter, list/special_filter)
|
||||
/mob/living/get_tts_voice(list/filter, list/special_filter)
|
||||
. = voice
|
||||
var/obj/item/clothing/mask/mask = get_item_by_slot(ITEM_SLOT_MASK)
|
||||
if(!istype(mask) || mask.up)
|
||||
@@ -514,19 +524,6 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
|
||||
message = capitalize(message)
|
||||
tts_message = capitalize(tts_message)
|
||||
|
||||
///caps the length of individual letters to 3: ex: heeeeeeyy -> heeeyy
|
||||
/// prevents TTS from choking on unrealistic text while keeping emphasis
|
||||
var/static/regex/length_regex = regex(@"(.+)\1\1\1", "gi")
|
||||
while(length_regex.Find(tts_message))
|
||||
var/replacement = tts_message[length_regex.index]+tts_message[length_regex.index]+tts_message[length_regex.index]
|
||||
tts_message = replacetext(tts_message, length_regex.match, replacement, length_regex.index)
|
||||
|
||||
// removes repeated consonants at the start of a word: ex: sss
|
||||
var/static/regex/word_start_regex = regex(@"\b([^aeiou\L])\1", "gi")
|
||||
while(word_start_regex.Find(tts_message))
|
||||
var/replacement = tts_message[word_start_regex.index]
|
||||
tts_message = replacetext(tts_message, word_start_regex.match, replacement, word_start_regex.index)
|
||||
|
||||
return list("message" = message, "tts_message" = tts_message, "tts_filter" = tts_filter)
|
||||
|
||||
/mob/living/proc/radio(message, list/message_mods = list(), list/spans, language)
|
||||
|
||||
@@ -542,13 +542,8 @@
|
||||
var/mob/living/bot = bot_ref?.resolve()
|
||||
if(!bot)
|
||||
return
|
||||
var/summon_success
|
||||
if(isbasicbot(bot))
|
||||
var/mob/living/basic/bot/basic_bot = bot
|
||||
summon_success = basic_bot.summon_bot(src, waypoint, grant_all_access = TRUE)
|
||||
else
|
||||
var/mob/living/simple_animal/bot/simple_bot = bot
|
||||
summon_success = simple_bot.call_bot(src, waypoint)
|
||||
var/mob/living/basic/bot/basic_bot = bot
|
||||
var/summon_success = basic_bot.summon_bot(src, waypoint, grant_all_access = TRUE)
|
||||
|
||||
var/chat_message = summon_success ? "Sending command to bot..." : "Interface error. Unit is already in use."
|
||||
to_chat(src, span_notice("[chat_message]"))
|
||||
@@ -901,9 +896,8 @@
|
||||
to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.")
|
||||
view_core() //A BYOND bug requires you to be viewing your core before your verbs update
|
||||
malf_picker = new /datum/module_picker
|
||||
if(!IS_MALF_AI(src)) //antagonists have their modules built into their antag info panel. this is for adminbus and the combat upgrade
|
||||
modules_action = new(malf_picker)
|
||||
modules_action.Grant(src)
|
||||
modules_action = new(malf_picker)
|
||||
modules_action.Grant(src)
|
||||
|
||||
/mob/living/silicon/ai/reset_perspective(atom/new_eye)
|
||||
SHOULD_CALL_PARENT(FALSE) // I hate you all
|
||||
|
||||
@@ -30,14 +30,17 @@
|
||||
return FALSE
|
||||
. = ..()
|
||||
if(.)
|
||||
do_tts_message(message, language, message_mods, list(), list())
|
||||
return .
|
||||
if(message_mods[MODE_HEADSET])
|
||||
if(radio)
|
||||
radio.talk_into(src, message, , spans, language, message_mods)
|
||||
do_tts_message(message, language, message_mods, list(), list())
|
||||
return NOPASS
|
||||
else if(message_mods[RADIO_EXTENSION] in GLOB.default_radio_channels)
|
||||
if(radio)
|
||||
radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
|
||||
do_tts_message(message, language, message_mods, list(), list())
|
||||
return NOPASS
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
// Camera updates will never be second guessed.
|
||||
// Track the number of times the camera has queued an update instead of opacity (just for fun)
|
||||
if(istype(update_source, /obj/machinery/camera))
|
||||
update_sources[update_source] += 1
|
||||
update_sources[update_key] += 1
|
||||
|
||||
// Otherwise track this atom's opacity at time of queue
|
||||
else if(isnull(update_sources[update_key]))
|
||||
|
||||
@@ -35,37 +35,22 @@
|
||||
var/turf/ai_current_turf = get_turf(owner)
|
||||
|
||||
data["robots"] = list()
|
||||
for(var/mob/living/our_bot as anything in GLOB.bots_list)
|
||||
for(var/mob/living/basic/bot/our_bot as anything in GLOB.bots_list)
|
||||
if(!isbot(our_bot) || !is_valid_z_level(ai_current_turf, get_turf(our_bot)))
|
||||
continue
|
||||
|
||||
if(isbasicbot(our_bot))
|
||||
var/mob/living/basic/bot/basic_bot = our_bot
|
||||
if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
continue
|
||||
var/list/basic_bot_data = list(
|
||||
name = basic_bot.name,
|
||||
model = basic_bot.bot_type,
|
||||
mode = basic_bot.mode,
|
||||
hacked = !!(basic_bot.bot_access_flags & BOT_COVER_HACKED),
|
||||
location = get_area_name(basic_bot, TRUE),
|
||||
ref = REF(basic_bot),
|
||||
)
|
||||
data["robots"] += list(basic_bot_data)
|
||||
var/mob/living/basic/bot/basic_bot = our_bot
|
||||
if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
continue
|
||||
|
||||
var/mob/living/simple_animal/bot/simple_bot = our_bot
|
||||
if(!(simple_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
continue
|
||||
var/list/simple_bot_data = list(
|
||||
name = simple_bot.name,
|
||||
model = simple_bot.bot_type,
|
||||
mode = simple_bot.get_mode(),
|
||||
hacked = !!(simple_bot.bot_cover_flags & BOT_COVER_HACKED),
|
||||
location = get_area_name(simple_bot, TRUE),
|
||||
ref = REF(simple_bot),
|
||||
var/list/basic_bot_data = list(
|
||||
name = basic_bot.name,
|
||||
model = basic_bot.bot_type,
|
||||
mode = basic_bot.mode,
|
||||
hacked = !!(basic_bot.bot_access_flags & BOT_COVER_HACKED),
|
||||
location = get_area_name(basic_bot, TRUE),
|
||||
ref = REF(basic_bot),
|
||||
)
|
||||
data["robots"] += list(simple_bot_data)
|
||||
data["robots"] += list(basic_bot_data)
|
||||
|
||||
return data
|
||||
|
||||
@@ -84,28 +69,19 @@
|
||||
|
||||
switch(action)
|
||||
if("callbot") //Command a bot to move to a selected location.
|
||||
if(isbasicbot(bot))
|
||||
var/mob/living/basic/bot/basic_bot = bot
|
||||
if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
return
|
||||
else
|
||||
var/mob/living/simple_animal/bot/simple_bot = bot
|
||||
if(!(simple_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
return
|
||||
var/mob/living/basic/bot/basic_bot = bot
|
||||
if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
return
|
||||
|
||||
owner.bot_ref = WEAKREF(bot)
|
||||
owner.setting_waypoint = TRUE
|
||||
to_chat(our_user, span_notice("Set your waypoint by clicking on a valid location free of obstructions."))
|
||||
if("interface") //Remotely connect to a bot!
|
||||
owner.bot_ref = WEAKREF(bot)
|
||||
if(isbasicbot(bot))
|
||||
var/mob/living/basic/bot/basic_bot = bot
|
||||
if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
return
|
||||
else
|
||||
var/mob/living/basic/bot/simple_bot = bot
|
||||
if(!(simple_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
return
|
||||
var/mob/living/basic/bot/basic_bot = bot
|
||||
if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED))
|
||||
return
|
||||
|
||||
bot.attack_ai(our_user)
|
||||
|
||||
return TRUE
|
||||
|
||||
@@ -257,7 +257,6 @@
|
||||
if(module && module_active != held_items[module_num])
|
||||
module.icon_state = "[module.base_icon_state] +a"
|
||||
module_active = held_items[module_num]
|
||||
SEND_SIGNAL(module_active, COMSIG_SILICON_MODULE_ACTIVATION, TRUE)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
@@ -269,8 +268,6 @@
|
||||
var/atom/movable/screen/robot/module_slot/module = hud_used?.screen_objects[HUD_KEY_HAND_SLOT(module_num)]
|
||||
if(module)
|
||||
module.icon_state = module.base_icon_state
|
||||
if(module_active)
|
||||
SEND_SIGNAL(module_active, COMSIG_SILICON_MODULE_ACTIVATION, FALSE)
|
||||
module_active = null
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -214,6 +214,18 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
|
||||
|
||||
return NONE
|
||||
|
||||
//Checks blockchance of any items in any module slots
|
||||
/mob/living/silicon/robot/check_block(atom/hit_by, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0, damage_type = BRUTE)
|
||||
. = ..()
|
||||
if(. == SUCCESSFUL_BLOCK)
|
||||
return SUCCESSFUL_BLOCK
|
||||
|
||||
var/block_chance_modifier = round(damage / -3)
|
||||
for(var/obj/item/module in held_items)
|
||||
var/final_block_chance = module.block_chance - (clamp((armour_penetration - module.armour_penetration) / 2, 0, 100)) + block_chance_modifier
|
||||
if(module.hit_reaction(src, hit_by, attack_text, final_block_chance, damage, attack_type, damage_type))
|
||||
return SUCCESSFUL_BLOCK
|
||||
|
||||
// This has to go at the very end of interaction so we don't block every interaction with ID-like items
|
||||
/mob/living/silicon/robot/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
||||
. = ..()
|
||||
@@ -527,6 +539,8 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
|
||||
return TRUE
|
||||
|
||||
/mob/living/silicon/robot/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
|
||||
if(check_block(hitting_projectile, hitting_projectile.damage, "\the [hitting_projectile]", PROJECTILE_ATTACK, hitting_projectile.armour_penetration, hitting_projectile.damage_type))
|
||||
return ..(hitting_projectile, def_zone, piercing_hit, 100)
|
||||
. = ..()
|
||||
if(prob(25) || . != BULLET_ACT_HIT)
|
||||
return
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
health = 100
|
||||
bubble_icon = "robot"
|
||||
designation = "Default" //used for displaying the prefix & getting the current model of cyborg
|
||||
has_limbs = TRUE
|
||||
hud_type = /datum/hud/robot
|
||||
unique_name = TRUE
|
||||
mouse_drop_zone = TRUE
|
||||
|
||||
@@ -450,6 +450,7 @@
|
||||
model_select_icon = "engineer"
|
||||
model_traits = list(TRAIT_NEGATES_GRAVITY)
|
||||
hat_offset = list("north" = list(0, -4), "south" = list(0, -4), "east" = list(4, -4), "west" = list(-4, -4))
|
||||
///Weakref to the night vision action
|
||||
var/datum/weakref/night_vision_ref
|
||||
|
||||
/datum/action/cooldown/borg_meson
|
||||
@@ -466,10 +467,12 @@
|
||||
borg.update_sight()
|
||||
|
||||
/obj/item/robot_model/engineering/be_transformed_to(obj/item/robot_model/old_model, forced = FALSE)
|
||||
var/datum/action/cooldown/borg_meson/night_vision = new(loc)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
//Grant night vision action
|
||||
var/datum/action/cooldown/borg_meson/night_vision = new(loc)
|
||||
night_vision.Grant(loc)
|
||||
night_vision_ref = WEAKREF(night_vision)
|
||||
|
||||
@@ -962,7 +965,7 @@
|
||||
name = "Syndicate Assault"
|
||||
basic_modules = list(
|
||||
/obj/item/assembly/flash/cyborg,
|
||||
/obj/item/melee/energy/sword/cyborg,
|
||||
/obj/item/melee/energy/sword/saber/cyborg,
|
||||
/obj/item/gun/energy/printer,
|
||||
/obj/item/gun/ballistic/revolver/grenadelauncher/cyborg,
|
||||
/obj/item/card/emag,
|
||||
@@ -979,6 +982,9 @@
|
||||
..()
|
||||
var/mob/living/silicon/robot/cyborg = loc
|
||||
cyborg.remove_faction(FACTION_SILICON) //ai turrets
|
||||
add_minimap_blip(cyborg, MINIMAP_NUKEOP_BORG_BLIP, "combatborg")
|
||||
var/datum/action/minimap/nuclear/tacmap_action = new
|
||||
tacmap_action.Grant(cyborg)
|
||||
|
||||
/obj/item/robot_model/syndicate/remove_module(obj/item/removed_module)
|
||||
..()
|
||||
@@ -995,7 +1001,7 @@
|
||||
/obj/item/borg/cyborg_omnitool/medical,
|
||||
/obj/item/borg/cyborg_omnitool/medical,
|
||||
/obj/item/blood_filter,
|
||||
/obj/item/melee/energy/sword/cyborg/saw,
|
||||
/obj/item/melee/energy/sword/saber/cyborg/saw,
|
||||
/obj/item/emergency_bed/silicon,
|
||||
/obj/item/crowbar/cyborg,
|
||||
/obj/item/extinguisher/mini,
|
||||
@@ -1011,6 +1017,13 @@
|
||||
model_traits = list(TRAIT_PUSHIMMUNE)
|
||||
hat_offset = list("north" = list(0, 3), "south" = list(0, 3), "east" = list(-1, 3), "west" = list(1, 3))
|
||||
|
||||
/obj/item/robot_model/syndicate_medical/rebuild_modules()
|
||||
..()
|
||||
var/mob/living/silicon/robot/cyborg = loc
|
||||
add_minimap_blip(cyborg, MINIMAP_NUKEOP_BORG_BLIP, "mediborg")
|
||||
var/datum/action/minimap/nuclear/tacmap_action = new
|
||||
tacmap_action.Grant(cyborg)
|
||||
|
||||
/obj/item/robot_model/saboteur
|
||||
name = "Syndicate Saboteur"
|
||||
basic_modules = list(
|
||||
@@ -1019,6 +1032,7 @@
|
||||
/obj/item/pipe_dispenser,
|
||||
/obj/item/restraints/handcuffs/cable/zipties,
|
||||
/obj/item/extinguisher,
|
||||
/obj/item/weldingtool/largetank/cyborg,
|
||||
/obj/item/analyzer,
|
||||
/obj/item/borg/cyborg_omnitool/engineering,
|
||||
/obj/item/borg/cyborg_omnitool/engineering,
|
||||
@@ -1041,6 +1055,13 @@
|
||||
canDispose = TRUE
|
||||
var/datum/weakref/thermal_vision_ref
|
||||
|
||||
/obj/item/robot_model/saboteur/rebuild_modules()
|
||||
..()
|
||||
var/mob/living/silicon/robot/cyborg = loc
|
||||
add_minimap_blip(cyborg, MINIMAP_NUKEOP_BORG_BLIP, "engiborg")
|
||||
var/datum/action/minimap/nuclear/tacmap_action = new
|
||||
tacmap_action.Grant(cyborg)
|
||||
|
||||
/datum/action/cooldown/borg_thermal
|
||||
name = "Toggle Thermal Night Vision"
|
||||
button_icon = 'icons/mob/actions/actions_mecha.dmi'
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
if(istype(brain))
|
||||
namepart = brain.mainframe.name
|
||||
designation = brain.mainframe.job
|
||||
|
||||
for(var/mob/hearing_mob in GLOB.player_list)
|
||||
if(hearing_mob.binarycheck())
|
||||
if(isAI(hearing_mob))
|
||||
@@ -88,14 +87,16 @@
|
||||
/mob/living/silicon/radio(message, list/message_mods = list(), list/spans, language)
|
||||
. = ..()
|
||||
if(.)
|
||||
do_tts_message(message, language, message_mods, list(), list())
|
||||
return
|
||||
if(message_mods[MODE_HEADSET])
|
||||
if(radio)
|
||||
radio.talk_into(src, message, , spans, language, message_mods)
|
||||
radio.talk_into(src, message, null, spans, language, message_mods)
|
||||
do_tts_message(message, language, message_mods, list(), list())
|
||||
return NOPASS
|
||||
else if(message_mods[RADIO_EXTENSION] in GLOB.default_radio_channels)
|
||||
if(radio)
|
||||
radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
|
||||
do_tts_message(message, language, message_mods, list(), list())
|
||||
return NOPASS
|
||||
|
||||
return FALSE
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -51,7 +51,7 @@
|
||||
/datum/action/cooldown/bot_announcement/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/mob/living/simple_animal/bot/bot_owner = owner
|
||||
var/mob/living/basic/bot/bot_owner = owner
|
||||
if(istype(bot_owner))
|
||||
var/list/channels = list()
|
||||
for(var/channel in bot_owner.internal_radio.channels)
|
||||
@@ -149,7 +149,7 @@
|
||||
|
||||
/// Speak the provided line on the provided radio channel
|
||||
/datum/action/cooldown/bot_announcement/proc/announce(line, channel)
|
||||
var/mob/living/simple_animal/bot/bot_owner = owner
|
||||
var/mob/living/basic/bot/bot_owner = owner
|
||||
if (!(bot_owner.bot_mode_flags & BOT_MODE_ON))
|
||||
return
|
||||
|
||||
|
||||
@@ -682,7 +682,7 @@
|
||||
if(!can_finish_build(tool, user))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
balloon_alert(user, "assembly finished")
|
||||
var/obj/vehicle/sealed/car/vim/new_vim = new(drop_location())
|
||||
var/obj/vehicle/sealed/mecha/vim/new_vim = new(drop_location())
|
||||
new_vim.name = created_name
|
||||
qdel(tool)
|
||||
qdel(src)
|
||||
|
||||
@@ -1,873 +0,0 @@
|
||||
|
||||
|
||||
// Mulebot - carries crates around for Quartermaster
|
||||
// Navigates via floor navbeacons
|
||||
// Remote Controlled from QM's PDA
|
||||
|
||||
#define SIGH 0
|
||||
#define ANNOYED 1
|
||||
#define DELIGHT 2
|
||||
#define CHIME 3
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot
|
||||
name = "\improper MULEbot"
|
||||
desc = "A Multiple Utility Load Effector bot."
|
||||
icon_state = "mulebot0"
|
||||
light_color = "#ffcc99"
|
||||
light_power = 0.8
|
||||
density = TRUE
|
||||
move_resist = MOVE_FORCE_STRONG
|
||||
animate_movement = SLIDE_STEPS
|
||||
health = 50
|
||||
maxHealth = 50
|
||||
speed = 3
|
||||
damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, STAMINA = 0, OXY = 0)
|
||||
combat_mode = TRUE //No swapping
|
||||
buckle_lying = 0
|
||||
mob_size = MOB_SIZE_LARGE
|
||||
buckle_prevents_pull = TRUE // No pulling loaded shit
|
||||
bot_mode_flags = ~BOT_MODE_ROUNDSTART_POSSESSION
|
||||
req_one_access = list(ACCESS_ROBOTICS, ACCESS_CARGO)
|
||||
radio_key = /obj/item/encryptionkey/headset_cargo
|
||||
radio_channel = RADIO_CHANNEL_SUPPLY
|
||||
bot_type = MULE_BOT
|
||||
path_image_color = "#7F5200"
|
||||
possessed_message = "You are a MULEbot! Do your best to make sure that packages get to their destination!"
|
||||
|
||||
/// unique identifier in case there are multiple mulebots.
|
||||
var/id
|
||||
|
||||
var/base_icon = "mulebot" /// icon_state to use in update_icon_state
|
||||
var/atom/movable/load /// what we're transporting
|
||||
var/mob/living/passenger /// who's riding us
|
||||
var/turf/target /// this is turf to navigate to (location of beacon)
|
||||
var/loaddir = 0 /// this the direction to unload onto/load from
|
||||
var/home_destination = "" /// tag of home delivery beacon
|
||||
|
||||
var/reached_target = TRUE ///true if already reached the target
|
||||
///Number of times retried a blocked path
|
||||
var/blockcount = 0
|
||||
|
||||
///flags of mulebot mode
|
||||
var/mulebot_delivery_flags = MULEBOT_RETURN_MODE | MULEBOT_AUTO_PICKUP_MODE | MULEBOT_REPORT_DELIVERY_MODE
|
||||
|
||||
var/obj/item/stock_parts/power_store/cell /// Internal Powercell
|
||||
var/cell_move_power_usage = 1///How much power we use when we move.
|
||||
var/num_steps = 0 ///The amount of steps we should take until we rest for a time.
|
||||
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
RegisterSignal(src, COMSIG_MOB_BOT_PRE_STEP, PROC_REF(check_pre_step))
|
||||
RegisterSignal(src, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(check_pre_step))
|
||||
RegisterSignal(src, COMSIG_MOB_BOT_STEP, PROC_REF(on_bot_step))
|
||||
RegisterSignal(src, COMSIG_MOB_CLIENT_MOVED, PROC_REF(on_bot_step))
|
||||
|
||||
ADD_TRAIT(src, TRAIT_NOMOBSWAP, INNATE_TRAIT)
|
||||
|
||||
if(prob(0.666) && mapload)
|
||||
new /mob/living/simple_animal/bot/mulebot/paranormal(loc)
|
||||
return INITIALIZE_HINT_QDEL
|
||||
set_wires(new /datum/wires/mulebot(src))
|
||||
|
||||
// Doing this hurts my soul, but simplebot access reworks are for another day.
|
||||
var/datum/id_trim/job/cargo_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/cargo_technician]
|
||||
access_card.add_access(cargo_trim.access + cargo_trim.wildcard_access)
|
||||
prev_access = access_card.access.Copy()
|
||||
|
||||
cell = new /obj/item/stock_parts/power_store/cell/upgraded(src, 2000)
|
||||
|
||||
AddElement(/datum/element/ridable, /datum/component/riding/creature/mulebot)
|
||||
diag_hud_set_mulebotcell()
|
||||
|
||||
set_id(suffix || assign_random_name())
|
||||
suffix = null
|
||||
if(name == "\improper MULEbot")
|
||||
name = "\improper MULEbot [id]"
|
||||
set_home(get_turf(src))
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/Exited(atom/movable/gone, direction)
|
||||
. = ..()
|
||||
if(gone == load)
|
||||
unload(0)
|
||||
if(gone == cell)
|
||||
turn_off()
|
||||
cell = null
|
||||
diag_hud_set_mulebotcell()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/examine(mob/user)
|
||||
. = ..()
|
||||
if(bot_cover_flags & BOT_COVER_MAINTS_OPEN)
|
||||
if(cell)
|
||||
. += span_notice("[p_They()] [p_have()] \a [cell] installed.")
|
||||
. += span_info("You can use a <b>crowbar</b> to remove it.")
|
||||
else
|
||||
. += span_notice("[p_They()] [p_have()] an empty compartment where a <b>power cell</b> can be installed.")
|
||||
if(load) //observer check is so we don't show the name of the ghost that's sitting on it to prevent metagaming who's ded.
|
||||
. += span_notice("\A [isobserver(load) ? "ghostly figure" : load] is on [p_their()] load platform.")
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/Destroy()
|
||||
UnregisterSignal(src, list(COMSIG_MOB_BOT_PRE_STEP, COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_MOB_BOT_STEP, COMSIG_MOB_CLIENT_MOVED))
|
||||
unload(0)
|
||||
QDEL_NULL(cell)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/get_cell()
|
||||
return cell
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/turn_on()
|
||||
if(!has_power())
|
||||
return
|
||||
return ..()
|
||||
|
||||
/// returns true if the bot is fully powered.
|
||||
/mob/living/simple_animal/bot/mulebot/proc/has_power()
|
||||
return cell && cell.charge > 0 && (!wires.is_cut(WIRE_POWER1) && !wires.is_cut(WIRE_POWER2))
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/attack_hand(mob/living/carbon/human/user, list/modifiers)
|
||||
if(bot_cover_flags & BOT_COVER_MAINTS_OPEN && !HAS_AI_ACCESS(user))
|
||||
wires.interact(user)
|
||||
return
|
||||
if(wires.is_cut(WIRE_RX) && HAS_AI_ACCESS(user))
|
||||
return
|
||||
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/set_id(new_id)
|
||||
id = new_id
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/set_home(turf/home_loc)
|
||||
if(!istype(home_loc))
|
||||
CRASH("MULEbot [id] was requested to set a home location to [home_loc ? "an invalid home loc ([home_loc.type])" : "null"]")
|
||||
|
||||
var/obj/machinery/navbeacon/home_beacon = locate() in home_loc
|
||||
if(!isnull(home_beacon))
|
||||
home_destination = home_beacon.location
|
||||
log_transport("[id]: MULEbot successfuly set home location to ID [home_destination] at [home_beacon.x], [home_beacon.y], [home_beacon.z]")
|
||||
return
|
||||
|
||||
log_transport("[id]: MULEbot failed to set home at [home_loc.x], [home_loc.y], [home_loc.z]")
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/bot_reset()
|
||||
..()
|
||||
reached_target = FALSE
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
. = ..()
|
||||
update_appearance()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/crowbar_act(mob/living/user, obj/item/tool)
|
||||
if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN) || user.combat_mode)
|
||||
return
|
||||
if(!cell)
|
||||
to_chat(user, span_warning("[src] doesn't have a power cell!"))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
cell.add_fingerprint(user)
|
||||
user.visible_message(
|
||||
span_notice("[user] crowbars [cell] out from [src]."),
|
||||
span_notice("You pry [cell] out of [src]."),
|
||||
)
|
||||
if(Adjacent(user) && !issilicon(user))
|
||||
user.put_in_hands(cell)
|
||||
else
|
||||
cell.forceMove(drop_location())
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
||||
if(istype(tool, /obj/item/stock_parts/power_store/cell) && (bot_cover_flags & BOT_COVER_MAINTS_OPEN))
|
||||
if(cell)
|
||||
to_chat(user, span_warning("[src] already has a power cell!"))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if(!user.transferItemToLoc(tool, src))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
cell = tool
|
||||
diag_hud_set_mulebotcell()
|
||||
user.visible_message(
|
||||
span_notice("[user] inserts \a [cell] into [src]."),
|
||||
span_notice("You insert [cell] into [src]."),
|
||||
)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
if(is_wire_tool(tool) && (bot_cover_flags & BOT_COVER_MAINTS_OPEN))
|
||||
attack_hand(user)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/attackby(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers)
|
||||
. = ..()
|
||||
if(ismob(load) && prob(1 + attacking_item.force * 2))
|
||||
user.visible_message(
|
||||
span_danger("[user] knocks [load] off [src] with \the [attacking_item]!"),
|
||||
span_danger("You knock [load] off [src] with \the [attacking_item]!"),
|
||||
)
|
||||
unload(0)
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/emag_act(mob/user, obj/item/card/emag/emag_card)
|
||||
if(!(bot_cover_flags & BOT_COVER_EMAGGED))
|
||||
bot_cover_flags |= BOT_COVER_EMAGGED
|
||||
if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN))
|
||||
bot_cover_flags ^= BOT_COVER_LOCKED
|
||||
balloon_alert(user, "controls [bot_cover_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"]")
|
||||
flick("[base_icon]-emagged", src)
|
||||
playsound(src, SFX_SPARKS, 100, FALSE, SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/update_icon_state() //if you change the icon_state names, please make sure to update /datum/wires/mulebot/on_pulse() as well. <3
|
||||
. = ..()
|
||||
icon_state = "[base_icon][(bot_mode_flags & BOT_MODE_ON) ? wires.is_cut(WIRE_AVOIDANCE) : 0]"
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/update_overlays()
|
||||
. = ..()
|
||||
if(bot_cover_flags & BOT_COVER_MAINTS_OPEN)
|
||||
. += "[base_icon]-hatch"
|
||||
if(!load || ismob(load)) //mob offsets and such are handled by the riding component / buckling
|
||||
return
|
||||
var/mutable_appearance/load_overlay = mutable_appearance(load.icon, load.icon_state, layer + 0.01)
|
||||
load_overlay.pixel_z = initial(load.pixel_z) + 11
|
||||
. += load_overlay
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/ex_act(severity)
|
||||
unload(0)
|
||||
switch(severity)
|
||||
if(EXPLODE_DEVASTATE)
|
||||
qdel(src)
|
||||
if(EXPLODE_HEAVY)
|
||||
wires.cut_random()
|
||||
wires.cut_random()
|
||||
if(EXPLODE_LIGHT)
|
||||
wires.cut_random()
|
||||
|
||||
return TRUE
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/proj)
|
||||
. = ..()
|
||||
if(. && !QDELETED(src)) //Got hit and not blown up yet.
|
||||
if(prob(50) && !isnull(load))
|
||||
unload(0)
|
||||
if(prob(25))
|
||||
visible_message(span_danger("Something shorts out inside [src]!"))
|
||||
wires.cut_random(source = proj.firer)
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "Mule", name)
|
||||
ui.open()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["powerStatus"] = bot_mode_flags & BOT_MODE_ON
|
||||
data["locked"] = bot_cover_flags & BOT_COVER_LOCKED
|
||||
data["siliconUser"] = HAS_SILICON_ACCESS(user)
|
||||
data["mode"] = mode ? "[mode]" : "Ready"
|
||||
data["modeStatus"] = ""
|
||||
switch(mode)
|
||||
if(BOT_IDLE, BOT_DELIVER, BOT_GO_HOME)
|
||||
data["modeStatus"] = "good"
|
||||
if(BOT_BLOCKED, BOT_NAV, BOT_WAIT_FOR_NAV)
|
||||
data["modeStatus"] = "average"
|
||||
if(BOT_NO_ROUTE)
|
||||
data["modeStatus"] = "bad"
|
||||
data["load"] = get_load_name()
|
||||
data["destination"] = destination
|
||||
data["homeDestination"] = home_destination
|
||||
data["destinationsList"] = GLOB.deliverybeacontags
|
||||
data["cellPercent"] = cell?.percent()
|
||||
data["autoReturn"] = mulebot_delivery_flags & MULEBOT_RETURN_MODE
|
||||
data["autoPickup"] = mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE
|
||||
data["reportDelivery"] = mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE
|
||||
data["botId"] = id
|
||||
data["allowPossession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT
|
||||
data["possessionEnabled"] = can_be_possessed
|
||||
data["paiInserted"] = !!paicard
|
||||
return data
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
var/mob/user = ui.user
|
||||
if(. || (bot_cover_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(user)))
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("lock")
|
||||
if(HAS_SILICON_ACCESS(user))
|
||||
bot_cover_flags ^= BOT_COVER_LOCKED
|
||||
return TRUE
|
||||
if("on")
|
||||
if(bot_mode_flags & BOT_MODE_ON)
|
||||
turn_off()
|
||||
else if(bot_cover_flags & BOT_COVER_MAINTS_OPEN)
|
||||
to_chat(user, span_warning("[name]'s maintenance panel is open!"))
|
||||
return
|
||||
else if(cell)
|
||||
if(!turn_on())
|
||||
to_chat(user, span_warning("You can't switch on [src]!"))
|
||||
return
|
||||
return TRUE
|
||||
else
|
||||
bot_control(action, user, params) // Kill this later. // Kill PDAs in general please
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/bot_control(command, mob/user, list/params = list(), pda = FALSE)
|
||||
if(pda && wires.is_cut(WIRE_RX)) // MULE wireless is controlled by wires.
|
||||
return
|
||||
|
||||
switch(command)
|
||||
if("stop")
|
||||
if(mode != BOT_IDLE)
|
||||
bot_reset()
|
||||
if("go")
|
||||
if(mode == BOT_IDLE)
|
||||
start()
|
||||
if("home")
|
||||
if(mode == BOT_IDLE || mode == BOT_DELIVER)
|
||||
start_home()
|
||||
if("destination")
|
||||
var/new_dest
|
||||
if(pda)
|
||||
new_dest = tgui_input_list(user, "Enter Destination", "Mulebot Settings", GLOB.deliverybeacontags, destination)
|
||||
else
|
||||
new_dest = params["value"]
|
||||
if(new_dest)
|
||||
set_destination(new_dest)
|
||||
if("setid")
|
||||
var/new_id = tgui_input_text(user, "Enter ID", "ID Assignment", id, max_length = MAX_NAME_LEN)
|
||||
if(new_id)
|
||||
set_id(new_id)
|
||||
name = "\improper MULEbot [new_id]"
|
||||
if("sethome")
|
||||
var/new_home = tgui_input_list(user, "Enter Home", "Mulebot Settings", GLOB.deliverybeacontags, home_destination)
|
||||
if(new_home)
|
||||
home_destination = new_home
|
||||
if("unload")
|
||||
if(load && mode != BOT_HUNT)
|
||||
if(loc == target)
|
||||
unload(loaddir)
|
||||
else
|
||||
unload(0)
|
||||
if("autoret")
|
||||
mulebot_delivery_flags ^= MULEBOT_RETURN_MODE
|
||||
if("autopick")
|
||||
mulebot_delivery_flags ^= MULEBOT_AUTO_PICKUP_MODE
|
||||
if("report")
|
||||
mulebot_delivery_flags ^= MULEBOT_REPORT_DELIVERY_MODE
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/buzz(type)
|
||||
switch(type)
|
||||
if(SIGH)
|
||||
audible_message(span_hear("[src] makes a sighing buzz."))
|
||||
playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 50, FALSE)
|
||||
if(ANNOYED)
|
||||
audible_message(span_hear("[src] makes an annoyed buzzing sound."))
|
||||
playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, FALSE)
|
||||
if(DELIGHT)
|
||||
audible_message(span_hear("[src] makes a delighted ping!"))
|
||||
playsound(src, 'sound/machines/ping.ogg', 50, FALSE)
|
||||
if(CHIME)
|
||||
audible_message(span_hear("[src] makes a chiming sound!"))
|
||||
playsound(src, 'sound/machines/chime.ogg', 50, FALSE)
|
||||
flick("[base_icon]1", src)
|
||||
|
||||
|
||||
// mousedrop a crate to load the bot
|
||||
// can load anything if hacked
|
||||
/mob/living/simple_animal/bot/mulebot/mouse_drop_receive(atom/movable/AM, mob/user, params)
|
||||
if(!isliving(user))
|
||||
return
|
||||
|
||||
if(!istype(AM) || isdead(AM) || iseyemob(AM) || istype(AM, /obj/effect/dummy/phased_mob))
|
||||
return
|
||||
|
||||
load(AM)
|
||||
|
||||
// called to load a crate
|
||||
/mob/living/simple_animal/bot/mulebot/proc/load(atom/movable/AM)
|
||||
if(load || AM.anchored)
|
||||
return
|
||||
|
||||
if(!isturf(AM.loc)) //To prevent the loading from stuff from someone's inventory or screen icons.
|
||||
return
|
||||
|
||||
var/obj/structure/closet/crate/crate = AM
|
||||
if(!istype(crate))
|
||||
if(!wires.is_cut(WIRE_LOADCHECK))
|
||||
buzz(SIGH)
|
||||
return // if not hacked, only allow crates to be loaded
|
||||
crate = null
|
||||
|
||||
if(crate || isobj(AM))
|
||||
var/obj/O = AM
|
||||
if(O.has_buckled_mobs() || (locate(/mob) in AM)) //can't load non crates objects with mobs buckled to it or inside it.
|
||||
buzz(SIGH)
|
||||
return
|
||||
|
||||
if(crate)
|
||||
crate.close() //make sure the crate is closed
|
||||
|
||||
O.forceMove(src)
|
||||
|
||||
else if(isliving(AM))
|
||||
if(!load_mob(AM)) //forceMove() is handled in buckling
|
||||
return
|
||||
|
||||
load = AM
|
||||
mode = BOT_IDLE
|
||||
update_appearance()
|
||||
|
||||
///resolves the name to display for the loaded mob. primarily needed for the paranormal subtype since we don't want to show the name of ghosts riding it.
|
||||
/mob/living/simple_animal/bot/mulebot/proc/get_load_name()
|
||||
return load ? load.name : null
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/load_mob(mob/living/M)
|
||||
can_buckle = TRUE
|
||||
if(buckle_mob(M))
|
||||
passenger = M
|
||||
load = M
|
||||
can_buckle = FALSE
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/post_unbuckle_mob(mob/living/M)
|
||||
load = null
|
||||
return ..()
|
||||
|
||||
// called to unload the bot
|
||||
// argument is optional direction to unload
|
||||
// if zero, unload at bot's location
|
||||
/mob/living/simple_animal/bot/mulebot/proc/unload(dirn)
|
||||
if(QDELETED(load))
|
||||
if(load) //if our thing was qdel'd, there's likely a leftover reference. just clear it and remove the overlay. we'll let the bot keep moving around to prevent it abruptly stopping somewhere.
|
||||
load = null
|
||||
update_appearance()
|
||||
return
|
||||
|
||||
mode = BOT_IDLE
|
||||
|
||||
var/atom/movable/cached_load = load //cache the load since unbuckling mobs clears the var.
|
||||
|
||||
unbuckle_all_mobs()
|
||||
|
||||
if(load) //don't have to do any of this for mobs.
|
||||
load = null
|
||||
cached_load.forceMove(loc)
|
||||
cached_load.pixel_y = initial(cached_load.pixel_y)
|
||||
cached_load.layer = initial(cached_load.layer)
|
||||
SET_PLANE_EXPLICIT(cached_load, initial(cached_load.plane), src)
|
||||
|
||||
if(dirn) //move the thing to the delivery point.
|
||||
cached_load.Move(get_step(loc,dirn), dirn)
|
||||
|
||||
update_appearance()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/get_status_tab_items()
|
||||
. = ..()
|
||||
if(cell)
|
||||
. += "Charge Left: [cell.charge]/[cell.maxcharge]"
|
||||
else
|
||||
. += "No Cell Inserted!"
|
||||
if(load)
|
||||
. += "Current Load: [get_load_name()]"
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/call_bot()
|
||||
..()
|
||||
if(path && length(path))
|
||||
target = ai_waypoint //Target is the end point of the path, the waypoint set by the AI.
|
||||
destination = get_area_name(target, TRUE)
|
||||
pathset = TRUE //Indicates the AI's custom path is initialized.
|
||||
start()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
|
||||
. = ..()
|
||||
if(has_gravity())
|
||||
for(var/mob/living/carbon/human/future_pancake in loc)
|
||||
if(future_pancake.body_position == LYING_DOWN)
|
||||
run_over(future_pancake)
|
||||
|
||||
diag_hud_set_mulebotcell()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/handle_automated_action()
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return
|
||||
if(!has_power())
|
||||
turn_off()
|
||||
return
|
||||
if(mode == BOT_IDLE)
|
||||
return
|
||||
if(HAS_TRAIT(src, TRAIT_IMMOBILIZED))
|
||||
return
|
||||
|
||||
var/speed = (wires.is_cut(WIRE_MOTOR1) ? 0 : 2) + (wires.is_cut(WIRE_MOTOR2) ? 0 : 1)
|
||||
if(!speed)//Devide by zero man bad
|
||||
return
|
||||
num_steps = round(10/speed) //10, 5, or 3 steps, depending on how many wires we have cut
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/process()
|
||||
if(!(bot_mode_flags & BOT_MODE_ON) || client || (num_steps <= 0) || !has_power())
|
||||
return PROCESS_KILL
|
||||
num_steps--
|
||||
|
||||
switch(mode)
|
||||
if(BOT_IDLE) // idle
|
||||
return
|
||||
|
||||
if(BOT_DELIVER, BOT_GO_HOME, BOT_BLOCKED) // navigating to deliver,home, or blocked
|
||||
if(loc == target) // reached target
|
||||
at_target()
|
||||
return
|
||||
|
||||
else if(length(path) && target) // valid path
|
||||
var/turf/next = path[1]
|
||||
reached_target = FALSE
|
||||
if(next == loc)
|
||||
path -= next
|
||||
return
|
||||
if(isturf(next))
|
||||
if(SEND_SIGNAL(src, COMSIG_MOB_BOT_PRE_STEP) & COMPONENT_MOB_BOT_BLOCK_PRE_STEP)
|
||||
return
|
||||
var/oldloc = loc
|
||||
var/moved = step_towards(src, next) // attempt to move
|
||||
if(moved && oldloc != loc) // successful move
|
||||
SEND_SIGNAL(src, COMSIG_MOB_BOT_STEP)
|
||||
blockcount = 0
|
||||
path -= loc
|
||||
if(destination == home_destination)
|
||||
mode = BOT_GO_HOME
|
||||
else
|
||||
mode = BOT_DELIVER
|
||||
|
||||
else // failed to move
|
||||
|
||||
blockcount++
|
||||
mode = BOT_BLOCKED
|
||||
if(blockcount == 3)
|
||||
buzz(ANNOYED)
|
||||
|
||||
if(blockcount > 10) // attempt 10 times before recomputing
|
||||
// find new path excluding blocked turf
|
||||
buzz(SIGH)
|
||||
mode = BOT_WAIT_FOR_NAV
|
||||
blockcount = 0
|
||||
addtimer(CALLBACK(src, PROC_REF(process_blocked), next), 2 SECONDS)
|
||||
return
|
||||
return
|
||||
else
|
||||
buzz(ANNOYED)
|
||||
mode = BOT_NAV
|
||||
return
|
||||
else
|
||||
mode = BOT_NAV
|
||||
return
|
||||
|
||||
if(BOT_NAV) // calculate new path
|
||||
mode = BOT_WAIT_FOR_NAV
|
||||
INVOKE_ASYNC(src, PROC_REF(process_nav))
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/process_blocked(turf/next)
|
||||
calc_path(avoid=next)
|
||||
if(length(path))
|
||||
buzz(DELIGHT)
|
||||
mode = BOT_BLOCKED
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/process_nav()
|
||||
calc_path()
|
||||
|
||||
if(length(path))
|
||||
blockcount = 0
|
||||
mode = BOT_BLOCKED
|
||||
buzz(DELIGHT)
|
||||
|
||||
else
|
||||
buzz(SIGH)
|
||||
|
||||
mode = BOT_NO_ROUTE
|
||||
|
||||
// calculates a path to the current destination
|
||||
// given an optional turf to avoid
|
||||
/mob/living/simple_animal/bot/mulebot/calc_path(turf/avoid = null)
|
||||
path = get_path_to(src, target, max_distance=250, access=access_card.GetAccess(), exclude=avoid, diagonal_handling=DIAGONAL_REMOVE_ALL)
|
||||
|
||||
// sets the current destination
|
||||
// signals all beacons matching the delivery code
|
||||
// beacons will return a signal giving their locations
|
||||
/mob/living/simple_animal/bot/mulebot/proc/set_destination(new_dest)
|
||||
new_destination = new_dest
|
||||
get_nav()
|
||||
|
||||
// starts bot moving to current destination
|
||||
/mob/living/simple_animal/bot/mulebot/proc/start()
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return
|
||||
if(destination == home_destination)
|
||||
mode = BOT_GO_HOME
|
||||
else
|
||||
mode = BOT_DELIVER
|
||||
get_nav()
|
||||
|
||||
// starts bot moving to home
|
||||
// sends a beacon query to find
|
||||
/mob/living/simple_animal/bot/mulebot/proc/start_home()
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return
|
||||
INVOKE_ASYNC(src, PROC_REF(do_start_home))
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/do_start_home()
|
||||
set_destination(home_destination)
|
||||
mode = BOT_BLOCKED
|
||||
|
||||
// called when bot reaches current target
|
||||
/mob/living/simple_animal/bot/mulebot/proc/at_target()
|
||||
if(!reached_target)
|
||||
radio_channel = RADIO_CHANNEL_SUPPLY //Supply channel
|
||||
buzz(CHIME)
|
||||
reached_target = TRUE
|
||||
|
||||
if(pathset) //The AI called us here, so notify it of our arrival.
|
||||
loaddir = dir //The MULE will attempt to load a crate in whatever direction the MULE is "facing".
|
||||
if(calling_ai)
|
||||
to_chat(calling_ai, span_notice("[icon2html(src, calling_ai)] [src] wirelessly plays a chiming sound!"))
|
||||
calling_ai.playsound_local(calling_ai, 'sound/machines/chime.ogg', 40, FALSE)
|
||||
calling_ai = null
|
||||
radio_channel = RADIO_CHANNEL_AI_PRIVATE //Report on AI Private instead if the AI is controlling us.
|
||||
|
||||
if(load) // if loaded, unload at target
|
||||
if(mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE)
|
||||
speak("Destination [RUNECHAT_BOLD("[destination]")] reached. Unloading [load].",radio_channel)
|
||||
unload(loaddir)
|
||||
else
|
||||
// not loaded
|
||||
if(mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE) // find a crate
|
||||
var/atom/movable/AM
|
||||
if(wires.is_cut(WIRE_LOADCHECK)) // if hacked, load first unanchored thing we find
|
||||
for(var/atom/movable/A in get_step(loc, loaddir))
|
||||
if(!A.anchored)
|
||||
AM = A
|
||||
break
|
||||
else // otherwise, look for crates only
|
||||
AM = locate(/obj/structure/closet/crate) in get_step(loc,loaddir)
|
||||
if(AM?.Adjacent(src))
|
||||
load(AM)
|
||||
if(mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE)
|
||||
speak("Now loading [load] at [RUNECHAT_BOLD("[get_area_name(src)]")].", radio_channel)
|
||||
// whatever happened, check to see if we return home
|
||||
|
||||
if((mulebot_delivery_flags & MULEBOT_RETURN_MODE) && home_destination && destination != home_destination)
|
||||
// auto return set and not at home already
|
||||
start_home()
|
||||
mode = BOT_BLOCKED
|
||||
else
|
||||
bot_reset() // otherwise go idle
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/MobBump(mob/M) // called when the bot bumps into a mob
|
||||
if(mind || !isliving(M)) //if there's a sentience controlling the bot, they aren't allowed to harm folks.
|
||||
return ..()
|
||||
var/mob/living/L = M
|
||||
if(wires.is_cut(WIRE_AVOIDANCE)) // usually just bumps, but if the avoidance wire is cut, knocks them over.
|
||||
if(iscyborg(L))
|
||||
visible_message(span_danger("[src] bumps into [L]!"))
|
||||
else if(L.Knockdown(8 SECONDS))
|
||||
log_combat(src, L, "knocked down")
|
||||
visible_message(span_danger("[src] knocks over [L]!"))
|
||||
return ..()
|
||||
|
||||
// when mulebot is in the same loc
|
||||
/mob/living/simple_animal/bot/mulebot/proc/run_over(mob/living/carbon/human/crushed)
|
||||
if (!(bot_cover_flags & BOT_COVER_EMAGGED) && !wires.is_cut(WIRE_AVOIDANCE))
|
||||
if (!has_status_effect(/datum/status_effect/careful_driving))
|
||||
crushed.visible_message(span_notice("[src] slows down to avoid crushing [crushed]."))
|
||||
apply_status_effect(/datum/status_effect/careful_driving)
|
||||
return // Player mules must be emagged before they can trample
|
||||
|
||||
log_combat(src, crushed, "run over", addition = "(DAMTYPE: [uppertext(BRUTE)])")
|
||||
crushed.visible_message(
|
||||
span_danger("[src] drives over [crushed]!"),
|
||||
span_userdanger("[src] drives over you!"),
|
||||
)
|
||||
|
||||
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
|
||||
|
||||
var/damage = rand(5, 15)
|
||||
crushed.apply_damage(2 * damage, BRUTE, BODY_ZONE_HEAD, run_armor_check(BODY_ZONE_HEAD, MELEE))
|
||||
crushed.apply_damage(2 * damage, BRUTE, BODY_ZONE_CHEST, run_armor_check(BODY_ZONE_CHEST, MELEE))
|
||||
crushed.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_LEG, run_armor_check(BODY_ZONE_L_LEG, MELEE))
|
||||
crushed.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_LEG, run_armor_check(BODY_ZONE_R_LEG, MELEE))
|
||||
crushed.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_ARM, run_armor_check(BODY_ZONE_L_ARM, MELEE))
|
||||
crushed.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_ARM, run_armor_check(BODY_ZONE_R_ARM, MELEE))
|
||||
|
||||
add_mob_blood(crushed)
|
||||
|
||||
var/turf/below_us = get_turf(src)
|
||||
below_us.add_mob_blood(crushed)
|
||||
|
||||
AddComponent(/datum/component/blood_walk, \
|
||||
blood_type = /obj/effect/decal/cleanable/blood/tracks, \
|
||||
target_dir_change = TRUE, \
|
||||
transfer_blood_dna = TRUE, \
|
||||
max_blood = 4)
|
||||
|
||||
// player on mulebot attempted to move
|
||||
/mob/living/simple_animal/bot/mulebot/relaymove(mob/living/user, direction)
|
||||
if(user.incapacitated)
|
||||
return
|
||||
if(load == user)
|
||||
unload(0)
|
||||
|
||||
|
||||
//Update navigation data. Called when commanded to deliver, return home, or a route update is needed...
|
||||
/mob/living/simple_animal/bot/mulebot/proc/get_nav()
|
||||
if(!(bot_mode_flags & BOT_MODE_ON) || wires.is_cut(WIRE_BEACON))
|
||||
return
|
||||
|
||||
for(var/obj/machinery/navbeacon/NB in GLOB.deliverybeacons)
|
||||
if(NB.location == new_destination) // if the beacon location matches the set destination
|
||||
// the we will navigate there
|
||||
destination = new_destination
|
||||
target = NB.loc
|
||||
var/direction = NB.codes[NAVBEACON_DELIVERY_DIRECTION] // this will be the load/unload dir
|
||||
if(!direction)
|
||||
direction = NB.dir // fallback
|
||||
if(direction)
|
||||
loaddir = text2num(direction)
|
||||
else
|
||||
loaddir = 0
|
||||
if(destination) // No need to calculate a path if you do not have a destination set!
|
||||
calc_path()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/emp_act(severity)
|
||||
. = ..()
|
||||
if(cell && !(. & EMP_PROTECT_CONTENTS))
|
||||
cell.emp_act(severity)
|
||||
if(load)
|
||||
load.emp_act(severity)
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/explode()
|
||||
var/atom/Tsec = drop_location()
|
||||
|
||||
new /obj/item/assembly/prox_sensor(Tsec)
|
||||
new /obj/item/stack/rods(Tsec)
|
||||
new /obj/item/stack/rods(Tsec)
|
||||
new /obj/item/stack/cable_coil/cut(Tsec)
|
||||
if(cell)
|
||||
cell.forceMove(Tsec)
|
||||
cell = null
|
||||
|
||||
new /obj/effect/decal/cleanable/blood/oil(loc)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/remove_air(amount) //To prevent riders suffocating
|
||||
return loc ? loc.remove_air(amount) : null
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/execute_resist()
|
||||
. = ..()
|
||||
if(load)
|
||||
unload()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/UnarmedAttack(atom/A, proximity_flag, list/modifiers)
|
||||
if(!can_unarmed_attack())
|
||||
return
|
||||
if(isturf(A) && isturf(loc) && loc.Adjacent(A) && load)
|
||||
unload(get_dir(loc, A))
|
||||
else
|
||||
return ..()
|
||||
|
||||
/// Checks whether the bot can complete a step_towards, checking whether the bot is on and has the charge to do the move. Returns COMPONENT_MOB_BOT_CANCELSTEP if the bot should not step.
|
||||
/mob/living/simple_animal/bot/mulebot/proc/check_pre_step(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return COMPONENT_MOB_BOT_BLOCK_PRE_STEP
|
||||
|
||||
if((cell && (cell.charge < cell_move_power_usage)) || !has_power())
|
||||
turn_off()
|
||||
return COMPONENT_MOB_BOT_BLOCK_PRE_STEP
|
||||
|
||||
/// Uses power from the cell when the bot steps.
|
||||
/mob/living/simple_animal/bot/mulebot/proc/on_bot_step(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
cell?.use(cell_move_power_usage)
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/post_possession()
|
||||
. = ..()
|
||||
visible_message(span_notice("[src]'s safeties are locked on."))
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/paranormal//allows ghosts only unless hacked to actually be useful
|
||||
name = "\improper GHOULbot"
|
||||
desc = "A rather ghastly looking... Multiple Utility Load Effector bot? It only seems to accept paranormal forces, and for this reason is fucking useless."
|
||||
icon_state = "paranormalmulebot0"
|
||||
base_icon = "paranormalmulebot"
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/paranormal/mouse_drop_receive(atom/movable/AM, mob/user, params)
|
||||
var/mob/living/L = user
|
||||
|
||||
if(user.incapacitated || (istype(L) && L.body_position == LYING_DOWN))
|
||||
return
|
||||
|
||||
if(!istype(AM) || iseyemob(AM) || istype(AM, /obj/effect/dummy/phased_mob)) //allows ghosts!
|
||||
return
|
||||
|
||||
load(AM)
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/paranormal/load(atom/movable/movable_atom)
|
||||
if(load || movable_atom.anchored)
|
||||
return
|
||||
|
||||
if(!isturf(movable_atom.loc)) //To prevent the loading from stuff from someone's inventory or screen icons.
|
||||
return
|
||||
|
||||
if(isobserver(movable_atom))
|
||||
visible_message(span_warning("A ghostly figure appears on [src]!"))
|
||||
movable_atom.forceMove(src)
|
||||
RegisterSignal(movable_atom, COMSIG_MOVABLE_MOVED, PROC_REF(ghostmoved))
|
||||
|
||||
else if(!wires.is_cut(WIRE_LOADCHECK))
|
||||
buzz(SIGH)
|
||||
return // if not hacked, only allow ghosts to be loaded
|
||||
|
||||
else if(isobj(movable_atom))
|
||||
if(movable_atom.has_buckled_mobs() || (locate(/mob) in movable_atom)) //can't load non crates objects with mobs buckled to it or inside it.
|
||||
buzz(SIGH)
|
||||
return
|
||||
|
||||
if(istype(movable_atom, /obj/structure/closet/crate))
|
||||
var/obj/structure/closet/crate/crate = movable_atom
|
||||
crate.close() //make sure it's closed
|
||||
|
||||
movable_atom.forceMove(src)
|
||||
|
||||
else if(isliving(movable_atom) && !load_mob(movable_atom))
|
||||
return
|
||||
|
||||
load = movable_atom
|
||||
mode = BOT_IDLE
|
||||
update_appearance()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/paranormal/update_overlays()
|
||||
. = ..()
|
||||
if(!isobserver(load))
|
||||
return
|
||||
var/mutable_appearance/ghost_overlay = mutable_appearance('icons/mob/simple/mob.dmi', "ghost", layer + 0.01) //use a generic ghost icon, otherwise you can metagame who's dead if they have a custom ghost set
|
||||
ghost_overlay.pixel_z = 12
|
||||
. += ghost_overlay
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/paranormal/get_load_name() //Don't reveal the name of ghosts so we can't metagame who died and all that.
|
||||
. = ..()
|
||||
if(. && isobserver(load))
|
||||
return "Unknown"
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/paranormal/proc/ghostmoved()
|
||||
SIGNAL_HANDLER
|
||||
visible_message(span_notice("The ghostly figure vanishes..."))
|
||||
UnregisterSignal(load, COMSIG_MOVABLE_MOVED)
|
||||
unload(0)
|
||||
|
||||
#undef SIGH
|
||||
#undef ANNOYED
|
||||
#undef DELIGHT
|
||||
#undef CHIME
|
||||
@@ -213,27 +213,34 @@
|
||||
animate(src, alpha = 255, time = duration)
|
||||
|
||||
/obj/effect/temp_visual/lava_warning/proc/fall(reset_time)
|
||||
var/turf/T = get_turf(src)
|
||||
playsound(T,'sound/effects/magic/fleshtostone.ogg', 80, TRUE)
|
||||
var/turf/our_turf = get_turf(src)
|
||||
playsound(our_turf,'sound/effects/magic/fleshtostone.ogg', 80, TRUE)
|
||||
sleep(duration)
|
||||
playsound(T,'sound/effects/magic/fireball.ogg', 200, TRUE)
|
||||
playsound(our_turf,'sound/effects/magic/fireball.ogg', 200, TRUE)
|
||||
var/can_transform_turf = !isclosedturf(our_turf) && !islava(our_turf)
|
||||
|
||||
for(var/mob/living/L in T.contents - owner)
|
||||
if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon))
|
||||
for(var/mob/living/victim in our_turf)
|
||||
if(istype(victim, /mob/living/simple_animal/hostile/megafauna/dragon) || victim == owner)
|
||||
continue
|
||||
L.adjust_fire_loss(10)
|
||||
to_chat(L, span_userdanger("You fall directly into the pool of lava!"))
|
||||
victim.adjust_fire_loss(10)
|
||||
if(can_transform_turf)
|
||||
to_chat(victim, span_userdanger("You fall directly into the pool of lava!"))
|
||||
else
|
||||
to_chat(victim, span_userdanger("You are set ablaze by a fireball from above!"))
|
||||
|
||||
// deals damage to mechs
|
||||
for(var/obj/vehicle/sealed/mecha/M in T.contents)
|
||||
M.take_damage(45, BRUTE, MELEE, 1)
|
||||
for(var/obj/vehicle/sealed/mecha/mech in our_turf)
|
||||
mech.take_damage(45, BRUTE, MELEE, 1)
|
||||
|
||||
// changes turf to lava temporarily
|
||||
if(!isclosedturf(T) && !islava(T))
|
||||
var/lava_turf = /turf/open/lava/smooth
|
||||
var/reset_turf = T.type
|
||||
T.TerraformTurf(lava_turf, flags = CHANGETURF_INHERIT_AIR)
|
||||
addtimer(CALLBACK(T, TYPE_PROC_REF(/turf, ChangeTurf), reset_turf, null, CHANGETURF_INHERIT_AIR), reset_time, TIMER_OVERRIDE|TIMER_UNIQUE)
|
||||
// changes turf to lava temporarily if possible, create a fire visual otherwise
|
||||
if(!can_transform_turf)
|
||||
new /obj/effect/temp_visual/fire/light(our_turf)
|
||||
return
|
||||
|
||||
var/lava_turf = /turf/open/lava/smooth
|
||||
var/reset_turf = our_turf.type
|
||||
our_turf.TerraformTurf(lava_turf, flags = CHANGETURF_INHERIT_AIR)
|
||||
addtimer(CALLBACK(our_turf, TYPE_PROC_REF(/turf, ChangeTurf), reset_turf, null, CHANGETURF_INHERIT_AIR), reset_time, TIMER_OVERRIDE|TIMER_UNIQUE)
|
||||
|
||||
/obj/effect/temp_visual/drakewall
|
||||
desc = "An ash drakes true flame."
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear
|
||||
name = "polar bear"
|
||||
desc = "An aggressive animal that defends its territory with incredible power. These beasts don't run from their enemies."
|
||||
icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi'
|
||||
icon_state = "polarbear"
|
||||
icon_living = "polarbear"
|
||||
icon_dead = "polarbear_dead"
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST|MOB_MINING
|
||||
mouse_opacity = MOUSE_OPACITY_ICON
|
||||
friendly_verb_continuous = "growls at"
|
||||
friendly_verb_simple = "growl at"
|
||||
speak_emote = list("growls")
|
||||
speed = 3
|
||||
move_to_delay = 8
|
||||
maxHealth = 300
|
||||
health = 300
|
||||
obj_damage = 40
|
||||
melee_damage_lower = 25
|
||||
melee_damage_upper = 25
|
||||
attack_verb_continuous = "claws"
|
||||
attack_verb_simple = "claw"
|
||||
attack_sound = 'sound/items/weapons/bladeslice.ogg'
|
||||
attack_vis_effect = ATTACK_EFFECT_CLAW
|
||||
vision_range = 2 // don't aggro unless you basically antagonize it, though they will kill you worse than a goliath will
|
||||
aggro_vision_range = 9
|
||||
move_force = MOVE_FORCE_VERY_STRONG
|
||||
move_resist = MOVE_FORCE_VERY_STRONG
|
||||
pull_force = MOVE_FORCE_VERY_STRONG
|
||||
butcher_results = list(/obj/item/food/meat/slab/bear = 3, /obj/item/stack/sheet/bone = 2)
|
||||
guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/goliath_hide/polar_bear_hide = 1)
|
||||
loot = list()
|
||||
crusher_loot = /obj/item/crusher_trophy/bear_paw
|
||||
stat_attack = HARD_CRIT
|
||||
robust_searching = TRUE
|
||||
footstep_type = FOOTSTEP_MOB_CLAW
|
||||
/// Message for when the polar bear starts to attack faster
|
||||
var/aggressive_message_said = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(\
|
||||
/datum/element/change_force_on_death,\
|
||||
move_force = MOVE_FORCE_DEFAULT,\
|
||||
move_resist = MOVE_RESIST_DEFAULT,\
|
||||
pull_force = PULL_FORCE_DEFAULT,\
|
||||
)
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
|
||||
. = ..()
|
||||
if(health > maxHealth*0.5)
|
||||
rapid_melee = initial(rapid_melee)
|
||||
return
|
||||
if(!aggressive_message_said && target)
|
||||
visible_message(span_danger("\The [src] gets an enraged look at [target]!"))
|
||||
aggressive_message_said = TRUE
|
||||
rapid_melee = 2
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/Life(seconds_per_tick = SSMOBS_DT)
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
return
|
||||
aggressive_message_said = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/lesser
|
||||
name = "magic polar bear"
|
||||
desc = "It seems sentient somehow."
|
||||
faction = list(FACTION_NEUTRAL)
|
||||
@@ -11,7 +11,7 @@
|
||||
emote_see = list("jiggles", "bounces in place")
|
||||
speak_emote = list("blorbles")
|
||||
atmos_requirements = null
|
||||
hud_type = /datum/hud/ooze
|
||||
hud_type = /datum/hud/living/ooze
|
||||
minbodytemp = 250
|
||||
maxbodytemp = INFINITY
|
||||
faction = list(FACTION_SLIME)
|
||||
@@ -323,7 +323,7 @@
|
||||
///Ability that allows the owner to fire healing globules at mobs, targeting specific limbs.
|
||||
/datum/action/cooldown/globules
|
||||
name = "Fire Mending globule"
|
||||
desc = "Fires a mending globule at someone, healing a specific limb of theirs."
|
||||
desc = "Fires a mending globule at someone, healing a specific limb of theirs. Costs 5 nutrition."
|
||||
background_icon_state = "bg_hive"
|
||||
overlay_icon_state = "bg_hive_border"
|
||||
button_icon = 'icons/mob/actions/actions_slime.dmi'
|
||||
@@ -333,10 +333,16 @@
|
||||
click_to_activate = TRUE
|
||||
|
||||
/datum/action/cooldown/globules/set_click_ability(mob/on_who)
|
||||
var/mob/living/simple_animal/hostile/ooze/oozy_owner = owner
|
||||
if(istype(oozy_owner))
|
||||
if(oozy_owner.ooze_nutrition < 5)
|
||||
to_chat(oozy_owner, span_warning("You need at least 5 nutrition to launch a mending globule."))
|
||||
return
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
oozy_owner.adjust_ooze_nutrition(-5)
|
||||
to_chat(on_who, span_notice("You prepare to launch a mending globule. <B>Left-click to fire at a target!</B>"))
|
||||
|
||||
/datum/action/cooldown/globules/unset_click_ability(mob/on_who, refund_cooldown = TRUE)
|
||||
@@ -345,21 +351,10 @@
|
||||
return
|
||||
|
||||
if(refund_cooldown)
|
||||
var/mob/living/simple_animal/hostile/ooze/oozy_owner = owner
|
||||
oozy_owner.adjust_ooze_nutrition(5)
|
||||
to_chat(on_who, span_notice("You stop preparing your mending globules."))
|
||||
|
||||
/datum/action/cooldown/globules/Activate(atom/target)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return FALSE
|
||||
|
||||
var/mob/living/simple_animal/hostile/ooze/oozy_owner = owner
|
||||
if(istype(oozy_owner))
|
||||
if(oozy_owner.ooze_nutrition < 5)
|
||||
to_chat(oozy_owner, span_warning("You need at least 5 nutrition to launch a mending globule."))
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/action/cooldown/globules/InterceptClickOn(mob/living/clicker, params, atom/target)
|
||||
. = ..()
|
||||
if(!.)
|
||||
@@ -427,7 +422,7 @@
|
||||
///This action lets you put a mob inside of a cacoon that will inject it with some chemicals.
|
||||
/datum/action/cooldown/gel_cocoon
|
||||
name = "Gel Cocoon"
|
||||
desc = "Puts a mob inside of a cocoon, allowing it to slowly heal."
|
||||
desc = "Puts a mob inside of a cocoon, allowing it to slowly heal. Costs 30 nutrition."
|
||||
background_icon_state = "bg_hive"
|
||||
overlay_icon_state = "bg_hive_border"
|
||||
button_icon = 'icons/mob/actions/actions_slime.dmi'
|
||||
|
||||
@@ -489,6 +489,49 @@
|
||||
return quirk
|
||||
return null
|
||||
|
||||
/**
|
||||
* get_quirk_string() is used to get a printable string of all the quirk traits someone has for certain criteria
|
||||
*
|
||||
* Arguments:
|
||||
* * Medical- If we want the long, fancy descriptions that show up in medical records, or if not, just the name
|
||||
* * Category- Which types of quirks we want to print out. Defaults to everything
|
||||
* * from_scan- If the source of this call is like a health analyzer or HUD, in which case QUIRK_HIDE_FROM_MEDICAL hides the quirk.
|
||||
*/
|
||||
/mob/living/proc/get_quirk_string(medical = FALSE, category = CAT_QUIRK_ALL, from_scan = FALSE)
|
||||
var/list/dat = list()
|
||||
for(var/datum/quirk/candidate as anything in quirks)
|
||||
if(from_scan && (candidate.quirk_flags & QUIRK_HIDE_FROM_SCAN))
|
||||
continue
|
||||
switch(category)
|
||||
if(CAT_QUIRK_MAJOR_DISABILITY)
|
||||
if(candidate.value >= -4)
|
||||
continue
|
||||
if(CAT_QUIRK_MINOR_DISABILITY)
|
||||
if(!ISINRANGE(candidate.value, -4, -1))
|
||||
continue
|
||||
if(CAT_QUIRK_NOTES)
|
||||
if(candidate.value < 0)
|
||||
continue
|
||||
dat += medical ? candidate.medical_record_text : candidate.name
|
||||
|
||||
if(!length(dat))
|
||||
return medical ? "No issues have been declared." : "None"
|
||||
return medical ? dat.Join("<br>") : dat.Join(", ")
|
||||
|
||||
/mob/living/proc/cleanse_quirk_datums() //removes all trait datums
|
||||
QDEL_LAZYLIST(quirks)
|
||||
|
||||
/mob/living/proc/transfer_quirk_datums(mob/living/to_mob)
|
||||
// We could be done before the client was moved or after the client was moved
|
||||
var/datum/preferences/to_pass = client || to_mob.client
|
||||
|
||||
for(var/datum/quirk/quirk as anything in quirks)
|
||||
if(quirk.quirk_flags & QUIRK_NO_TRANSFER)
|
||||
continue
|
||||
quirk.remove_from_current_holder(quirk_transfer = TRUE)
|
||||
quirk.add_to_holder(to_mob, quirk_transfer = TRUE, client_source = to_pass)
|
||||
|
||||
|
||||
/// Helper to easily add a personality by a typepath
|
||||
/mob/living/proc/add_personality(personality_type)
|
||||
var/datum/personality/personality = SSpersonalities.personalities_by_type[personality_type]
|
||||
@@ -509,6 +552,14 @@
|
||||
for(var/personality_type in personalities)
|
||||
remove_personality(personality_type)
|
||||
|
||||
/// Returns a string with the names of the personalities of this mob, and their description as tooltip
|
||||
/mob/living/proc/get_parsonality_string()
|
||||
var/list/return_list = list()
|
||||
for(var/personality_type in personalities)
|
||||
var/datum/personality/personality = SSpersonalities.personalities_by_type[personality_type]
|
||||
return_list += span_tooltip(personality.desc, personality.name)
|
||||
return english_list(return_list)
|
||||
|
||||
/mob/living/proc/cure_husk(source)
|
||||
REMOVE_TRAIT(src, TRAIT_HUSK, source)
|
||||
if(HAS_TRAIT(src, TRAIT_HUSK))
|
||||
|
||||
@@ -131,8 +131,8 @@
|
||||
SEND_SIGNAL(client, COMSIG_CLIENT_MOB_LOGIN, src)
|
||||
client.init_verbs()
|
||||
|
||||
AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
|
||||
AddElement(/datum/element/weather_listener, /datum/weather/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
|
||||
AddElement(/datum/element/weather_listener, /datum/weather/particle/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
|
||||
AddElement(/datum/element/weather_listener, /datum/weather/particle/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
|
||||
AddElement(/datum/element/weather_listener, /datum/weather/sand_storm, ZTRAIT_SANDSTORM, GLOB.sand_storm_sounds)
|
||||
AddElement(/datum/element/weather_listener, /datum/weather/snow_storm, ZTRAIT_SNOWSTORM, GLOB.snowstorm_sounds)
|
||||
|
||||
|
||||
@@ -46,21 +46,26 @@
|
||||
///Adds the cliented mob reference to the list of all player-mobs, besides to either the of dead or alive player-mob lists, as appropriate. Called on Login().
|
||||
/mob/proc/add_to_player_list()
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
|
||||
GLOB.player_list |= src
|
||||
if(stat == DEAD)
|
||||
add_to_current_dead_players()
|
||||
else
|
||||
add_to_current_living_players()
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PLAYER_LOGIN, src)
|
||||
|
||||
///Removes the mob reference from the list of all player-mobs, besides from either the of dead or alive player-mob lists, as appropriate. Called on Logout().
|
||||
/mob/proc/remove_from_player_list()
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
GLOB.player_list -= src
|
||||
GLOB.keyloop_list -= src
|
||||
if(stat == DEAD)
|
||||
remove_from_current_dead_players()
|
||||
else
|
||||
remove_from_current_living_players()
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PLAYER_LOGOUT, src)
|
||||
|
||||
|
||||
///Adds the cliented mob reference to either the list of dead player-mobs or to the list of observers, depending on how they joined the game.
|
||||
|
||||
@@ -604,3 +604,8 @@
|
||||
if(new_turf && (istype(new_turf, /turf/cordon/secret) || is_secret_level(new_turf.z)) && !client?.holder)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
||||
. = ..()
|
||||
if(client?.sound_tokens.len)
|
||||
SSsound_tokens.clients_needing_update[client] = TRUE
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
return FALSE
|
||||
|
||||
///The amount of items we are looking for in the message
|
||||
#define MESSAGE_MODS_LENGTH 6
|
||||
#define MESSAGE_MODS_LENGTH 7
|
||||
|
||||
/mob/proc/check_for_custom_say_emote(message, list/mods)
|
||||
var/customsaypos = findtext(message, "*")
|
||||
|
||||
Reference in New Issue
Block a user