解釋一下AvZ的IQ/NIQ
僅為群友而寫。
AvZ里,有一個神奇的東西叫“InsertOperation”。
對萌新而言,是完全用不上這個東西的。以經(jīng)典12腳本為例:

無非就是一直SetTime,然后pao_operator.pao,再配合C++自帶的循環(huán)語法就夠了。
不用InsertOperation,你可以滿足99%的鍵控需求,至少把炮陣一百選或者我空間里絕大多數(shù)視頻打一遍毫無問題。
那么這個InsertOperation又是何方神圣呢?
舉個??,假設(shè)你知道avz_more(一個AvZ擴展)里提供了讀取當(dāng)前陽光的函數(shù):

現(xiàn)在你想在wave11刷新時,調(diào)用這個函數(shù),輸出當(dāng)前陽光。
萌新可能會這么寫:

然而實際上,這段代碼會輸出本次選卡開始前的陽光數(shù),而非實時數(shù)值。
這是不是很令人迷惑?畢竟,輸出語句的確是wave11刷新時執(zhí)行的。它怎么會輸出一個舊的值呢?
要理解這個事情,你只需要記住一句話:
AvZ腳本里所有代碼在進入生存無盡的一瞬間就運行完了。
AvZ腳本里所有代碼在進入生存無盡的一瞬間就運行完了。
AvZ腳本里所有代碼在進入生存無盡的一瞬間就運行完了。
這件事非常重要!不理解的話請換個姿勢多讀幾遍。
上面這個例子里:

GetSun()是代碼,沒錯吧?
前面那句話說,AvZ的所有代碼都是在進入生存無盡的一瞬間運行的。
因此,GetSun()是進入生存無盡的一瞬間運行的。
由于你點進游戲生存無盡的一瞬間是本次選卡開始前,所以GetSun()的值當(dāng)然就是選卡開始前的陽光數(shù)(比如8000),而非實時數(shù)值咯。
如果你在腳本里加一句 SetErrorMode(CONSOLE),當(dāng)AvZ注入完畢后,點開游戲時會跳一個黑色調(diào)試窗口。實際上跳出這個黑色窗口的瞬間,所有代碼就都運行完畢了。
等等……
你逗我呢?
如果所有代碼都是一開始就運行完了,那AvZ是怎么發(fā)炮的?

如圖,pao_operator.pao(2, 9)也是代碼啊。那它豈不也是剛點進游戲就運行了?可是實際上它明明是在我們指定的時間點才會發(fā)炮。
原因很簡單……?
運行代碼,不等于現(xiàn)在就執(zhí)行操作,也可能是過一會執(zhí)行操作。
打個比方,“運行”就像是定罪,但定罪后不一定立即執(zhí)行,也可以是緩刑?。ǎ?/p>

這一段代碼,的確是一進入游戲就運行的。但由于SetTime的作用,它的實際含義為:
我決定,在未來的一個時間點(wave1刷新前95cs),發(fā)射一門炮
請記?。哼@個決定,是進入游戲的瞬間就完成了的。它只是過一段時間才執(zhí)行,看似有“延遲”,像是“wave1快要刷新了才發(fā)炮”,但其實發(fā)炮這件事早就定了,你把玉米炮全挖了它一樣會執(zhí)行(順便報個錯),不存在任何取消的方式。
正題——InsertOperation
理解了以上內(nèi)容后,就很容易理解InsertOperation的意義了。
它的作用是:將我內(nèi)部的所有代碼,推遲到最近一次SetTime設(shè)定的時間點執(zhí)行。

這個最近一次SetTime,是根據(jù)代碼運行的順序來的,也就是進入游戲的那一瞬間發(fā)生的事。
AvZ里許多函數(shù)都封裝了InsertOperation。比如,pao_operator.pao()實際內(nèi)容是這樣的:

有沒有看到那個熟悉的InsertOperation?
它的作用,就是讓紅框里所有的代碼,都等到最近一次SetTime設(shè)定的時間點再執(zhí)行,而非立刻執(zhí)行。
在你眼里,代碼是這樣的:

在電腦眼里,它其實是這樣的:

以上兩段完全等價。pao()函數(shù)的唯一作用,就是把這一大段東西(InsertOperation+一堆代碼)縮略一下。
其實pao也好,Card也好,各種常用鍵控函數(shù)底層都會用到InsertOperation。這也是理所當(dāng)然的——要不然所有操作都一上來就執(zhí)行了,還玩毛啊。
現(xiàn)在你知道了,為什么你可以一個SetTime后跟好多操作。

以上三個語句,都會在 (-95, 1) 這個時間點執(zhí)行。原因就在于,它們底層都用到了InsertOperation,而InsertOperation只看最近一次SetTime。因為這里只SetTime過一次,大家當(dāng)然就都共用 (-95, 1) 這個時間點咯。
你還知道了,為什么不SetTime直接用pao會翻車。

還是經(jīng)典12腳本,本來白框里是SetTime(-150, 20),也就是炮消珊瑚時機。
如果強行把這個SetTime去掉,會發(fā)生很奇怪的事…… 為什么?
原因在于,pao_operator.pao底層是InsertOperation,而InsertOperation永遠(yuǎn)會去找最近一次SetTime。在這里,就是上面那個循環(huán)里的最后一次SetTime:

這顯然不是我們想要的。但很可惜,SetTime就是認(rèn)這個死理。所以你的炸珊瑚炮會在 (300, 20) 這個謎の時間點發(fā)射,水路炮早就被啃光了,而你以為AvZ又出了bug怒錘鍵盤。
區(qū)分IQ和NIQ
所謂“IQ”,就是“In Queue”,指必須要配合SetTime使用的函數(shù)。操作在指定時間點進行。緩刑。
所謂“NIQ”,就是“Not In Queue”,指不需要配合SetTime使用的函數(shù)。操作立刻馬上執(zhí)行。
所有常規(guī)代碼都是NIQ。而IQ其實就是NIQ外面套了一層InsertOperation。
換句話說:IQ = InsertOperation + NIQ。
看個例子就懂了。AvZ里許多函數(shù)都分IQ和NIQ兩個版本(pao并沒有)。
我們看Card():

沒錯,你最喜歡的Card函數(shù),其實就是CardNotInQueue外面套了一層InsertOperation的皮…… 有沒有被騙了的感覺。Card這個函數(shù)并沒有任何實質(zhì)內(nèi)容。
ShowError當(dāng)然也是一回事啦:

總而言之,真正“干活”的代碼,都是NIQ。IQ函數(shù)只是方便你在SetTime后調(diào)用的工具人。
回到剛開始這個例子:

這一段代碼為什么不對,現(xiàn)在就很明顯了吧?
ShowError是AvZ官方函數(shù),內(nèi)部套了InsertOperation,所以它可以配合SetTime正確執(zhí)行。
但是GetSun()只是在讀取內(nèi)存啊親!它內(nèi)部才一行啊親!它當(dāng)然是立刻執(zhí)行的啊親!
解決方法自然就是把它丟進InsertOperation:

以上為正解。over。
你沒事吧?沒事別亂用IQ
眼尖的你可能發(fā)現(xiàn)了:“錯誤寫法”變?yōu)檎_寫法后,ShowError悄悄變成了ShowErrorNotInQueue:

解釋這件事并不難。
“錯誤寫法”里,我們試圖用【SetTime + 調(diào)用IQ函數(shù)】的一般套路,理應(yīng)用ShowError(雖然在GetSun上翻車了)。
正確寫法里,所有代碼都在InsertOperation內(nèi)部,而我們知道InsertOperation就是用來包裹NIQ的,所以理應(yīng)用ShowErrorNotInQueue。
可是有人就不服啊。他說,我就要IQ和NIQ反著用,會發(fā)生啥事?
CASE 1:我偏要在SetTime后用NIQ

這顯然是瞎折騰。NIQ函數(shù)就是一段普通的代碼,而普通代碼根本不鳥SetTime。它將在進入游戲的一瞬間立刻執(zhí)行,立刻輸出選卡前的陽光。
CASE 2:我偏要在InsertOperation里用IQ

相比之下,這個行為就非常重量級。
首先,AvZ是允許多重InsertOperation嵌套的。以上代碼,實際可以轉(zhuǎn)換為:

看上去,它就是犯了個蠢,賣了個萌,精靈球里又套了個精靈球,但好像沒啥實際影響?
錯!別忘了,InsertOperation永遠(yuǎn)會找上一個SetTime,這是死理。
對于第一個InsertOperation,它的上一個SetTime是(0, 11),沒毛病。
但對于第二個InsertOperation,由于它在第一個InsertOperation內(nèi)部,它會等到 (0, 11) 也就是wave11刷新這個時間點才執(zhí)行。
此時,AvZ腳本已運行完畢,因此它實際上會使用腳本里最后一個SetTime設(shè)定的時間點。
如果把無辜的炮消珊瑚加進來:

圖上的白色數(shù)字,是每行代碼的運行順序。其中,①、②、③、④是進入游戲的一瞬間運行的;⑤、⑥分別在各自InsertOperation設(shè)定的時間點運行。
不難看出,運行⑤時,最近一次SetTime其實是炮消珊瑚的時間點,也就是 (-150, 20)。
對電腦來說,它看到的是:

因而,這句輸出語句會一直等到 (-150, 20) 才執(zhí)行!你怎么等也等不到輸出,以為AvZ又出bug了然后怒砸鍵盤。
這個問題,應(yīng)該就是萌新亂用InsertOperation時最容易犯的錯誤了,也是很多人表示“無法理解InsertOperation”的根源。
它有三個解決方法:
1. 你沒事吧?沒事別亂用IQ
記住一句話:
IQ必須要配合SetTime用。
IQ必須要配合SetTime用。
IQ必須要配合SetTime用。
IQ函數(shù)的本質(zhì)是InsertOperation,而InsertOperation永遠(yuǎn)會去找上一個SetTime,這就是AvZ的死理。
因此,不用SetTime卻用InsertOperation的行為被稱作“裸奔”,踩香蕉皮滑到哪里是哪里,會造成一系列費解的bug。
除了InsertOperation,pao、Card等等函數(shù)也都可能“裸奔”,因為這些IQ函數(shù)在本質(zhì)上都使用的是InsertOperation。

在這個錯誤例子中,ShowError就是一個裸奔的IQ函數(shù),很輕易地就翻了車。
2. InsertOperation里先無腦SetNowTime()
SetNowTime()是一個鮮為人知卻非常好用的函數(shù)。
它的作用是:把當(dāng)前實際時間傳進SetTime。
再看上面那個例子:

加上SetNowTime后,腰也不酸了,腿也不痛了,代碼也正確執(zhí)行了。
我們知道,SetNowTime和ShowError一樣,都是在(0, 11)這個時間點才執(zhí)行。而SetNowTime會忠實地把當(dāng)前時間點傳進SetTime,供后續(xù)使用。
這樣一來,上面的代碼實際上就是先 SetTime(0, 11),然后再ShowError,完美解決“裸奔”問題。
總體而言,SetNowTime的確是個萬金油,因為它非常符合人類的自然邏輯。對于IQ函數(shù),它默認(rèn)讓它們在當(dāng)前時間點執(zhí)行,符合人類的預(yù)期;對于NIQ函數(shù),它沒有任何效果,不礙事。
當(dāng)然,如果某個函數(shù)有NIQ版本,那還是建議你直接用NIQ,而非SetNowTime + IQ…… 其實兩者沒啥大區(qū)別,就是后者看著略蠢。。。
3. 用InsertGuard
這個是AvZ官方推薦的方式,效果和上一種類似,但是略難理解。
寫出來是這樣的:

ig是變量名,取任何名字都行。
它的作用是:在當(dāng)前大括號括起的段落里,使所有InsertOperation失去原本效果,直接運行。那個false就代表失效,如果是true的話就是讓它重新生效。
過多的就不介紹了,如果你覺得難以理解的話,先試試看前兩種辦法吧。
一些理所當(dāng)然的補充...
理解了以上內(nèi)容后,以下幾點都是顯然易證自然成立的。僅作補充用意。
① 我要在XX時刻判斷OO僵尸狀態(tài),怎么寫?
“判斷場上僵尸”,其實就是讀內(nèi)存,一個for循環(huán)里來幾句if。這些都是普通代碼,也就是NIQ代碼,因此需要寫在InsertOperation里(你總不想一進游戲就判斷吧,啥僵尸都沒有呢)。
在22年6月之前的AvZ里,寫法是這樣的:

zombieTotal是僵尸總數(shù),zombieArray是僵尸數(shù)組,然后isDisappeared() 和 isDead() 檢查僵尸是否存在,最后檢查type是否為紅眼…… 這些你都可以在 pvzstruct.h 里搜到,可自行搜索。
重申:以上代碼需放進InsertOperation里。
別忘了,如果在這里要執(zhí)行pao啊Card之類的操作,記得SetNowTime或者InsertGuard,不理解的話請重讀前面一章。
22年6月版本起,你可以直接用“filter”功能:

AliveFilter自動遍歷所有還活著的生物。AliveFilter<Zombie>就是遍歷所有“活著”的僵尸。
同樣,以上代碼需放進InsertOperation里。
因為本文的重點是學(xué)會使用InsertOperation,所以這里就不更多說明怎么讀內(nèi)存了~ 如果有很多人好奇的話我可能會另寫一篇文章詳解。
② InsertTimeOperation是什么玩意?
直接上源碼:

template什么的看不懂可以無視,你只需要關(guān)注里面那個“InsertOperation”。
是的,“InsertTimeOperation”就是InsertOperation的套皮版。它額外接受兩個參數(shù)(time和wave),然后先 SetTime(time, wave),再正常執(zhí)行InsertOperation。
說到底,因為SetTime和InsertOperation必定要一起用(不然就裸奔了),所以AvZ干脆再提供了一個函數(shù),讓你可以把它們直接寫在一起。

這兩段代碼完全是一回事。
簡化了很多嗎……?好像也就那樣(
需要注意的是,InsertTimeOperation內(nèi)部自帶SetTime(可以再去看眼源碼,就在上面),這個SetTime當(dāng)然也是算在“最近一次SetTime”里的!好處是媽媽再也不擔(dān)心我忘了SetTime了,壞處是它是隱藏性SetTime,看代碼的時候容易忘了還有這回事,總之各有利弊吧~ 我覺得只用InsertOperation也挺好的。
請你不要問:如果SetTime和InsertOperation總要一起用,為啥不能只保留InsertTimeOperation?親,如果這樣的話,不就沒法SetTime一次InsertOperation多次了。。
下面這個寫法就不成立了:

沒人愿意把 -95, 1 寫三遍吧(

以上就是所有關(guān)于AvZ里IQ/NIQ的事啦~
其實這個事情本身沒有那么復(fù)雜,但可能是原先的教程有點難懂吧,有些人不太理解,那么希望這篇文章能幫到你。
一個小tip:AvZ目錄里inc文件夾是AvZ官方函數(shù)的頭文件,里面所有In Queue的函數(shù)都在注釋里標(biāo)明了,而其它的則是Not In Queue,你可以自行查看感受一下兩者的區(qū)別喔。