From 717a4cc5859eecaade70f16605715637f592c5a0 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 22 Feb 2020 20:55:37 -0500 Subject: [PATCH 1/3] Add buildmode AI status indicators --- code/__defines/_planes+layers.dm | 2 ++ code/__defines/mobs.dm | 4 +++- code/global.dm | 1 + code/modules/admin/verbs/buildmode.dm | 2 ++ code/modules/ai/ai_holder.dm | 16 +++++++++++++++- .../mob/living/simple_mob/simple_mob.dm | 9 ++++++++- code/modules/mob/mob_planes.dm | 2 ++ icons/misc/buildmode.dmi | Bin 1520 -> 1955 bytes 8 files changed, 33 insertions(+), 3 deletions(-) diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index c54fbe93cc..01d890b152 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -130,6 +130,8 @@ What is the naming convention for planes or layers? #define PLANE_ADMIN2 33 //Purely for shenanigans (above lighting) +#define PLANE_BUILDMODE 39 //Things that only show up when you have buildmode on + //Fullscreen overlays under inventory #define PLANE_FULLSCREEN 90 //Blindness, mesons, druggy, etc #define OBFUSCATION_LAYER 5 //Where images covering the view for eyes are put diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm index 5278b8c526..cfbcfcd1dd 100644 --- a/code/__defines/mobs.dm +++ b/code/__defines/mobs.dm @@ -394,7 +394,9 @@ #define VIS_OBJS 20 #define VIS_MOBS 21 -#define VIS_COUNT 21 //Must be highest number from above. +#define VIS_BUILDMODE 22 + +#define VIS_COUNT 22 //Must be highest number from above. //Some mob icon layering defines #define BODY_LAYER -100 diff --git a/code/global.dm b/code/global.dm index 29bc8b1a1e..084ec5905a 100644 --- a/code/global.dm +++ b/code/global.dm @@ -178,6 +178,7 @@ var/list/station_departments = list("Command", "Medical", "Engineering", "Scienc //Icons for in-game HUD glasses. Why don't we just share these a little bit? var/static/icon/ingame_hud = icon('icons/mob/hud.dmi') var/static/icon/ingame_hud_med = icon('icons/mob/hud_med.dmi') +var/static/icon/buildmode_hud = icon('icons/misc/buildmode.dmi') //Keyed list for caching icons so you don't need to make them for records, IDs, etc all separately. //Could be useful for AI impersonation or something at some point? diff --git a/code/modules/admin/verbs/buildmode.dm b/code/modules/admin/verbs/buildmode.dm index 26575fd41a..3107b0c6d5 100644 --- a/code/modules/admin/verbs/buildmode.dm +++ b/code/modules/admin/verbs/buildmode.dm @@ -6,6 +6,7 @@ log_admin("[key_name(usr)] has left build mode.") M.client.buildmode = 0 M.client.show_popup_menus = 1 + M.plane_holder.set_vis(VIS_BUILDMODE, FALSE) for(var/obj/effect/bmode/buildholder/H) if(H.cl == M.client) qdel(H) @@ -13,6 +14,7 @@ log_admin("[key_name(usr)] has entered build mode.") M.client.buildmode = 1 M.client.show_popup_menus = 0 + M.plane_holder.set_vis(VIS_BUILDMODE, TRUE) var/obj/effect/bmode/buildholder/H = new/obj/effect/bmode/buildholder() var/obj/effect/bmode/builddir/A = new/obj/effect/bmode/builddir(H) diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index 4ef5507d5b..c61bfe6cbf 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -46,9 +46,22 @@ home_turf = null return ..() +/datum/ai_holder/proc/update_stance_hud() + var/image/state = holder.grab_hud(LIFE_HUD) + state.icon_state = "ais_[stance]" + holder.apply_hud(LIFE_HUD, state) + +/datum/ai_holder/proc/update_active_hud() + var/image/state = holder.grab_hud(STATUS_HUD) + state.icon_state = "ais_[busy || stance == STANCE_SLEEP]" + holder.apply_hud(STATUS_HUD, state) // Now for the actual AI stuff. +/datum/ai_holder/proc/set_busy(var/value = 0) + busy = value + update_active_hud() + // Makes this ai holder not get processed. // Called automatically when the host mob is killed. // Potential future optimization would be to sleep AIs which mobs that are far away from in-round players. @@ -136,6 +149,7 @@ stance = new_stance if(stance_coloring) // For debugging or really weird mobs. stance_color() + update_stance_hud() // This is called every half a second. /datum/ai_holder/proc/handle_stance_tactical() @@ -263,7 +277,7 @@ // Helper proc to turn AI 'busy' mode on or off without having to check if there is an AI, to simplify writing code. /mob/living/proc/set_AI_busy(value) if(ai_holder) - ai_holder.busy = value + ai_holder.set_busy(value) /mob/living/proc/is_AI_busy() if(!ai_holder) diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm index c80a6b7329..c9030b0860 100644 --- a/code/modules/mob/living/simple_mob/simple_mob.dm +++ b/code/modules/mob/living/simple_mob/simple_mob.dm @@ -14,6 +14,8 @@ mob_swap_flags = ~HEAVY mob_push_flags = ~HEAVY + has_huds = TRUE // We do show AI status huds for buildmode players + var/tt_desc = null //Tooltip description //Settings for played mobs @@ -295,4 +297,9 @@ return mob_class & MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL|MOB_CLASS_SLIME // Update this if needed. /mob/living/simple_mob/get_nametag_desc(mob/user) - return "[tt_desc]" \ No newline at end of file + return "[tt_desc]" + +/mob/living/simple_mob/make_hud_overlays() + hud_list[STATUS_HUD] = gen_hud_image(buildmode_hud, src, "ai_0", plane = PLANE_BUILDMODE) + hud_list[LIFE_HUD] = gen_hud_image(buildmode_hud, src, "ais_1", plane = PLANE_BUILDMODE) + add_overlay(hud_list) diff --git a/code/modules/mob/mob_planes.dm b/code/modules/mob/mob_planes.dm index 2ba47421a4..86173ee7fe 100644 --- a/code/modules/mob/mob_planes.dm +++ b/code/modules/mob/mob_planes.dm @@ -35,6 +35,8 @@ plane_masters[VIS_MESONS] = new /obj/screen/plane_master{plane = PLANE_MESONS} //Meson-specific things like open ceilings. + plane_masters[VIS_BUILDMODE] = new /obj/screen/plane_master{plane = PLANE_BUILDMODE} //Things that only show up while in build mode + // Real tangible stuff planes plane_masters[VIS_TURFS] = new /obj/screen/plane_master/main{plane = TURF_PLANE} plane_masters[VIS_OBJS] = new /obj/screen/plane_master/main{plane = OBJ_PLANE} diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi index dc8de0b6d0ec4d3fd461aca3fa5aee4aab121807..580c30658420940c14ea6953fe4191deacb94999 100644 GIT binary patch literal 1955 zcmV;U2VD4xP)+AmjGXVc)z`(#!rRCB90004WQchCV=-0C=38luHW2Fc3i3$tgm+m;U@M+(?UbVXq*SL>p*L zZBo6yQ5SZEfhM~dcyAWrk&x|+Ls_x>Ski|Wv{W~?W7#62V}O!YsUDFMA1U>bG9PK_BdvU-wU4y% zk+!Z6y3CrU8XU3@Ef(K%toe3hfe5 zXo!G9%L5de8hlms@k3Tr`RDI?0UOy=S_SN?sQ>^5JxN4CRCt{2T1%4KHV|cYTqcTh z0ObZbLaJDWbvE${LR;v2-bGa5fOKzt|>nsQudjXioI#Kvo&f!E*+ z-~ABS_pR}8`8lv_Ydl=8)ZyjY8V{ErCt|-`TjSyK)9q<>k;MvGL47J zJL>V*tHl5zj<1Ip#56HTV#Q!4hCP_K9(WvI4>31Drf`k{6Av&jZ6}t(83uL?jxpe_ zy-eU141_~0fg3R3TYI_34H&Quial<{03c-GaRUa}67#=s zfB~9`v3a{OV2ojVAq3mwA=Bd>)^`QO=G}H(a?Y{6l#=i9a4_aI1~_FX1CMhIlCvf| z1~cd%KL!LJWl+GnyJqzjd;AcCLLL~Cd)$hF`M%iW1`M__iN}b~O&H+1Z@w=QxCH~( zWuC&80kaePDF%~WKSc^VRbf`Y!zv=enAaSVxWyPGBCug4yl6~OIJL!ZARS*VeqS!% zdwuY=)CYNe|5RoZJk<34bD6#2p{DQE4y6PF=b)8B3e(KpU-Q{mB3M*o;jB(TxU?!_o#zhkL{it;VOf&zHh*wtnU$va10`Rj6qi4D+~&8eAzLH z@T}i~#MCj@6+hKRX;Y(%%KHA0s!&|mwkIP$Ynnk>-yf=;-E-;S>3}GYTu7?=oGz1tuUf z2JtK^>w7)#^^5yV=Tc{a>#?0h+gg^&gRH*SnX5>EU_^Tg)qP0zIF|=@{Y>k9jHZsP zU#nDv)h{@gZ}()b~1b}Vx@|4h9QGbrl& z1`NvjzKyCV==<{wdVNq6zr8;AX6gg8bUpC=;6bc+Jx;Cz&153wvY9E$*KyxwjIIRk zI|T=Rz(b$*|Gh80M@cSI;1Y-hXV;~Iy62tHrju`++F!namLA1S`&3@t;7zdMHAq)e?&M=^BlRn1?YBcjyM;>QTUS0KgFu(RNw|0pv10Uvt>RH+}N$1U; zfm^N*YftPS6NHg1M053lv*Q&^^u!#u5`bj;;7Q=~6-~5%gk!L&1bISL%;71e|Eq@F zv&=^sWb27uAN&vMgW;@IDV}yv8>G_?YUlr_9n>oI(+=L*WkW9@iA+4tzzTply_z;) zU?t3yK8tR^z-bt-gPJq26j*XcYr()$fb_X%3kId9PE8nS3J~f0rVQ|07cx6i69$F9 zt82s{|CfP{7|gyhe-VR%Drmz1q^qf3!vLqyuFh@Az@7BIih;?2^b?IH3``F2Y2>At_y7+;{yz~6E8^Y$+!FcAKLz}`~Bbe9^*Qye+$9uxDJbB z@P2z{^XFbwCemu}us8=5{eQcE3~qS)@aaJZ6@7oZf37=-LKHfv zNXNfFhGUQl(Jh~Y@7p}1X6oaAbq7>MB^5Pn){()!`+bFfAZG~Oa&9Gni( pSYdENEx(0>uxcFQs`2?*^aTXd-41y7ytV)U002ovPDHLkV1j_9pr`-< literal 1520 zcmV004jl0{{R3eocQU0000OP)t-sz`(!; zArAvp9R)iU3tc4uP#FM77iP==M!PP700001bW%=J06^y0W&i*Hta?;fbVOxyV{&P5 zbZKvH004NLozk%i!Y~*F;5m7U(C%W|s&#OY7U^K$p^{${NKH&qeSM=2q9BrgyMz0- z%h|Twbvps&paO$R45~0_fk8_QT47L)LF-f`LEBVwnN&zH0Y<{D&PhZ;RA_qH#PVbxB7}j zx5uq^A_D6kA@~b~a_$Nzgeit|gf5>SZX)zq!99eoo*!-@gev`ggoyVM@|hUFR=7y$ zLc{{d1(FJSnCYE*Guc~#(Gak z2XTe6PqF@PtmT4Z;76*ZrwATv43)ndYnpTM;mS&KAO#zV#34h9IU^a(G=AfxVqLUob(M9Vexk}+OtovgvcvBXB*@1#+p_} z*n+;w%{;XJZbFY%iuHFhdazQgznjqmLhJ9QbRONRw{x%~eMb24haZ3V%lO0o%)hF8 zT@kHeaGZx%Yxz8TB=>c8oQKPLf)H>_h-$Alu^+}Xx`Zx1W(oI>Ha^wahs$*Q!6ac2 zK$Wp=k5&R0E)dEeRGu5sea*H z5tcAbx~~yoPG7eX=5gd$Un0z7(!tJGwQy6_yQDw<@Z%3}^oO#wyU!oSUAt2&0cTdy z$x7kA0+xU_B08l|(8_sePY_RH(R-p&$&bQH34$Zia~`H)*NE@;Bo zr74;_3RvDjM$C2jfH2lr(AmR_=S%L2JuLciToB@QTsoB9=Vdn1lS{t zEe`EaA>Uin1an>E)N=@AZdBdP-8fC42;*~Xjw`}=MZH%wLwa47z!0-TT)Tv29}-Ro z)m7fkJ_ivADP+LiB1H(kA#A)%_mdilko*@KLKiHdt1Ct3V58d@u4(dhW7ynP>vu^K zA-4bvVO%5IRvqlNyE94l7W;(ct~_`s^U!-C1n3d+CEm?bGY?}su(*1;BOkWVBg~zq zv))&12bTS@IBwq|lsSlpo?Nm`NZXF%eaMbb=Ab*=ol3Uh*M}MtSW_dov%|+SHM<$R znfW}+@Hj@KF*{nE_lM1X*u=2z@bS2&99O-~-X&9?vii67WPi?Ay_fHZfBfP5cJ4na Wq)_%33UM?50000 Date: Sun, 23 Feb 2020 11:53:55 -0500 Subject: [PATCH 2/3] Readd some AI features Like calling for help and investigating the last known position of enemies --- .../ai/aI_holder_subtypes/simple_mob_ai.dm | 36 ++++++++++ .../ai/aI_holder_subtypes/slime_xenobio_ai.dm | 6 +- code/modules/ai/ai_holder.dm | 46 ++++--------- code/modules/ai/ai_holder_combat.dm | 39 ++--------- code/modules/ai/ai_holder_combat_unseen.dm | 17 ++--- code/modules/ai/ai_holder_communication.dm | 2 +- code/modules/ai/ai_holder_cooperation.dm | 32 +++++---- code/modules/ai/ai_holder_targeting.dm | 68 ++++++++++++++----- code/modules/xenobio/items/slimepotions.dm | 6 +- polaris.dme | 1 + 10 files changed, 138 insertions(+), 115 deletions(-) diff --git a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm index 9b4fb3044f..5a25fa7de0 100644 --- a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm +++ b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm @@ -149,3 +149,39 @@ // Simple mobs that retaliate and support others in their faction who get attacked. /datum/ai_holder/simple_mob/retaliate/cooperative cooperative = TRUE + +// With all the bells and whistles +/datum/ai_holder/simple_mob/humanoid + intelligence_level = AI_SMART //Purportedly + retaliate = TRUE //If attacked, attack back + threaten = TRUE //Verbal threats + firing_lanes = TRUE //Avoid shooting allies + conserve_ammo = TRUE //Don't shoot when it can't hit target + can_breakthrough = TRUE //Can break through doors + violent_breakthrough = FALSE //Won't try to break through walls (humans can, but usually don't) + speak_chance = 2 //Babble chance + cooperative = TRUE //Assist each other + wander = TRUE //Wander around + returns_home = TRUE //But not too far + use_astar = TRUE //Path smartly + +// The hostile subtype is implied to be trained combatants who use ""tactics"" +/datum/ai_holder/simple_mob/humanoid/hostile + var/run_if_this_close = 4 // If anything gets within this range, it'll try to move away. + hostile = TRUE //Attack! + +// Juke +/datum/ai_holder/simple_mob/humanoid/hostile/post_melee_attack(atom/A) + holder.IMove(get_step(holder, pick(alldirs))) + holder.face_atom(A) + +/datum/ai_holder/simple_mob/humanoid/hostile/post_ranged_attack(atom/A) + //Pick a random turf to step into + var/turf/T = get_step(holder, pick(alldirs)) + if(check_trajectory(A, T)) // Can we even hit them from there? + holder.IMove(T) + holder.face_atom(A) + + if(get_dist(holder, A) < run_if_this_close) + holder.IMove(get_step_away(holder, A)) + holder.face_atom(A) diff --git a/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm b/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm index b723c98695..94fae4f9b3 100644 --- a/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm +++ b/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm @@ -77,7 +77,7 @@ if(rabid) return var/justified = my_slime.is_justified_to_discipline() // This will also consider the AI-side of that proc. - lost_target() // Stop attacking. + remove_target() // Stop attacking. if(justified) obedience++ @@ -137,7 +137,7 @@ // Called when using a pacification agent (or it's Kendrick being initalized). /datum/ai_holder/simple_mob/xenobio_slime/proc/pacify() - lost_target() // So it stops trying to kill them. + remove_target() // So it stops trying to kill them. rabid = FALSE hostile = FALSE retaliate = FALSE @@ -247,7 +247,7 @@ else delayed_say("Fine...", speaker) adjust_discipline(1, TRUE) // This must come before losing the target or it will be unjustified. - lost_target() + remove_target() if(leader) // We're being asked to stop following someone. diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index c61bfe6cbf..cdff43d31f 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -93,9 +93,7 @@ /datum/ai_holder/proc/forget_everything() // Some of these might be redundant, but hopefully this prevents future bugs if that changes. lose_follow() - lose_target() - lose_target_position() - give_up_movement() + remove_target() // 'Tactical' processes such as moving a step, meleeing an enemy, firing a projectile, and other fairly cheap actions that need to happen quickly. /datum/ai_holder/proc/handle_tactics() @@ -116,33 +114,6 @@ /datum/ai_holder/proc/handle_special_strategical() -/* - //AI Actions - if(!ai_inactive) - //Stanceyness - handle_stance() - - //Movement - if(!stop_automated_movement && wander && !anchored) //Allowed to move? - handle_wander_movement() - - //Speaking - if(speak_chance && stance == STANCE_IDLE) // Allowed to chatter? - handle_idle_speaking() - - //Resisting out buckles - if(stance != STANCE_IDLE && incapacitated(INCAPACITATION_BUCKLED_PARTIALLY)) - handle_resist() - - //Resisting out of closets - if(istype(loc,/obj/structure/closet)) - var/obj/structure/closet/C = loc - if(C.welded) - resist() - else - C.open() -*/ - // For setting the stance WITHOUT processing it /datum/ai_holder/proc/set_stance(var/new_stance) ai_log("set_stance() : Setting stance from [stance] to [new_stance].", AI_LOG_INFO) @@ -221,7 +192,8 @@ if(STANCE_REPOSITION) // This is the same as above but doesn't stop if an enemy is visible since its an 'in-combat' move order. ai_log("handle_stance_tactical() : STANCE_REPOSITION, going to walk_to_destination().", AI_LOG_TRACE) - walk_to_destination() + if(!target && !find_target()) + walk_to_destination() if(STANCE_FOLLOW) ai_log("handle_stance_tactical() : STANCE_FOLLOW, going to walk_to_leader().", AI_LOG_TRACE) @@ -247,12 +219,18 @@ ai_log("++++++++++ Slow Process Beginning ++++++++++", AI_LOG_TRACE) ai_log("handle_stance_strategical() : Called.", AI_LOG_TRACE) + ai_log("handle_stance_strategical() : LTT=[lose_target_time]", AI_LOG_TRACE) + if(lose_target_time && (lose_target_time + lose_target_timeout < world.time)) // We were tracking an enemy but they are gone. + ai_log("handle_stance_strategical() : Giving up a chase.", AI_LOG_DEBUG) + remove_target() + + if(stance in STANCES_COMBAT) + request_help() // Call our allies. + switch(stance) if(STANCE_IDLE) - if(speak_chance) // In the long loop since otherwise it wont shut up. handle_idle_speaking() - if(hostile) ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE) find_target() @@ -260,6 +238,7 @@ if(target) ai_log("handle_stance_strategical() : STANCE_APPROACH, going to calculate_path([target]).", AI_LOG_TRACE) calculate_path(target) + walk_to_target() if(STANCE_MOVE) if(hostile && find_target()) // This will switch its stance. ai_log("handle_stance_strategical() : STANCE_MOVE, found target and was inturrupted.", AI_LOG_TRACE) @@ -269,6 +248,7 @@ else if(leader) ai_log("handle_stance_strategical() : STANCE_FOLLOW, going to calculate_path([leader]).", AI_LOG_TRACE) calculate_path(leader) + walk_to_leader() ai_log("handle_stance_strategical() : Exiting.", AI_LOG_TRACE) ai_log("++++++++++ Slow Process Ending ++++++++++", AI_LOG_TRACE) diff --git a/code/modules/ai/ai_holder_combat.dm b/code/modules/ai/ai_holder_combat.dm index 63154e4fe4..65fd1785b1 100644 --- a/code/modules/ai/ai_holder_combat.dm +++ b/code/modules/ai/ai_holder_combat.dm @@ -9,47 +9,23 @@ var/violent_breakthrough = TRUE // If false, the AI is not allowed to destroy things like windows or other structures in the way. Requires above var to be true. var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range. - - + // This does the actual attacking. /datum/ai_holder/proc/engage_target() ai_log("engage_target() : Entering.", AI_LOG_DEBUG) // Can we still see them? -// if(!target || !can_attack(target) || (!(target in list_targets())) ) if(!target || !can_attack(target)) ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE) - lose_target() // We lost them. - - if(!find_target()) // If we can't get a new one, then wait for a bit and then time out. - set_stance(STANCE_IDLE) - lost_target() - ai_log("engage_target() : No more targets. Exiting.", AI_LOG_DEBUG) + if(lose_target()) // We lost them (returns TRUE if we found something else to do) + ai_log("engage_target() : Pursuing other options (last seen, or a new target).", AI_LOG_TRACE) return - // if(lose_target_time + lose_target_timeout < world.time) - // ai_log("engage_target() : Unseen enemy timed out.", AI_LOG_TRACE) - // set_stance(STANCE_IDLE) // It must've been the wind. - // lost_target() - // ai_log("engage_target() : Exiting.", AI_LOG_DEBUG) - // return - - // // But maybe we do one last ditch effort. - // if(!target_last_seen_turf || intelligence_level < AI_SMART) - // ai_log("engage_target() : No last known position or is too dumb to fight unseen enemies.", AI_LOG_TRACE) - // set_stance(STANCE_IDLE) - // else - // ai_log("engage_target() : Fighting unseen enemy.", AI_LOG_TRACE) - // engage_unseen_enemy() - else - ai_log("engage_target() : Got new target ([target]).", AI_LOG_TRACE) var/distance = get_dist(holder, target) ai_log("engage_target() : Distance to target ([target]) is [distance].", AI_LOG_TRACE) holder.face_atom(target) last_conflict_time = world.time - request_help() // Call our allies. - // Do a 'special' attack, if one is allowed. // if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range)) if(holder.ICheckSpecialAttack(target)) @@ -198,12 +174,8 @@ // Make sure we can still chase/attack them. if(!target || !can_attack(target)) ai_log("walk_to_target() : Lost target.", AI_LOG_INFO) - if(!find_target()) - lost_target() - ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG) - return - else - ai_log("walk_to_target() : Found new target ([target]).", AI_LOG_INFO) + lose_target() + return // Find out where we're going. var/get_to = closest_distance(target) @@ -220,7 +192,6 @@ ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG) return - // Otherwise keep walking. if(!stand_ground) walk_path(target, get_to) diff --git a/code/modules/ai/ai_holder_combat_unseen.dm b/code/modules/ai/ai_holder_combat_unseen.dm index 0cb518f08c..c854377c89 100644 --- a/code/modules/ai/ai_holder_combat_unseen.dm +++ b/code/modules/ai/ai_holder_combat_unseen.dm @@ -2,21 +2,21 @@ // Used when a target is out of sight or invisible. /datum/ai_holder/proc/engage_unseen_enemy() + ai_log("engage_unseen_enemy() : Entering.", AI_LOG_TRACE) // Lets do some last things before giving up. - if(!ranged) - if(get_dist(holder, target_last_seen_turf > 1)) // We last saw them over there. + if(conserve_ammo || !holder.ICheckRangedAttack(target_last_seen_turf)) + if(get_dist(holder, target_last_seen_turf) > 1) // We last saw them over there. // Go to where you last saw the enemy. - give_destination(target_last_seen_turf, 1, TRUE) // This will set it to STANCE_REPOSITION. - else // We last saw them next to us, so do a blind attack on that tile. + give_destination(target_last_seen_turf, 1, TRUE) // Sets stance as well + else if(lose_target_time == world.time) // We last saw them next to us, so do a blind attack on that tile. melee_on_tile(target_last_seen_turf) - - else if(!conserve_ammo) + else + find_target() + else shoot_near_turf(target_last_seen_turf) // This shoots semi-randomly near a specific turf. /datum/ai_holder/proc/shoot_near_turf(turf/targeted_turf) - if(!ranged) - return // Can't shoot. if(get_dist(holder, targeted_turf) > max_range(targeted_turf)) return // Too far to shoot. @@ -32,6 +32,7 @@ // Attempts to attack something on a specific tile. // TODO: Put on mob/living? /datum/ai_holder/proc/melee_on_tile(turf/T) + ai_log("melee_on_tile() : Entering.", AI_LOG_TRACE) var/mob/living/L = locate() in T if(!L) T.visible_message("\The [holder] attacks nothing around \the [T].") diff --git a/code/modules/ai/ai_holder_communication.dm b/code/modules/ai/ai_holder_communication.dm index 4cfec6f7af..ef8fcf253d 100644 --- a/code/modules/ai/ai_holder_communication.dm +++ b/code/modules/ai/ai_holder_communication.dm @@ -15,7 +15,7 @@ /datum/ai_holder/proc/should_threaten() if(!threaten) return FALSE // We don't negotiate. - if(target in attackers) + if(check_attacker(target)) return FALSE // They (or someone like them) attacked us before, escalate immediately. if(!will_threaten(target)) return FALSE // Pointless to threaten an animal, a mindless drone, or an object. diff --git a/code/modules/ai/ai_holder_cooperation.dm b/code/modules/ai/ai_holder_cooperation.dm index 0f6b0bcfa2..dd6228f460 100644 --- a/code/modules/ai/ai_holder_cooperation.dm +++ b/code/modules/ai/ai_holder_cooperation.dm @@ -74,7 +74,7 @@ ai_log("request_help() : Exiting.", AI_LOG_DEBUG) -// What allies receive when someone else is calling for help. +// What allies receive when someone else is calling for help.1 /datum/ai_holder/proc/help_requested(mob/living/friend) ai_log("help_requested() : Entering.", AI_LOG_DEBUG) if(stance == STANCE_SLEEP) @@ -92,24 +92,26 @@ if(!holder.IIsAlly(friend)) // Extra sanity. ai_log("help_requested() : Help requested by [friend] but we hate them.", AI_LOG_INFO) return - if(friend.ai_holder && friend.ai_holder.target && !can_attack(friend.ai_holder.target)) - ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO) - return - if(get_dist(holder, friend) <= follow_distance) - ai_log("help_requested() : Help requested by [friend] but we're already here.", AI_LOG_INFO) - return - if(get_dist(holder, friend) <= vision_range) // Within our sight. - ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO) - if(friend.ai_holder) // AI calling for help. - if(friend.ai_holder.target && can_attack(friend.ai_holder.target)) // Friend wants us to attack their target. - last_conflict_time = world.time // So we attack immediately and not threaten. - give_target(friend.ai_holder.target) // This will set us to the appropiate stance. - ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG) - return + var/their_target = friend?.ai_holder?.target + if(their_target) // They have a target and aren't just shouting for no reason + if(!can_attack(their_target, vision_required = FALSE)) + ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO) + return + if(get_dist(holder, friend) <= follow_distance) + ai_log("help_requested() : Help requested by [friend] but we're already here.", AI_LOG_INFO) + return + if(get_dist(holder, friend) <= vision_range) // Within our sight. + ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO) + last_conflict_time = world.time // So we attack immediately and not threaten. + give_target(their_target, urgent = TRUE) // This will set us to the appropiate stance. + ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG) + return // Otherwise they're outside our sight, lack a target, or aren't AI controlled, but within call range. // So assuming we're AI controlled, we'll go to them and see whats wrong. ai_log("help_requested() : Help requested by [friend], going to go to friend.", AI_LOG_INFO) + if(their_target) + add_attacker(their_target) // We won't wait and 'warn' them while they're stabbing our ally set_follow(friend, 10 SECONDS) ai_log("help_requested() : Exiting.", AI_LOG_DEBUG) diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm index e70e991d6e..40f2c5752f 100644 --- a/code/modules/ai/ai_holder_targeting.dm +++ b/code/modules/ai/ai_holder_targeting.dm @@ -26,6 +26,7 @@ // Step 1, find out what we can see. /datum/ai_holder/proc/list_targets() . = hearers(vision_range, holder) - holder // Remove ourselves to prevent suicidal decisions. ~ SRC is the ai_holder. + . -= dview_mob // Not the dview mob either, nerd. var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha)) @@ -35,6 +36,7 @@ // Step 2, filter down possible targets to things we actually care about. /datum/ai_holder/proc/find_target(var/list/possible_targets, var/has_targets_list = FALSE) + ai_log("find_target() : Entered.", AI_LOG_TRACE) if(!hostile) // So retaliating mobs only attack the thing that hit it. return null . = list() @@ -70,13 +72,17 @@ return chosen_target // Step 4, give us our selected target. -/datum/ai_holder/proc/give_target(new_target) +/datum/ai_holder/proc/give_target(new_target, urgent = FALSE) + ai_log("give_target() : Given '[new_target]', urgent=[urgent].", AI_LOG_TRACE) target = new_target + if(target != null) - if(should_threaten()) + lose_target_time = 0 + track_target_position() + if(should_threaten() && !urgent) set_stance(STANCE_ALERT) else - set_stance(STANCE_APPROACH) + set_stance(STANCE_FIGHT) last_target_time = world.time return TRUE @@ -109,8 +115,8 @@ sorted_targets += A return sorted_targets -/datum/ai_holder/proc/can_attack(atom/movable/the_target) - if(!can_see_target(the_target)) +/datum/ai_holder/proc/can_attack(atom/movable/the_target, vision_required = TRUE) + if(!can_see_target(the_target) && vision_required) return FALSE if(istype(the_target, /mob/zshadow)) @@ -157,21 +163,38 @@ /datum/ai_holder/proc/found(atom/movable/the_target) return FALSE -//We can't see the target, go look or attack where they were last seen. +// 'Soft' loss of target. They may still exist, we still have some info about them maybe. /datum/ai_holder/proc/lose_target() + ai_log("lose_target() : Entering.", AI_LOG_TRACE) if(target) + ai_log("lose_target() : Had a target, setting to null and LTT.", AI_LOG_DEBUG) target = null lose_target_time = world.time give_up_movement() + if(target_last_seen_turf && intelligence_level >= AI_SMART) + ai_log("lose_target() : Going into 'engage unseen enemy' mode.", AI_LOG_INFO) + engage_unseen_enemy() + return TRUE //We're still working on it + else + ai_log("lose_target() : Can't chase target, so giving up.", AI_LOG_INFO) + remove_target() + return find_target() //Returns if we found anything else to do -//Target is no longer valid (?) -/datum/ai_holder/proc/lost_target() - set_stance(STANCE_IDLE) + return FALSE //Nothing new to do + +// 'Hard' loss of target. Clean things up and return to idle. +/datum/ai_holder/proc/remove_target() + ai_log("remove_target() : Entering.", AI_LOG_TRACE) + if(target) + target = null + + lose_target_time = 0 + give_up_movement() lose_target_position() - lose_target() - + set_stance(STANCE_IDLE) + // Check if target is visible to us. /datum/ai_holder/proc/can_see_target(atom/movable/the_target, view_range = vision_range) ai_log("can_see_target() : Entering.", AI_LOG_TRACE) @@ -235,7 +258,7 @@ ai_log("react_to_attack() : Was attacked by [attacker], but we already have a target.", AI_LOG_TRACE) on_attacked(attacker) // So we attack immediately and not threaten. return FALSE - else if(attacker in attackers && world.time > last_target_time + 3 SECONDS) // Otherwise, let 'er rip + else if(check_attacker(attacker) && world.time > last_target_time + 3 SECONDS) // Otherwise, let 'er rip ai_log("react_to_attack() : Was attacked by [attacker]. Can retaliate, waited 3 seconds.", AI_LOG_INFO) on_attacked(attacker) // So we attack immediately and not threaten. return give_target(attacker) // Also handles setting the appropiate stance. @@ -246,16 +269,25 @@ ai_log("react_to_attack() : Was attacked by [attacker].", AI_LOG_INFO) on_attacked(attacker) // So we attack immediately and not threaten. - return give_target(attacker) // Also handles setting the appropiate stance. + return give_target(attacker, urgent = TRUE) // Also handles setting the appropiate stance. // Sets a few vars so mobs that threaten will react faster to an attacker or someone who attacked them before. /datum/ai_holder/proc/on_attacked(atom/movable/AM) - if(isliving(AM)) - var/mob/living/L = AM - if(!(L.name in attackers)) - attackers |= L.name - last_conflict_time = world.time + last_conflict_time = world.time + add_attacker(AM) +// Checks to see if an atom attacked us lately +/datum/ai_holder/proc/check_attacker(var/atom/movable/A) + return (A in attackers) + +// We were attacked by this thing recently +/datum/ai_holder/proc/add_attacker(var/atom/movable/A) + attackers |= A.name + +// Forgive this attacker +/datum/ai_holder/proc/remove_attacker(var/atom/movable/A) + attackers -= A.name + // Causes targeting to prefer targeting the taunter if possible. // This generally occurs if more than one option is within striking distance, including the taunter. // Otherwise the default filter will prefer the closest target. diff --git a/code/modules/xenobio/items/slimepotions.dm b/code/modules/xenobio/items/slimepotions.dm index 944853bec7..2a600fb801 100644 --- a/code/modules/xenobio/items/slimepotions.dm +++ b/code/modules/xenobio/items/slimepotions.dm @@ -109,7 +109,7 @@ to_chat(user, "You feed \the [SM] the agent, calming it.") playsound(src, 'sound/effects/bubbles.ogg', 50, 1) - AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. + AI.remove_target() // So hostile things stop attacking people even if not hostile anymore. var/newname = copytext(sanitize(input(user, "Would you like to give \the [M] a name?", "Name your new pet", M.name) as null|text),1,MAX_NAME_LEN) if(newname) @@ -202,7 +202,7 @@ to_chat(user, "You feed \the [M] the agent. It will now try to murder things that want to murder you instead.") to_chat(M, "\The [user] feeds you \the [src], and feel that the others will regard you as an outsider now.") M.faction = user.faction - AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. + AI.remove_target() // So hostile things stop attacking people even if not hostile anymore. playsound(src, 'sound/effects/bubbles.ogg', 50, 1) qdel(src) @@ -237,7 +237,7 @@ to_chat(user, "You feed \the [M] the agent. It will now be your best friend.") to_chat(M, "\The [user] feeds you \the [src], and feel that \the [user] wants to be best friends with you.") M.friends.Add(user) - AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. + AI.remove_target() // So hostile things stop attacking people even if not hostile anymore. playsound(src, 'sound/effects/bubbles.ogg', 50, 1) qdel(src) diff --git a/polaris.dme b/polaris.dme index 3effb8a361..cc6030b775 100644 --- a/polaris.dme +++ b/polaris.dme @@ -1359,6 +1359,7 @@ #include "code\modules\ai\_defines.dm" #include "code\modules\ai\ai_holder.dm" #include "code\modules\ai\ai_holder_combat.dm" +#include "code\modules\ai\ai_holder_combat_unseen.dm" #include "code\modules\ai\ai_holder_communication.dm" #include "code\modules\ai\ai_holder_cooperation.dm" #include "code\modules\ai\ai_holder_debug.dm" From 5eb30f00171221e18ba05b12ab432e7e86eab146 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 22 Feb 2020 23:49:06 -0500 Subject: [PATCH 3/3] Further improve buildmode "RTS" ai mode --- code/modules/admin/verbs/buildmode.dm | 237 +++++++++++++----- .../ai/aI_holder_subtypes/simple_mob_ai.dm | 1 + code/modules/ai/ai_holder.dm | 24 +- code/modules/client/client procs.dm | 8 + code/modules/mob/living/living.dm | 2 +- icons/misc/buildmode.dmi | Bin 1955 -> 2198 bytes 6 files changed, 196 insertions(+), 76 deletions(-) diff --git a/code/modules/admin/verbs/buildmode.dm b/code/modules/admin/verbs/buildmode.dm index 3107b0c6d5..a5e9c15029 100644 --- a/code/modules/admin/verbs/buildmode.dm +++ b/code/modules/admin/verbs/buildmode.dm @@ -1,3 +1,15 @@ +#define BUILDMODE_BASIC 1 +#define BUILDMODE_ADVANCED 2 +#define BUILDMODE_EDIT 3 +#define BUILDMODE_THROW 4 +#define BUILDMODE_ROOM 5 +#define BUILDMODE_LADDER 6 +#define BUILDMODE_CONTENTS 7 +#define BUILDMODE_LIGHTS 8 +#define BUILDMODE_AI 9 + +#define LAST_BUILDMODE 9 + /proc/togglebuildmode(mob/M as mob in player_list) set name = "Toggle Build Mode" set category = "Special Verbs" @@ -74,7 +86,8 @@ screen_loc = "NORTH,WEST+1" Click() switch(master.cl.buildmode) - if(1) // Basic Build + + if(BUILDMODE_BASIC) to_chat(usr, "***********************************************************") to_chat(usr, "Left Mouse Button = Construct / Upgrade") to_chat(usr, "Right Mouse Button = Deconstruct / Delete / Downgrade") @@ -84,7 +97,8 @@ to_chat(usr, "Use the button in the upper left corner to") to_chat(usr, "change the direction of built objects.") to_chat(usr, "***********************************************************") - if(2) // Adv. Build + + if(BUILDMODE_ADVANCED) to_chat(usr, "***********************************************************") to_chat(usr, "Right Mouse Button on buildmode button = Set object type") to_chat(usr, "Middle Mouse Button on buildmode button= On/Off object type saying") @@ -96,44 +110,56 @@ to_chat(usr, "Use the button in the upper left corner to") to_chat(usr, "change the direction of built objects.") to_chat(usr, "***********************************************************") - if(3) // Edit + + if(BUILDMODE_EDIT) to_chat(usr, "***********************************************************") to_chat(usr, "Right Mouse Button on buildmode button = Select var(type) & value") to_chat(usr, "Left Mouse Button on turf/obj/mob = Set var(type) & value") to_chat(usr, "Right Mouse Button on turf/obj/mob = Reset var's value") to_chat(usr, "***********************************************************") - if(4) // Throw + + if(BUILDMODE_THROW) to_chat(usr, "***********************************************************") to_chat(usr, "Left Mouse Button on turf/obj/mob = Select") to_chat(usr, "Right Mouse Button on turf/obj/mob = Throw") to_chat(usr, "***********************************************************") - if(5) // Room Build + + if(BUILDMODE_ROOM) to_chat(usr, "***********************************************************") to_chat(usr, "Left Mouse Button on turf = Select as point A") to_chat(usr, "Right Mouse Button on turf = Select as point B") to_chat(usr, "Right Mouse Button on buildmode button = Change floor/wall type") to_chat(usr, "***********************************************************") - if(6) // Make Ladders + + if(BUILDMODE_LADDER) to_chat(usr, "***********************************************************") to_chat(usr, "Left Mouse Button on turf = Set as upper ladder loc") to_chat(usr, "Right Mouse Button on turf = Set as lower ladder loc") to_chat(usr, "***********************************************************") - if(7) // Move Into Contents + + if(BUILDMODE_CONTENTS) to_chat(usr, "***********************************************************") to_chat(usr, "Left Mouse Button on turf/obj/mob = Select") to_chat(usr, "Right Mouse Button on turf/obj/mob = Move into selection") to_chat(usr, "***********************************************************") - if(8) // Make Lights + + if(BUILDMODE_LIGHTS) to_chat(usr, "***********************************************************") to_chat(usr, "Left Mouse Button on turf/obj/mob = Make it glow") to_chat(usr, "Right Mouse Button on turf/obj/mob = Reset glowing") to_chat(usr, "Right Mouse Button on buildmode button = Change glow properties") to_chat(usr, "***********************************************************") - if(9) // Control mobs with ai_holders. + + if(BUILDMODE_AI) to_chat(usr, "***********************************************************") + to_chat(usr, "Left Mouse Button drag box = Select only mobs in box") + to_chat(usr, "Left Mouse Button drag box + shift = Select additional mobs in area") + to_chat(usr, "Left Mouse Button on non-mob = Deselect all mobs") to_chat(usr, "Left Mouse Button on AI mob = Select/Deselect mob") to_chat(usr, "Left Mouse Button + alt on AI mob = Toggle hostility on mob") - to_chat(usr, "Left Mouse Button + ctrl on AI mob = Reset target/following/movement") + to_chat(usr, "Left Mouse Button + shift on AI mob = Toggle AI (also resets)") + to_chat(usr, "Left Mouse Button + ctrl on AI mob = Copy mob faction") + to_chat(usr, "Right Mouse Button + ctrl on any mob = Paste mob faction copied with Left Mouse Button + shift") to_chat(usr, "Right Mouse Button on enemy mob = Command selected mobs to attack mob") to_chat(usr, "Right Mouse Button on allied mob = Command selected mobs to follow mob") to_chat(usr, "Right Mouse Button + shift on any mob = Command selected mobs to follow mob regardless of faction") @@ -161,6 +187,7 @@ var/obj/effect/bmode/buildquit/buildquit = null var/atom/movable/throw_atom = null var/list/selected_mobs = list() + var/copied_faction = null /obj/effect/bmode/buildholder/Destroy() qdel(builddir) @@ -186,7 +213,6 @@ selected_mobs -= unit C.images -= unit.selected_image - /obj/effect/bmode/buildmode icon_state = "buildmode1" screen_loc = "NORTH,WEST+2" @@ -209,48 +235,25 @@ if(pa.Find("middle")) switch(master.cl.buildmode) - if(2) + if(BUILDMODE_ADVANCED) objsay=!objsay - if(pa.Find("left")) - switch(master.cl.buildmode) - if(1) - master.cl.buildmode = 2 - src.icon_state = "buildmode2" - if(2) - master.cl.buildmode = 3 - src.icon_state = "buildmode3" - if(3) - master.cl.buildmode = 4 - src.icon_state = "buildmode4" - if(4) - master.cl.buildmode = 5 - src.icon_state = "buildmode5" - if(5) - master.cl.buildmode = 6 - src.icon_state = "buildmode6" - if(6) - master.cl.buildmode = 7 - src.icon_state = "buildmode7" - if(7) - master.cl.buildmode = 8 - src.icon_state = "buildmode8" - if(8) - master.cl.buildmode = 9 - src.icon_state = "buildmode9" - if(9) - master.cl.buildmode = 1 - src.icon_state = "buildmode1" + if(master.cl.buildmode == LAST_BUILDMODE) + master.cl.buildmode = 1 + else + master.cl.buildmode++ + src.icon_state = "buildmode[master.cl.buildmode]" else if(pa.Find("right")) switch(master.cl.buildmode) - if(1) // Basic Build + if(BUILDMODE_BASIC) + return 1 - if(2) // Adv. Build + if(BUILDMODE_ADVANCED) objholder = get_path_from_partial_text(/obj/structure/closet) - if(3) // Edit + if(BUILDMODE_EDIT) var/list/locked = list("vars", "key", "ckey", "client", "firemut", "ishulk", "telekinesis", "xray", "virus", "viruses", "cuffed", "ka", "last_eaten", "urine") master.buildmode.varholder = input(usr,"Enter variable name:" ,"Name", "name") @@ -269,14 +272,16 @@ master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as obj in world if("turf-reference") master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as turf in world - if(5) // Room build + + if(BUILDMODE_ROOM) var/choice = alert("Would you like to change the floor or wall holders?","Room Builder", "Floor", "Wall") switch(choice) if("Floor") floor_holder = get_path_from_partial_text(/turf/simulated/floor/plating) if("Wall") wall_holder = get_path_from_partial_text(/turf/simulated/wall) - if(8) // Lights + + if(BUILDMODE_LIGHTS) var/choice = alert("Change the new light range, power, or color?", "Light Maker", "Range", "Power", "Color") switch(choice) if("Range") @@ -303,7 +308,7 @@ var/list/pa = params2list(params) switch(buildmode) - if(1) // Basic Build + if(BUILDMODE_BASIC) if(istype(object,/turf) && pa.Find("left") && !pa.Find("alt") && !pa.Find("ctrl") ) if(istype(object,/turf/space)) var/turf/T = object @@ -352,7 +357,8 @@ if(NORTHWEST) var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object)) WIN.set_dir(NORTHWEST) - if(2) // Adv. Build + + if(BUILDMODE_ADVANCED) if(pa.Find("left") && !pa.Find("ctrl")) if(ispath(holder.buildmode.objholder,/turf)) var/turf/T = get_turf(object) @@ -371,8 +377,7 @@ if(holder.buildmode.objsay) to_chat(usr, "[object.type]") - - if(3) // Edit + if(BUILDMODE_EDIT) if(pa.Find("left")) //I cant believe this shit actually compiles. if(object.vars.Find(holder.buildmode.varholder)) log_admin("[key_name(usr)] modified [object.name]'s [holder.buildmode.varholder] to [holder.buildmode.valueholder]") @@ -386,7 +391,7 @@ else to_chat(user, "[initial(object.name)] does not have a var called '[holder.buildmode.varholder]'") - if(4) // Throw + if(BUILDMODE_THROW) if(pa.Find("left")) if(istype(object, /atom/movable)) holder.throw_atom = object @@ -394,7 +399,8 @@ if(holder.throw_atom) holder.throw_atom.throw_at(object, 10, 1) log_admin("[key_name(usr)] threw [holder.throw_atom] at [object]") - if(5) // Room build + + if(BUILDMODE_ROOM) if(pa.Find("left")) holder.buildmode.coordA = get_turf(object) to_chat(user, "Defined [object] ([object.type]) as point A.") @@ -413,7 +419,8 @@ ) holder.buildmode.coordA = null holder.buildmode.coordB = null - if(6) // Ladders + + if(BUILDMODE_LADDER) if(pa.Find("left")) holder.buildmode.coordA = get_turf(object) to_chat(user, "Defined [object] ([object.type]) as upper ladder location.") @@ -432,7 +439,8 @@ B.update_icon() holder.buildmode.coordA = null holder.buildmode.coordB = null - if(7) // Move into contents + + if(BUILDMODE_CONTENTS) if(pa.Find("left")) if(istype(object, /atom)) holder.throw_atom = object @@ -440,23 +448,32 @@ if(holder.throw_atom && istype(object, /atom/movable)) object.forceMove(holder.throw_atom) log_admin("[key_name(usr)] moved [object] into [holder.throw_atom].") - if(8) // Lights + + if(BUILDMODE_LIGHTS) if(pa.Find("left")) if(object) object.set_light(holder.buildmode.new_light_range, holder.buildmode.new_light_intensity, holder.buildmode.new_light_color) if(pa.Find("right")) if(object) object.set_light(0, 0, "#FFFFFF") - if(9) // AI control + + if(BUILDMODE_AI) if(pa.Find("left")) if(isliving(object)) var/mob/living/L = object - // Reset processes. - if(pa.Find("ctrl")) - if(!isnull(L.get_AI_stance())) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on. + + // Pause/unpause AI + if(pa.Find("shift")) + var/stance = L.get_AI_stance() + if(!isnull(stance)) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on. var/datum/ai_holder/AI = L.ai_holder - AI.forget_everything() - to_chat(user, span("notice", "\The [L]'s AI has forgotten its target/movement destination/leader.")) + if(stance == STANCE_SLEEP) + AI.go_wake() + to_chat(user, span("notice", "\The [L]'s AI has been enabled.")) + else + AI.go_sleep() + to_chat(user, span("notice", "\The [L]'s AI has been disabled.")) + return else to_chat(user, span("warning", "\The [L] is not AI controlled.")) return @@ -471,6 +488,12 @@ to_chat(user, span("warning", "\The [L] is not AI controlled.")) return + // Copy faction + if(pa.Find("ctrl")) + holder.copied_faction = L.faction + to_chat(user, span("notice", "Copied faction '[holder.copied_faction]'.")) + return + // Select/Deselect if(!isnull(L.get_AI_stance())) if(L in holder.selected_mobs) @@ -479,10 +502,27 @@ else holder.select_AI_mob(user.client, L) to_chat(user, span("notice", "Selected \the [L].")) + return else to_chat(user, span("warning", "\The [L] is not AI controlled.")) + return + else //Not living + for(var/mob/living/unit in holder.selected_mobs) + holder.deselect_AI_mob(user.client, unit) + if(pa.Find("right")) + // Paste faction + if(pa.Find("ctrl") && isliving(object)) + if(!holder.copied_faction) + to_chat(user, span("warning", "LMB+Shift a mob to copy their faction before pasting.")) + return + else + var/mob/living/L = object + L.faction = holder.copied_faction + to_chat(user, span("notice", "Pasted faction '[holder.copied_faction]'.")) + return + if(istype(object, /atom)) // Force attack. var/atom/A = object @@ -493,6 +533,9 @@ AI.give_target(A) i++ to_chat(user, span("notice", "Commanded [i] mob\s to attack \the [A].")) + var/image/orderimage = image(buildmode_hud,A,"ai_targetorder") + orderimage.plane = PLANE_BUILDMODE + flick_overlay(orderimage, list(user.client), 8, TRUE) return if(isliving(object)) // Follow or attack. @@ -517,16 +560,67 @@ if(j) message += "[j] mob\s to follow \the [L]." to_chat(user, span("notice", message)) + var/image/orderimage = image(buildmode_hud,L,"ai_targetorder") + orderimage.plane = PLANE_BUILDMODE + flick_overlay(orderimage, list(user.client), 8, TRUE) + return if(isturf(object)) // Move or reposition. var/turf/T = object - var/i = 0 + var/forced = 0 + var/told = 0 for(var/mob/living/unit in holder.selected_mobs) var/datum/ai_holder/AI = unit.ai_holder - AI.give_destination(T, 1, pa.Find("shift")) // If shift is held, the mobs will not stop moving to attack a visible enemy. - i++ - to_chat(user, span("notice", "Commanded [i] mob\s to move to \the [T].")) + if(unit.get_AI_stance() == STANCE_SLEEP) + unit.forceMove(T) + forced++ + else + AI.give_destination(T, 1, pa.Find("shift")) // If shift is held, the mobs will not stop moving to attack a visible enemy. + told++ + to_chat(user, span("notice", "Commanded [told] mob\s to move to \the [T], and manually placed [forced] of them.")) + var/image/orderimage = image(buildmode_hud,T,"ai_turforder") + orderimage.plane = PLANE_BUILDMODE + flick_overlay(orderimage, list(user.client), 8, TRUE) + return +/proc/build_drag(var/client/user, buildmode, var/atom/fromatom, var/atom/toatom, var/atom/fromloc, var/atom/toloc, var/fromcontrol, var/tocontrol, params) + var/obj/effect/bmode/buildholder/holder = null + for(var/obj/effect/bmode/buildholder/H) + if(H.cl == user) + holder = H + break + if(!holder) return + var/list/pa = params2list(params) + + switch(buildmode) + if(BUILDMODE_AI) + + //Holding shift prevents the deselection of existing + if(!pa.Find("shift")) + for(var/mob/living/unit in holder.selected_mobs) + holder.deselect_AI_mob(user, unit) + + var/turf/c1 = get_turf(fromatom) + var/turf/c2 = get_turf(toatom) + if(!c1 || !c2) + return //Dragged outside window or something + + var/low_x = min(c1.x,c2.x) + var/low_y = min(c1.y,c2.y) + var/hi_x = max(c1.x,c2.x) + var/hi_y = max(c1.y,c2.y) + var/z = c1.z //Eh + + var/i = 0 + for(var/mob/living/L in living_mob_list) + if(L.z != z || L.client) + continue + if(L.x >= low_x && L.x <= hi_x && L.y >= low_y && L.y <= hi_y) + holder.select_AI_mob(user, L) + i++ + + to_chat(user, span("notice", "Band-selected [i] mobs.")) + return /obj/effect/bmode/buildmode/proc/get_path_from_partial_text(default_path) var/desired_path = input("Enter full or partial typepath.","Typepath","[default_path]") @@ -598,4 +692,15 @@ if(isturf(floor_type)) T.ChangeTurf(floor_type) else - new floor_type(T) \ No newline at end of file + new floor_type(T) + +#undef BUILDMODE_BASIC +#undef BUILDMODE_ADVANCED +#undef BUILDMODE_EDIT +#undef BUILDMODE_THROW +#undef BUILDMODE_ROOM +#undef BUILDMODE_LADDER +#undef BUILDMODE_CONTENTS +#undef BUILDMODE_LIGHTS +#undef BUILDMODE_AI +#undef LAST_BUILDMODE \ No newline at end of file diff --git a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm index 5a25fa7de0..1cfc90eb6d 100644 --- a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm +++ b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm @@ -164,6 +164,7 @@ wander = TRUE //Wander around returns_home = TRUE //But not too far use_astar = TRUE //Path smartly + home_low_priority = TRUE //Following/helping is more important // The hostile subtype is implied to be trained combatants who use ""tactics"" /datum/ai_holder/simple_mob/humanoid/hostile diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index cdff43d31f..e22abf8ee0 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -47,20 +47,24 @@ return ..() /datum/ai_holder/proc/update_stance_hud() - var/image/state = holder.grab_hud(LIFE_HUD) - state.icon_state = "ais_[stance]" - holder.apply_hud(LIFE_HUD, state) + var/image/stanceimage = holder.grab_hud(LIFE_HUD) + stanceimage.icon_state = "ais_[stance]" + holder.apply_hud(LIFE_HUD, stanceimage) -/datum/ai_holder/proc/update_active_hud() - var/image/state = holder.grab_hud(STATUS_HUD) - state.icon_state = "ais_[busy || stance == STANCE_SLEEP]" - holder.apply_hud(STATUS_HUD, state) +/datum/ai_holder/proc/update_paused_hud() + var/image/sleepingimage = holder.grab_hud(STATUS_HUD) + var/asleep = 0 + if(busy) + asleep = 2 + else if (stance == STANCE_SLEEP) + asleep = 1 + sleepingimage.icon_state = "ai_[asleep]" + holder.apply_hud(STATUS_HUD, sleepingimage) // Now for the actual AI stuff. - /datum/ai_holder/proc/set_busy(var/value = 0) busy = value - update_active_hud() + update_paused_hud() // Makes this ai holder not get processed. // Called automatically when the host mob is killed. @@ -71,6 +75,7 @@ forget_everything() // If we ever wake up, its really unlikely that our current memory will be of use. set_stance(STANCE_SLEEP) SSai.processing -= src + update_paused_hud() // Reverses the above proc. // Revived mobs will wake their AI if they have one. @@ -81,6 +86,7 @@ return set_stance(STANCE_IDLE) SSai.processing += src + update_paused_hud() /datum/ai_holder/proc/should_wake() if(holder.client && !autopilot) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index bf57cf057c..d997733d8e 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -320,6 +320,14 @@ if(inactivity > duration) return inactivity return 0 +//Called when the client performs a drag-and-drop operation. +/client/MouseDrop(start_object,end_object,start_location,end_location,start_control,end_control,params) + if(buildmode && start_control == "mapwindow.map" && start_control == end_control) + build_drag(src,buildmode,start_object,end_object,start_location,end_location,start_control,end_control,params) + else + . = ..() + + // Byond seemingly calls stat, each tick. // Calling things each tick can get expensive real quick. // So we slow this down a little. diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 052f7e7ed7..024b1f2134 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -16,7 +16,7 @@ dsma.blend_mode = BLEND_ADD dsoverlay.appearance = dsma - selected_image = image(icon = 'icons/mob/screen1.dmi', loc = src, icon_state = "centermarker") + selected_image = image(icon = buildmode_hud, loc = src, icon_state = "ai_sel") /mob/living/Destroy() dsoverlay.loc = null //I'll take my coat with me diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi index 580c30658420940c14ea6953fe4191deacb94999..82214656ebe9396affdb3818ae155253afc5e47d 100644 GIT binary patch delta 2192 zcmV;B2ygeJ50()niBL{Q4GJ0x0000DNk~Le0002s0002s1Oos70O$jE(f|MeDv>20 ze={>P0RLwH05i-0|G>b&1@vnO00001bW%=J06^y0W&i*I5_(ivbVOxyV{&P5bZKvH z004NLos_|9!!Qhn&-PQ8_F6`gW?OsMrL>G5)>jxFRvUver?#@Ox1ZY`8jT7Ps_R(8sW>Od% zV(M2?9D{D*QkVaz)*C2qXh~4vyzdyP%8`~iQk^3`=15OD(sPcq%8}Mnr3PF#)j9VT zCpZXg=TbdE(e}OI@FB#;atN_896)i@yf|uL966CqI-%HT6N=3;q1f;eicKq_f7o~u zip?Xv)lrWf!QIEwA7{YnpUZZ!C<|g?AC5j42>0Fk=2naAV)BSOoB(rAnycMmd;O?? z0dw({%)KQ}0RR97{7FPXRCt{2T1%4KHV{R2teQ=tI0sN}kRzl@Raj?}oFK#n5MawC zm0ZBGbAWg^%eZ_3SNTwB{D=k!e-NNV(qjkBnUUTQn(l|jryn@vo9_bqc5h5hKL&Q* z8`-mnS-w!b}K-AzIL&{e$G;J@m24@)B9M&;puDz(he_Jr*4$%s{ z0Ym27i!$DTA@z`#@m36_3>jtd1`Jh8slP6?j8`#a28*!0uoF$amT@gZDwUa>n75uG zrBt=&oYOKU8Ada#y-0=4JAYp=##DPD1S?}wnX?i@H3TuEEY2}Zj>>Efd#S&soGsv5 zh6`ASZ*~`6#;X`E%tK?Me~hn?^DyC3ix?5Z}inpan85mxIClT~<&942}wkD(C8IZ=h{zW6hg`bUeI!{u7jhhK|6 zjP=~gm1YGdNj;NnL5BFkU&bYASo5%=H}9e^^|90p}DhF{HA@ zFw%1jDoFSSMogq|kzp^bdVk;bR?hi8cOoCL(wWpf(gUcZw<1#RvFe`o{RK_nT%A7E;LxSA%`b43Q4 zEA=&t;%_a<5O#aLf8#rbv7W1#XOK(myM7CWK*3vXQ)04Em&rh3NuQjKnyQl&ruz`ii*1k;jN@xW^%Z8 zl*W1v;?)}$*=sA@Q zotbI%hYZ1stkoJ73sn)}LR~meu%=H{B45Al!l$IFH7`i`~*)3u{S7cDa zZhOk^_)8Hzf>S+2QhM%DhR#wwMv{8Y*@IYQtF{Lf@EY9oVbh0Y2eIwmg)@hH50aU~ zI(=uB+I#fif6QUs^Fg(NFmzwmt~_&xuOBUPH}^3>NPx^Qz?RXB2L z?&@=b)U~Bx42(Pqk|pGAXAVs5vJXsw-L817=)%hWfpX?k|s0!_wEG zlyZ)066&TZVh}J^3h+|RkVz{M(Nk%gwmd^LO8cef{waof*}x1TWmHw)h#}<>!>9(z z{%1oae?!nmGGNF=$dLJ!#>~M8uVd)qTgYMb6d+&qB}ZxB#|)JuoUmSF`ra$te)c2> zY zYbSMQWjw>s%)zumu432=d#sW;XGt+MbC9J^e-CRI>i8)~jP1cj=447vQ}bSi@yelQ znM~&huypz|bq3Il<4|Te!~VQ;o%w5F|4cF`tZ~r{a4s;{*2&{`RBpm zKld=aTR&cJZz6^}>%r^oUF2}*Qvc64aSnGDGuL;K!@cF&Y+5vH)~s2xX3d&4YxWW> ze=byaFI@k+-TuY+`sw5AxJ@^9@I4?rb|XP?!EJ&A)y>Dzw;$9e48Fd98#uUr{Cn6O= zf?Y2qBJci5r9 z`1ATwHju!&6%M_J#~nf!;7`oDG8Ae@!q4~`Ve{nb> z4elI9U5fi$>peWGNj$3kC_P-Ry#PZrk6}DuaD=#N4!?Yd2^+25i$7Sab~u=q?q2*M zc)7=+AyAE$e|{b#m{h&C$go(f#B^a#?R(uSBHw=p$>=c zYj=2fm+n1i4xbjw`0P19(EYK z9AK-9o`)T-E$be3xVCV8+@ZGSusM9u4v(G|hfn;Ym$t(f@58sJX3d&?R{IZSM6>%E S7n?=^0000qbl delta 1947 zcmV;M2W0q`5u*^5JxN4CRCt{2T1%4KHV|cYTqcTh0ObZbLaJDWbvE${LR;v2-bGa5fOKzt|>nsQudjXioI#Kvo&f!E*+-~ABS_pR}8`8lv_Ydl=8)ZyjY z8V{ErCt|-`TjSyK)9<; z4T?Q(#Q-2=;Bf;6*b?;5#P;|h2Hap7wwHF|k4BEp#09Fn-j7$hRFe_-yf= z;-E-;S>3}GYTu7?=oGz1tuUf2JtK^>w7)#^^5yV=Tc{a z>#?0h+gg^&gRH*Sf0?UDfM7&>3e|l`_BfXZcKuB2eT=4#tY52Cgw-!Nwwjjpz0O?J zX(0o^mFnnfTGaPCbLA}QEUM&1%%H6Ao9t*Meg90o5Hl$1`vwfk`o4{-DCqn140?S~ z6TiJa_-5(@vvfW1{NO>XcRfz71I=V2<+7P6%GYt0%Btz4FM9v@EML707)?RHqt?~y`0T{;u#nRq()=*zhH-(w(EigjF~$N zreg?M2FkZqf0~>!;NbNCtmnMsgfR~pnt7LJpgN;x38Ot?6IC6I2|-i`bE!fYl*T}Y z3^>@5fmES3?0E*^EJ}`nV&U|^2pOm=4AmZCz(m9#$b$p}3^>Rv1Er@SV8CU_fRjwF z&Y~RyH%|&<;7>7gc@WHNw-{{MHAq)e? z&M=^BlRn1?YBcjyM;>QTUS0KgFu(RNw|0pv10Uvt>RH+}N$1U;fm^N*YftPS6NHg1 zM053lv*Q&^^u!#u5`bj;;7Q=~6-~5%gk!L&1bISL%;71e|Eq@Fv&=^sWb27uAN&vM zgW;@Ie<_}JP#dJv4r=HBrybNP_0taC*=0j7Ac;&o&%g?RIlY=TU|=Q8ls=1Yz`$u3 zuY;O1uoPHwM{B{rQh@ZiXA1_Ur%p{6XbKSN`=$)=To*DsQWFM+zpHD+Ape(vjTp?n zGJg?+f+}dk0Hmv_Uc&&V(5}vH$-tfTzKVg#e}VK9jV26C4)Ek@OA7|>zz9<6x&;Gp z6;A4z%($)#Z6D(U47L+5NbJeC`~4r<{;&J}-}oNmI;wvQ!RxpVi(~M9duH?JUR5Xu z|IwS>K79xr>Kp3U5`M5a2NnH)yMGLBc>D0_K?fCmf4hIKJBUIQI;cp;zdwd!kP6W- zf8re@O5Q<5{N6s-2f65=Cf|=tQx&3h4BMz#w6433bnDiwTeoiAx^;WCq12;_O>5ar znQ1UmJf!flVMGf0PnKmIsQgB(KXd-@;wBD_@C@-twBx93pbkbh~Y@7p}1X6oaAbq7>MB^5Pn){()!`+bFfAZG~Oa&9Gni(SYdENEx(0> huxcFQs`2?*^aTXd-41y7ytV)U002ovPDHLkV1lTip)3FZ