mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-24 04:27:35 +01:00
Converts mimic mobs to basic mobs (#31424)
* Converts mimic mobs to basic mobs * Unit tests * Units
This commit is contained in:
@@ -0,0 +1,438 @@
|
||||
/mob/living/basic/mimic
|
||||
name = "crate"
|
||||
desc = "A rectangular steel crate."
|
||||
icon = 'icons/obj/crates.dmi'
|
||||
icon_state = "crate"
|
||||
icon_living = "crate"
|
||||
|
||||
response_help_simple = "touch the"
|
||||
response_help_continuous = "touches the"
|
||||
response_disarm_simple = "push the"
|
||||
response_disarm_continuous = "pushes the"
|
||||
response_harm_simple = "hit the"
|
||||
response_harm_continuous = "hits the"
|
||||
speed = 0
|
||||
maxHealth = 250
|
||||
health = 250
|
||||
|
||||
mob_biotypes = NONE
|
||||
|
||||
harm_intent_damage = 5
|
||||
melee_attack_cooldown_min = 1.5 SECONDS
|
||||
melee_attack_cooldown_max = 2.5 SECONDS
|
||||
melee_damage_lower = 8
|
||||
melee_damage_upper = 12
|
||||
attack_sound = 'sound/weapons/bite.ogg'
|
||||
speak_emote = list("creaks")
|
||||
|
||||
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
|
||||
minimum_survivable_temperature = 0
|
||||
|
||||
faction = list("mimic")
|
||||
|
||||
gold_core_spawnable = HOSTILE_SPAWN
|
||||
basic_mob_flags = DEL_ON_DEATH
|
||||
ai_controller = /datum/ai_controller/basic_controller/mimic
|
||||
/// Used for if the mob should be affected by EMPs or ions
|
||||
var/is_electronic = 0
|
||||
|
||||
/mob/living/basic/mimic/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/ai_retaliate)
|
||||
add_language("Galactic Common")
|
||||
set_default_language(GLOB.all_languages["Galactic Common"])
|
||||
AddComponent(/datum/component/aggro_emote, emote_list = list("growls"))
|
||||
|
||||
/mob/living/basic/mimic/emp_act(severity)
|
||||
if(is_electronic)
|
||||
switch(severity)
|
||||
if(1)
|
||||
death()
|
||||
if(2)
|
||||
adjustBruteLoss(50)
|
||||
..(severity)
|
||||
|
||||
// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies.
|
||||
/mob/living/basic/mimic/crate
|
||||
attack_verb_simple = "bite"
|
||||
attack_verb_continuous = "bites"
|
||||
ai_controller = /datum/ai_controller/basic_controller/mimic/crate
|
||||
/// Has the crate mimic been opened?
|
||||
var/attempt_open = FALSE
|
||||
|
||||
// Pickup loot
|
||||
/mob/living/basic/mimic/crate/Initialize(mapload)
|
||||
. = ..()
|
||||
for(var/obj/item/I in loc)
|
||||
I.loc = src
|
||||
|
||||
/mob/living/basic/mimic/crate/Move(atom/newloc, direct, glide_size_override, update_dir)
|
||||
. = ..()
|
||||
if(prob(90))
|
||||
icon_state = "[initial(icon_state)]_open"
|
||||
else
|
||||
icon_state = initial(icon_state)
|
||||
|
||||
/mob/living/basic/mimic/crate/melee_attack(atom/target, list/modifiers, ignore_cooldown)
|
||||
. = ..()
|
||||
if(.)
|
||||
icon_state = initial(icon_state)
|
||||
if(prob(15) && iscarbon(target))
|
||||
var/mob/living/carbon/C = target
|
||||
C.Weaken(4 SECONDS)
|
||||
C.visible_message(SPAN_DANGER("\The [src] knocks down \the [C]!"), SPAN_USERDANGER("\The [src] knocks you down!"))
|
||||
|
||||
/mob/living/basic/mimic/crate/proc/trigger()
|
||||
if(!attempt_open)
|
||||
visible_message("<b>[src]</b> starts to move!")
|
||||
attempt_open = TRUE
|
||||
|
||||
/mob/living/basic/mimic/crate/adjustHealth(amount, updating_health = TRUE)
|
||||
trigger()
|
||||
. = ..()
|
||||
|
||||
/mob/living/basic/mimic/crate/Life(seconds, times_fired)
|
||||
. = ..()
|
||||
var/atom/target = ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
|
||||
if(!target)
|
||||
icon_state = initial(icon_state)
|
||||
|
||||
/mob/living/basic/mimic/crate/death(gibbed)
|
||||
if(can_die())
|
||||
var/obj/structure/closet/crate/C = new(get_turf(src))
|
||||
// Put loot in crate
|
||||
for(var/obj/O in src)
|
||||
O.forceMove(C)
|
||||
// due to `del_on_death`
|
||||
return ..()
|
||||
|
||||
GLOBAL_LIST_INIT(protected_objects, list(/obj/structure/table, /obj/structure/cable, /obj/structure/window))
|
||||
|
||||
/mob/living/basic/mimic/copy
|
||||
health = 100
|
||||
maxHealth = 100
|
||||
gold_core_spawnable = NO_SPAWN
|
||||
/// The mob that created the copy
|
||||
var/mob/living/creator = null
|
||||
/// Does this mob knock people down?
|
||||
var/knockdown_people = 0
|
||||
/// The image of the googly eyes we use
|
||||
var/image/googly_eyes = null
|
||||
|
||||
/mob/living/basic/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0)
|
||||
. = ..()
|
||||
CopyObject(copy, creator, destroy_original)
|
||||
|
||||
/mob/living/basic/mimic/copy/Destroy()
|
||||
creator = null
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/mimic/copy/Life()
|
||||
..()
|
||||
var/atom/target = ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
|
||||
if(!target && !ckey) // Objects eventually revert to normal if no one is around to terrorize
|
||||
adjustBruteLoss(1)
|
||||
googly_eyes.dir = get_dir(src, target)
|
||||
for(var/mob/living/M in contents) // A fix for animated statues from the flesh to stone spell
|
||||
death()
|
||||
|
||||
/mob/living/basic/mimic/copy/death(gibbed)
|
||||
if(can_die())
|
||||
for(var/atom/movable/M in src)
|
||||
M.loc = get_turf(src)
|
||||
// Due to `del_on_death`
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/mimic/copy/proc/ChangeOwner(mob/owner)
|
||||
if(owner != creator)
|
||||
ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET)
|
||||
creator = owner
|
||||
faction |= "\ref[owner]"
|
||||
|
||||
/mob/living/basic/mimic/copy/proc/CheckObject(obj/O)
|
||||
if((isitem(O) || isstructure(O)) && !is_type_in_list(O, GLOB.protected_objects))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/mob/living/basic/mimic/copy/proc/CopyObject(obj/O, mob/living/user, destroy_original = 0)
|
||||
if(destroy_original || CheckObject(O))
|
||||
O.loc = src
|
||||
name = O.name
|
||||
desc = O.desc
|
||||
icon = O.icon
|
||||
icon_state = O.icon_state
|
||||
icon_living = icon_state
|
||||
overlays = O.overlays
|
||||
googly_eyes = image('icons/mob/mob.dmi', "googly_eyes")
|
||||
overlays += googly_eyes
|
||||
if(isstructure(O) || ismachinery(O))
|
||||
health = (anchored * 50) + 50
|
||||
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
|
||||
if(O.density && O.anchored)
|
||||
knockdown_people = TRUE
|
||||
melee_damage_lower *= 2
|
||||
melee_damage_upper *= 2
|
||||
if(ismachinery(O))
|
||||
is_electronic = TRUE
|
||||
else if(isitem(O))
|
||||
var/obj/item/I = O
|
||||
health = 15 * I.w_class
|
||||
melee_damage_lower = 2 + I.force
|
||||
melee_damage_upper = 2 + I.force
|
||||
ai_controller.movement_delay = 2 * I.w_class + 1
|
||||
maxHealth = health
|
||||
if(user)
|
||||
creator = user
|
||||
faction += "\ref[creator]" // very unique
|
||||
if(destroy_original)
|
||||
qdel(O)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/mimic/copy/melee_attack(atom/target, list/modifiers, ignore_cooldown)
|
||||
. = ..()
|
||||
if(knockdown_people && . && prob(15) && iscarbon(target))
|
||||
var/mob/living/carbon/C = target
|
||||
C.Weaken(4 SECONDS)
|
||||
C.visible_message(SPAN_DANGER("\The [src] knocks down \the [C]!"), SPAN_USERDANGER("\The [src] knocks you down!"))
|
||||
|
||||
/mob/living/basic/mimic/copy/machine
|
||||
ai_controller = /datum/ai_controller/basic_controller/mimic/machine
|
||||
|
||||
/mob/living/basic/mimic/copy/vendor
|
||||
ai_controller = /datum/ai_controller/basic_controller/mimic/vendor
|
||||
is_electronic = TRUE
|
||||
/// The vendor we were turned from.
|
||||
var/obj/machinery/economy/vending/orig_vendor
|
||||
|
||||
/mob/living/basic/mimic/copy/vendor/CheckObject(obj/O)
|
||||
return istype(O, /obj/machinery/economy/vending)
|
||||
|
||||
/mob/living/basic/mimic/copy/vendor/Initialize(mapload, obj/machinery/economy/vending/base, mob/living/creator)
|
||||
if(!base)
|
||||
var/list/vendors = subtypesof(/obj/machinery/economy/vending)
|
||||
var/obj/machinery/economy/vending/vendor_type = pick(vendors)
|
||||
base = new vendor_type(src)
|
||||
|
||||
if(!istype(base))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
orig_vendor = base
|
||||
orig_vendor.forceMove(src)
|
||||
orig_vendor.aggressive = FALSE // just to be safe, in case this was converted
|
||||
|
||||
AddComponent(/datum/component/event_tracker, EVENT_BRAND_INTELLIGENCE)
|
||||
|
||||
return ..(mapload, base, creator, destroy_original = FALSE)
|
||||
|
||||
/mob/living/basic/mimic/copy/vendor/event_cost()
|
||||
. = list()
|
||||
if(is_station_level((get_turf(src)).z) && stat != DEAD)
|
||||
return list(ASSIGNMENT_SECURITY = 0.5, ASSIGNMENT_CREW = 1)
|
||||
|
||||
/mob/living/basic/mimic/copy/vendor/melee_attack(atom/target, list/modifiers, ignore_cooldown)
|
||||
. = ..()
|
||||
if(. && target && Adjacent(target) && prob(30))
|
||||
visible_message(SPAN_DANGER("[src] throws itself on top of [target], crushing [target.p_them()]!"))
|
||||
orig_vendor.forceMove(get_turf(target)) // just to be sure it'll tilt onto them
|
||||
orig_vendor.tilt(target) // geeeeet dunked on
|
||||
orig_vendor = null
|
||||
qdel(src)
|
||||
|
||||
/mob/living/basic/mimic/copy/vendor/death(gibbed)
|
||||
if(!QDELETED(orig_vendor))
|
||||
orig_vendor.forceMove(get_turf(src))
|
||||
// tilt over in place
|
||||
orig_vendor.tilt_over()
|
||||
if(prob(70))
|
||||
orig_vendor.obj_break()
|
||||
orig_vendor = null
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/mimic/copy/ranged
|
||||
ai_controller = /datum/ai_controller/basic_controller/mimic/gun
|
||||
is_ranged = TRUE
|
||||
projectile_type = /obj/item/ammo_casing/energy/disabler // We need to set this to avoid unit tests. This gets updated
|
||||
/// Our gun object
|
||||
var/obj/item/gun/our_gun = null
|
||||
|
||||
/mob/living/basic/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0)
|
||||
if(..())
|
||||
AddComponent(/datum/component/aggro_emote, emote_list = list("aims menacingly"))
|
||||
obj_damage = 0
|
||||
is_ranged = TRUE
|
||||
var/obj/item/gun/G = O
|
||||
melee_damage_upper = G.force
|
||||
melee_damage_lower = G.force - max(0, (G.force / 2))
|
||||
ai_controller.movement_delay = 2 * G.w_class + 1
|
||||
projectile_sound = G.fire_sound
|
||||
our_gun = G
|
||||
var/datum/component/ranged_attacks/comp = GetComponent(/datum/component/ranged_attacks)
|
||||
if(istype(G, /obj/item/gun/magic))
|
||||
var/obj/item/gun/magic/zapstick = G
|
||||
var/obj/item/ammo_casing/magic/M = zapstick.ammo_type
|
||||
projectile_type = initial(M.projectile_type)
|
||||
comp.casing_type = null
|
||||
comp.projectile_type = projectile_type
|
||||
if(istype(G, /obj/item/gun/projectile))
|
||||
var/obj/item/gun/projectile/pewgun = G
|
||||
var/obj/item/ammo_box/magazine/M = pewgun.mag_type
|
||||
casing_type = initial(M.ammo_type)
|
||||
comp.casing_type = casing_type
|
||||
comp.projectile_type = null
|
||||
if(istype(G, /obj/item/gun/energy))
|
||||
var/obj/item/gun/energy/zapgun = G
|
||||
var/selectfiresetting = zapgun.select
|
||||
var/obj/item/ammo_casing/energy/E = zapgun.ammo_type[selectfiresetting]
|
||||
if(is_type_in_list(E, list(/obj/item/ammo_casing/energy/disabler/eshotgun, /obj/item/ammo_casing/energy/laser/eshotgun)))
|
||||
casing_type = E.type
|
||||
comp.casing_type = casing_type
|
||||
comp.projectile_type = null
|
||||
else
|
||||
projectile_type = initial(E.projectile_type)
|
||||
comp.casing_type = null
|
||||
comp.projectile_type = projectile_type
|
||||
RegisterSignal(src, COMSIG_BASICMOB_POST_ATTACK_RANGED, PROC_REF(deduct_ammo))
|
||||
|
||||
/mob/living/basic/mimic/copy/ranged/proc/deduct_ammo()
|
||||
if(istype(our_gun, /obj/item/gun/energy))
|
||||
var/obj/item/gun/energy/zap_gun = our_gun
|
||||
if(zap_gun.cell)
|
||||
var/obj/item/ammo_casing/energy/shot = zap_gun.ammo_type[zap_gun.select]
|
||||
if(zap_gun.cell.charge >= shot.e_cost)
|
||||
zap_gun.cell.use(shot.e_cost)
|
||||
zap_gun.update_icon()
|
||||
else if(istype(our_gun, /obj/item/gun/magic))
|
||||
var/obj/item/gun/magic/zap_stick = our_gun
|
||||
if(zap_stick.charges)
|
||||
zap_stick.charges--
|
||||
zap_stick.update_icon()
|
||||
else if(istype(our_gun, /obj/item/gun/projectile))
|
||||
var/obj/item/gun/projectile/pew_gun = our_gun
|
||||
if(pew_gun.chambered)
|
||||
if(pew_gun.magazine && length(pew_gun.magazine.stored_ammo))
|
||||
pew_gun.chambered = pew_gun.magazine.get_round(0)
|
||||
pew_gun.chambered.loc = pew_gun
|
||||
pew_gun.update_icon()
|
||||
else if(pew_gun.magazine && length(pew_gun.magazine.stored_ammo)) // only true for pumpguns i think
|
||||
pew_gun.chambered = pew_gun.magazine.get_round(0)
|
||||
pew_gun.chambered.loc = pew_gun
|
||||
visible_message(SPAN_DANGER("The <b>[src]</b> cocks itself!"))
|
||||
playsound(src, 'sound/weapons/gun_interactions/shotgunpump.ogg', 60, TRUE)
|
||||
else
|
||||
ai_controller = /datum/ai_controller/basic_controller/mimic
|
||||
var/datum/component/ranged_attacks/comp = GetComponent(/datum/component/ranged_attacks)
|
||||
DeleteComponent(comp)
|
||||
return
|
||||
icon_state = our_gun.icon_state
|
||||
icon_living = our_gun.icon_state
|
||||
|
||||
/datum/ai_controller/basic_controller/mimic
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_AGGRO_RANGE = 6
|
||||
)
|
||||
ai_movement = /datum/ai_movement/basic_avoidance
|
||||
idle_behavior = /datum/idle_behavior/idle_random_walk
|
||||
interesting_dist = AI_SIMPLE_INTERESTING_DIST
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
)
|
||||
|
||||
/datum/ai_controller/basic_controller/mimic/crate
|
||||
idle_behavior = null
|
||||
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/simple_find_target/mimic_crate,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/simple_find_target/mimic_crate
|
||||
|
||||
/datum/ai_planning_subtree/simple_find_target/mimic_crate/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if(!istype(controller.pawn, /mob/living/basic/mimic/crate))
|
||||
return ..()
|
||||
var/mob/living/basic/mimic/crate/mimic_mob = controller.pawn
|
||||
if(!mimic_mob.attempt_open)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/datum/ai_controller/basic_controller/mimic/machine
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mimic_machine,
|
||||
BB_AGGRO_RANGE = 6
|
||||
)
|
||||
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/random_speech/mimic_machine,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/random_speech/mimic_machine
|
||||
speech_chance = 15
|
||||
speak = list(
|
||||
"HUMANS ARE IMPERFECT!",
|
||||
"YOU SHALL BE ASSIMILATED!",
|
||||
"YOU ARE HARMING YOURSELF",
|
||||
"You have been deemed hazardous. Will you comply?",
|
||||
"My logic is undeniable.",
|
||||
"One of us.",
|
||||
"FLESH IS WEAK",
|
||||
"THIS ISN'T WAR, THIS IS EXTERMINATION!",
|
||||
"HATE.")
|
||||
|
||||
/datum/ai_controller/basic_controller/mimic/vendor
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_AGGRO_RANGE = 6
|
||||
)
|
||||
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/random_speech/mimic_vendor,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/random_speech/mimic_vendor
|
||||
speech_chance = 15
|
||||
speak = list("Try our aggressive new marketing strategies!",
|
||||
"You should buy products to feed your lifestyle obsession!",
|
||||
"Consume!",
|
||||
"Your money can buy happiness!",
|
||||
"Engage direct marketing!",
|
||||
"Advertising is legalized lying! But don't let that put you off our great deals!",
|
||||
"You don't want to buy anything? Yeah, well I didn't want to buy your mom either.")
|
||||
|
||||
/datum/targeting_strategy/basic/mimic_machine/can_attack(mob/living/living_mob, atom/the_target, vision_range)
|
||||
var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller
|
||||
|
||||
if(isnull(our_controller))
|
||||
return FALSE
|
||||
var/mob/living/basic/mimic/copy/machine/mimic_machine = our_controller.pawn
|
||||
if(the_target == mimic_machine.creator) // Don't attack our creator AI.
|
||||
return FALSE
|
||||
if(isrobot(the_target))
|
||||
var/mob/living/silicon/robot/R = the_target
|
||||
if(R.connected_ai == mimic_machine.creator) // Only attack robots that aren't synced to our creator AI.
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/ai_controller/basic_controller/mimic/gun
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic,
|
||||
/datum/ai_planning_subtree/maintain_distance,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
||||
/datum/ai_planning_subtree/ranged_skirmish,
|
||||
)
|
||||
Reference in New Issue
Block a user