diff --git a/code/__defines/ces/signals_mob.dm b/code/__defines/ces/signals_mob.dm index 257ce8dac40..26b03aa5bc3 100644 --- a/code/__defines/ces/signals_mob.dm +++ b/code/__defines/ces/signals_mob.dm @@ -21,3 +21,6 @@ /// from turf CtrlClickOn(): (/mob) #define SIGNAL_MOB_CTRL_CLICK "mob_ctrl_click" + +// Called on '/mob/proc/add_spell' (/mob, ) +#define SIGNAL_MOB_SPELL_LEARNED "mob_spell_learned" diff --git a/code/modules/mob/living/simple_animal/hostile/commanded/_command_defines.dm b/code/modules/mob/living/simple_animal/hostile/commanded/_command_defines.dm index 3000499e896..4480b122829 100644 --- a/code/modules/mob/living/simple_animal/hostile/commanded/_command_defines.dm +++ b/code/modules/mob/living/simple_animal/hostile/commanded/_command_defines.dm @@ -1,3 +1,3 @@ -#define COMMANDED_STOP 6 //basically 'do nothing' -#define COMMANDED_FOLLOW 7 //follows a target -#define COMMANDED_MISC 8 //catch all state for misc commands that need one. +#define COMMANDED_STOP 7 //basically 'do nothing' +#define COMMANDED_FOLLOW 8 //follows a target +#define COMMANDED_MISC 9 //catch all state for misc commands that need one. diff --git a/code/modules/mob/living/simple_animal/hostile/commanded/bear_companion.dm b/code/modules/mob/living/simple_animal/hostile/commanded/bear_companion.dm index 8d38fa8cd8b..cd0fba1b02d 100644 --- a/code/modules/mob/living/simple_animal/hostile/commanded/bear_companion.dm +++ b/code/modules/mob/living/simple_animal/hostile/commanded/bear_companion.dm @@ -1,13 +1,14 @@ /mob/living/simple_animal/hostile/commanded/bear name = "bear" - desc = "A large brown bear." + desc = "A large bear." stance = HOSTILE_STANCE_ALERT - icon_state = "brownbear" - icon_living = "brownbear" - icon_dead = "brownbear_dead" - icon_gib = "brownbear_gib" + icon_state = "bear" + icon_living = "bear" + icon_dead = "bear_dead" + icon_gib = "bear_gib" + var/icon/icon_sit = "bear_sit" health = 75 maxHealth = 75 @@ -26,46 +27,208 @@ response_disarm = "pushes" bodyparts = /decl/simple_animal_bodyparts/quadruped - known_commands = list("stay", "stop", "attack", "follow", "dance", "boogie", "boogy") + known_commands = list("stay", "stop", "attack", "follow", "dance", "add friend", "remove friend") /mob/living/simple_animal/hostile/commanded/bear/hit_with_weapon(obj/item/O, mob/living/user, effective_force, hit_zone) . = ..() if(!.) - emote("roars in rage!") + audible_emote("roars in rage!") /mob/living/simple_animal/hostile/commanded/bear/attack_hand(mob/living/carbon/human/M) . = ..() if(M.a_intent == I_HURT) - emote("roars in rage!") + audible_emote("roars in rage!") /mob/living/simple_animal/hostile/commanded/bear/listen() if(stance != COMMANDED_MISC) //cant listen if its booty shakin' return ..() -//WE DANCE! -/mob/living/simple_animal/hostile/commanded/bear/misc_command(mob/speaker,text) - stay_command() +/mob/living/simple_animal/hostile/commanded/bear/Life() + . = ..() + if(. && stance == COMMANDED_MISC) + stop_automated_movement = TRUE + +//Handles cursed dancing command as well as adds/removes friends +/mob/living/simple_animal/hostile/commanded/bear/misc_command(mob/speaker, text) + for(var/command in known_commands) + if(findtext(text, command)) + switch(command) + if("dance") + dance() + if("add friend") + add_friend(speaker, text) + if("remove friend") + remove_friend(speaker, text) + +/mob/living/simple_animal/hostile/commanded/bear/on_radial_click(mob/living/carbon/human/M, command) + if(stance == COMMANDED_MISC) // bear won't accept commands while dancing + to_chat(M, SPAN_WARNING("Your [src] is dancing, it won't listen to your orders for a while.")) + return + + . = ..() + if(!.) + return + + var/list/possible_targets = radial_targets(M, FALSE) + var/mob/target = null + switch(command) + if("add friend") + for(var/mob/T in possible_targets) + if(weakref(T) in friends) + possible_targets -= M + if(!possible_targets.len) + return + + target = input(M, "Choose whom to follow.", "Targeting") as null|anything in possible_targets + if(!target) + return + + add_friend(M, text, target) + if("remove friend") + for(var/mob/T in possible_targets) + if(!(weakref(T) in friends)) + possible_targets -= M + if(!possible_targets.len) + return + + target = input(M, "Choose whom to follow.", "Targeting") as null|anything in possible_targets + if(!target) + return + + remove_friend(M, null, target) + if("dance") + dance() + +/mob/living/simple_animal/hostile/commanded/bear/proc/dance() + stop_automated_movement = TRUE stance = COMMANDED_MISC //nothing can stop this ride + update_icon() + visible_message("\The [src] starts to dance!.") + var/datum/gender/G = gender_datums[gender] + var/decl/emote/human/dance/dance_emote = new /decl/emote/human/dance spawn(0) - src.visible_message("\The [src] starts to dance!.") - var/datum/gender/G = gender_datums[gender] - for(var/i in 1 to 10) - if(stance != COMMANDED_MISC || incapacitated()) //something has stopped this ride. - return - var/message = pick(\ - "moves [G.his] head back and forth!",\ - "bobs [G.his] booty!",\ - "shakes [G.his] paws in the air!",\ - "wiggles [G.his] ears!",\ - "taps [G.his] foot!",\ - "shrugs [G.his] shoulders!",\ - "dances like you've never seen!") - if(dir != WEST) - set_dir(WEST) - else - set_dir(EAST) - src.visible_message("\The [src] [message]") - sleep(30) - stance = COMMANDED_STOP - set_dir(SOUTH) - src.visible_message("\The [src] bows, finished with [G.his] dance.") + dance_emote.do_emote(src) + for(var/i in 1 to 10) + if(stance != COMMANDED_MISC || incapacitated()) //something has stopped this ride. + return + + var/message = pick(\ + "moves [G.his] head back and forth!",\ + "bobs [G.his] booty!",\ + "shakes [G.his] paws in the air!",\ + "wiggles [G.his] ears!",\ + "taps [G.his] foot!",\ + "shrugs [G.his] shoulders!",\ + "dances like you've never seen!") + if(dir != WEST) + set_dir(WEST) + else + set_dir(EAST) + visible_message("\The [src] [message]") + sleep(30) + + dance_emote.dancing.Remove(weakref(src)) + set_dir(SOUTH) + visible_message("\The [src] bows, finished with [G.his] dance.") + stance = COMMANDED_STOP + stop_automated_movement = FALSE + +/mob/living/simple_animal/hostile/commanded/bear/proc/add_friend(mob/speaker, text, mob/target = null) + var/mob/living/future_friend = null + + if(!target) + var/list/targets = get_targets_by_name(text) + if(targets.len > 1 || !targets.len) + return FALSE + + if(!isliving(targets[1])) //Ghosts are not worthy of friendships + return + + future_friend = targets[1] + else + future_friend = target + + if(weakref(future_friend) in friends) // Already befriended + audible_emote("shakes his head, visibly confused!") // Feedback for players + return FALSE + + friends += weakref(future_friend) + audible_emote("growls affirmatevly, slightly bowing to [future_friend]!") + +/mob/living/simple_animal/hostile/commanded/bear/proc/remove_friend(mob/speaker, text, mob/target = null) + var/mob/living/former_friend = null + + if(!target) + var/list/targets = get_targets_by_name(text) + if(targets.len > 1 || !targets.len) + return FALSE + + if(!isliving(targets[1])) //something is wrong. VERY wrong. + return + + former_friend = targets[1] + else + former_friend = target + + if(weakref(former_friend) in friends) + friends -= weakref(former_friend) + audible_emote("roars at [former_friend]!") + else + audible_emote("shakes his head, visibly confused!") // Feedback for players + + + +/mob/living/simple_animal/hostile/commanded/bear/_examine_text(mob/user) + . = ..() + if(is_ic_dead()) + . += SPAN("deadsay", "It appears to be dead.\n") + else if(health < maxHealth) + . += SPAN("warning", "It looks [health >= maxHealth / 2 ? "slightly" : "severely"] beaten!\n") + switch(stance) + if(HOSTILE_STANCE_IDLE) + . += SPAN("warning", "[src] wanders aimlessly.") + if(HOSTILE_STANCE_ALERT) + . += SPAN("warning", "[src] looks alert!") + if(HOSTILE_STANCE_ATTACK) + . += SPAN("warning", "[src] is in an aggressive stance!") + if(HOSTILE_STANCE_ATTACKING) + . += SPAN("warning", "[src] !\n") + if(HOSTILE_STANCE_TIRED) + . += SPAN("warning", "[src] looks severly tired!") + if(COMMANDED_STOP) + . += SPAN("warning", "[src] sits patiently, waiting for its master!") + return + +/mob/living/simple_animal/hostile/commanded/bear/stay_command() + ..() + update_icon() + +/mob/living/simple_animal/hostile/commanded/bear/update_icon() + if(stance == COMMANDED_STOP) + icon_state = icon_sit + else + icon_state = icon_living + +/mob/living/simple_animal/hostile/commanded/bear/find_target() + . = ..() + update_icon() + +/mob/living/simple_animal/hostile/commanded/bear/follow_target() + . = ..() + update_icon() + +/mob/living/simple_animal/hostile/commanded/bear/stop_command() + . = ..() + update_icon() + +/mob/living/simple_animal/hostile/commanded/bear/follow_command() + . = ..() + update_icon() + +/mob/living/simple_animal/hostile/commanded/bear/attack_command() + . = ..() + update_icon() + +/mob/living/simple_animal/hostile/commanded/bear/MoveToTarget() + . = ..() + update_icon() diff --git a/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm b/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm index 688cad714ed..5e63e32b7f7 100644 --- a/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm +++ b/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm @@ -1,3 +1,7 @@ +#define FONT_SIZE "6pt" +#define FONT_COLOR "#ffffff" +#define FONT_STYLE "Small Fonts" + /mob/living/simple_animal/hostile/commanded name = "commanded" stance = COMMANDED_STOP @@ -10,6 +14,8 @@ var/list/allowed_targets = list() //WHO CAN I KILL D: var/retribution = TRUE //whether or not they will attack us if we attack them like some kinda dick. + var/static/list/radial_choices = list() //list of possible buttons in radial_menu + /mob/living/simple_animal/hostile/commanded/hear_say(message, verb = "says", datum/language/language = null, alt_name = "", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol) if((weakref(speaker) in friends) || speaker == master) command_buffer.Add(speaker) @@ -27,10 +33,10 @@ var/mob/speaker = command_buffer[1] var/text = command_buffer[2] var/filtered_name = lowertext(html_decode(name)) - if(dd_hasprefix(text,filtered_name) || dd_hasprefix(text,"everyone") || dd_hasprefix(text, "everybody")) //in case somebody wants to command 8 bears at once. - var/substring = copytext(text,length(filtered_name)+1) //get rid of the name. - listen(speaker,substring) - command_buffer.Remove(command_buffer[1],command_buffer[2]) + if(dd_hasprefix(text, filtered_name) || dd_hasprefix(text, "everyone") || dd_hasprefix(text, "everybody")) //in case somebody wants to command 8 bears at once. + var/substring = copytext(text, length(filtered_name)+1) //get rid of the name. + listen(speaker, substring) + command_buffer.Remove(command_buffer[1], command_buffer[2]) . = ..() if(.) switch(stance) @@ -44,74 +50,89 @@ /mob/living/simple_animal/hostile/commanded/find_target(new_stance = HOSTILE_STANCE_ATTACK) if(!allowed_targets.len) return null + var/mode = "specific" + if(allowed_targets[1] == "everyone") //we have been given the golden gift of murdering everything. Except our master, of course. And our friends. So just mostly everyone. mode = "everyone" + for(var/atom/A in ListTargets(10)) var/mob/M = null if(A == src) continue + if(isliving(A)) M = A - else if(istype(A,/obj/mecha)) + else if(istype(A, /obj/mecha)) var/obj/mecha/mecha = A if(!mecha.occupant) continue + M = mecha.occupant else // If it is not living and not /obj/mecha/, then we have something strange going on. continue + if(M && M.stat) continue + if(mode == "specific") if(!(A in allowed_targets)) continue + stance = new_stance return A else if(M == master || (weakref(M) in friends)) continue + stance = new_stance return A /mob/living/simple_animal/hostile/commanded/proc/follow_target() stop_automated_movement = TRUE + if(!target_mob) return + if(target_mob in ListTargets(10)) - walk_to(src,target_mob,1,move_to_delay) + walk_to(src, target_mob, 1, move_to_delay) -/mob/living/simple_animal/hostile/commanded/proc/commanded_stop() //basically a proc that runs whenever we are asked to stay put. Probably going to remain unused. +/mob/living/simple_animal/hostile/commanded/proc/commanded_stop() //Overrides enabling automated movement in /hostile/Life() + stop_automated_movement = TRUE + walk_to(src, 0) return /mob/living/simple_animal/hostile/commanded/proc/listen(mob/speaker, text) for(var/command in known_commands) - if(findtext(text,command)) + if(findtext(text, command)) switch(command) if("stay") - if(stay_command(speaker,text)) //find a valid command? Stop. Dont try and find more. + if(stay_command(speaker, text)) //find a valid command? Stop. Dont try and find more. break if("stop") - if(stop_command(speaker,text)) + if(stop_command(speaker, text)) break if("attack") - if(attack_command(speaker,text)) + if(attack_command(speaker, text)) break if("follow") - if(follow_command(speaker,text)) + if(follow_command(speaker, text)) break else - misc_command(speaker,text) //for specific commands + misc_command(speaker, text) //for specific commands return TRUE //returns a list of everybody we wanna do stuff with. /mob/living/simple_animal/hostile/commanded/proc/get_targets_by_name(text, filter_friendlies = 0) - var/list/possible_targets = hearers(src,10) + var/list/possible_targets = hearers(src, 10) . = list() + for(var/mob/M in possible_targets) if(filter_friendlies && ((weakref(M) in friends) || M.faction == faction || M == master)) continue + var/found = FALSE if(findtext(text, "[M]")) found = TRUE @@ -120,33 +141,39 @@ for(var/a in parsed_name) if(a == "the" || length(a) < 2) //get rid of shit words. continue - if(findtext(text,"[a]")) + + if(findtext(text, "[a]")) found = TRUE break + if(found) . += M -/mob/living/simple_animal/hostile/commanded/proc/attack_command(mob/speaker,text) +/mob/living/simple_animal/hostile/commanded/proc/attack_command(mob/speaker, text, mob/target = null) set_target_mob(null) //want me to attack something? Well I better forget my old target. walk_to(src, 0) stance = HOSTILE_STANCE_IDLE - if(text == "attack" || findtext(text,"everyone") || findtext(text,"anybody") || findtext(text, "somebody") || findtext(text, "someone")) //if its just 'attack' then just attack anybody, same for if they say 'everyone', somebody, anybody. Assuming non-pickiness. + if(target) + allowed_targets |= target + set_target_mob(target) + else if(findtext(text, "everyone") || findtext(text, "anybody") || findtext(text, "somebody") || findtext(text, "someone")) //if its just 'attack' then just attack anybody, same for if they say 'everyone', somebody, anybody. Assuming non-pickiness. allowed_targets = list("everyone")//everyone? EVERYONE - return 1 + return TRUE - var/list/targets = get_targets_by_name(text) - allowed_targets += targets - return targets.len != 0 + else + var/list/targets = get_targets_by_name(text) + allowed_targets += targets + return targets.len != 0 -/mob/living/simple_animal/hostile/commanded/proc/stay_command(mob/speaker,text) +/mob/living/simple_animal/hostile/commanded/proc/stay_command(mob/speaker, text) set_target_mob(null) stance = COMMANDED_STOP stop_automated_movement = TRUE walk_to(src, 0) - return 1 + return TRUE -/mob/living/simple_animal/hostile/commanded/proc/stop_command(mob/speaker,text) +/mob/living/simple_animal/hostile/commanded/proc/stop_command(mob/speaker, text) allowed_targets = list() walk_to(src, 0) set_target_mob(null) //gotta stop SOMETHIN @@ -154,22 +181,29 @@ stop_automated_movement = FALSE return TRUE -/mob/living/simple_animal/hostile/commanded/proc/follow_command(mob/speaker,text) - //we can assume 'stop following' is handled by stop_command - if(findtext(text,"me")) - stance = COMMANDED_FOLLOW - set_target_mob(speaker) //this wont bite me in the ass later. - return 1 - var/list/targets = get_targets_by_name(text) - if(targets.len > 1 || !targets.len) //CONFUSED. WHO DO I FOLLOW? - return 0 +/mob/living/simple_animal/hostile/commanded/proc/follow_command(mob/speaker, text, mob/target = null) + var/mob/to_follow = null + if(!target) + //we can assume 'stop following' is handled by stop_command + if(findtext(text, "me")) + stance = COMMANDED_FOLLOW + set_target_mob(speaker) //this wont bite me in the ass later. + return TRUE + + var/list/targets = get_targets_by_name(text) + if(targets.len > 1 || !targets.len) //CONFUSED. WHO DO I FOLLOW? + return FALSE + + to_follow = targets[1] + else + to_follow = target stance = COMMANDED_FOLLOW //GOT SOMEBODY. BETTER FOLLOW EM. - set_target_mob(targets[1]) //YEAH GOOD IDEA + set_target_mob(to_follow) //YEAH GOOD IDEA return TRUE -/mob/living/simple_animal/hostile/commanded/proc/misc_command(mob/speaker,text) +/mob/living/simple_animal/hostile/commanded/proc/misc_command(mob/speaker, text) return FALSE @@ -183,7 +217,6 @@ if(weakref(user) in friends) //We were buds :'( friends -= weakref(user) - /mob/living/simple_animal/hostile/commanded/attack_hand(mob/living/carbon/human/M) . = ..() if(M.a_intent == I_HURT && retribution && !client) //assume he wants to hurt us. @@ -193,7 +226,97 @@ if(weakref(M) in friends) friends -= weakref(M) +/mob/living/simple_animal/hostile/commanded/CtrlShiftClick(mob/user) + if(user.is_ic_dead() || !isliving(user)|| user.is_muzzled() || !(user==master)) + return + + radial_click(user) + +/mob/living/simple_animal/hostile/commanded/proc/radial_click(mob/living/M) + if(!radial_choices.len) + radial_choices = collect_radial_choices() + + var/command = show_radial_menu(M, src, radial_choices) + on_radial_click(M, command) + +/mob/living/simple_animal/hostile/commanded/proc/on_radial_click(mob/living/carbon/human/M, command) + var/mob/target = null + + switch(command) + if("stay") + stay_command(M, "stay") + if("stop") + stop_command(M, "stop") + if("attack") + var/list/possible_targets = radial_targets(M, FALSE) + possible_targets += "everyone" + target = input(M, "Choose whom to attack.", "Targeting") as null|anything in possible_targets + if(!target) + return FALSE + + if(target == "everyone") + attack_command(M, "attack everyone") + else + attack_command(M, null, target) + if("follow") + target = input(M, "Choose whom to follow.", "Targeting") as null|anything in radial_targets(M, TRUE) + if(!target) + return FALSE + + follow_command(M, null, target) + else + return command + + if(prob(50)) + M.whisper("whispers something.") + + return command + +/mob/living/simple_animal/hostile/commanded/proc/radial_targets(mob/commander = null, include_user = FALSE) + var/list/possible_targets = list() + + for(var/atom/A in ListTargets()) + var/mob/M + if(A == src) + continue + + if(isliving(A)) + M = A + else if(istype(A, /obj/mecha)) + var/obj/mecha/mecha = A + if(!mecha.occupant) + continue + + M = mecha.occupant + else + continue + + if(!include_user && M == commander) + continue + + possible_targets += M + + return possible_targets + +/mob/living/simple_animal/hostile/commanded/proc/collect_radial_choices() + var/list/choices = list() + + for(var/C in known_commands) + choices[C] = generate_radial_image(C) + + return choices + +/mob/living/simple_animal/hostile/commanded/proc/generate_radial_image(text) + var/image/radial_image = image(loc = src.loc, layer = ABOVE_HUD_LAYER) + radial_image.maptext = {"