分类目录归档:魔兽改图

DOTA 互通 作弊 原理

Return Bug+GameCache的应用

一、排除泄露

在讲解完Return Bug的原理以及跟GameCache的简单应用后,我并没有进一步讲解更复杂的数据绑定,而是穿插讲了一篇中级教程《JASS动态注册事件》,因为在用Return Bug+GameCache写一些复杂的系统时,经常会用到动态注册事件。

但在讲解动态注册事件时,一个很重要的内容没有被提到,那就是排除在销毁触发器时由触发器条件和动作引起的泄露。不过在排泄时必须用到Return Bug+GameCache,因此我把排泄留到这章讲,当作Return Bug+GameCache的一个应用实例。

  好吧,让我们先回顾下动态注册事件,依旧用Tiveone的经典教程

 

jass: Copy code
//当触发注册的事件时调用的函数
function OnUnitAmort takes nothing returns nothing
//设置触发单位为无敌
call SetUnitInvulnerable( GetTriggerUnit(), true )
//破坏这个触发器,即下面的RegisterUnitAmortEvent所创建的触发器
//这样就不会导致该触发被多次调用,并且还能清理该触发所占用的内存
call DestroyTrigger(GetTriggeringTrigger())
endfunction

//注册模板
function RegisterUnitAmortEvent takes unit witchUnit returns nothing
//创建一个触发器
local trigger rTrg = CreateTrigger()
//为创建的触发器注册事件:当单位witchUnit的生命值小于或等于50的时触发事件
call TriggerRegisterUnitLifeEvent( rTrg , witchUnit, LESS_THAN_OR_EQUAL, 50 )
//注册触发器事件响应后所调用的函数
call TriggerAddAction( rTrg, function OnUnitAmort )
endfunction

这样的写法不是很好,它会造成内存泄露。我们来看下TriggerAddAction这个函数
native TriggerAddAction takes trigger whichTrigger, code actionFunc returns triggeraction

这里先生成了一个triggeraction,然后把它添加给了触发器;而当我们删除触发器的时候,这个triggeraction却并没有被删除。

那么,为了维护这世界的爱与正义,我们来排除上面动态注册事件的泄露吧~

首先,准备好下列Return Bug常用函数

jass: Copy code
function H2I takes handle h returns integer
return h
return 0
endfunction

function I2TG takes integer i returns trigger
return i
return null
endfunction

function I2TC takes integer i returns triggercondition
return i
return null
endfunction

function I2TA takes integer i returns triggeraction
return i
return null
endfunction

然后,上面的动态注册事件可以这样写

jass: Copy code
function RegisterUnitAmortAction takes nothing returns nothing
local trigger trg = GetTriggeringTrigger()
local triggeraction Act=I2TA(GetStoredInteger(udg_GC,I2S(H2I(trg)),"Triggeraction"))
call SetUnitInvulnerable(GetTriggerUnit(), true)
call TriggerRemoveAction(trg,Act)
call DestroyTrigger(trg)
call FlushStoredMission(udg_GC,I2S(H2I(trg)))
set trg = null
set Act=null
endfunction

function RegisterUnitAmortEvent takes unit witchUnit returns nothing
local trigger trg = CreateTrigger()
local triggeraction Act
call TriggerRegisterUnitStateEvent(trg, witchUnit, UNIT_STATE_LIFE, LESS_THAN_OR_EQUAL, 50)
set Act=TriggerAddAction(trg,function RegisterUnitAmortAction)
call StoreInteger(udg_GC,I2S(H2I(trg)),"Triggeraction ",H2I(Act))
set trg = null
set Act=null
endfunction

在这里,我们定义了一个触发器动作变量Act,用它来保存生成的触发器,然后把它删除;大家是不是觉得这种方法似曾相识呢——以前我们排除点泄露的时候,也是用个点变量来保存生存的点,之后再清除这个变量保存的点。在删除点时,我们使用 RemoveLocation( )函数;而删除触发器条件或动作,我们使用TriggerRemoveCondition()和TriggerAddAction()这两个函数。(至于TriggerClearActions()和TriggerClearConditions()这两个函数,老狼说是废函数,“相当于把垃圾扫床底下”,那我们就不去理会它们吧。)

触发器条件和动作的清除的确比较麻烦,不过大家可以采用老狼写的两个删除触发器自定义函数:

jass: Copy code
function DestroyTriggerAllById takes integer t returns nothing
call TriggerRemoveCondition(I2TG(t),I2TC(GetStoredInteger (udg_GC,I2S(t),"TriggerCondition")))
call DestroyTrigger(I2TG(t))
call FlushStoredMission(udg_GC,I2S(t))
endfunction

function DestroyTriggerAll takes trigger trg returns nothing
call TriggerRemoveCondition(trg,I2TC(GetStoredInteger(udg_GC,I2S(H2I(trg)),"TriggerCondition")))
call DestroyTrigger(trg)
call FlushStoredMission(udg_GC,I2S(H2I(trg)))
endfunction
//========================================================
function RegisterUnitAmortCond takes nothing returns nothing
call SetUnitInvulnerable(GetTriggerUnit(), true)
call DestroyTriggerAll(GetTriggeringTrigger())
endfunction

function RegisterUnitAmortEvent takes unit witchUnit returns nothing
local trigger trg = CreateTrigger()
call TriggerRegisterUnitStateEvent(trg, witchUnit, UNIT_STATE_LIFE, LESS_THAN_OR_EQUAL, 50)
call StoreInteger(udg_GC,I2S(H2I(trg)),"TriggerCondition",H2I (TriggerAddCondition(trg,Condition(function RegisterUnitAmortCond))))
set trg = null
endfunction

老狼把触发器动作里的内容写到了条件里,为什么这样写大家可以去搜索教程,这里就不赘述了。这里DestroyTriggerAll()和DestroyTriggerAllById()两个函数区别仅仅在于一个传递触发器类型参数,一个传递整数类型参数(这里的整数参数是通过H2I(trg)得到的) ,大家按自己写JASS的习惯,选择其中一个来使用就可以了。此外,为了详细讲解步骤,我定义了Act这个触发器动作变量,但实际写代码时,可以像老狼那样省略这个中间变量,直接把数据存入缓存。
大家不妨以老狼这段代码,作为动态注册事件的模板。

二、缓存的使用

在之前讲解缓存时,我曾说Return Bug改变了GameCache的命运,发掘了GameCache的潜力;那么GameCache究竟强大在哪里。此外,当被问到什么是GameCache,高手们往往会回答,“一个数据库”、“一个仓库”。GameCache是如何担当存储数据的重任呢,我们不妨看下GameCache与全局变量的对比。

1.单个变量

我们要储存一个单位,比如everguo,

数据
GameCache
全局变量

Everguo(单位)
StoreInteger(udg_GC,”Handsome”,”Everguo”,H2I(Everuo))
Set Handsome=Everguo

(Handsome为单位型全局变量)

2.数组

数据
GameCache
全局变量

1 Everguo

2 汤姆.汗克斯

3 莱昂纳多

StoreInteger(udg_GC,”Handsome”,”1”,H2I(Everuo))

StoreInteger(udg_GC,”Handsome”,”2”,H2I(汤姆.汗克斯))

StoreInteger(udg_GC,”Handsome”,”3”,H2I(莱昂纳多))

Set Handsome[1]=Everguo

Set Handsome[2]=汤姆.汗克斯

Set Handsome[3]=莱昂纳多

(Handsome为单位型全局变量数组)

3.多个数组

数据
GameCache
全局变量

1
2
StoreInteger(udg_GC,”Handsome”,”1”,H2I(Everuo))

StoreInteger(udg_GC,”Handsome”,”2”,H2I(汤姆.汗克斯))

StoreInteger(udg_GC,”Handsome”,”3”,H2I(莱昂纳多))

StoreInteger(udg_GC,”SeLang”,”1”,H2I(Red_Wolf))

StoreInteger(udg_GC,”SeLang”,”2”,H2I(幽灵狼))

StoreInteger(udg_GC,”SeLang”,”3”,H2I(田伯光))

Set Handsome[1]=Everguo

Set Handsome[2]=汤姆.汗克斯

Set Handsome[3]=莱昂纳多

Set SeLang[1]= Red_Wolf

Set SeLang[2]= 幽灵狼

Set SeLang[3]= 田伯光

(Handsome、SeLang均为单位型全局变量数组)

1 Everguo

2 汤姆.汗克斯

3 莱昂纳多

1 Red_Wolf

2 幽灵狼

3 田伯光

以上是GameCache和全局变量在储存数据上的对比,看上去GameCache储存数据比全局变量繁琐得多;不知大家注意到没有,GameCache跟全局变量相比有个最大的优点——能动态地生成数据库。

比如,在游戏开始后,我们想再储存如下人妖排行榜:

1 东方不败

2 李宇春

3 看帖不回的

用GameCache能轻易做到

jass: Copy code
StoreInteger(udg_GC,”RY”,”1”,H2I(东方不败))

StoreInteger(udg_GC,”RY”,”2”,H2I(李宇春))

StoreInteger(udg_GC,”RY”,”3”,H2I(看帖不回的))

但用全局变量却做不到,除非我们一开始就定义了“RY”这个单位型全局变量数组。

回想起来,之前我们为什么说JASS不能用T替代,因为Return Bug+GameCache在T里无法做到。比如,在上文排除触发器泄露中,call StoreInteger(udg_GC,I2S(H2I(trg)),"Triggeraction ",H2I(Act)),如果把这条语句用全局变量来写,那么我们必须事先定义好一个触发器动作型全局变量数组;如果我们想用全局变量绑定数据,那得预先定义海量的全局变量数组。

此外,GameCache的灵活性也是无可替代,以下是上次教程讲解Return Bug+GameCache绑定数据时提到例子

jass: Copy code
function ManData takes unit man,integer stature,integer avoirdupois returns nothing
call StoreInteger(udg_GC,I2S(H2I(man)),"stature",stature)
call StoreInterger(udg_GC,I2S(H2I(man)),"avoirdupois",avoirdupois)
endfunction

然后

jass: Copy code
call ManData(Lucy,165,45)
call ManData(Jack,172,60)

将数据输入。可以看出,只需预先写个系统(比如上文的ManData函数) ,然后再向缓存写入数据就非常方便而且一目了然。

那么,用全局变量的话,如何做到呢

set stature[1]=165
set stature[2]=172
set avoirdupois[1]=45
set avoirdupois[2]=65

首先定义了stature和avoirdupois这两个全局变量数组后,我们还必须有惊人的记忆力,记住数组方括号内的数字对应的单位——这里1对应Lucy,2对应Jack,如果单位多了将很难区分;而缓存在记录数据时,是以字符窜为标识,很形象,比如我们称呼一个人,是称呼“9527”呢,还是“华安”。
缓存与全局变量数组的区别就在于此,它们各有优缺点。缓存以字符窜为标识,存储数据,这在我们写代码的时候能一目了然;但这也正是它的缺点,写入和读出数据速度比较慢。全局变量数组虽然不形象,难记忆,但效率高。
在这里还是向大家推荐缓存,虽然它效率不如全局变量高,但在游戏中差别不大;我们也没必要为提高一点点效率而牺牲代码的可读性。

此外,GameCache还可储存二维数组,比如上文的三个排行榜:

排行榜

排名

1
2
3

1
Everguo
Red_Wolf
东方不败

2
汤姆.汗克斯
幽灵狼
李宇春

3
莱昂纳多
田伯光
看帖不回的

把上面数据写入

jass: Copy code
StoreInteger(udg_GC,”1”,”1”,H2I(Everuo))
StoreInteger(udg_GC,”1”,”2”,H2I(汤姆.汗克斯))
StoreInteger(udg_GC,”1”,”3”,H2I(莱昂纳多))
StoreInteger(udg_GC,”2”,”1”,H2I(Red_Wolf))
StoreInteger(udg_GC,”2”,”2”,H2I(幽灵狼))
StoreInteger(udg_GC,”2”,”3”,H2I(田伯光))
StoreInteger(udg_GC,”3”,”1”,H2I(东方不败))
StoreInteger(udg_GC,”3”,”2”,H2I(李宇春))
StoreInteger(udg_GC,”3”,”3”,H2I(看帖不回的))

然后我们写个函数方便查找数据

jass: Copy code
function UnitData takes integer x,integer y returns unit
return I2U(GetStoredInteger(udg_GC,I2S(x),I2S(y)))
endfunction

如果我们想调用这个二维数组(2,2)对应的单位,只需set unit=UnitData(2,2),就把幽灵狼赋值给了unit。

在T中只有一维数组,因此在处理很多问题时极不方便。而在使用GameCache后,我们可以StoreInteger(udg_GC,I2S(H2I(xx)), I2S(H2I(xx)),H2I(xx))或是GetStoredXXXX(udg_GC,I2S(H2I(xx)),I2S(H2I(xx))),要多方便有多方便。

或许此刻你已经在心中呐喊“Return Bug+GameCache”实在是太YD了,的确,我以前就把他们比喻成奸夫淫妇来着~

不得不说,Return Bug+GameCache改变了WEer写代码的思维,灵活地运用它们我们可以写出很多实用的系统。
或许上面关于Return Bug+GameCache替代全局变量存储数据的例子你不是很懂,不要紧,看完下面的演示你将对Return Bug+GameCache的使用有更清晰的认识。

三.Return bug+GameCache+JASS动态注册事件实例

演示永远是最好的教程。那么,在学习了JASS动态注册事件,以及用Return bug+GameCache存储数据后,我们来看个实例。
大多数RPG地图都有这样的T——“当单位进入XX区域,立刻移动单位到XX区域”,比如常见的进洞,当英雄进入洞口,立刻被传送到洞内,再从洞内的出口出来。下面是我写的一个3C地图的进洞函数。

jass: Copy code

function H2I takes handle h returns integer
return h
return 0
endfunction
function I2U takes integer i returns unit
return i
return null
endfunction

function Trig_RectUnitMove_Conditions takes nothing returns boolean
local unit hero = GetTriggerUnit() //获得触发单位
local trigger trig = GetTriggeringTrigger() //获得当前触发
local player P = GetOwningPlayer(hero)
local real x = GetStoredReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveX") //从缓存中读出目标区域中心的X坐标
local real y = GetStoredReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveY") //从缓存中读出目标区域中心的Y坐标
local integer level = GetStoredInteger(udg_GC,I2S(H2I(trig)),"RectUnitMoveLevel") //从缓存中读出英雄等级限制
local integer i=0
if IsUnitType(hero, UNIT_TYPE_HERO) != true or (GetPlayerId(P)>11) then //如果触发单位不是英雄,或触发玩家是中立玩家
set hero=null
set

trig=null
return false //直接返回,不执行下面的动作
endif
set i = GetHeroLevel(hero) //获得英雄等级
if i<level then
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "英雄等级未满"+I2S(level)+"级,不能进入")
//英雄等级不够,不能进洞
else
call SetUnitX(hero,x)
call SetUnitY(hero,y) //移动英雄到目标区域中心
call PanCameraToTimedForPlayer(P,x,y,0) //镜头跟随
call IssueImmediateOrderById(hero,OrderId("stop"))
//由于用坐标函数移动单位,单位当前状态不变,因此进洞后会继续移动,所以得让它停下来
endif
set hero=null
set trig=null
return false
endfunction

function RectUnitMoveTrigger takes integer level,rect r1,rect r2 returns nothing
local real x = GetRectCenterX(r2) //获得目标区域中心的X坐标
local real y = GetRectCenterY(r2) //获得目标区域中心的Y坐标
local trigger trig=CreateTrigger()
call StoreReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveX",x) // 储存目标区域中心的X坐标
call StoreReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveY",y) // 储存目标区域中心的Y坐标
call StoreInteger(udg_GC,I2S(H2I(trig)),"RectUnitMoveLevel",level) //存储英雄等级限制
call TriggerRegisterEnterRectSimple( trig, r1 ) //以单位进入指定区域为事件
call TriggerAddCondition(trig,Condition(function Trig_RectUnitMove_Conditions)) //添加条件
set trig = null
endfunction

上面是个很简单的Return bug+GameCache+JASS动态注册事件运用的例子,在学习了前面Return bug原理教程的朋友很容易能看懂,需要说明的是,这里使用Return bug+GameCache绑定数据,没用把目标区域(r2)中心坐标和等级限制绑定在指定区域(r1),而是绑定在了新生成的触发器(trig)上,因为我们可以通过GetTriggeringTrigger()来得到当前的触发,却不能得到当前英雄所在的区域(r1)。此外,我们在绑定数据的时候,除了触发器,一般是跟单位或计时器绑定,然后通过GetTriggerUnit()和GetExpiredTimer()分别获得单位和计时器,然后从单位和计时器上读出预先绑定的数据。
从上文的例子可以看出,我们先把目标区域中心的坐标写到缓存里,保存在“I2S(H2I(trig))”下,然后通过local trigger trig = GetTriggeringTrigger()来获得当前触发,用GetStoredReal(udg_GC,I2S(H2I(trig)),"XXXXXX")来读出数据。(原理我就不复述了,参看前面Return bug原理教程。)
(给十分钟,好好看下这段代码。)
相信你已经明白了,所谓的数据绑定,无非是把数据用Return bug+GameCache把数据存储到缓存中“XXX”的类别名下(这里的类别“XXX”,大都是通过I2S(H2I())将触发器、单位和计时器转换得来的),然后在按上文的方法从缓存读出来。
那么,我们为何要称这方法为数据绑定呢?
在讲解Return bug原理的时候,我曾提了两种存储数据的方法,一是上文的以I2S(H2I())转化的handle类参数为类别名,如

jass: Copy code
call StoreReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveX",x)
call StoreReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveY",y)
call StoreInteger(udg_GC,I2S(H2I(trig)),"RectUnitMoveLevel",level)

一是以I2S(H2I())转化的handle类参数为缓存项目名

jass: Copy code
call StoreReal(udg_GC,"RectUnitMoveX",I2S(H2I(trig)),x)
call StoreReal(udg_GC,"RectUnitMoveY",I2S(H2I(trig)),y)
call StoreInteger(udg_GC,"RectUnitMoveLevel",I2S(H2I(trig)),level)

我曾说,第二种方法,不合我们的习惯,为何?
比如,我们希望这个触发器在运行后自动销毁,此时我们应当将缓存中的数据也清除掉。
以I2S(H2I())转化的handle类参数为类别名的清除缓存数据

jass: Copy code
call FlushStoredMission(udg_GC,I2S(H2I(trig)))

以I2S(H2I())转化的handle类参数为缓存项目名的清除缓存数据

jass: Copy code
FlushStoredReal(udg_GC,"RectUnitMoveX",I2S(H2I(trig)))
FlushStoredReal(udg_GC,"RectUnitMoveY",I2S(H2I(trig)))
FlushStoredInteger(udg_GC,"RectUnitMoveLevel",I2S(H2I(trig)))

比较上面两种方法,大家应该明白了为何我们要以I2S(H2I())转化的handle类参数为类别名了吧。用这种方法,可以很容易的把数据与handle类参数挂钩,也可很容易地清除,因此我们称呼这种方法为数据绑定。

当我们把数据绑定到一个单位上,比如以前那个仿暗黑佣兵系统,当单位嗝屁后,可以把与它相关的数据清理掉,释放内存;把数据绑定在触发器上,比如那个环绕函数模板,当环绕时间到了或是释放技能的英雄挂了后,要立刻销毁计时器,同时清空缓存;在前面讲解动态注册事件排泄时,我们看到了,在删除触发器后,也有清除缓存数据的语句。
这便是数据绑定,也是JASS强大所在;为了让大家印象深刻,我再举个例子。

如果我们想只让特定类型单位进入区域才触发事件,比如3C里羊进圈选英雄,那么我们只需修改下上面的代码

jass: Copy code

function Trig_WC_Conditions takes nothing returns boolean
local unit hero = GetTriggerUnit() //获得触发单位
local trigger trig = GetTriggeringTrigger() //获得当前触发
local player P = GetOwningPlayer(hero)
local real x = GetStoredReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveX") //从缓存中读出目标区域中心的X坐标
local real y = GetStoredReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveY") //从缓存中读出目标区域中心的Y坐标
local integer WCuid = GetStoredInteger(udg_GC,I2S(H2I(trig)),"WCuid") //从缓存中读出指定单位类型
local integer uid=GetUnitTypeId(hero)
local string s=GetStoredString(udg_GC,I2S(H2I(trig)),"String")
if WCuid!=uid then
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, s) //与指定单位类型不匹配,不能入内
else
call SetUnitX(hero,x)
call SetUnitY(hero,y) //移动单位到目标区域中心
call PanCameraToTimedForPlayer(P,x,y,0) //镜头跟随
call IssueImmediateOrderById(hero,OrderId("stop"))
//由于用坐标函数移动单位,单位当前状态不变,因此进洞后会继续移动,所以得让它停下来
endif
set hero=null
set trig=null
return false
endfunction

function WCMoveTrigger takes integer uid,rect r1,rect r2,string s returns nothing
local real x = GetRectCenterX(r2) //获得目标区域中心的X坐标
local real y = GetRectCenterY(r2) //获得目标区域中心的Y坐标
local trigger trig=CreateTrigger()
call StoreReal(ud

g_GC,I2S(H2I(trig)),"RectUnitMoveX",x) // 储存目标区域中心的X坐标
call StoreReal(udg_GC,I2S(H2I(trig)),"RectUnitMoveY",y) // 储存目标区域中心的Y坐标
call StoreInteger(udg_GC,I2S(H2I(trig)),"WCuid",uid) //存储进入区域单位类型
call StoreString(udg_GC,I2S(H2I(trig)),"string",s) //存储提示文字
call TriggerRegisterEnterRectSimple( trig, r1 ) //以单位进入指定区域为事件
call TriggerAddCondition(trig,Condition(function Trig_WC_Conditions)) //添加条件
set trig = null
endfunction

上面代码使以单位类型判断替换英雄等级判断,只有特定单位才能进入区域;类似地,你也可以加入单位判断,只有XX单位才能进入,比如“只有FRJJ才能进入看帖不回的人的房间”,只需StoreReal(udg_GC,I2S(H2I(trig)),"Lover",FRJJ),然后比较触发单位是不是FRJJ,这里就不把演示写出来了。

相信通过上面的例子,你已经对Return bug+GameCache有一定了解了,但那是不够的,在下次的教程,我将讲解如何用Return bug+GameCache做更复杂的系统和演示。

BCC工具的使用:
1. 运行bcc.bat
2. 然后将你修改过的地图中的J文件和原正常地图中的J文件复制到软件目录, 比如我的修改过的地图名为 war3map.j 原始未修改的J文件为 war3map_o.j.
3. 在命令行敲入 bcc war3map_o.j war3map.j 如果提示" 恭喜, 生成成功"字样时则为成功, 软件目录中会生成一个new_war3map.j文件,将这个new_war3map.j重命名为war3map.j替换正版地图中的J文件即可.

功能:
1. 可以使作弊地图拥有和正规地图相同的HASH值. 即你使用的是作弊图, 而主机使用的是正规图, 你不会因为地图不同而下载地图.

注意: J文件并不是所有东西都可以改, 引用 Sc2DotA.CoM 的 Thewisp (目前的57c作弊版作者) 一段话:

凡是实体数据都不能改,例如触发,timer,等等所有类型为handle的东西

整数,实数,boolean,字符串等都可以,此外缓存也可以操作。

原理是把缓存单方修改后,用sync系列函数把数据扩散出去,因此他就是合法数据了

点击下载

魔兽地图修改之JASS入门篇(二)

魔兽地图修改之JASS入门篇(二)

上一章我讲解了魔兽地图的常用加密方式与解密方法,这一章我主要讲解JASS语言,首先即然是一种语言,我们当然要先了解一下语法

Jass变量类型

integer
integer(数值)是的范围在 -2147483647 和 2147483647 之间的整数, 不能有小数位

real
real是范围很大的32字节数字, 可以有小数位, 123456.33就是real

boolean
boolean的值只有true(真)和false(假), 多用于条件判断语句
if (条件==true)
then
(符合条件做某事)
else
(不符合条件就做另一件事)
endif

string
string是字符串变量, 可以是null(空值). 注意Jass的字符串是大小写区分的, 赋值时用双引号 “” 引用

handle
handle句柄, 可以是null(空值). 是用于指向Warcraft III定义的数据结构的指针. 比如上表中的location/player等除了

integer/real/boean/string外的的变量实际上就是handle类型的子变量

code
code(程序代码), 可以是null(空值). 函数可以有code类型的传递参数, 表示该函数必须要有其他函数作为参数, 如:
function RunFunctionForAllPlayers takes code theFunction returns nothing
我们可以这样调用函数: call RunFunctionForAllPlayers(function someFunction) //先运行someFunction
也可以: call RunFunctionForAllPlayers(null) //不运行其他函数

trigger
trigger为事件变量,例如trigger gg_trg_mingling01=CreateTrigger( )

Boolean boolean 布尔型(用于真/假判断)
Destructible destructable 可破坏物
Dialog dialog 对话
Dialog Button button 按钮
Floating Text texttag 漂浮文字
Integer integer 数值
Item item 物品
Leaderboard leaderboard 排行榜
Player player 玩家
Player Group force 玩家组
Point location 位置(点)
Real real 真值型数字
Region rect 地区
Special Effect effect 特效
String string 字符串
Terrain Deformation terraindeformation 地形
Timer timer 计时器
Timer Window timerdialog 计时器窗口
Unit unit 单位
Unit Group group 单位组
Player Score playerscore 积分(1.13版新类型)

全局变量声明格式:
globals
变量类型 变量名称 = 初始值 //有初始化的全局变量
变量类型 变量名称 //无初始化的全局变量
变量类型 array 变量名称 //数组全局变量, 不能再次初始化赋值

endglobals

看完语法部分之后,相信大家还不太明白,不过没关系,因为没动手改过JASS,所以当然会有不解,本章将举出实际的例子给大家动手的机会:) ,在动手之前还要讲一讲魔兽脚本文件的工作原理,我将他粗分为两部分,第一部分是”全局变量声明”也就是脚本中globals与endglobals之间的部分,这是此地图用到的所有的变量(JASS中用到的所有的变量都要求先声明,也就是写在globals与endglobals之间,定义变量的类型与数值什么的),第二部分就是函数部分,函数部分包括地图的初始化,创建单位,触发等等,全是在这里完成的,所以他是地图的灵魂。函数是从哪里开始执行的呢?无论哪个地图都有一个固定的入口函数”function main takes

nothing returns nothing”用UltraEdit-32打开war3map.j然后选中搜索->查找 在框中输入”function main”就会查找到入口函数”function main takes nothing returns nothing”函数是以function开头main是函数名(不同的函数名字不同,呵呵傻子都知道)以endfunction结束,其它的函数全是由这个函数开始,一层层的嵌套调用的.

下面我以’猛将传v2.01test1.w3x’这个地图为例,实现输入”give”后+1000钱和1000木的功能
第一,用MPQMaster提取出scripts文件夹下的war3map.j这个文件,然后用UltraEdit-32打开

第二,查找入口函数function main在这个函数中加入
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(0),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(1),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(2),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(3),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(4),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(5),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(6),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(7),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(8),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(9),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(10),”give”,true)
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(11),”give”,true )
call TriggerAddAction(gg_trg_mingling01,function O9981)
这段代码,语句的含义如下
call TriggerRegisterPlayerChatEvent(gg_trg_mingling01,Player(0),”give”,true)为例
call为调用函数的语句,TriggerRegisterPlayerChatEvent是对话的触发器,其中有4个参数,第一个为事件变量,在全局声明中以经建立了,第二个参数为玩家,玩家是从0到11共12个,第三个为触发的语句,也就是我们在对话中输入什么话才会响应,第四个为是否完全匹配,这里是为真,也就是只有输入give后才会响应,如果为假(false)则只要说的话中包含有’give’就可以了,12个触发有任意一个执行了,就会调用call TriggerAddAction(gg_trg_mingling01,function O9981),这个语句也是由call开始调用TriggerAddAction是事件对象,他有两个参数第一个是事件变量,它是和触发器的事件变量是对应的。第二个是调用的子程序,这里用的函数名是O9981(第一个是大写字母O) 如图:

第三,加入function O9981代码如下
function O9981 takes nothing returns nothing
call AdjustPlayerStateBJ(1000,GetTriggerPlayer(),PLAYER_STATE_RESOURCE_GOLD)
call AdjustPlayerStateBJ(1000,GetTriggerPlayer(),PLAYER_STATE_RESOURCE_LUMBER)
endfunction
含义如下AdjustPlayerStateBJ为加资源的函数,其中有三个参数,第一个是加多少资源,第三个是把资源给谁,第三个是加什么资源,其中第二个参数GetTriggerPlayer()是一个函数,他的作用是得到触发的玩家,也就是谁打的give这个命令就给谁钱,第三个参数有很多不同的值,我们这里用到的是钱和木,还有许多其它的,将在以后的文章中介绍了我是简单的把这个子程放在了入口函数的上边,如图:

第四,加入声名的变量,也就是gg_trg_mingling01代码如下:
trigger gg_trg_mingling01=CreateTrigger()
声名是加在globals与endglobals之间,trigger是事件类型,gg_trg_mingling01是变量名,CreateTrigger()是一个建立事件的函数,这条语句的意思就是建立一个事件,放在名叫gg_trg_mingling01的事件变量里.这个语句我就加在了脚本的一开头,如图:

现在脚本的编辑工作就完成了,我们点一下保存,UltraEdit-32会自动生成一个备份文件,防止改动出错时无法回复的问题发生

第五,用MPQMaster把原图中的war3map.j这个文件删除,然后再导入修改过的文件,再从新压缩一下,就大功告成了。:)

我发布了修改过的war3map.j,如果有明白的,下载后,同时用UltraEdit-32打开改前和改后的文件,然后用对比功能,可以看出两个脚本的不同,方便学习。
war3map.j
原图下载

小结:
看完这篇文章后要求独立更改一张地图的脚本,实现上述的功能

本文由小杰(SSJ)原创,转载请保留出处,谢谢合作!!

魔兽地图修改之工具与解密篇(一)

魔兽地图修改

 

-工具与解密遍

我学习改图有一段时间了,改的最成功的图是发表在万狐上的城海5.45的变态技能神器+改变归属(SSJ版),还有七龙珠的许愿版。在我学习中得到了 花开堪折 的帮助,在此谢过:)
在网上找魔兽改图的贴子有不少,但是讲JASS语言的并不多,或者说对入门者而还是太深了一点,所以出于我对改图的学习过程写一个系统的教程,现在开始说正题:)
改图必备工具:
1.MPQMaster 点击下载
2.MPQWorkshop 点击下载
3.W3MMASTER 点击下载
4.UltraEdit-32
5.DnD WE 点击下载

MPQMaster,MPQWorkshop都是MPQ文件格式的压缩工具,一看就知道怎么用了,但是要注意几点,首先替换文件是时候最好是先删除原有的同名文件,然后再加入修改后的文件,否则有可能看使地图变大,或出错。其次,导入文件后要重新压缩一下文件'操作->重新压缩'如果你改的是war3map.j文件,如果不重压一下,会让地图变成原来数倍大小。

W3MMASTER是一个可以直接修改地图的工具,比较简单,但是存在严重的BUG,所以如果只是改一些数值,还可以使用,太复杂的修改会使地图无法使用.

UltraEdit-32是一个功能强大的32位文本编辑器,用来修改war3map.j文件用的。

DnD WE地图编辑器,用他打开地图可以修改技能,物品,魔法等文件,但是对于现在的地图千万不要另存为成一张新的地图,因为如果你这么做大部分的图是无法使用的,这个工具的具体用法将在以后的文章中介绍

现在大多数的图都是加密的,但是常见的加密方式并不多,下面我将一一介绍
1.隐藏war3map.j大法
war3map.j文件是地图的核心文件,现在的地图用地图编辑器无法修改脚本的主要是因为地图的作者删除了war3map.wtg(触发器)文件,这个文件是用地图编辑器(WE)作图的时候所创建的,游戏本身并不使用这个文件,当生成地图后,编辑器会将war3map.wtg转换为war3map.j(脚本文件)。由于没有了war3map.wtg,所有改图的人无法方便的用WE(地图编辑器,以后称为WE)来修改触发器,但是可以通过war3map.j来达到修改触发器的目的,这就是为什么要隐藏这个文件。如果你用MPQMaster打开一个地图,可能看不到war3map.j,他可能是因为把这个文件放在了scriptswar3map.j。你可以通过修改MPQMasterListfilesWarcraft III.txt文件让他显形,只要在这个文件在加入一行scriptswar3map.j就行了,再重新打开地图,就看到了scripts文件夹下的war3map.j了

2.加密文件(attributes)与(listfile)
打开地图文件后如果发现存在(attributes)这个文件,将它删除,然后从新压缩,这样改过的地图才可以正常使用
(listfile)文件是一个地图中文件信息的列表,当使用软件打开地图时咱们会选用Warcraft III这个软件自带的列表,所以(listfile)这个文件没有什么大用处,只要了解一下就可以了。

3.破坏地图的头文件
如果你用MPQMaster或MPQWorkshop打开地图的时候,提示打开文件失败,那一定就是这种加密方式,对于这种加密方式可以通于下载一些还原头文件的软件进行修复,如果没有软件也可以用手工的方式进行修复,下面我主要讲解一下手工的修复方法 如图:
http://www.kumouse.com/uploads/200711/jie.jpg

附加:(地图中各文件的作用)

1.war3map.w3a 地图的自定义技能文件,主要储存地图的自定义技能信息,修改此处能实现技能的简单变化,但单独不能创造出很漂亮的自定义技能
2.war3map.w3t 地图的自定义物品文件,储存地图的自定义物品信息
3.war3map.w3b 存储树啊,门啊等可破环物的信息,没事一般不修改~~~~~~~~~
4.war3map.w3d储存装饰物的信息,没事也不弄~~~~~
5.war3map.w3q 储存自定义的科技信息,也就是升级之类的东东
6.war3map.w3h 储存自定义效果信息。很少用到~~~
7.war3map.w3i 储存地图的基本设置,也就是地图读图时的预览啊等的详细信息
8.war3map.w3e储存地图的地形信息,没什么好解释了吧~~
9.war3map.w3u 储存自定义的单位信息,与w3t,w3a,j 文件并列为修改最多的4个文件
10.war3map.wtg 事实上是触发器的j文件,地球上的加密地图都把wtg文件给删了,大家也就不用怎么了解了
11.war3map.wts 注悉文件,这年头基本上没有,因为大部分对w3u,w3a,w3t的注释都由那几个文件自带,方法是在we中把那几个文件导出~~~~
12.war3map.mmp 储存地图预览的小文件信息,耍cool用的~~~
13.war3map.shd 阴影文件信息~~
14.war3map.wpm 地图的路径信息,这两个大家基本不用弄,we会帮你搞好的
15.war3map.doo 装饰物信息
16.war3mapMisc.txt 地图的平衡常数信息,关于英雄的最大级别等的设定~~~
17.war3mapunits.doo 地图的物品摆放信息
18.war3map.shd 地层数据
19.war3map.wpm mp3,wav等媒体文件的设置
20.war3mapPreview.tga 地图缩略图
21.war3mapmap.blp 图形文件的设置
22.war3mapmisc.txt 游戏平衡常数

总结:
看完这篇文章后,要求会使用MPQMaster或MPQWorkshop解压文件和加入文件
会使用W3MMASTER的插件更改地图中物品的价格等,数值方面的内容,并能正常游戏
熟悉DnD WE菜单功能,方便日后使用.

本文由小杰(SSJ)原创,转载请保留出处,谢谢合作!!

澄海3C 5.45终结变态版+技能神器+作弊菜单(SSJ版)

ChengHai_3c_5.45.w3x
触发条件:
选完英雄后,先打开作弊模式,方法是键入“ssjandwfox.com”再
按四个方向键中的“<-”+“ESC”出现菜单,要什么功能自己选吧
为了防止别人作弊,可以关闭作弊模式,键入“1”即可(开局时默认是关闭作弊模式的)
新版本修正
自动加血为血量的70%,自动加蓝为60%,加属性,还原英雄,加钱,免CD时间,免状态
新版在老版本的基础上对“BT面照”加入了新功能(隐身和反隐形的被动技能),在菜单中加入BT面照选项,
选中后包里出现“吸血面照”注意只是名字一样
你用鼠标点击“吸血面照”出现全新的技能栏,其它包括全新设计的技能:
1.全屏闪现
2.克隆,功击力为本身的5倍(可是克隆任何东西,包括建筑)
3.距离为无限远的沉默
4.召唤毁灭守卫
5.召唤炮楼
6.操纵死尸
7.把任意单位变成无敌
8.还原无敌单位
9.秒杀任意单位
10.把任意单位从地图上移除

ChengHaii_3c_5.45.w3x
1.加入了把任意单位的控制权给任意一个玩家的新技能(改变归属)
使用方法是当玩家输入“+”屏幕会出现 单位归玩家1成功 的字样,这时点击BT面照里的改变归属技能,再选中任意单位
这个单位就是玩家1的了,再按输入一次“+”会出现 单位归玩家2成功 以次类推。也就是说可以给自己,可以给别人
具体的方法,请大家进入游戏自己体会,爽!!!有人发现你作弊,你可以用这方法栽赃别人哟!!!哈哈哈
2.在ChengHai_3c_5.45.w3x的基础去,去掉了隐身的功能(不容易让人发现作弊)
3.修改了刷变态装备时,计数出错的BUG

ChengHaii_3c_5.45 new SSJ.w3x是集前两个版本之大成,并改变了开启命令“ssjandwfox.com”解决了有些人无法作弊的BUG
加入了全屏命令“quanping”
改变了归属配置命令“set player01”~“set player12”
加入了自选英雄功能,在主机选模式时按ESC,金币变为1,2秒后变为0,说明成功开启,此后点击即可
注:同时只能有一人自选,如果开始时没有抢到自选功能,在正始游戏后,输入“ssjandwfox.com”
可将自选功能的使用权抢回来.

这个是我很久前改的一张图,如果有人支持,最新的图我会发出来的.
点击下载