Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MIRROR] Basic Guardians/Holoparasites #561

Merged
merged 1 commit into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// Sent when a guardian is manifested
#define COMSIG_GUARDIAN_MANIFESTED "guardian_manifested"
/// Sent when a guardian is recalled
#define COMSIG_GUARDIAN_RECALLED "guardian_recalled"

/// Sent when an assassin guardian is forced to exit stealth
#define COMSIG_GUARDIAN_ASSASSIN_REVEALED "guardian_assassin_revealed"
27 changes: 25 additions & 2 deletions code/__DEFINES/guardian_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,28 @@
#define GUARDIAN_THEME_CARP "carp"
#define GUARDIAN_THEME_MINER "miner"

#define GUARDIAN_COLOR_LAYER 1
#define GUARDIAN_TOTAL_LAYERS 1
#define GUARDIAN_MAGIC "magic"
#define GUARDIAN_TECH "tech"

#define GUARDIAN_ASSASSIN "assassin"
#define GUARDIAN_CHARGER "charger"
#define GUARDIAN_DEXTROUS "dextrous"
#define GUARDIAN_EXPLOSIVE "explosive"
#define GUARDIAN_GASEOUS "gaseous"
#define GUARDIAN_GRAVITOKINETIC "gravitokinetic"
#define GUARDIAN_LIGHTNING "lightning"
#define GUARDIAN_PROTECTOR "protector"
#define GUARDIAN_RANGED "ranged"
#define GUARDIAN_STANDARD "standard"
#define GUARDIAN_SUPPORT "support"

/// List of all guardians currently extant
GLOBAL_LIST_EMPTY(parasites)

/// Assoc list of guardian theme singletons
GLOBAL_LIST_INIT(guardian_themes, list(
GUARDIAN_THEME_TECH = new /datum/guardian_fluff/tech,
GUARDIAN_THEME_MAGIC = new /datum/guardian_fluff,
GUARDIAN_THEME_CARP = new /datum/guardian_fluff/carp,
GUARDIAN_THEME_MINER = new /datum/guardian_fluff/miner,
))
2 changes: 1 addition & 1 deletion code/__DEFINES/is_helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list(

#define isregalrat(A) (istype(A, /mob/living/basic/regal_rat))

#define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian))
#define isguardian(A) (istype(A, /mob/living/basic/guardian))

#define ismegafauna(A) (istype(A, /mob/living/simple_animal/hostile/megafauna))

Expand Down
1 change: 1 addition & 0 deletions code/__DEFINES/span.dm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#define span_bold(str) ("<span class='bold'>" + str + "</span>")
#define span_boldannounce(str) ("<span class='boldannounce'>" + str + "</span>")
#define span_bolddanger(str) ("<span class='bolddanger'>" + str + "</span>")
#define span_bolditalic(str) ("<span class='bolditalic'>" + str + "</span>")
#define span_boldnicegreen(str) ("<span class='boldnicegreen'>" + str + "</span>")
#define span_boldnotice(str) ("<span class='boldnotice'>" + str + "</span>")
#define span_boldwarning(str) ("<span class='boldwarning'>" + str + "</span>")
Expand Down
2 changes: 1 addition & 1 deletion code/_globalvars/lists/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for
GLOBAL_LIST_INIT(abstract_mob_types, list(
/mob/living/basic/blob_minion,
/mob/living/basic/construct,
/mob/living/basic/guardian,
/mob/living/basic/heretic_summon,
/mob/living/basic/mining,
/mob/living/basic/pet,
Expand All @@ -26,7 +27,6 @@ GLOBAL_LIST_INIT(abstract_mob_types, list(
/mob/living/simple_animal/bot,
/mob/living/simple_animal/hostile/asteroid/elite,
/mob/living/simple_animal/hostile/asteroid,
/mob/living/simple_animal/hostile/guardian,
/mob/living/simple_animal/hostile/megafauna,
/mob/living/simple_animal/hostile/mimic, // Cannot exist if spawned without being passed an item reference
/mob/living/simple_animal/hostile/retaliate,
Expand Down
3 changes: 3 additions & 0 deletions code/_globalvars/lists/names.dm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.
GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt"))
GLOBAL_LIST_INIT(cyberauth_names, world.file2list("strings/names/cyberauth.txt"))
GLOBAL_LIST_INIT(syndicate_monkey_names, world.file2list("strings/names/syndicate_monkey.txt"))
GLOBAL_LIST_INIT(guardian_first_names, world.file2list("strings/names/guardian_descriptions.txt"))
GLOBAL_LIST_INIT(guardian_tech_surnames, world.file2list("strings/names/guardian_gamepieces.txt"))
GLOBAL_LIST_INIT(guardian_fantasy_surnames, world.file2list("strings/names/guardian_tarot.txt"))

GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt"))
GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt"))
Expand Down
2 changes: 1 addition & 1 deletion code/_globalvars/phobias.dm
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ GLOBAL_LIST_INIT(phobia_mobs, list(
/mob/living/carbon/alien,
/mob/living/simple_animal/slime,
)),
"anime" = typecacheof(list(/mob/living/simple_animal/hostile/guardian)),
"anime" = typecacheof(list(/mob/living/basic/guardian)),
"birds" = typecacheof(list(
/mob/living/basic/chick,
/mob/living/basic/chicken,
Expand Down
1 change: 1 addition & 0 deletions code/_globalvars/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_BLOOD_DEFICIENCY" = TRAIT_BLOOD_DEFICIENCY,
"TRAIT_JOLLY" = TRAIT_JOLLY,
"TRAIT_NO_GLIDE" = TRAIT_NO_GLIDE,
"TRAIT_NO_FLOATING_ANIM" = TRAIT_NO_FLOATING_ANIM,
"TRAIT_NOCRITDAMAGE" = TRAIT_NOCRITDAMAGE,
"TRAIT_NO_SLIP_WATER" = TRAIT_NO_SLIP_WATER,
"TRAIT_NO_SLIP_ICE" = TRAIT_NO_SLIP_ICE,
Expand Down
8 changes: 1 addition & 7 deletions code/_onclick/hud/alert.dm
Original file line number Diff line number Diff line change
Expand Up @@ -607,19 +607,13 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."

//GUARDIANS

/atom/movable/screen/alert/cancharge
name = "Charge Ready"
desc = "You are ready to charge at a location!"
icon_state = "guardian_charge"
alerttooltipstyle = "parasite"

/atom/movable/screen/alert/canstealth
name = "Stealth Ready"
desc = "You are ready to enter stealth!"
icon_state = "guardian_canstealth"
alerttooltipstyle = "parasite"

/atom/movable/screen/alert/instealth
/atom/movable/screen/alert/status_effect/instealth
name = "In Stealth"
desc = "You are in stealth and your next attack will do bonus damage!"
icon_state = "guardian_instealth"
Expand Down
20 changes: 10 additions & 10 deletions code/_onclick/hud/guardian.dm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/datum/hud/guardian
ui_style = 'icons/hud/guardian.dmi'

/datum/hud/guardian/New(mob/living/simple_animal/hostile/guardian/owner)
/datum/hud/guardian/New(mob/living/basic/guardian/owner)
..()
var/atom/movable/screen/using

Expand Down Expand Up @@ -34,10 +34,10 @@
using.screen_loc = ui_back
static_inventory += using

/datum/hud/dextrous/guardian/New(mob/living/simple_animal/hostile/guardian/owner) //for a dextrous guardian
/datum/hud/dextrous/guardian/New(mob/living/basic/guardian/owner) //for a dextrous guardian
..()
var/atom/movable/screen/using
if(istype(owner, /mob/living/simple_animal/hostile/guardian/dextrous))
if(istype(owner, /mob/living/basic/guardian/dextrous))
var/atom/movable/screen/inventory/inv_box

inv_box = new /atom/movable/screen/inventory(null, src)
Expand Down Expand Up @@ -86,8 +86,8 @@
/datum/hud/dextrous/guardian/persistent_inventory_update()
if(!mymob)
return
if(istype(mymob, /mob/living/simple_animal/hostile/guardian/dextrous))
var/mob/living/simple_animal/hostile/guardian/dextrous/dex_guardian = mymob
if(istype(mymob, /mob/living/basic/guardian/dextrous))
var/mob/living/basic/guardian/dextrous/dex_guardian = mymob

if(hud_shown)
if(dex_guardian.internal_storage)
Expand All @@ -109,7 +109,7 @@

/atom/movable/screen/guardian/manifest/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/user = usr
var/mob/living/basic/guardian/user = usr
user.manifest()


Expand All @@ -120,7 +120,7 @@

/atom/movable/screen/guardian/recall/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/user = usr
var/mob/living/basic/guardian/user = usr
user.recall()

/atom/movable/screen/guardian/toggle_mode
Expand All @@ -130,7 +130,7 @@

/atom/movable/screen/guardian/toggle_mode/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/user = usr
var/mob/living/basic/guardian/user = usr
user.toggle_modes()

/atom/movable/screen/guardian/toggle_mode/inactive
Expand All @@ -153,7 +153,7 @@

/atom/movable/screen/guardian/communicate/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/user = usr
var/mob/living/basic/guardian/user = usr
user.communicate()


Expand All @@ -164,5 +164,5 @@

/atom/movable/screen/guardian/toggle_light/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/user = usr
var/mob/living/basic/guardian/user = usr
user.toggle_light()
2 changes: 2 additions & 0 deletions code/datums/actions/cooldown_action.dm
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@
/// Starts a cooldown time for other abilities that share a cooldown with this. Has some niche usage with more complicated attack ai!
/// Will use default cooldown time if an override is not specified
/datum/action/cooldown/proc/StartCooldownOthers(override_cooldown_time)
if(!length(owner.actions))
return // Possible if they have an action they don't control
for(var/datum/action/cooldown/shared_ability in owner.actions - src)
if(!(shared_cooldown & shared_ability.shared_cooldown))
continue
Expand Down
44 changes: 26 additions & 18 deletions code/datums/actions/mobs/charge.dm
Original file line number Diff line number Diff line change
Expand Up @@ -156,17 +156,23 @@
SSexplosions.med_mov_atom += target

INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
hit_target(source, target, charge_damage)

/datum/action/cooldown/mob_cooldown/charge/proc/hit_target(atom/movable/source, atom/target, damage_dealt)
if(!isliving(target))
return
var/mob/living/living_target = target
living_target.visible_message("<span class='danger'>[source] slams into [living_target]!</span>", "<span class='userdanger'>[source] tramples you into the ground!</span>")
source.forceMove(get_turf(living_target))
living_target.apply_damage(damage_dealt, BRUTE, wound_bonus = CANT_WOUND)
playsound(get_turf(living_target), 'sound/effects/meteorimpact.ogg', 100, TRUE)
shake_camera(living_target, 4, 3)
try_hit_target(source, target)

/// Attempt to hit someone with our charge
/datum/action/cooldown/mob_cooldown/charge/proc/try_hit_target(atom/movable/source, atom/target)
if (can_hit_target(source, target))
hit_target(source, target, charge_damage)

/// Returns true if we're allowed to charge into this target
/datum/action/cooldown/mob_cooldown/charge/proc/can_hit_target(atom/movable/source, atom/target)
return isliving(target)

/// Actually hit someone
/datum/action/cooldown/mob_cooldown/charge/proc/hit_target(atom/movable/source, mob/living/target, damage_dealt)
target.visible_message(span_danger("[source] slams into [target]!"), span_userdanger("[source] tramples you into the ground!"))
target.apply_damage(damage_dealt, BRUTE, wound_bonus = CANT_WOUND)
playsound(get_turf(target), 'sound/effects/meteorimpact.ogg', 100, TRUE)
shake_camera(target, 4, 3)
shake_camera(source, 2, 3)

/datum/action/cooldown/mob_cooldown/charge/basic_charge
Expand All @@ -187,18 +193,21 @@
/datum/action/cooldown/mob_cooldown/charge/basic_charge/do_charge_indicator(atom/charger, atom/charge_target)
charger.Shake(shake_pixel_shift, shake_pixel_shift, shake_duration)

/datum/action/cooldown/mob_cooldown/charge/basic_charge/can_hit_target(atom/movable/source, atom/target)
if(!isliving(target))
if(!target.density || target.CanPass(source, get_dir(target, source)))
return FALSE
return TRUE
return ..()

/datum/action/cooldown/mob_cooldown/charge/basic_charge/hit_target(atom/movable/source, atom/target, damage_dealt)
var/mob/living/living_source
if(isliving(source))
living_source = source

if(!isliving(target))
if(!target.density || target.CanPass(source, get_dir(target, source)))
return
source.visible_message(span_danger("[source] smashes into [target]!"))
if(!living_source)
return
living_source.Stun(recoil_duration, ignore_canstun = TRUE)
living_source?.Stun(recoil_duration, ignore_canstun = TRUE)
return

var/mob/living/living_target = target
Expand All @@ -208,10 +217,9 @@
living_source.Stun(recoil_duration, ignore_canstun = TRUE)
return

living_target.visible_message(span_danger("[source] charges on [living_target]!"), span_userdanger("[source] charges into you!"))
living_target.visible_message(span_danger("[source] charges into [living_target]!"), span_userdanger("[source] charges into you!"))
living_target.Knockdown(knockdown_duration)


/datum/status_effect/tired_post_charge
id = "tired_post_charge"
duration = 1 SECONDS
Expand Down
112 changes: 112 additions & 0 deletions code/datums/components/damage_chain.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Draws a line between you and another atom, hurt anyone stood in the line
*/
/datum/component/damage_chain
dupe_mode = COMPONENT_DUPE_ALLOWED
/// How often do we attempt to deal damage?
var/tick_interval
/// Tracks when we can next deal damage
COOLDOWN_DECLARE(tick_cooldown)
/// Damage inflicted per tick
var/damage_per_tick
/// Type of damage to inflict
var/damage_type
/// Optional callback which checks if we can damage the target
var/datum/callback/validate_target
/// Optional callback for additional visuals or text display when dealing damage
var/datum/callback/chain_damage_feedback
/// We will fire the damage feedback callback on every x successful attacks
var/feedback_interval
/// How many successful attacks have we made?
var/successful_attacks = 0
/// Time between making any attacks at which we just reset the successful attack counter
var/reset_feedback_timer = 0
/// Our chain
var/datum/beam/chain

/datum/component/damage_chain/Initialize(
atom/linked_to,
max_distance = 7,
beam_icon = 'icons/effects/beam.dmi',
beam_state = "medbeam",
beam_type = /obj/effect/ebeam,
tick_interval = 0.3 SECONDS,
damage_per_tick = 1.2,
damage_type = BURN,
datum/callback/validate_target = null,
datum/callback/chain_damage_feedback = null,
feedback_interval = 5,
)
. = ..()
if (!isatom(parent))
return COMPONENT_INCOMPATIBLE
if (!isatom(linked_to))
CRASH("Attempted to create [type] linking [parent.type] with non-atom [linked_to]!")

src.tick_interval = tick_interval
src.damage_per_tick = damage_per_tick
src.damage_type = damage_type
src.validate_target = validate_target
src.chain_damage_feedback = chain_damage_feedback
src.feedback_interval = feedback_interval

var/atom/atom_parent = parent
chain = atom_parent.Beam(linked_to, icon = beam_icon, icon_state = beam_state, beam_type = beam_type, maxdistance = max_distance)
RegisterSignal(chain, COMSIG_QDELETING, PROC_REF(end_beam))
START_PROCESSING(SSfastprocess, src)

/datum/component/damage_chain/RegisterWithParent()
RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(end_beam)) // We actually don't really use many signals it's all processing

/datum/component/damage_chain/UnregisterFromParent()
UnregisterSignal(parent, COMSIG_LIVING_DEATH)

/datum/component/damage_chain/Destroy(force, silent)
if (!QDELETED(chain))
UnregisterSignal(chain, COMSIG_QDELETING)
QDEL_NULL(chain)
chain = null
STOP_PROCESSING(SSfastprocess, src)
return ..()

/// Destroy ourself
/datum/component/damage_chain/proc/end_beam()
SIGNAL_HANDLER
qdel(src)

/datum/component/damage_chain/process(seconds_per_tick)
var/successful_hit = FALSE
var/list/target_turfs = list()
for(var/obj/effect/ebeam/chainpart in chain.elements)
if (isnull(chainpart) || !chainpart.x || !chainpart.y || !chainpart.z)
continue
var/turf/overlaps = get_turf_pixel(chainpart)
target_turfs |= overlaps
if(overlaps == get_turf(chain.origin) || overlaps == get_turf(chain.target))
continue
for(var/turf/nearby_turf in circle_range(overlaps, 1))
target_turfs |= nearby_turf

for(var/turf/hit_turf as anything in target_turfs)
for(var/mob/living/victim in hit_turf)
if (victim == parent || victim.stat == DEAD)
continue
if (!isnull(validate_target) && !validate_target.Invoke(victim))
continue
if (successful_attacks == 0)
chain_damage_feedback?.Invoke(victim)
victim.apply_damage(damage_per_tick, damage_type, wound_bonus = CANT_WOUND)
successful_hit = TRUE

if (isnull(chain_damage_feedback))
return
if (successful_hit)
successful_attacks++
reset_feedback_timer = addtimer(CALLBACK(src, PROC_REF(reset_feedback)), 10 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE|TIMER_DELETE_ME)
if (successful_attacks > feedback_interval)
reset_feedback()

/// Make it so that the next time we hit something we'll invoke the feedback callback
/datum/component/damage_chain/proc/reset_feedback()
successful_attacks = 0
deltimer(reset_feedback_timer)
Loading
Loading