From 1db2040daa2bbf2b4b7d2dcc4bbe89770409228b Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Mon, 23 Mar 2015 15:19:13 +0100 Subject: [PATCH 1/5] Refactors the AI eye. Generalizes the eye itself, allowing it to be created and used by any other mob. --- baystation12.dme | 1 + code/modules/mob/freelook/eye.dm | 67 +++++++++++++++++ .../living/silicon/ai/freelook/cameranet.dm | 20 ++--- .../mob/living/silicon/ai/freelook/chunk.dm | 46 ++++++------ .../mob/living/silicon/ai/freelook/eye.dm | 71 +++--------------- code/setup.dm | 2 +- icons/mob/AI.dmi | Bin 226052 -> 225452 bytes icons/mob/eye.dmi | Bin 0 -> 855 bytes 8 files changed, 111 insertions(+), 96 deletions(-) create mode 100644 code/modules/mob/freelook/eye.dm create mode 100644 icons/mob/eye.dmi diff --git a/baystation12.dme b/baystation12.dme index fbe5aba289..3ace652aeb 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1052,6 +1052,7 @@ #include "code\modules\mob\dead\observer\logout.dm" #include "code\modules\mob\dead\observer\observer.dm" #include "code\modules\mob\dead\observer\say.dm" +#include "code\modules\mob\freelook\eye.dm" #include "code\modules\mob\language\generic.dm" #include "code\modules\mob\language\language.dm" #include "code\modules\mob\language\outsider.dm" diff --git a/code/modules/mob/freelook/eye.dm b/code/modules/mob/freelook/eye.dm new file mode 100644 index 0000000000..153609cfa5 --- /dev/null +++ b/code/modules/mob/freelook/eye.dm @@ -0,0 +1,67 @@ +// EYE +// +// A mob that another mob controls to look around the station with. +// It streams chunks as it moves around, which will show it what the controller can and cannot see. + +/mob/eye + name = "Eye" + icon = 'icons/mob/eye.dmi' + icon_state = "default-eye" + alpha = 127 + var/list/visibleChunks = list() + var/mob/living/owner = null + density = 0 + status_flags = GODMODE // You can't damage it. + see_in_dark = 7 + invisibility = INVISIBILITY_EYE + var/ghostimage = null + var/datum/cameranet/visualnet + +/mob/eye/New() + ghostimage = image(src.icon,src,src.icon_state) + ghost_darkness_images |= ghostimage //so ghosts can see the eye when they disable darkness + ghost_sightless_images |= ghostimage //so ghosts can see the eye when they disable ghost sight + updateallghostimages() + ..() + +mob/eye/Del() + if (ghostimage) + ghost_darkness_images -= ghostimage + ghost_sightless_images -= ghostimage + del(ghostimage) + ghostimage = null; + updateallghostimages() + ..() + +// Movement code. Returns 0 to stop air movement from moving it. +/mob/eye/Move() + return 0 + +/mob/eye/examinate() + set popup_menu = 0 + set src = usr.contents + return 0 + +/mob/eye/pointed() + set popup_menu = 0 + set src = usr.contents + return 0 + +/mob/eye/examine(mob/user) + +// Use this when setting the eye's location. +// It will also stream the chunk that the new loc is in. +/mob/eye/proc/setLoc(var/T) + if(owner) + T = get_turf(T) + loc = T + + visualnet.visibility(src) + return 1 + return 0 + +/mob/eye/proc/getLoc() + if(owner) + if(!isturf(owner.loc) || !owner.client) + return + return loc diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index f05fdda45f..45523a10b6 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -37,29 +37,29 @@ var/datum/cameranet/cameranet = new() // Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set. -/datum/cameranet/proc/visibility(mob/aiEye/ai) +/datum/cameranet/proc/visibility(mob/eye/eye) // 0xf = 15 - var/x1 = max(0, ai.x - 16) & ~0xf - var/y1 = max(0, ai.y - 16) & ~0xf - var/x2 = min(world.maxx, ai.x + 16) & ~0xf - var/y2 = min(world.maxy, ai.y + 16) & ~0xf + var/x1 = max(0, eye.x - 16) & ~0xf + var/y1 = max(0, eye.y - 16) & ~0xf + var/x2 = min(world.maxx, eye.x + 16) & ~0xf + var/y2 = min(world.maxy, eye.y + 16) & ~0xf var/list/visibleChunks = list() for(var/x = x1; x <= x2; x += 16) for(var/y = y1; y <= y2; y += 16) - visibleChunks += getCameraChunk(x, y, ai.z) + visibleChunks += getCameraChunk(x, y, eye.z) - var/list/remove = ai.visibleCameraChunks - visibleChunks - var/list/add = visibleChunks - ai.visibleCameraChunks + var/list/remove = eye.visibleChunks - visibleChunks + var/list/add = visibleChunks - eye.visibleChunks for(var/chunk in remove) var/datum/camerachunk/c = chunk - c.remove(ai) + c.remove(eye) for(var/chunk in add) var/datum/camerachunk/c = chunk - c.add(ai) + c.add(eye) // Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open. diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index 90147f5b26..0b07e4cd61 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -3,7 +3,7 @@ // CAMERA CHUNK // // A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. -// Allows the AI Eye to stream these chunks and know what it can and cannot see. +// Allows the Eye to stream these chunks and know what it can and cannot see. /datum/camerachunk var/list/obscuredTurfs = list() @@ -19,28 +19,28 @@ var/y = 0 var/z = 0 -// Add an AI eye to the chunk, then update if changed. +// Add an eye to the chunk, then update if changed. -/datum/camerachunk/proc/add(mob/aiEye/ai) - if(!ai.ai) +/datum/camerachunk/proc/add(mob/eye/eye) + if(!eye.owner) return - ai.visibleCameraChunks += src - if(ai.ai.client) - ai.ai.client.images += obscured + eye.visibleChunks += src + if(eye.owner.client) + eye.owner.client.images += obscured visible++ - seenby += ai + seenby += eye if(changed && !updating) update() -// Remove an AI eye from the chunk, then update if changed. +// Remove an eye from the chunk, then update if changed. -/datum/camerachunk/proc/remove(mob/aiEye/ai) - if(!ai.ai) +/datum/camerachunk/proc/remove(mob/eye/eye) + if(!eye.owner) return - ai.visibleCameraChunks -= src - if(ai.ai.client) - ai.ai.client.images -= obscured - seenby -= ai + eye.visibleChunks -= src + if(eye.owner.client) + eye.owner.client.images -= obscured + seenby -= eye if(visible > 0) visible-- @@ -103,11 +103,11 @@ if(t.obscured) obscured -= t.obscured for(var/eye in seenby) - var/mob/aiEye/m = eye - if(!m || !m.ai) + var/mob/eye/m = eye + if(!m || !m.owner) continue - if(m.ai.client) - m.ai.client.images -= t.obscured + if(m.owner.client) + m.owner.client.images -= t.obscured for(var/turf in visRemoved) var/turf/t = turf @@ -117,12 +117,12 @@ obscured += t.obscured for(var/eye in seenby) - var/mob/aiEye/m = eye - if(!m || !m.ai) + var/mob/eye/m = eye + if(!m || !m.owner) seenby -= m continue - if(m.ai.client) - m.ai.client.images += t.obscured + if(m.owner.client) + m.owner.client.images += t.obscured // Create a new camera chunk, since the chunks are made as they are needed. diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 7bfaf6eac6..e22ab39190 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -3,84 +3,29 @@ // A mob that the AI controls to look around the station with. // It streams chunks as it moves around, which will show it what the AI can and cannot see. -/mob/aiEye +/mob/eye/aiEye name = "Inactive AI Eye" - icon = 'icons/mob/AI.dmi' - icon_state = "eye" - alpha = 127 - var/list/visibleCameraChunks = list() + icon_state = "AI-eye" var/mob/living/silicon/ai/ai = null - density = 0 - status_flags = GODMODE // You can't damage it. - see_in_dark = 7 - invisibility = INVISIBILITY_AI_EYE - var/ghostimage = null - -/mob/aiEye/New() - ghostimage = image(src.icon,src,src.icon_state) - ghost_darkness_images |= ghostimage //so ghosts can see the AI eye when they disable darkness - ghost_sightless_images |= ghostimage //so ghosts can see the AI eye when they disable ghost sight - updateallghostimages() - ..() - -mob/aiEye/Del() - if (ghostimage) - ghost_darkness_images -= ghostimage - ghost_sightless_images -= ghostimage - del(ghostimage) - ghostimage = null; - updateallghostimages() - ..() - -// Movement code. Returns 0 to stop air movement from moving it. -/mob/aiEye/Move() - return 0 - -/mob/aiEye/examinate() - set popup_menu = 0 - set src = usr.contents - return 0 - -/mob/aiEye/pointed() - set popup_menu = 0 - set src = usr.contents - return 0 - -/mob/aiEye/examine(mob/user) - -// Use this when setting the aiEye's location. -// It will also stream the chunk that the new loc is in. -/mob/aiEye/proc/setLoc(var/T, var/cancel_tracking = 1) - - if(ai) - if(!isturf(ai.loc)) - return +/mob/eye/aiEye/setLoc(var/T, var/cancel_tracking = 1) + if(..()) if(cancel_tracking) ai.ai_cancel_tracking() - T = get_turf(T) - loc = T - cameranet.visibility(src) if(ai.client) ai.client.eye = src //Holopad if(ai.holo) ai.holo.move_hologram(ai) - -/mob/aiEye/proc/getLoc() - - if(ai) - if(!isturf(ai.loc) || !ai.client) - return - return ai.eyeobj.loc + return 1 // AI MOVEMENT // The AI's "eye". Described on the top of the page. /mob/living/silicon/ai - var/mob/aiEye/eyeobj = new() + var/mob/eye/aiEye/eyeobj = new() var/sprint = 10 var/cooldown = 0 var/acceleration = 1 @@ -90,7 +35,9 @@ mob/aiEye/Del() /mob/living/silicon/ai/New() ..() eyeobj.ai = src + eyeobj.owner = src eyeobj.name = "[src.name] (AI Eye)" // Give it a name + eyeobj.visualnet = cameranet spawn(5) eyeobj.loc = src.loc @@ -152,7 +99,7 @@ mob/aiEye/Del() if(client && client.eye) client.eye = src - for(var/datum/camerachunk/c in eyeobj.visibleCameraChunks) + for(var/datum/camerachunk/c in eyeobj.visibleChunks) c.remove(eyeobj) src.eyeobj.setLoc(src) diff --git a/code/setup.dm b/code/setup.dm index cee4313d93..efad87a5dd 100644 --- a/code/setup.dm +++ b/code/setup.dm @@ -457,7 +457,7 @@ #define INVISIBILITY_LEVEL_ONE 35 #define INVISIBILITY_LEVEL_TWO 45 #define INVISIBILITY_OBSERVER 60 -#define INVISIBILITY_AI_EYE 61 +#define INVISIBILITY_EYE 61 #define SEE_INVISIBLE_LIVING 25 #define SEE_INVISIBLE_OBSERVER_NOLIGHTING 15 diff --git a/icons/mob/AI.dmi b/icons/mob/AI.dmi index 0653f3b482e552b0f341021669b4aed9b3cda671..4525fea606a643590991a0c296737aa1db2f128f 100644 GIT binary patch delta 12244 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 XTq=R9bfgFd`lF$u`>;moam4=zdq&$1 delta 12872 zcmZvC1yodR7w!-e(jg#9r%0%PbW2IMv~)>#9vTH{6hS(a5D@7cloX^vBxLBJhaQ+= zX72d?e*gXNT9@^57Bj;-v(J9_e)ita^L|<-i`^ni;{>Kc44?TuaqzYCaq;qV@$>+J zg7RjKwLI5ZDP9d_9i5k0%!9;VFldasxuSWGA>X0kqxk*O8VfZfPk! z7+#Vu$l_Q{TJH3#8>oKSpgr$i5d3}o?m0NT;u(7H!I8k$IYbEa5ar?)Fu?Bp{fLGB zo=%Vv_;j$w^<_zf760V>7HJs^Oy0kRdn z4(@g`s}S}((zEXkf~l1eta^-E-$<$8MFf_YGo+1FifOEkfI;vf60*3VC35I6}4 zoIk&~EeISu0tEn$R(NYMn-7I9{eV}Q;p*M|889mTbJ4lx_WT#E5q){KhZ^5A7MWt0 zN}1|xH{0=Rgg_uxkopq^L(vWo3jCFclu{K+`4bRaJZXs-gnU zAq<9ZjfMq}bwLZHdZ=~>zqPIJc}$N~wJC>R?3rKmubcgF;QOhSYbjYrhwFs+;Q4*61k)m9uRR88dYg1m{T8FD~`!Wo1&_b0Tu~;jXVMwN9K9XZPZUb0o zd<@Y>I;@%Ne6*G^D7s62J;{Qa6s2glqz!=}yC93_%V%1U|5*SnB3uH@E==g;7q?Ta zYVj6IK0Lh9=rMS=s4Bpyu0=DZ`;Os7@=R$_I{@n}U>Cg%8Cz2^bt|h- znT&Nsq3;6h*QR0xP}+V;;F8n5^ZtKUsy2?7&fqskdGO!sH1v=MgO9_f#r|ip zly^VGzapB~v{O#Gn|t`4Ijkc`PI4q&X_~g(oXNpl6xT36?J>FL|2hkDmTakAu!|fj zc1hVkbDsI?A|TpglUy!HxV@P~-xAUZkR4Z5R(2Qu@8e_tPSuC};#^#L`C6meFVdt?#hWIwIA2`L>>53*S<>b1T7vOW}z832>J^ns)^sYSEV8dO74ZR=lfs zYf7X))H;G$kwAV(&LBCJ_#DSbS@h`JT!AP`U^^S8Or<{DMw;uUBnxKa<;!YU2c@2@ z&4JrL?){Sq7s!TLgl9N@g)L}QFyH!?P8-dDOR-&ioz4DTx8-rDN!dQ=w(v#hXPMYO zHcEg+_9{l@i9Ejl@eCaqvj}Bg?~U@!h7-2TATHq58l~curM%sO-T^TG4n1%_5}wx^ zM|s+Y%Xt-{Kn2x(l^aXy6^lPa7Sm+`{0Wv8NMX%SU{P^JW%HVLFdzHSKarjcQmWpC zMskbp@H`2*#hB{ZG&3f38nwHeDV&u+6lVoZ-g^+PxPqFb)YJV<8e59zN8}#`q8-(a z)O7RsmP7Rmi)C5EnR`CSm342-S2s>eRRs~<6c-WkR#1>xZlPl7Arv`v?r4Jl_xbw~ zo=JEjPoW<}t|Qvx>kmGOPCY!N{LCb{Xyp|hU|@Q|S1TJeV_>1L#0~K1Wv@+1sxc~2 zQqzZST1*9R-=g@sGBQD^r>xi$M_2Nc=r*IYi*DIuU&7<={2a9LIf?tWppG6L5KgKv zN&1D3`cdN~E2q1mF+?!579FZYtPe_$$_FF$SX6cMI_I&FNS z-xU_*2ztF))r=9HTL!xFtkba{O9Fn)*UqjXjFCCsqcJ1?CW3t}^gM1FSz3u#xyq@`$3{#|+i$Y=NqB<$lJs1Q z6TdH1(YZKVNU1@APiq=d5}{xsVMv@C)tt^x8AcLDGg>kUOutQPH6|f8frp{sCEdXM z!QQFoIg2N%#Q^-g4uc*#IXS8N_DNMFf_b~MP^#PYy&i*dsYtgmH{qQ-!qZntdbbs2 z20Y~)8Kn-c{aP@suUGV}@M7{<8CFFnJ{|;4T}9z=bRs{Y2$LU=v*d3M6?Y!1hG7b& z1Ec!wM#9B_`E7}u{QOyVlJOe3UavHpmHWHbV~f5ku8xTrPs}; z@}h-ayGr}XB<@4GMFnY1dWBTo$VaQYx4Hv%r+aq!#1SCSF-& z?HUmByQ4IF5HE9~A`yICRL~iu8)^;X6i&WX$(U}$1#J4wzjtL?Ycme=g02W@7;5liZjCB z!28L%kBTyH!p$+>EtrZiOaKO*=ah(jHdw7QrugnZx!|>p;|NX3ra)x^P{9XMfq8LC zrQXp5w70nbY)M0mWh>kJmOFgSYsXE}NA{irRjc9&e? zh?SYE*T^axUzzr)Bq0t}-bKlg=Msp-3aZCgD-t`*bd1tTbH5A7* z*yT7P_f3;fF4!x6FyRw$3>`Y}=eVrcI}!Pku~Notx5=eo@ib#5D9k&3OUQ5+BHha& zRAY(Rg*fiPxXJrYF;gg!_Z_ML#~g+=oJ;9Dktq*Zosw6^8sO@mVU}2}{Cn*h-hmdY z2In9=6Cx%aH{Ib5ItpCw5ZaSFykI%Mf{Z~5u@*|7_N{l6pb~QN8aru|3zG&9?RK|c zot$2xgZ6JPe|?aaNl&r6(dI%o#smvl5n?uXT%Ewnla6K1kp$Zzk_O*mE4Ddf(JW;} z^f!f^`7u#IKi(^hE>*zS{&1yv=;F)Q3kcOg^tT~_g_5Em;AHLVYF(67jDO5QvoW&w zu_d02J4DB~HwBhKTJzV*FZaPJ3t7EXni-@9TX#IIz=^=izI}~1c7r~BO^+;c*P=OmMW5#@ z6C$^zXrCxNilv=#uR=SR)={BeRpN-DFt`ZC>b*5tSL8IrGb&)aw*P2PPA^e^71x;t6{KxB6WojT&zM>78(ItaTAQ zTogt&)+knj=idpYXijLAok z@#^o^`OB*OytpCdO*R3pZTTH81|Jn*1&CR3{K*>$#Ghp0a|s5`sSt_l=@w#|C?Dk! z$6+9 zfv|~l&rP8IuC^`Tv-E&DS<2LMK}U}a{KNc0>sz?+qvV)93LIi)`jYgfc>tj#BG24? zOjE76GEfO#K8I8b;k$}y9yLR*X9a&Z4X;pgS}41mccBv|A+D&FDk1zsWHK-y){PQ~ z3^&DiH)ATs|K#|RSWAz4Af)U50$@iz@%6T)IDKdYeX2l1=NafUX_6?%`n3!g8-Q&= z``ylY`(FHE$3-v{7`Vq5zgk4u95FyK;Xrh&t{P&J+R-Upnn2EE2JgHp>8F;R9XdQy zYU1A=G9o-dRK;MRG;T8Qa6qvbNM;B& zoW|9zUcj<{*Fv1)2CTX;b5(QGA9Z|fU5bSj*_cO7)>CBMfZr6XMEMV5($cDmWR=2I zN*IMf4(^ISdyx0>-C37QWhoU``lfKLBTgC|Bno81b5-f+RX<>c)@-vQsBp#jPzr2b zz1uu&Y{cp!FS1U2>G6rX8w!c|1mi1S7FFRXtq|-~)6Ddri+d@ejQm~e+EZTLv3_WX ziV8v##`Yv@06^CsxU?{>`^%UR`aS$@7t9FjbWl;><83UiY679V;9!(C3bS2^xsS$P zs!#6jw;)nKq6U4@El4;NYnx7?>>sc9vU$~i6l0X>GKD0@hON(U+#UTbJnkBA79jSN0NQG+&Ejqa*VU=s)n z3W1O8YiJZ(r~>SeN-YmtO%p}Nl^HC(nRTpQt{T2?BXInHFq>xva3U)`8yK zlll3;9Sa)@HE-J(xb6g$Tzj}<4zS^WH(~9X778ZtML$h^YSCs6=u$&Z zT3_7Y;qCk78Wov3A0ZOYdMV9RRiPSF!nV@IQ+x`tqM3%IMWjh@$*C)_#fDkSi^dlO zvFK*8-ez|(c{%(5(PYB>T>oxWVqg?zU2M76-$jH;_H07Qe$>pOFh-iPoX)FBl98K| zO7ZQgnSkP{)cY#TuXD@cjr-h}fHed)Vi6F9ftp0zhzyiPIJbCcbKmBR*5LW>wscXT)K^H?kg*gbAWg7wp-DhjZ0qa)s-*5H_>)C zL8OtpT;TTIq@;vl_JPW?f4Q?6!_IPe(`!<3NU=-;+J z{_WlXKl;Ae5OyXEj|6BkM3lMRVjJT%dbrf*@Oag!6}#p0<0G5z@A2VtU2@wKk4~_= z@HOi9IbB}k+*P8KH9hoQ+?T=DN>Ub?>#g){EC`K5Fuf`0z70+xFR8l)%&-0+(R15~ z?ov#BM{6LOAN!Avy9Q!YJ}L9oVN!F}KxDOP3}*EM>;P!DvPM-^PJwt^GaSN@n@N_1 z;d^o5EqTfBnLW$ zRb(f90JbswA;=8#iAJ0|!pGVw(L%3*!c1s8S;n`td+)4&kQEF(tu%-*p8q)f-Ire6 z$1iu&?t#C3Q$W*;ekas0#5;n4&!&H95lrFDR1mX?j9^pf4|xCZ1Sz4=YlSB~oy9>t z>sGWoA^#$Q-PhK0QAm*4tUT~UF=A9WY|%5}wBgPIIJ=~~t5n|GvS7yp8qw1PSmmXD zBaYTKZc-rTXy;Ea z=E)W48qIPkWl2%Ai}zWJ)vdYpaxNRxdNMV#f|nDLn{95NE#!N6nmX+v=FKjGuF6kf z{qbf`Ub^cVKb2pAt&k}h+iC$xVZpVfkQXAYE-tUelhrUE`jIYU`B91(`mv)+je0bw z?h?(i?}~iIzcVG|hvO=vw?9XiAo&%}e}PVfVHC_Dmy#JyQOI=bcQJt*h*4qwE|FFg zW|{+5p8f482x*7ONR>YG04-m;ffvew>lTR4(aM*{3(lFA?L-+Tz>o%9crFhwAORgR z2N;3({b9kD4A0@1g*w%PHekVMIpBVgi6HfY3Bn3{s&ECosU7yG30cKsvTJEK9>;PmbWoOwDk{U@o@p_2 z=J64k4M{%q*ro9y zE0P<+)00PLRv|?qepNC{T`z`y?d1R*(~EXDfBQ4j0@i0B`uQ&*Edo46aB+Fi{Y+sq zhKw#Gt%Ipm$a5nGO;3rNh&NsBsQ3lP9|zVCc&m+OL5Bi%UsaM(5v$s6v!N5Bx)z1A z2S`giBaWb83GUm^st=-0a#=ncMAfE87OO-DC?F|Zk-KfX@b|i2$KP)J)JOtkcr`zn z)IG0;19f=#cRC0jhhiwI7EhKG{!GR1J;5<@vZwrVD1(O9J#H^C&D9LX4DrNQpV*@* zq1}{{J9$Q@prhg5@q~cu<%E;f(i*y&Y6zDI z^^|->jRId^XD(eYENX<`0G7WVgl%a-gsD-XvcMY(-?wYH6BLlK`M|u*D7Ibe2_tfz z$)h1i^#H%-F5cG`;%q_cS`i8KeC85*bum3~<9u#$HE?wc0C$~_3-+hIn*xpG9$$ku z`8t`NV3W$R4nqjQdgVzmSAiJlq`q87Sfz%EC^vY*k;c8{vH=*s?yuSdcn-k|=deJG zdowx}l^J-H1V;YquEB^e0(nsIb78O?aI)hNu~mzO;09~PP$WgbJ>T_@2;G`^qVrNl zlL)Lcjo&Swg)k_mde$j?_K7kY(~(2%djAx;K$PX&5P#%vm3ZzKvbW^6bhd{h*bEb; zOJY950yog;6%8Lizl?)#^GaZE95`#fgGW#@s3UcT6OUthj@+y(Kp z@3TugHTdn>Q5O|wH)y(obEExd<2<@KfxWQ-f`8SG`}zI?xbSqkTF+u6Ca(}nak!-r znTS@MGb%5_eIt{6;P(yQ6xT&bch)`CgV*H+0~zJzTr&V8M4KqNT5XC`eXUP__MM7I zEqr3j$2(~$`3{Kds)Sa-!?l?E6@H$L>D~4be=s3e+zV4zptphI5 ze}?;JjQBXlI0De-7dUz&+);``O3B|^|7g7wRBtNBQ5DGC@A$gNsXf#0D|c0;P$oBp4j}8T zke)m-AsA$nx95CFYC{1|=iAe19p4Gj2~YG$Q9w&MdKjBxu3ta*F&XPlh3Mo=nx!1GT~;Nv+2lL|D~MHKIK!KNq6sn*S#F`hNApT&@VvZ^s!H2hXb zAZhgGgzV8}^Lp3;UE-_Dm~W8BieSCu2&nsW1}mMQg^+tWVv>xrskT1hqCY$)9ui=C zf-HyXv$5%Uj}*=P>`eM+x41ig%1ot>Hh=f%z=V!;78rjO0O~?zc)*v|mn$sG@to0( z`d;pNzV=lQ7friWVy09?WU~y{qXXGuwZ*e9q=OQ(CSaA-P^c zSWvIp8B0MS6hJ&v1rOmuVHK%Q0A0FPn_XC9t-`s?1`Mh22#R3)n{gr5GW|wJ@^%ua|1B{~0X>>W~!Jjte=CGaMS3 zbT;w+VOAo_nA^!u*cYeD?HTEvMQaACO|#*#5UoEHpHt-8tMtoxx( z;EfIa5@yzp)D;sg3ccx3k3u$uD}Y12@4_)=n`U!u@AGF>x_P0a!k)1Re4iCiMyvFN zq(W0DicxPZ@%NhqErsI+7v@l=FTcz`-kx$Ul|8yPumBQ@BSFVG;9t{N&Hzz7zdQ&1r!n%(MmL=xh0hc# zI2>rkC|(p!iv94uxegsF-btDCjkt5uJ3@JwwmZS`3`>D-0(VnJvrf%yv8^$-O@1F%Lyp1QrTpmCj8FaAQ23+IM~rhd>*x#h2P&$u?J*I@CchxlJy_oVO=%zldln zj+&sYnz~%`?5hjX{OPWY*FtF7qc`S5I^p8Bl7?Y-ARTJ4I}|lSSDYYX;^jbiS^(9W z1xS&=lmrF{Tl}lXB4Ie-njeJjF>RBwstXH*{s4Dyk z{lwqdI_g%GHCXm+gDPd;;G>htq+Q;W-Md5lQ&p!H$WXV+pIYOb=j8CE;pK^Oeo?ZQ zz!GNjiC&Z;wHmFpruZmJfYpuc4XFwa5`nyE5Wv{ZVsJCIgm7YTse}X4Vu#CSTzjGd%vn<-Ivc`FMjD$Gd&bXJ+bZjc0?{~g?>me|IWtZ~q9j#$| z=ysxv?WGb`sX&lXhSCtLgVx#*|*LZq~wRBGk6=Bs2Ml$cx7Yn5>tmFIR(|A zr`4Zo$H>-O2e;W6SLkr9P{(S+`hBgw-GfAt)O)6Y|LmqjZ6t0I?9wE$GCSZOa@ z^O*I~zw#$U2(bma9dEO5FffaB#43gi>rWVy2X-T+?d(5&dL2kUvcJDCzDf3v%1CNg zR<_QJ#tOW5`Tz4qF9XxPeVDp#hXUlU68U;kcMPX}Cjr1uZ~c|V>8()8-k+zjR-!sq zz6tQvs%NA*+-6Ty(~qcSyHCNa7+F+7yR(h^SL$0ZO7q?WV#(_^J4x%66??ayivP9mSfjIfWD-BRiaw=fUC#nB%;L9 zXX3`Q>!^8JA)=0?zwc;V2q|4`N#@e?Tt&EZqRAk}VD;9}~thFhUCh_I3G`?=8$gjq#MXuN} zv0 zdvjS3K_ z43YTAbnVFMe!bjB3JTFHM7V-1nMAwlicg8|W!S5!_3*qL*KLa);g%QfZVz)bQt0mX zi8lvM9?b9z)C$pvV)dkLKdy7}lgu*l?13^OYSn_9?p#H=Jkm$^81;$VIW@d?iB0c7 zX%wrKGn%dzG7ABg_G0Zq*_s3jmgR|N>~(9V(qIc8n|^;{SB2tU55@RWe_>778_qVG z?4Oe&Ts2zqKA=7Y0ZEpzwnrSf8{G!0__35;jJi>M<@nGv%^nHEN8dQhMhUtPB*tkm z+XYg?inNF|G=0XyZSX}w=+z93HQ%|ziKK<(t1=hBuO~yKu<(fl?uJFnY57JDBtxEv zm@%WZjv%k&X$MooFZB$sf_v!IA2mv1KOyJ?2zgiPKel1bW73bTtI4x*aUo9U@uDFl z%3$Su$-&JfZ;NOsO49-KzHAeL5~Fz)oqE_Db&>{;rxNQdvC5HnePfTX4wfHTp+x}d zCs*PN?8LQns!@hh*W&qpGi;|D>I+^&Z_f*Z*N%-4YCY;q$u6rt?g~c_CGc<(BG2so zNfMox^!4cttdaQX_}QF6>VIxIXS@|}Mz}3n7feQ+w=>}-QJG<~Uv zfor4iELsyEL`{EPC_X{BgRiQu4?`o8E;O>&AHSaf9~^dEZR~uMxagB%WpjK#Z6-}q zGC50g<2&UECqQKOy8BXm!Qh7yJLo2330_%B-+jv7<7gtRVsm8YdA*@ac8)b2=cr#O zqW*dx3ZS7QbHEWHpxYhsd(Uc6416_Oobs=akvN1$n>=zOugc@zJ!Z81`N(|*bxZLl ztNp`jvywyv>G|!-o9b%sT90qdSot%Zo6G`V`}du96Zc=RNs_F|haHOZVwKDw#M#al z%D4!NOA$#rHy0CYyiQcha_=9}Afx+Bfg8m~%-PO#-ZQi9*Ap*Q`;Am`ajB&R4VT>x zdE;XZ%d1~x+)(_g*LDHD((@q!=4qLy1 z7DBOE2g;N5ui87^z34~(1yUPQq9|WN57wEzR*_`ws3cz?8W!8-z^KN((0L$+xdAfy z@;Vg^6;o66yUWPvY<(@5NX@Cl8Ry>%R&=^kNww>bJ>F7HZPDXm_F}dl%e)driebq% zpK!0R6BJl!y+dmO=$0sx^^Qm_tFWgPevQ=!f)yC^rF+_fS|fR4`BN@IvO0qTyOTxE zL_sk1mn2Od-1xO#jvn1tj)$G6+Op@BI`yAFgPuU4N2|SeWy9X-gzp+G1w->K7wO5< z|IJewHZK;`sn{QO8G#8R3A~FYt_7n(CJh8S04f2<_WUgvcs9z|ZCa|e!a>n>XIq?g z7r*f9F(g9ntC9ds<0Q8)u3&uRZ!8nMS^pF@=#=xWooZ^2Agegp^C#$!H%=#2uO^fz z-V~yCkR`$am^u_f7tu1h+{=?aelj_}gr}`GMVs@9YKl=1C!13@w7L#r|WVpNmEBB8RJToS)@Mv7X&MIqMK!rbt3&^m!|}i>_ctPhmIA zI!&`Nc&`*jSR$OI*{DGXl&!E_FD;6vY5QEA7O9JZ7BeCDaHf7ypzUM$xWmz(vWU3_7P+1cc=rR%hhLy#HFU=Fn` z$L8Re9>`7=rrKJx-{0kKvMbF{u7Du=oC{RjdG_d1{zJjmCfz)+Ig*FGUT<8~$|Fh! zyF%H#?u_G_jG~*D;d>4V!v85&uTh#NL>A80U*qnbO^K~`+!LgjL-Y}%{pW~=Fz1V4 zOG(hf>rX#7U#8i|f(kL)FgA93(|ETg=f7NJEG28Bcd|xf?!)({At*A?-9qF?Q0Hb~ z#795i+aL8v`tnz)^uL^HA?^nU zn|70)(F`7&($Qr=e6z4(hjPFUb#b^uKKITRw(`F<_^7>e&;rr!vVtKF@mMZFfy==> zBqSuAdzP?WMD47r_r<>Hi-(5)DZ@bTHftn$#my6{Q-I*}M1Pm^%FJx_!=)Pb51hL5 zLDz`d#M>ti5BiixUUV##_~DhQAHHE!E1mX{`#0<^-`7HKOpjduu$23D>IBA@FUC*+ zYlG=#OgZm*Bobck3925F;&dLY4?gADuLn+21a=@{NT%!K9H*qgLdwe|1wD?+(T zg@~Q(h|0YNPXM_HTJq{8+hYz52ngUf`3orCT4}h_NJER4hA0*)v)g>?c>qM8|0sCJ z!7xkHP0j4nI0rs0kIRoQ#w*l@Z{!EnkAewA+?`h5W3g`KzLO-|^YRF!+$z(I! zJ(`rC;Q#P+X#9=i%_$W_5Wp2Hf0iC5-6HE46?##*N~b6jdcp4*)O)9W3yzHu0Z-F& z(=ID0iS;FJAS4_fpZZ^@kE*2!t(thRibKTP9QEhTsQHIpb zDsQg5R&wVYGxP9xN9Hh3@)q*t&_=T2{Nl>D+RvPDCT9Tz3^OR#;f}x(Q_NumW%jaJ zr8)|>V@FtC>b>(1kg9Gpk-nr#E>A2;+i9khLJrTVfQ8hZs;H{7u^W>9yR^Wm7|;t3 z8;5&c9<6Gz%>A7?E=X{UJ;a$deLV%At;Ck zn8n7Z%U8T1*B~ms*3ms14xCiU)RDUqQMH1@B`}*#Al@_J?XIcWzr1!*MWG-@rIQnkiuClJ;JGc#qf}wME}u zIp9ri3u^otUI+MNb*#;%#HSd-tZPawngKZ-tt^V>V#Jcg3 zZxF(sPx4ZX3jZ=TVTU@>D%rKT)a`lsRf3Vz;)pVUEvZyFmo546F5bA@d7M7LKuEMw zKgc}Y)+Q0@BTxiGcEj#NaRdYej#pwRJTBk;GaCNssF1IwutVL%ftmnUWx2CJN@vCW z&XlrsQeAPT<7%JxvI&CF{tCrKrFFhWvrGQ*8<5ATOO18EpSFs;x8@JMr<^RsY!=EG$*)xZ<@1CZHZEz?_$UXma z4E0Y!)q{DVpkN5M_fzgPPI4Vqwy_4ikFiVN=xw>hjlAxuz1$l!R8?yyV$5WS#U5T< zc5409AZN2A6S_2gLjTg){Ogk?x*Z+P1m$4!SZ z2>5lV-z=a0AiK3io}-Fdc*XE|IEF%&S3^U?EF8QU!}uQ@84%TWz#qa(jMe>+CP)fY ze1aRyn-0w_;>61T$Rse8g?^AXva5L35r>ctHKSm|Pa}mer*@A&;xyoNTW^Sz+$n%( zjuQ|6=C`#4Ug)M9Iq-=TzvJNihYQ(UARr|AvGee6<*~WPRJ0PVdHN~QndTPTccw^6 zaIx2eD%w&J^@0G#*>@n;lqkQcXN1WHww!(vJRK@>r)nh=0cy_Jq0cWG%Y&%CKe?e7 zURXx@rWA)fTT0&Q*7y8J#7~c^nI8P90KlJo4Ja5R((TY1dgN7`H$h$IL>oIyq$&Cp z)c=g|C7&z{;f}qkL6)ryn*a@=;q8`g$J=Zh+B#mFvhofas*}tKANj4*9~A2kq-7WL zt6ESs-FN%Lcl@(TOZ_6zyWlSIF`w6#m~UcZsYd^0CGvUV#zLgT*KNUj|IY6Z8K-Qi qbnT=+VR?=YqW|0?zTWc^gM-hVCl5*e@)CsoQCHS}Qmgnp`u_kV^%JN7 diff --git a/icons/mob/eye.dmi b/icons/mob/eye.dmi new file mode 100644 index 0000000000000000000000000000000000000000..3bcd9971658395a0d58106409f420cfc03bddb77 GIT binary patch literal 855 zcmV-d1E~CoP)V=-0C=2JR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRLOex6# za*U0*I5Sc+(=$pSoZ^zil2jm5DJ3;6u{5VdH?=ZViHkEOv#1y-YRJWzR+N~V3St|X z0)fMH`s+ik3si?cAo_y3$b^Fu)j=BlKLjq*N!kV^K&#*RsK7h?bb-=vtt+U+FM zc)PIm_OT&S#}`FemOq5Nto5+As;UBCw0BvUQJ@{Y2$}3qeyf1?EnA(0001qP#>T^Kz)Gv0QCXt1Jnno z4*&oF004mJLw$hy0QCXt1Jnno4^SVVJ^%m!0001jqU04`?t2=JHNkuhqt!h zJ-=TR+e>(B`~CC#%LTlQw|0ApwfMW|_owXn9DmtfY%j4EpFY2@o`KZ$fkKH_&%h4q z183L7oAmj8>H`1(0000W!p%}oBtpy2yU$scccA6pjcd*_yaV<3`HKz6atpNlzcF4` z{xI!;fAsWJ>hJT7Kb$w%tD4 z!lmm{Pv#n#e-rD5Q{av+)BlCOK(^o{Uty=6Zh+{UrfuaA^r hpzrrRe*pMq{{e$3QXQrjOymFn002ovPDHLkV1jvmr&a&} literal 0 HcmV?d00001 From 35ba4282a62e6f3e10ff0b742591b559577ec331 Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Mon, 23 Mar 2015 15:55:26 +0100 Subject: [PATCH 2/5] Refactors the AI eye. Generalizes the chunks used by the eye to decide coverage. --- baystation12.dme | 1 + code/modules/mob/freelook/chunk.dm | 141 ++++++++++++++++++ .../living/silicon/ai/freelook/cameranet.dm | 14 +- .../mob/living/silicon/ai/freelook/chunk.dm | 140 +---------------- .../mob/living/silicon/ai/freelook/eye.dm | 2 +- 5 files changed, 155 insertions(+), 143 deletions(-) create mode 100644 code/modules/mob/freelook/chunk.dm diff --git a/baystation12.dme b/baystation12.dme index 3ace652aeb..30cd41c9e9 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1052,6 +1052,7 @@ #include "code\modules\mob\dead\observer\logout.dm" #include "code\modules\mob\dead\observer\observer.dm" #include "code\modules\mob\dead\observer\say.dm" +#include "code\modules\mob\freelook\chunk.dm" #include "code\modules\mob\freelook\eye.dm" #include "code\modules\mob\language\generic.dm" #include "code\modules\mob\language\language.dm" diff --git a/code/modules/mob/freelook/chunk.dm b/code/modules/mob/freelook/chunk.dm new file mode 100644 index 0000000000..ecc9d0b4d3 --- /dev/null +++ b/code/modules/mob/freelook/chunk.dm @@ -0,0 +1,141 @@ +#define UPDATE_BUFFER 25 // 2.5 seconds + +// CHUNK +// +// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. +// Allows the Eye to stream these chunks and know what it can and cannot see. + +/datum/chunk + var/list/obscuredTurfs = list() + var/list/visibleTurfs = list() + var/list/obscured = list() + var/list/turfs = list() + var/list/seenby = list() + var/visible = 0 + var/changed = 0 + var/updating = 0 + var/x = 0 + var/y = 0 + var/z = 0 + +// Add an eye to the chunk, then update if changed. + +/datum/chunk/proc/add(mob/eye/eye) + if(!eye.owner) + return + eye.visibleChunks += src + if(eye.owner.client) + eye.owner.client.images += obscured + visible++ + seenby += eye + if(changed && !updating) + update() + +// Remove an eye from the chunk, then update if changed. + +/datum/chunk/proc/remove(mob/eye/eye) + if(!eye.owner) + return + eye.visibleChunks -= src + if(eye.owner.client) + eye.owner.client.images -= obscured + seenby -= eye + if(visible > 0) + visible-- + +// Called when a chunk has changed. I.E: A wall was deleted. + +/datum/chunk/proc/visibilityChanged(turf/loc) + if(!visibleTurfs[loc]) + return + hasChanged() + +// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will +// instead be flagged to update the next time an AI Eye moves near it. + +/datum/chunk/proc/hasChanged(var/update_now = 0) + if(visible || update_now) + if(!updating) + updating = 1 + spawn(UPDATE_BUFFER) // Batch large changes, such as many doors opening or closing at once + update() + updating = 0 + else + changed = 1 + +// The actual updating. + +/datum/chunk/proc/update() + + set background = 1 + + var/list/newVisibleTurfs = new() + acquireVisibleTurfs(newVisibleTurfs) + + // Removes turf that isn't in turfs. + newVisibleTurfs &= turfs + + var/list/visAdded = newVisibleTurfs - visibleTurfs + var/list/visRemoved = visibleTurfs - newVisibleTurfs + + visibleTurfs = newVisibleTurfs + obscuredTurfs = turfs - newVisibleTurfs + + for(var/turf in visAdded) + var/turf/t = turf + if(t.obscured) + obscured -= t.obscured + for(var/eye in seenby) + var/mob/eye/m = eye + if(!m || !m.owner) + continue + if(m.owner.client) + m.owner.client.images -= t.obscured + + for(var/turf in visRemoved) + var/turf/t = turf + if(obscuredTurfs[t]) + if(!t.obscured) + t.obscured = image('icons/effects/cameravis.dmi', t, "black", 15) + + obscured += t.obscured + for(var/eye in seenby) + var/mob/eye/m = eye + if(!m || !m.owner) + seenby -= m + continue + if(m.owner.client) + m.owner.client.images += t.obscured + +/datum/chunk/proc/acquireVisibleTurfs(var/list/visible) + +// Create a new camera chunk, since the chunks are made as they are needed. + +/datum/chunk/New(loc, x, y, z) + + // 0xf = 15 + x &= ~0xf + y &= ~0xf + + src.x = x + src.y = y + src.z = z + + for(var/turf/t in range(10, locate(x + 8, y + 8, z))) + if(t.x >= x && t.y >= y && t.x < x + 16 && t.y < y + 16) + turfs[t] = t + + acquireVisibleTurfs(visibleTurfs) + + // Removes turf that isn't in turfs. + visibleTurfs &= turfs + + obscuredTurfs = turfs - visibleTurfs + + for(var/turf in obscuredTurfs) + var/turf/t = turf + if(!t.obscured) + t.obscured = image('icons/effects/cameravis.dmi', t, "black", 15) + obscured += t.obscured + +#undef UPDATE_BUFFER diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index 45523a10b6..ffebebe851 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -31,7 +31,7 @@ var/datum/cameranet/cameranet = new() y &= ~0xf var/key = "[x],[y],[z]" if(!chunks[key]) - chunks[key] = new /datum/camerachunk(null, x, y, z) + chunks[key] = new /datum/chunk/camera(null, x, y, z) return chunks[key] @@ -54,11 +54,11 @@ var/datum/cameranet/cameranet = new() var/list/add = visibleChunks - eye.visibleChunks for(var/chunk in remove) - var/datum/camerachunk/c = chunk + var/datum/chunk/c = chunk c.remove(eye) for(var/chunk in add) - var/datum/camerachunk/c = chunk + var/datum/chunk/c = chunk c.add(eye) // Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open. @@ -73,7 +73,7 @@ var/datum/cameranet/cameranet = new() // 0xf = 15 if(!chunkGenerated(x, y, z)) return - var/datum/camerachunk/chunk = getCameraChunk(x, y, z) + var/datum/chunk/chunk = getCameraChunk(x, y, z) chunk.hasChanged() // Removes a camera from a chunk. @@ -119,7 +119,7 @@ var/datum/cameranet/cameranet = new() for(var/x = x1; x <= x2; x += 16) for(var/y = y1; y <= y2; y += 16) if(chunkGenerated(x, y, T.z)) - var/datum/camerachunk/chunk = getCameraChunk(x, y, T.z) + var/datum/chunk/camera/chunk = getCameraChunk(x, y, T.z) // Only add actual cameras to the list of cameras if(istype(c, /obj/machinery/camera)) if(choice == 0) @@ -139,7 +139,7 @@ var/datum/cameranet/cameranet = new() return checkTurfVis(position) /datum/cameranet/proc/checkTurfVis(var/turf/position) - var/datum/camerachunk/chunk = getCameraChunk(position.x, position.y, position.z) + var/datum/chunk/chunk = getCameraChunk(position.x, position.y, position.z) if(chunk) if(chunk.changed) chunk.hasChanged(1) // Update now, no matter if it's visible or not. @@ -153,6 +153,6 @@ var/datum/cameranet/cameranet = new() set src in world if(cameranet.chunkGenerated(x, y, z)) - var/datum/camerachunk/chunk = cameranet.getCameraChunk(x, y, z) + var/datum/chunk/chunk = cameranet.getCameraChunk(x, y, z) usr.client.debug_variables(chunk) */ \ No newline at end of file diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index 0b07e4cd61..89a07bb465 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -1,77 +1,12 @@ -#define UPDATE_BUFFER 25 // 2.5 seconds - // CAMERA CHUNK // // A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. // Allows the Eye to stream these chunks and know what it can and cannot see. -/datum/camerachunk - var/list/obscuredTurfs = list() - var/list/visibleTurfs = list() - var/list/obscured = list() +/datum/chunk/camera var/list/cameras = list() - var/list/turfs = list() - var/list/seenby = list() - var/visible = 0 - var/changed = 0 - var/updating = 0 - var/x = 0 - var/y = 0 - var/z = 0 - -// Add an eye to the chunk, then update if changed. - -/datum/camerachunk/proc/add(mob/eye/eye) - if(!eye.owner) - return - eye.visibleChunks += src - if(eye.owner.client) - eye.owner.client.images += obscured - visible++ - seenby += eye - if(changed && !updating) - update() - -// Remove an eye from the chunk, then update if changed. - -/datum/camerachunk/proc/remove(mob/eye/eye) - if(!eye.owner) - return - eye.visibleChunks -= src - if(eye.owner.client) - eye.owner.client.images -= obscured - seenby -= eye - if(visible > 0) - visible-- - -// Called when a chunk has changed. I.E: A wall was deleted. - -/datum/camerachunk/proc/visibilityChanged(turf/loc) - if(!visibleTurfs[loc]) - return - hasChanged() - -// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will -// instead be flagged to update the next time an AI Eye moves near it. - -/datum/camerachunk/proc/hasChanged(var/update_now = 0) - if(visible || update_now) - if(!updating) - updating = 1 - spawn(UPDATE_BUFFER) // Batch large changes, such as many doors opening or closing at once - update() - updating = 0 - else - changed = 1 - -// The actual updating. It gathers the visible turfs from cameras and puts them into the appropriate lists. - -/datum/camerachunk/proc/update() - - set background = 1 - - var/list/newVisibleTurfs = list() +/datum/chunk/camera/acquireVisibleTurfs(var/list/visible) for(var/camera in cameras) var/obj/machinery/camera/c = camera @@ -87,77 +22,12 @@ cameras -= c for(var/turf/t in c.can_see()) - newVisibleTurfs[t] = t - - // Removes turf that isn't in turfs. - newVisibleTurfs &= turfs - - var/list/visAdded = newVisibleTurfs - visibleTurfs - var/list/visRemoved = visibleTurfs - newVisibleTurfs - - visibleTurfs = newVisibleTurfs - obscuredTurfs = turfs - newVisibleTurfs - - for(var/turf in visAdded) - var/turf/t = turf - if(t.obscured) - obscured -= t.obscured - for(var/eye in seenby) - var/mob/eye/m = eye - if(!m || !m.owner) - continue - if(m.owner.client) - m.owner.client.images -= t.obscured - - for(var/turf in visRemoved) - var/turf/t = turf - if(obscuredTurfs[t]) - if(!t.obscured) - t.obscured = image('icons/effects/cameravis.dmi', t, "black", 15) - - obscured += t.obscured - for(var/eye in seenby) - var/mob/eye/m = eye - if(!m || !m.owner) - seenby -= m - continue - if(m.owner.client) - m.owner.client.images += t.obscured + visible[t] = t // Create a new camera chunk, since the chunks are made as they are needed. -/datum/camerachunk/New(loc, x, y, z) - - // 0xf = 15 - x &= ~0xf - y &= ~0xf - - src.x = x - src.y = y - src.z = z - +/datum/chunk/camera/New(loc, x, y, z) for(var/obj/machinery/camera/c in range(16, locate(x + 8, y + 8, z))) if(c.can_use()) cameras += c - - for(var/turf/t in range(10, locate(x + 8, y + 8, z))) - if(t.x >= x && t.y >= y && t.x < x + 16 && t.y < y + 16) - turfs[t] = t - - // At this point we only have functional cameras - for(var/obj/machinery/camera/c in cameras) - for(var/turf/t in c.can_see()) - visibleTurfs[t] = t - - // Removes turf that isn't in turfs. - visibleTurfs &= turfs - - obscuredTurfs = turfs - visibleTurfs - - for(var/turf in obscuredTurfs) - var/turf/t = turf - if(!t.obscured) - t.obscured = image('icons/effects/cameravis.dmi', t, "black", 15) - obscured += t.obscured - -#undef UPDATE_BUFFER \ No newline at end of file + ..() diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index e22ab39190..a30883812e 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -99,7 +99,7 @@ if(client && client.eye) client.eye = src - for(var/datum/camerachunk/c in eyeobj.visibleChunks) + for(var/datum/chunk/c in eyeobj.visibleChunks) c.remove(eyeobj) src.eyeobj.setLoc(src) From 1c3c9ca48d825d61a5dcac36acc3b159b4e5f7a2 Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Mon, 23 Mar 2015 16:44:13 +0100 Subject: [PATCH 3/5] Refactors the AI eye. Generalizes the visual net containing and updating chunks. --- baystation12.dme | 2 + code/game/machinery/camera/tracking.dm | 2 +- code/modules/mob/freelook/eye.dm | 2 +- code/modules/mob/freelook/update_triggers.dm | 53 +++++++ code/modules/mob/freelook/visualnet.dm | 133 ++++++++++++++++ .../living/silicon/ai/freelook/cameranet.dm | 144 ++---------------- .../mob/living/silicon/ai/freelook/eye.dm | 4 + .../silicon/ai/freelook/update_triggers.dm | 60 -------- 8 files changed, 210 insertions(+), 190 deletions(-) create mode 100644 code/modules/mob/freelook/update_triggers.dm create mode 100644 code/modules/mob/freelook/visualnet.dm diff --git a/baystation12.dme b/baystation12.dme index 30cd41c9e9..fbf18f8a94 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1054,6 +1054,8 @@ #include "code\modules\mob\dead\observer\say.dm" #include "code\modules\mob\freelook\chunk.dm" #include "code\modules\mob\freelook\eye.dm" +#include "code\modules\mob\freelook\update_triggers.dm" +#include "code\modules\mob\freelook\visualnet.dm" #include "code\modules\mob\language\generic.dm" #include "code\modules\mob\language\language.dm" #include "code\modules\mob\language\outsider.dm" diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm index 1b3619b5a4..c2a64dbad3 100644 --- a/code/game/machinery/camera/tracking.dm +++ b/code/game/machinery/camera/tracking.dm @@ -213,7 +213,7 @@ mob/living/proc/near_camera() if (!isturf(loc)) return 0 - else if(!cameranet.checkCameraVis(src)) + else if(!cameranet.checkVis(src)) return 0 return 1 diff --git a/code/modules/mob/freelook/eye.dm b/code/modules/mob/freelook/eye.dm index 153609cfa5..337ff65910 100644 --- a/code/modules/mob/freelook/eye.dm +++ b/code/modules/mob/freelook/eye.dm @@ -15,7 +15,7 @@ see_in_dark = 7 invisibility = INVISIBILITY_EYE var/ghostimage = null - var/datum/cameranet/visualnet + var/datum/visualnet/visualnet /mob/eye/New() ghostimage = image(src.icon,src,src.icon_state) diff --git a/code/modules/mob/freelook/update_triggers.dm b/code/modules/mob/freelook/update_triggers.dm new file mode 100644 index 0000000000..609787efcd --- /dev/null +++ b/code/modules/mob/freelook/update_triggers.dm @@ -0,0 +1,53 @@ +//UPDATE TRIGGERS, when the chunk (and the surrounding chunks) should update. + +// TURFS + +/proc/updateVisibility(atom/A, var/opacity_check = 1) + if(ticker) + for(var/datum/visualnet/VN in visual_nets) + VN.updateVisibility(A, opacity_check) + +/turf + var/image/obscured + +/turf/drain_power() + return -1 + +/turf/simulated/Del() + updateVisibility(src) + ..() + +/turf/simulated/New() + ..() + updateVisibility(src) + + +// STRUCTURES + +/obj/structure/Del() + updateVisibility(src) + ..() + +/obj/structure/New() + ..() + updateVisibility(src) + +// EFFECTS + +/obj/effect/Del() + updateVisibility(src) + ..() + +/obj/effect/New() + ..() + updateVisibility(src) + +// DOORS + +// Simply updates the visibility of the area when it opens/closes/destroyed. +/obj/machinery/door/update_nearby_tiles(need_rebuild) + . = ..(need_rebuild) + // Glass door glass = 1 + // don't check then? + if(!glass) + updateVisibility(src, 0) \ No newline at end of file diff --git a/code/modules/mob/freelook/visualnet.dm b/code/modules/mob/freelook/visualnet.dm new file mode 100644 index 0000000000..dedc23ab2d --- /dev/null +++ b/code/modules/mob/freelook/visualnet.dm @@ -0,0 +1,133 @@ +// VISUAL NET +// +// The datum containing all the chunks. + +var/global/list/visual_nets = new() + +/datum/visualnet + // The chunks of the map, mapping the areas that an object can see. + var/list/chunks = list() + var/ready = 0 + var/chunk_type = /datum/chunk + +/datum/visualnet/New() + ..() + visual_nets += src + +/datum/visualnet/Del() + visual_nets -= src + ..() + +// Checks if a chunk has been Generated in x, y, z. +/datum/visualnet/proc/chunkGenerated(x, y, z) + x &= ~0xf + y &= ~0xf + var/key = "[x],[y],[z]" + return (chunks[key]) + +// Returns the chunk in the x, y, z. +// If there is no chunk, it creates a new chunk and returns that. +/datum/visualnet/proc/getChunk(x, y, z) + x &= ~0xf + y &= ~0xf + var/key = "[x],[y],[z]" + if(!chunks[key]) + chunks[key] = new chunk_type(null, x, y, z) + + return chunks[key] + +// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set. + +/datum/visualnet/proc/visibility(mob/eye/eye) + // 0xf = 15 + var/x1 = max(0, eye.x - 16) & ~0xf + var/y1 = max(0, eye.y - 16) & ~0xf + var/x2 = min(world.maxx, eye.x + 16) & ~0xf + var/y2 = min(world.maxy, eye.y + 16) & ~0xf + + var/list/visibleChunks = list() + + for(var/x = x1; x <= x2; x += 16) + for(var/y = y1; y <= y2; y += 16) + visibleChunks += getChunk(x, y, eye.z) + + var/list/remove = eye.visibleChunks - visibleChunks + var/list/add = visibleChunks - eye.visibleChunks + + for(var/chunk in remove) + var/datum/chunk/c = chunk + c.remove(eye) + + for(var/chunk in add) + var/datum/chunk/c = chunk + c.add(eye) + +// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open. + +/datum/visualnet/proc/updateVisibility(atom/A, var/opacity_check = 1) + + if(!ticker || (opacity_check && !A.opacity)) + return + majorChunkChange(A, 2) + +/datum/visualnet/proc/updateChunk(x, y, z) + // 0xf = 15 + if(!chunkGenerated(x, y, z)) + return + var/datum/chunk/chunk = getChunk(x, y, z) + chunk.hasChanged() + +// Never access this proc directly!!!! +// This will update the chunk and all the surrounding chunks. +// It will also add the atom to the cameras list if you set the choice to 1. +// Setting the choice to 0 will remove the camera from the chunks. +// If you want to update the chunks around an object, without adding/removing a camera, use choice 2. + +/datum/visualnet/proc/majorChunkChange(atom/c, var/choice) + // 0xf = 15 + if(!c) + return + + var/turf/T = get_turf(c) + if(T) + var/x1 = max(0, T.x - 8) & ~0xf + var/y1 = max(0, T.y - 8) & ~0xf + var/x2 = min(world.maxx, T.x + 8) & ~0xf + var/y2 = min(world.maxy, T.y + 8) & ~0xf + + //world << "X1: [x1] - Y1: [y1] - X2: [x2] - Y2: [y2]" + + for(var/x = x1; x <= x2; x += 16) + for(var/y = y1; y <= y2; y += 16) + if(chunkGenerated(x, y, T.z)) + var/datum/chunk/chunk = getChunk(x, y, T.z) + onMajorChunkChange(c, choice, chunk) + chunk.hasChanged() + +/datum/visualnet/proc/onMajorChunkChange(atom/c, var/choice, var/datum/chunk/chunk) + +// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0. + +/datum/visualnet/proc/checkVis(mob/living/target as mob) + // 0xf = 15 + var/turf/position = get_turf(target) + return checkTurfVis(position) + +/datum/visualnet/proc/checkTurfVis(var/turf/position) + var/datum/chunk/chunk = getChunk(position.x, position.y, position.z) + if(chunk) + if(chunk.changed) + chunk.hasChanged(1) // Update now, no matter if it's visible or not. + if(chunk.visibleTurfs[position]) + return 1 + return 0 + +// Debug verb for VVing the chunk that the turf is in. +/* +/turf/verb/view_chunk() + set src in world + + if(cameranet.chunkGenerated(x, y, z)) + var/datum/chunk/chunk = cameranet.getCameraChunk(x, y, z) + usr.client.debug_variables(chunk) +*/ diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index ffebebe851..d2d042c778 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -2,157 +2,45 @@ // // The datum containing all the chunks. -var/datum/cameranet/cameranet = new() +var/datum/visualnet/camera/cameranet = new() -/datum/cameranet +/datum/visualnet/camera // The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Del(). var/list/cameras = list() var/cameras_unsorted = 1 - // The chunks of the map, mapping the areas that the cameras can see. - var/list/chunks = list() - var/ready = 0 + chunk_type = /datum/chunk/camera -/datum/cameranet/proc/process_sort() +/datum/visualnet/camera/proc/process_sort() if(cameras_unsorted) cameras = dd_sortedObjectList(cameras) cameras_unsorted = 0 -// Checks if a chunk has been Generated in x, y, z. -/datum/cameranet/proc/chunkGenerated(x, y, z) - x &= ~0xf - y &= ~0xf - var/key = "[x],[y],[z]" - return (chunks[key]) - -// Returns the chunk in the x, y, z. -// If there is no chunk, it creates a new chunk and returns that. -/datum/cameranet/proc/getCameraChunk(x, y, z) - x &= ~0xf - y &= ~0xf - var/key = "[x],[y],[z]" - if(!chunks[key]) - chunks[key] = new /datum/chunk/camera(null, x, y, z) - - return chunks[key] - -// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set. - -/datum/cameranet/proc/visibility(mob/eye/eye) - // 0xf = 15 - var/x1 = max(0, eye.x - 16) & ~0xf - var/y1 = max(0, eye.y - 16) & ~0xf - var/x2 = min(world.maxx, eye.x + 16) & ~0xf - var/y2 = min(world.maxy, eye.y + 16) & ~0xf - - var/list/visibleChunks = list() - - for(var/x = x1; x <= x2; x += 16) - for(var/y = y1; y <= y2; y += 16) - visibleChunks += getCameraChunk(x, y, eye.z) - - var/list/remove = eye.visibleChunks - visibleChunks - var/list/add = visibleChunks - eye.visibleChunks - - for(var/chunk in remove) - var/datum/chunk/c = chunk - c.remove(eye) - - for(var/chunk in add) - var/datum/chunk/c = chunk - c.add(eye) - -// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open. - -/datum/cameranet/proc/updateVisibility(atom/A, var/opacity_check = 1) - - if(!ticker || (opacity_check && !A.opacity)) - return - majorChunkChange(A, 2) - -/datum/cameranet/proc/updateChunk(x, y, z) - // 0xf = 15 - if(!chunkGenerated(x, y, z)) - return - var/datum/chunk/chunk = getCameraChunk(x, y, z) - chunk.hasChanged() - // Removes a camera from a chunk. -/datum/cameranet/proc/removeCamera(obj/machinery/camera/c) +/datum/visualnet/camera/proc/removeCamera(obj/machinery/camera/c) if(c.can_use()) majorChunkChange(c, 0) // Add a camera to a chunk. -/datum/cameranet/proc/addCamera(obj/machinery/camera/c) +/datum/visualnet/camera/proc/addCamera(obj/machinery/camera/c) if(c.can_use()) majorChunkChange(c, 1) // Used for Cyborg cameras. Since portable cameras can be in ANY chunk. -/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c) +/datum/visualnet/camera/proc/updatePortableCamera(obj/machinery/camera/c) if(c.can_use()) majorChunkChange(c, 1) //else // majorChunkChange(c, 0) -// Never access this proc directly!!!! -// This will update the chunk and all the surrounding chunks. -// It will also add the atom to the cameras list if you set the choice to 1. -// Setting the choice to 0 will remove the camera from the chunks. -// If you want to update the chunks around an object, without adding/removing a camera, use choice 2. - -/datum/cameranet/proc/majorChunkChange(atom/c, var/choice) - // 0xf = 15 - if(!c) - return - - var/turf/T = get_turf(c) - if(T) - var/x1 = max(0, T.x - 8) & ~0xf - var/y1 = max(0, T.y - 8) & ~0xf - var/x2 = min(world.maxx, T.x + 8) & ~0xf - var/y2 = min(world.maxy, T.y + 8) & ~0xf - - //world << "X1: [x1] - Y1: [y1] - X2: [x2] - Y2: [y2]" - - for(var/x = x1; x <= x2; x += 16) - for(var/y = y1; y <= y2; y += 16) - if(chunkGenerated(x, y, T.z)) - var/datum/chunk/camera/chunk = getCameraChunk(x, y, T.z) - // Only add actual cameras to the list of cameras - if(istype(c, /obj/machinery/camera)) - if(choice == 0) - // Remove the camera. - chunk.cameras -= c - else if(choice == 1) - // You can't have the same camera in the list twice. - chunk.cameras |= c - chunk.hasChanged() - -// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0. - -/datum/cameranet/proc/checkCameraVis(mob/living/target as mob) - - // 0xf = 15 - var/turf/position = get_turf(target) - return checkTurfVis(position) - -/datum/cameranet/proc/checkTurfVis(var/turf/position) - var/datum/chunk/chunk = getCameraChunk(position.x, position.y, position.z) - if(chunk) - if(chunk.changed) - chunk.hasChanged(1) // Update now, no matter if it's visible or not. - if(chunk.visibleTurfs[position]) - return 1 - return 0 - -// Debug verb for VVing the chunk that the turf is in. -/* -/turf/verb/view_chunk() - set src in world - - if(cameranet.chunkGenerated(x, y, z)) - var/datum/chunk/chunk = cameranet.getCameraChunk(x, y, z) - usr.client.debug_variables(chunk) -*/ \ No newline at end of file +/datum/visualnet/camera/onMajorChunkChange(atom/c, var/choice, var/datum/chunk/camera/chunk) +// Only add actual cameras to the list of cameras + if(istype(c, /obj/machinery/camera)) + if(choice == 0) + // Remove the camera. + chunk.cameras -= c + else if(choice == 1) + // You can't have the same camera in the list twice. + chunk.cameras |= c diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index a30883812e..ac21161de3 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -8,6 +8,10 @@ icon_state = "AI-eye" var/mob/living/silicon/ai/ai = null +/mob/eye/aiEye/New() + ..() + visualnet = cameranet + /mob/eye/aiEye/setLoc(var/T, var/cancel_tracking = 1) if(..()) if(cancel_tracking) diff --git a/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm b/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm index 5bcec964a7..828281567e 100644 --- a/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm +++ b/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm @@ -1,65 +1,5 @@ #define BORG_CAMERA_BUFFER 30 -//UPDATE TRIGGERS, when the chunk (and the surrounding chunks) should update. - -// TURFS - -/turf - var/image/obscured - -/turf/drain_power() - return -1 - -/turf/proc/visibilityChanged() - if(ticker) - cameranet.updateVisibility(src) - -/turf/simulated/Del() - visibilityChanged() - ..() - -/turf/simulated/New() - ..() - visibilityChanged() - - - -// STRUCTURES - -/obj/structure/Del() - if(ticker) - cameranet.updateVisibility(src) - ..() - -/obj/structure/New() - ..() - if(ticker) - cameranet.updateVisibility(src) - -// EFFECTS - -/obj/effect/Del() - if(ticker) - cameranet.updateVisibility(src) - ..() - -/obj/effect/New() - ..() - if(ticker) - cameranet.updateVisibility(src) - - -// DOORS - -// Simply updates the visibility of the area when it opens/closes/destroyed. -/obj/machinery/door/update_nearby_tiles(need_rebuild) - . = ..(need_rebuild) - // Glass door glass = 1 - // don't check then? - if(!glass && cameranet) - cameranet.updateVisibility(src, 0) - - // ROBOT MOVEMENT // Update the portable camera everytime the Robot moves. From bbe5d67e447982f78547564c70f3d0bea5f2a209 Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Tue, 24 Mar 2015 09:40:24 +0100 Subject: [PATCH 4/5] Refactors the AI eye. Now possible to customize chunk obfuscation image per chunk type. Obfuscation images are stored per type (thus the small datum structure) to reduce memory footprint in the case of multiple chunks using the same image. --- code/game/machinery/machinery.dm | 2 +- code/modules/mob/freelook/chunk.dm | 25 +++++++++++-------- code/modules/mob/freelook/eye.dm | 3 +++ code/modules/mob/freelook/update_triggers.dm | 2 +- .../mob/living/silicon/ai/freelook/eye.dm | 3 --- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 5a4e6d7255..481098c59d 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -196,7 +196,7 @@ Class Procs: user.set_machine(src) /obj/machinery/CouldNotUseTopic(var/mob/user) - usr.unset_machine() + user.unset_machine() //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/code/modules/mob/freelook/chunk.dm b/code/modules/mob/freelook/chunk.dm index ecc9d0b4d3..8c6c7d043a 100644 --- a/code/modules/mob/freelook/chunk.dm +++ b/code/modules/mob/freelook/chunk.dm @@ -5,6 +5,10 @@ // A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. // Allows the Eye to stream these chunks and know what it can and cannot see. +/datum/obfuscation + var/icon = 'icons/effects/cameravis.dmi' + var/icon_state = "black" + /datum/chunk var/list/obscuredTurfs = list() var/list/visibleTurfs = list() @@ -17,6 +21,7 @@ var/x = 0 var/y = 0 var/z = 0 + var/datum/obfuscation/obfuscation = new() // Add an eye to the chunk, then update if changed. @@ -83,29 +88,29 @@ for(var/turf in visAdded) var/turf/t = turf - if(t.obscured) - obscured -= t.obscured + if(t.obfuscations[obfuscation.type]) + obscured -= t.obfuscations[obfuscation.type] for(var/eye in seenby) var/mob/eye/m = eye if(!m || !m.owner) continue if(m.owner.client) - m.owner.client.images -= t.obscured + m.owner.client.images -= t.obfuscations[obfuscation.type] for(var/turf in visRemoved) var/turf/t = turf if(obscuredTurfs[t]) - if(!t.obscured) - t.obscured = image('icons/effects/cameravis.dmi', t, "black", 15) + if(!t.obfuscations[obfuscation.type]) + t.obfuscations[obfuscation.type] = image(obfuscation.icon, t, obfuscation.icon_state, 15) - obscured += t.obscured + obscured += t.obfuscations[obfuscation.type] for(var/eye in seenby) var/mob/eye/m = eye if(!m || !m.owner) seenby -= m continue if(m.owner.client) - m.owner.client.images += t.obscured + m.owner.client.images += t.obfuscations[obfuscation.type] /datum/chunk/proc/acquireVisibleTurfs(var/list/visible) @@ -134,8 +139,8 @@ for(var/turf in obscuredTurfs) var/turf/t = turf - if(!t.obscured) - t.obscured = image('icons/effects/cameravis.dmi', t, "black", 15) - obscured += t.obscured + if(!t.obfuscations[obfuscation.type]) + t.obfuscations[obfuscation.type] = image(obfuscation.icon, t, obfuscation.icon_state, 15) + obscured += t.obfuscations[obfuscation.type] #undef UPDATE_BUFFER diff --git a/code/modules/mob/freelook/eye.dm b/code/modules/mob/freelook/eye.dm index 337ff65910..e83f209028 100644 --- a/code/modules/mob/freelook/eye.dm +++ b/code/modules/mob/freelook/eye.dm @@ -56,6 +56,9 @@ mob/eye/Del() T = get_turf(T) loc = T + if(owner.client) + owner.client.eye = src + visualnet.visibility(src) return 1 return 0 diff --git a/code/modules/mob/freelook/update_triggers.dm b/code/modules/mob/freelook/update_triggers.dm index 609787efcd..9bba162c40 100644 --- a/code/modules/mob/freelook/update_triggers.dm +++ b/code/modules/mob/freelook/update_triggers.dm @@ -8,7 +8,7 @@ VN.updateVisibility(A, opacity_check) /turf - var/image/obscured + var/list/image/obfuscations = new() /turf/drain_power() return -1 diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index ac21161de3..b7dce613f8 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -17,8 +17,6 @@ if(cancel_tracking) ai.ai_cancel_tracking() - if(ai.client) - ai.client.eye = src //Holopad if(ai.holo) ai.holo.move_hologram(ai) @@ -41,7 +39,6 @@ eyeobj.ai = src eyeobj.owner = src eyeobj.name = "[src.name] (AI Eye)" // Give it a name - eyeobj.visualnet = cameranet spawn(5) eyeobj.loc = src.loc From 9b7834ff7ad37dce7da5a8f1882c0654219ff30b Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Wed, 25 Mar 2015 17:08:56 +0100 Subject: [PATCH 5/5] Refactors the AI eye. Relocates eye movement to the eye code itself. Adds a mask that allows any mob able of wearing it direct access to the camera network, as a proof of concept and adminbus. --- code/modules/clothing/masks/miscellaneous.dm | 33 ++++++++++++- code/modules/mob/freelook/eye.dm | 41 ++++++++++++++-- code/modules/mob/living/carbon/human/life.dm | 6 +++ .../mob/living/silicon/ai/freelook/eye.dm | 45 ++++-------------- code/modules/mob/mob_movement.dm | 10 ++-- icons/mob/eye.dmi | Bin 855 -> 845 bytes 6 files changed, 89 insertions(+), 46 deletions(-) diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 412f69bda6..3d3db7c736 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -100,4 +100,35 @@ body_parts_covered = HEAD|FACE|EYES w_class = 2 var/voicechange = 0 - siemens_coefficient = 0.9 \ No newline at end of file + siemens_coefficient = 0.9 + +/obj/item/clothing/mask/ai + name = "camera MIU" + desc = "Allows for direct mental connection to accessible camera networks." + icon_state = "s-ninja" + item_state = "s-ninja" + flags_inv = HIDEFACE + body_parts_covered = 0 + var/mob/eye/aiEye/eye + +/obj/item/clothing/mask/ai/New() + eye = new(src) + +/obj/item/clothing/mask/ai/equipped(var/mob/user, var/slot) + ..(user, slot) + if(slot == slot_wear_mask) + eye.owner = user + user.eyeobj = eye + + for(var/datum/chunk/c in eye.visibleChunks) + c.remove(eye) + eye.setLoc(user) + +/obj/item/clothing/mask/ai/dropped(var/mob/user) + ..() + if(eye.owner == user) + for(var/datum/chunk/c in eye.visibleChunks) + c.remove(eye) + + eye.owner.eyeobj = null + eye.owner = null diff --git a/code/modules/mob/freelook/eye.dm b/code/modules/mob/freelook/eye.dm index e83f209028..70ae8cb700 100644 --- a/code/modules/mob/freelook/eye.dm +++ b/code/modules/mob/freelook/eye.dm @@ -8,12 +8,18 @@ icon = 'icons/mob/eye.dmi' icon_state = "default-eye" alpha = 127 - var/list/visibleChunks = list() - var/mob/living/owner = null - density = 0 - status_flags = GODMODE // You can't damage it. + + var/sprint = 10 + var/cooldown = 0 + var/acceleration = 1 + see_in_dark = 7 + status_flags = GODMODE invisibility = INVISIBILITY_EYE + + var/mob/owner = null + var/list/visibleChunks = list() + var/ghostimage = null var/datum/visualnet/visualnet @@ -63,8 +69,35 @@ mob/eye/Del() return 1 return 0 +/mob/eye/EyeMove(n, direct) + var/initial = initial(sprint) + var/max_sprint = 50 + + if(cooldown && cooldown < world.timeofday) + sprint = initial + + for(var/i = 0; i < max(sprint, initial); i += 20) + var/turf/step = get_turf(get_step(src, direct)) + if(step) + setLoc(step) + + cooldown = world.timeofday + 5 + if(acceleration) + sprint = min(sprint + 0.5, max_sprint) + else + sprint = initial + /mob/eye/proc/getLoc() if(owner) if(!isturf(owner.loc) || !owner.client) return return loc + +/mob + var/mob/eye/eyeobj + +/mob/proc/EyeMove(n, direct) + if(!eyeobj) + return + + return eyeobj.EyeMove(n, direct) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 29a1d067cb..e8cfda4c93 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1342,6 +1342,12 @@ if(machine) if(!machine.check_eye(src)) reset_view(null) + else if(eyeobj) + if(eyeobj.owner != src) + + reset_view(null) + else + src.sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS else var/isRemoteObserve = 0 if((mRemote in mutations) && remoteview_target) diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index b7dce613f8..8bc60b819d 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -6,7 +6,6 @@ /mob/eye/aiEye name = "Inactive AI Eye" icon_state = "AI-eye" - var/mob/living/silicon/ai/ai = null /mob/eye/aiEye/New() ..() @@ -14,6 +13,7 @@ /mob/eye/aiEye/setLoc(var/T, var/cancel_tracking = 1) if(..()) + var/mob/living/silicon/ai/ai = owner if(cancel_tracking) ai.ai_cancel_tracking() @@ -27,23 +27,19 @@ // The AI's "eye". Described on the top of the page. /mob/living/silicon/ai - var/mob/eye/aiEye/eyeobj = new() - var/sprint = 10 - var/cooldown = 0 - var/acceleration = 1 + eyeobj = new /mob/eye/aiEye() var/obj/machinery/hologram/holopad/holo = null // Intiliaze the eye by assigning it's "ai" variable to us. Then set it's loc to us. /mob/living/silicon/ai/New() ..() - eyeobj.ai = src eyeobj.owner = src eyeobj.name = "[src.name] (AI Eye)" // Give it a name spawn(5) eyeobj.loc = src.loc /mob/living/silicon/ai/Del() - eyeobj.ai = null + eyeobj.owner = null del(eyeobj) // No AI, no Eye ..() @@ -53,32 +49,6 @@ if(AI.eyeobj && AI.client.eye == AI.eyeobj) AI.eyeobj.setLoc(src) -// This will move the AIEye. It will also cause lights near the eye to light up, if toggled. -// This is handled in the proc below this one. - -/client/proc/AIMove(n, direct, var/mob/living/silicon/ai/user) - - var/initial = initial(user.sprint) - var/max_sprint = 50 - - if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds - user.sprint = initial - - for(var/i = 0; i < max(user.sprint, initial); i += 20) - var/turf/step = get_turf(get_step(user.eyeobj, direct)) - if(step) - user.eyeobj.setLoc(step) - - user.cooldown = world.timeofday + 5 - if(user.acceleration) - user.sprint = min(user.sprint + 0.5, max_sprint) - else - user.sprint = initial - - //user.unset_machine() //Uncomment this if it causes problems. - //user.lightNearbyCamera() - - // Return to the Core. /mob/living/silicon/ai/proc/core() @@ -95,7 +65,7 @@ if(!src.eyeobj) src << "ERROR: Eyeobj not found. Creating new eye..." src.eyeobj = new(src.loc) - src.eyeobj.ai = src + src.eyeobj.owner = src src.SetName(src.name) if(client && client.eye) @@ -108,5 +78,8 @@ set category = "AI Commands" set name = "Toggle Camera Acceleration" - acceleration = !acceleration - usr << "Camera acceleration has been toggled [acceleration ? "on" : "off"]." + if(!eyeobj) + return + + eyeobj.acceleration = !eyeobj.acceleration + usr << "Camera acceleration has been toggled [eyeobj.acceleration ? "on" : "off"]." diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index efd2c52740..bd00faf953 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -188,9 +188,9 @@ if(mob.stat==2) return - // handle possible AI movement - if(isAI(mob)) - return AIMove(n,direct,mob) + // handle possible Eye movement + if(mob.eyeobj) + return mob.EyeMove(n,direct) if(mob.monkeyizing) return//This is sota the goto stop mobs from moving var @@ -457,12 +457,12 @@ var/area/A = turf.loc if(istype(A) && A.has_gravity == 0) var/can_walk = 0 - + if(ishuman(src)) // Only humans can wear magboots, so we give them a chance to. var/mob/living/carbon/human/H = src if(istype(H.shoes, /obj/item/clothing/shoes/magboots) && (H.shoes.flags & NOSLIP)) can_walk = 1 - + if(!can_walk) continue diff --git a/icons/mob/eye.dmi b/icons/mob/eye.dmi index 3bcd9971658395a0d58106409f420cfc03bddb77..64ca1b100e3a095c26e9deb751f6114c9a9d617b 100644 GIT binary patch delta 562 zcmV-20?qx`2F(VrCjoyENkl7|NnRH;3b-PVX19_ z^nE_qCU6QuvCBD25&!^z>qvWR?Wz}Oyqehh__ZN?$ERtQWlut0mU<|a=lQdcm!%PW z$0rBd{vvFT!$Q0L{8wQ|cndJTYkx0Rdi0oD6RmA3;FU)=z+ zJ@y?h-@)k?Eqw>4TfFog_8i}K7!Ju&2bGP7Y;6Fa@kvq?S+6LT+wOJ}7y z_;vsQ00000PpA)2AD})!eSrD^^#SSw)CT|n0002Md?U}t~3|NQ>m_FL!ox9@Oo`_1$FX}Z0Hd)sfH-=8kvJYK2oC3eN%Jip&( z&xiPVd%C^Eu6Y0XedihQT^~pt@y;_4L49C!NqpizzfXMt00000*64xniCjp358}K7 zU5=u>gK+~Q-2x*kf9!X__@2zN&HTGLjvMfE4Dk*e`FDR~%;|KJ)t9Thzx(u@9Ql7^ zN>lw-y3}%&_u<`vBmZwtfR>9qi;NEO6u9#L!!t;S`W$lQ|A$`z-9L+hy8&nZUDtNu zD_z-_I=uP2|3AO3?GBvnrLONjy?=1!-y`@X-p;?br$C={w0{fPEjY_h*g4n@aB9l3 zs^kzfz9%Yjv8Fu$@y@^9gNWDPYMQv`4**y82Ob?#9c8;2p#T5?07*qoM6N<$f@?<| A5C8xG delta 572 zcmV-C0>k~y2G<6#CjoyONkleYQq5VyS9jb)?7i{(tJo2&vK>aZe zaIwd!P%KEJP?fzhJT7Kb$w%tD4!lmm{Pv#n#e-rD5Q{av+)BlCOK(^o{ zUty=6Zh$oCo2G5$5cH`ha;0000< KMNUMnLSTZ);4SF@