diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 76f4064a84..ea9ebbec02 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -149,7 +149,8 @@ // /mob signals #define COMSIG_MOB_CLICKED_SHIFT_ON "mob_shift_click_on" //from base of /atom/ShiftClick(): (atom/A), for return values, see COMSIG_CLICK_SHIFT -#define COMSIG_MOB_FOV_VIEW "mob_visible_atoms" //from base of mob/fov_view(): (list/visible_atoms) +#define COMSIG_MOB_FOV_VIEW "mob_visible_atoms" //from base of /mob/fov_view(): (list/visible_atoms) +#define COMSIG_MOB_POINTED "mob_pointed" //from base of /mob/verb/pointed(): (atom/A) #define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A), for return values, see COMSIG_CLICK_SHIFT #define COMPONENT_EXAMINATE_BLIND 3 //outputs the "something is there but you can't see it" message. #define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed) diff --git a/code/datums/action.dm b/code/datums/action.dm index 00e7e0ad5d..0033df09d7 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -731,14 +731,6 @@ if(next_use_time > world.time) START_PROCESSING(SSfastprocess, src) - -//Stickmemes -/datum/action/item_action/stickmen - name = "Summon Stick Minions" - desc = "Allows you to summon faithful stickmen allies to aide you in battle." - icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' - button_icon_state = "art_summon" - //surf_ss13 /datum/action/item_action/bhop name = "Activate Jump Boots" diff --git a/code/datums/components/summoning.dm b/code/datums/components/summoning.dm index 61718301b3..1d66cf9307 100644 --- a/code/datums/components/summoning.dm +++ b/code/datums/components/summoning.dm @@ -59,7 +59,7 @@ var/mob/living/simple_animal/L = new chosen_mob_type(spawn_location) if(ishostile(L)) var/mob/living/simple_animal/hostile/H = L - H.friends += summoner // do not attack our summon boy + H.friends[summoner]++ // do not attack our summon boy spawned_mobs += L if(faction != null) L.faction = faction diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index e392a6b2d1..047dc7b7a3 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -165,31 +165,134 @@ var/robe_charge = TRUE actions_types = list(/datum/action/item_action/stickmen) +/obj/item/clothing/suit/wizrobe/paper/item_action_slot_check(slot, mob/user, datum/action/A) + if(A.type == /datum/action/item_action/stickmen && slot != SLOT_WEAR_SUIT) + return FALSE + return ..() -/obj/item/clothing/suit/wizrobe/paper/ui_action_click(mob/user, action) - stickmen() +//Stickmemes. VV-friendly. +/datum/action/item_action/stickmen + name = "Summon Stick Minions" + desc = "Allows you to summon faithful stickmen allies to aide you in battle." + icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "art_summon" + var/ready = TRUE + var/list/summoned_stickmen = list() + var/summoned_mob_path = /mob/living/simple_animal/hostile/stickman //Must be an hostile animal path. + var/max_stickmen = 8 + var/cooldown = 3 SECONDS + var/list/book_of_grudges = list() + +/datum/action/item_action/stickmen/New(Target) + ..() + if(isitem(Target)) + RegisterSignal(Target, COMSIG_PARENT_EXAMINE, .proc/give_infos) + +/datum/action/item_action/stickmen/Destroy() + for(var/A in summoned_stickmen) + var/mob/living/simple_animal/hostile/S = A + if(S.client) + to_chat(S, "A dizzying sensation strikes you as the comglomerate of pencil lines you call \ + your body crumbles under the pressure of an invisible eraser, soon to join bilions discarded sketches. \ + It seems whatever was keeping you in this realm has come to an end, like all things.") + animate(S, alpha = 0, time = 5 SECONDS) + QDEL_IN(S, 5 SECONDS) + return ..() + +/datum/action/item_action/stickmen/proc/give_infos(atom/source, mob/user, list/examine_list) + examine_list += "Making sure you are properly wearing or holding it, \ + point at whatever you want to rally your minions to its position." + examine_list += "While on harm intent, pointed mobs (minus you and the minions) \ + will also be marked as foes for your minions to attack for the next 2 minutes." + +/datum/action/item_action/stickmen/Grant(mob/M) + . = ..() + if(owner) + RegisterSignal(M, COMSIG_MOB_POINTED, .proc/rally) + if(book_of_grudges[M]) //Stop attacking your new master. + book_of_grudges -= M + for(var/A in summoned_stickmen) + var/mob/living/simple_animal/hostile/S = A + if(!S.mind) + S.LoseTarget() -/obj/item/clothing/suit/wizrobe/paper/verb/stickmen() - set category = "Object" - set name = "Summon Stick Minions" - set src in usr - if(!isliving(usr)) +/datum/action/item_action/stickmen/Remove(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_POINTED) + +/datum/action/item_action/stickmen/Trigger() + . = ..() + if(!.) return - if(!robe_charge) - to_chat(usr, "\The robe's internal magic supply is still recharging!") + if(!ready) + to_chat(owner, "[src]'s internal magic supply is still recharging!") + return FALSE + var/summon = TRUE + if(length(summoned_stickmen) >= max_stickmen) + var/mob/living/simple_animal/hostile/S = popleft(summoned_stickmen) + if(!S.client) + qdel(S) + else + S.forceMove(owner.drop_location()) + S.revive(TRUE) + summoned_stickmen[S] = TRUE + summon = FALSE + + owner.say("Rise, my creation! Off your page into this realm!", forced = "stickman summoning") + playsound(owner, 'sound/magic/summon_magic.ogg', 50, 1, 1) + if(summon) + var/mob/living/simple_animal/hostile/S = new summoned_mob_path (get_turf(usr)) + S.faction = owner.faction + S.foes = book_of_grudges + RegisterSignal(S, COMSIG_PARENT_QDELETING, .proc/remove_from_list) + ready = FALSE + addtimer(CALLBACK(src, .proc/ready_again), cooldown) + +/datum/action/item_action/stickmen/proc/remove_from_list(datum/source, forced) + summoned_stickmen -= source + +/datum/action/item_action/stickmen/proc/ready_again() + ready = TRUE + if(owner) + to_chat(owner, "[src] hums, its internal magic supply restored.") + +/** + * Rallies your army of stickmen to whichever target the user is pointing. + * Should the user be on harm intent and the target be a living mob that's not the user or a fellow stickman, + * said target will be added to a list of foes which the stickmen will gladly dispose regardless of faction. + * This is designed so stickmen will move toward whatever you point at even when you don't want to, that's the downside. + */ +/datum/action/item_action/stickmen/proc/rally(mob/source, atom/A) + var/turf/T = get_turf(A) + var/list/surrounding_turfs = block(locate(T.x - 1, T.y - 1, T.z), locate(T.x + 1, T.y + 1, T.z)) + if(!surrounding_turfs.len) return + if(source.a_intent == INTENT_HARM && A != source && !summoned_stickmen[A]) + var/mob/living/L + if(isliving(A)) //Gettem boys! + L = A + else if(ismecha(A)) + var/obj/mecha/M = A + L = M.occupant + if(L && L.stat != DEAD && !HAS_TRAIT(L, TRAIT_DEATHCOMA)) //Taking revenge on the deads would be proposterous. + addtimer(CALLBACK(src, .proc/clear_grudge, L), 2 MINUTES, TIMER_OVERRIDE|TIMER_UNIQUE) + if(!book_of_grudges[L]) + RegisterSignal(L, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_DEATH), .proc/grudge_settled) + book_of_grudges[L] = TRUE + for(var/k in summoned_stickmen) //Shamelessly copied from the blob rally power + var/mob/living/simple_animal/hostile/S = k + if(!S.mind && isturf(S.loc) && get_dist(S, T) <= 10) + S.LoseTarget() + S.Goto(pick(surrounding_turfs), S.move_to_delay) - usr.say("Rise, my creation! Off your page into this realm!", forced = "stickman summoning") - playsound(src.loc, 'sound/magic/summon_magic.ogg', 50, 1, 1) - var/mob/living/M = new /mob/living/simple_animal/hostile/stickman(get_turf(usr)) - var/list/factions = usr.faction - M.faction = factions - src.robe_charge = FALSE - sleep(30) - src.robe_charge = TRUE - to_chat(usr, "\The robe hums, its internal magic supply restored.") +/datum/action/item_action/stickmen/proc/clear_grudge(mob/living/L) + if(!QDELETED(L)) + book_of_grudges -= L +/datum/action/item_action/stickmen/proc/grudge_settled(mob/living/L) + UnregisterSignal(L, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_DEATH)) + book_of_grudges -= L //Shielded Armour diff --git a/code/modules/mining/equipment/lazarus_injector.dm b/code/modules/mining/equipment/lazarus_injector.dm index f8b155cb9f..db826fdb49 100644 --- a/code/modules/mining/equipment/lazarus_injector.dm +++ b/code/modules/mining/equipment/lazarus_injector.dm @@ -33,7 +33,7 @@ if(malfunctioning) H.faction |= list("lazarus", "[REF(user)]") H.robust_searching = 1 - H.friends += user + H.friends[user]++ H.attack_same = 1 log_game("[key_name(user)] has revived hostile mob [key_name(target)] with a malfunctioning lazarus injector") else diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm index b1a4f05e8c..a2fc3aec88 100644 --- a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm +++ b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm @@ -23,29 +23,21 @@ for(var/ab in boss_abilities) boss_abilities -= ab var/datum/action/boss/AB = new ab() - AB.boss = src AB.Grant(src) boss_abilities += AB atb.assign_abilities(boss_abilities) - /mob/living/simple_animal/hostile/boss/Destroy() - qdel(atb) - atb = null - for(var/ab in boss_abilities) - var/datum/action/boss/AB = ab - AB.boss = null - AB.Remove(src) - qdel(AB) - boss_abilities.Cut() + QDEL_NULL(atb) + QDEL_LIST(boss_abilities) return ..() - //Action datum for bosses //Override Trigger() as shown below to do things /datum/action/boss check_flags = AB_CHECK_CONSCIOUS //Incase the boss is given a player + required_mobility_flags = NONE var/boss_cost = 100 //Cost of usage for the boss' AI 1-100 var/usage_probability = 100 var/mob/living/simple_animal/hostile/boss/boss @@ -53,23 +45,34 @@ var/needs_target = TRUE //Does the boss need to have a target? (Only matters for the AI) var/say_when_triggered = "" //What does the boss Say() when the ability triggers? +/datum/action/boss/Destroy() + boss = null + return ..() + +/datum/action/boss/Grant(mob/M) + . = ..() + boss = owner + +/datum/action/boss/Remove(mob/M) + . = ..() + boss = null + /datum/action/boss/Trigger() . = ..() - if(.) - if(!istype(boss, boss_type)) - return 0 - if(!boss.atb) - return 0 - if(boss.atb.points < boss_cost) - return 0 - if(!boss.client) - if(needs_target && !boss.target) - return 0 - if(boss) - if(say_when_triggered) - boss.say(say_when_triggered, forced = "boss action") - if(!boss.atb.spend(boss_cost)) - return 0 + if(!.) + return + if(!istype(boss, boss_type)) + return FALSE + if(!boss.atb) + return FALSE + if(boss.atb.points < boss_cost) + return FALSE + if(!boss.client && needs_target && !boss.target) + return FALSE + if(!boss.atb.spend(boss_cost)) + return FALSE + if(say_when_triggered) + boss.say(say_when_triggered, forced = "boss action") //Example: /* @@ -83,7 +86,8 @@ /datum/boss_active_timed_battle var/list/abilities //a list of /datum/action/boss owned by a boss mob var/point_regen_delay = 5 - var/points = 50 //1-100, start with 50 so we can use some abilities but not insta-buttfug somebody + var/max_points = 100 + var/points = 50 //start with 50 so we can use some abilities but not insta-buttfug somebody var/next_point_time = 0 var/chance_to_hold_onto_points = 50 var/highest_cost = 0 @@ -108,22 +112,22 @@ /datum/boss_active_timed_battle/proc/spend(cost) if(cost <= points) points = max(0,points-cost) - return 1 - return 0 + return TRUE + return FALSE /datum/boss_active_timed_battle/proc/refund(cost) - points = min(points+cost, 100) + points = min(points+cost, max_points) /datum/boss_active_timed_battle/process() - if(world.time >= next_point_time) + if(world.time >= next_point_time && points < max_points) next_point_time = world.time + point_regen_delay - points = min(100, ++points) //has to be out of 100 + points = min(max_points, ++points) //has to be out of 100 if(abilities) chance_to_hold_onto_points = highest_cost*0.5 - if(points != 100 && prob(chance_to_hold_onto_points)) + if(points != max_points && prob(chance_to_hold_onto_points)) return //Let's save our points for a better ability (unless we're at max points, in which case we can't save anymore!) if(!boss.client) abilities = shuffle(abilities) @@ -135,5 +139,5 @@ /datum/boss_active_timed_battle/Destroy() abilities = null - SSobj.processing.Remove(src) + STOP_PROCESSING(SSobj, src) return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm b/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm index aef3fb714d..4d45120a59 100644 --- a/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm +++ b/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm @@ -32,25 +32,47 @@ name = "Summon Minions" icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' button_icon_state = "art_summon" - usage_probability = 40 + usage_probability = 20 boss_cost = 30 boss_type = /mob/living/simple_animal/hostile/boss/paper_wizard needs_target = FALSE say_when_triggered = "Rise, my creations! Jump off your pages and into this realm!" - var/static/summoned_minions = 0 + var/list/summoned_minions = list() + var/maximum_stickmen = 6 + var/stickmen_to_summon = 3 /datum/action/boss/wizard_summon_minions/Trigger() - if(summoned_minions <= 6 && ..()) - var/list/minions = list( - /mob/living/simple_animal/hostile/stickman, - /mob/living/simple_animal/hostile/stickman/ranged, - /mob/living/simple_animal/hostile/stickman/dog) - var/list/directions = GLOB.cardinals.Copy() - for(var/i in 1 to 3) - var/minions_chosen = pick_n_take(minions) - new minions_chosen (get_step(boss,pick_n_take(directions)), 1) - summoned_minions += 3; + . =..() + if(!.) + return + var/to_summon = stickmen_to_summon + var/current_len = length(summoned_minions) + if(current_len > maximum_stickmen - stickmen_to_summon) + for(var/a in (maximum_stickmen - stickmen_to_summon) to current_len) + var/mob/living/simple_animal/hostile/stickman/S = popleft(summoned_minions) + if(!S.client) + qdel(S) + else + S.forceMove(boss.drop_location()) + S.revive(TRUE) + summoned_minions += S + to_summon-- + var/static/list/minions = list( + /mob/living/simple_animal/hostile/stickman, + /mob/living/simple_animal/hostile/stickman/ranged, + /mob/living/simple_animal/hostile/stickman/dog) + + var/list/directions = GLOB.cardinals.Copy() + for(var/i in 1 to to_summon) + var/minions_chosen = pick(minions) + var/mob/living/simple_animal/hostile/stickman/S = new minions_chosen (get_step(boss,pick_n_take(directions)), 1) + S.faction = boss.faction + RegisterSignal(S, COMSIG_PARENT_QDELETING, .proc/remove_from_list) + summoned_minions += S + +/datum/action/boss/wizard_summon_minions/proc/remove_from_list(datum/source, forced) + summoned_minions -= source //Mimic Ability //Summons mimics of himself with magical papercraft @@ -66,28 +88,32 @@ say_when_triggered = "" /datum/action/boss/wizard_mimic/Trigger() - if(..()) - var/mob/living/target - if(!boss.client) //AI's target - target = boss.target - else //random mob - var/list/threats = boss.PossibleThreats() - if(threats.len) - target = pick(threats) - if(target) - var/mob/living/simple_animal/hostile/boss/paper_wizard/wiz = boss - var/directions = GLOB.cardinals.Copy() - for(var/i in 1 to 3) - var/mob/living/simple_animal/hostile/boss/paper_wizard/copy/C = new (get_step(target,pick_n_take(directions))) - wiz.copies += C - C.original = wiz - C.say("My craft defines me, you could even say it IS me!") - wiz.say("My craft defines me, you could even say it IS me!") - wiz.forceMove(get_step(target,pick_n_take(directions))) - wiz.minimum_distance = 1 //so he doesn't run away and ruin everything - wiz.retreat_distance = 0 + . = ..() + if(!.) + return + var/mob/living/target + if(!boss.client) //AI's target + target = boss.target + else //random mob + var/list/threats = boss.PossibleThreats() + if(threats.len) + target = pick(threats) else - boss.atb.refund(boss_cost) + to_chat(owner, "There is no potential foe of different faction around to attack") + if(target) + var/mob/living/simple_animal/hostile/boss/paper_wizard/wiz = boss + var/directions = GLOB.cardinals.Copy() + for(var/i in 1 to 3) + var/mob/living/simple_animal/hostile/boss/paper_wizard/copy/C = new (get_step(target,pick_n_take(directions))) + wiz.copies += C + C.original = wiz + C.say("My craft defines me, you could even say it IS me!") + wiz.say("My craft defines me, you could even say it IS me!") + wiz.forceMove(get_step(target,pick_n_take(directions))) + wiz.minimum_distance = 1 //so he doesn't run away and ruin everything + wiz.retreat_distance = 0 + else + boss.atb.refund(boss_cost) /mob/living/simple_animal/hostile/boss/paper_wizard/copy desc = "'Tis a ruse!" diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index cae8948fdd..e41fa7c896 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -20,6 +20,7 @@ var/casingtype //set ONLY it and NULLIFY projectiletype, if we have projectile IN CASING var/move_to_delay = 3 //delay for the automated movement. var/list/friends = list() + var/list/foes = list() var/list/emote_taunt = list() var/taunt_chance = 0 @@ -62,6 +63,8 @@ /mob/living/simple_animal/hostile/Destroy() targets_from = null + friends = null + foes = null return ..() /mob/living/simple_animal/hostile/Life() @@ -193,7 +196,7 @@ // Please do not add one-off mob AIs here, but override this function for your mob /mob/living/simple_animal/hostile/CanAttack(atom/the_target)//Can we actually attack a possible target? - if(isturf(the_target) || !the_target || the_target.type == /atom/movable/lighting_object) // bail out on invalids + if(!the_target || the_target.type == /atom/movable/lighting_object || isturf(the_target)) // bail out on invalids return FALSE if(ismob(the_target)) //Target is in godmode, ignore it. @@ -208,13 +211,13 @@ if(search_objects < 2) if(isliving(the_target)) var/mob/living/L = the_target - var/faction_check = faction_check_mob(L) + var/faction_check = !foes[L] && faction_check_mob(L) if(robust_searching) if(faction_check && !attack_same) return FALSE - if(L.stat > stat_attack) + if(L.stat > stat_attack || (L.stat == UNCONSCIOUS && stat_attack == UNCONSCIOUS && HAS_TRAIT(L, TRAIT_DEATHCOMA))) return FALSE - if(L in friends) + if(friends[L] > 0 && foes[L] < 1) return FALSE else if((faction_check && !attack_same) || L.stat) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm index c2faa002bc..31f925fb2b 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm @@ -298,5 +298,5 @@ var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/A = new /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion(user.loc) A.flags_1 |= (flags_1 & ADMIN_SPAWNED_1) A.GiveTarget(target) - A.friends = user + A.friends[user]++ A.faction = user.faction.Copy() diff --git a/code/modules/mob/living/simple_animal/hostile/stickman.dm b/code/modules/mob/living/simple_animal/hostile/stickman.dm index 2edcc4021a..226af952b1 100644 --- a/code/modules/mob/living/simple_animal/hostile/stickman.dm +++ b/code/modules/mob/living/simple_animal/hostile/stickman.dm @@ -13,7 +13,7 @@ speed = 0 blood_volume = 0 stat_attack = UNCONSCIOUS - robust_searching = 1 + robust_searching = TRUE //This is also required for the paper robe rallying to work. environment_smash = ENVIRONMENT_SMASH_NONE maxHealth = 100 health = 100 @@ -30,8 +30,6 @@ faction = list("hostile","stickman") check_friendly_fire = 1 status_flags = CANPUSH - var/datum/action/boss/wizard_summon_minions/changesummons = /datum/action/boss/wizard_summon_minions - var/summoned_by_wizard = 0 /mob/living/simple_animal/hostile/stickman/ranged ranged = 1 @@ -43,7 +41,6 @@ projectilesound = 'sound/misc/bang.ogg' loot = list(/obj/item/gun/ballistic/automatic/pistol/stickman) - /mob/living/simple_animal/hostile/stickman/dog name = "Angry Stick Dog" desc = "Stickman's best friend, if he could see him at least." @@ -52,12 +49,6 @@ icon_dead = "stickdog_dead" mob_biotypes = MOB_BEAST -/mob/living/simple_animal/hostile/stickman/Initialize(mapload, var/wizard_summoned) +/mob/living/simple_animal/hostile/stickman/Initialize(mapload) . = ..() new /obj/effect/temp_visual/paper_scatter(src) - summoned_by_wizard = wizard_summoned - -/mob/living/simple_animal/hostile/stickman/death() - ..() - if(summoned_by_wizard == 1) - changesummons.summoned_minions -- diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 4ef7623c83..e14ff2f721 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -373,7 +373,7 @@ mob/visible_message(message, self_message, blind_message, vision_distance = DEFA return FALSE new /obj/effect/temp_visual/point(A,invisibility) - + SEND_SIGNAL(src, COMSIG_MOB_POINTED, A) return TRUE /mob/proc/can_resist()