|
楼主 |
发表于 2008-12-9 19:02:51
|
显示全部楼层
[jass]
//********************************************************************************************************
//*
//* Inferno
//* ˉˉˉˉˉˉˉ
//*
//* This is the JASS example, you will note here total usage of Attacheable Variables and tables
//* it also shows how to use CollisionMissiles and Angles_MoveAngleTowardsAngle
//*
//* I included this , because it is a complex script, that considers a lot of things and
//* completelly shows the power of attacheable variables, and even it got to the issue
//* sometimes happen when dealing with return bug based systems such like the attacheable
//* variables.
//*
//* Requires:
//* - The Inferno Ability
//* - The Caster System ( http://wc3campaigns.net/vexorian/ )
//* - This Trigger (MAKE SURE TO change the rawcodes in the trigger to
//* the correct ones of your map)
//* Notes:
//* - The Ability's Caster art often appears on the caster
//* - The Ability's Second Caster art field is the attachment point of the caster art.
//* - The Ability's Missile art field is the bolt model
//* - The Ability's Target art appears on the affected units.
//* - The Ability's Second Target art field is the attachment point of the target art.
//*
//********************************************************************************************************
//========================================================================================================
// Inferno Spell Configuration:
//
constant function Inferno_SpellId takes nothing returns integer
return 'A00N' //Inferno's ability Rawcode in your map
endfunction
//
constant function Inferno_LaunchInterval takes real level returns real
return 0.12-0*level //The period of time between each bolt launch
endfunction
//
constant function Inferno_ManaDrainPerLaunch takes real level returns real
return 0.4+level*0.1 //Mana drained per bolt launch
endfunction
//
constant function Inferno_BufferMana takes real level returns real
return 9+2*level //The minimum mana the caster has to have to continue casting inferno
endfunction
//
constant function Inferno_Scale takes real level returns real
return 1.2+0.1*level //The scale of the bolts
endfunction
//
constant function Inferno_MaxTurnAngle takes real level returns real
return 2+0*level //The maximum angle changed per 0.05 seconds (when changing the direction)
endfunction
//
constant function Inferno_Speed takes real level returns real
return 700+0*level //The speed of the bolts
endfunction
//
constant function Inferno_Area takes real level returns real
return 400+100*level //The area of effect of the spell, aka max distance the bolts can move.
endfunction
//
constant function Inferno_MaxLaunchDistVariation takes real level returns real
return 50.0-0*level //The maximum variation of distance for the initial position of the bolts
endfunction
//
constant function Inferno_MaxLaunchAngleVariation takes real level returns real
return 45.0-0*level //The maximum variation of angular position for the initial position of the bolts
endfunction
//
constant function Inferno_Damage takes real level returns real
return 5+1*level //Damage done by each bolt
endfunction
//
constant function Inferno_BoltCollisionSize takes real level returns real
return 45+10*level //Collision size of the bolts (how close an unit must be to the bolt to get damage)
endfunction
//
constant function Inferno_TargetEffectCooldown takes nothing returns real
return 0.5 //The cooldown of the target effect spawn
endfunction
//
constant function Inferno_EffectDeadAnimationTime takes nothing returns real
return 0.5 //The duration of the missile effect's dead animation
endfunction
//
function Inferno_DamageOptions takes integer level returns integer
return DamageTypes(ATTACK_TYPE_CHAOS,DAMAGE_TYPE_FIRE)+DamageOnlyEnemies()
//
// Does chaos damage that only affects enemy units.
//
// Other damage options can be used, and the ones here changed, check caster system's readme
// for more information
//
endfunction
//
constant function Inferno_Order takes nothing returns integer
return OrderId("monsoon")
//
// The order id of the ability used as base ability, not important if you just copied the ability in the
// map, but if you needed to have it based on let's say, blizzard, just replace that here.
//
endfunction
//
constant function Inferno_EnableAI takes nothing returns boolean
return true
//
// When it is true, non human player owned units that cast this spell will use a special AI encoded here
// that will allow them to use Inferno in a clever way, rather than just staying on a line of
// as they usually do, to test the AI try facing the bloodmage on the top left zone of this map.
//
endfunction
//==================================================================================================
function Trig_Inferno_Conditions takes nothing returns boolean
return GetSpellAbilityId() == Inferno_SpellId()
endfunction
//=============================
struct Inferno_TriggerData
//A good, safe way to attach content of types different to integer using CSSafeCache is through the
// use of structs, here we are declaring the struct using to store the trigger's information.
//
// Every instance of the trigger requires a certain trigger to be created in order to handle orders
//
unit u = null
integer l = 0
real f = 0.0
triggeraction ac = null
integer b = 0
boolean hasturntimer = false
timer turntimer = null
endstruct
struct Inferno_Attach
// So this one just keeps things we usually attach to ... collision missiles, and timers
//
unit u
integer l
real f
real c
trigger trig
endstruct
function Inferno_AngleTurning takes nothing returns nothing
local timer t=GetExpiredTimer()
local Inferno_Attach dat=Inferno_Attach( GetCSData(t) )
local real f=dat.f
local real c=dat.c
local real x=Angles_MoveAngleTowardsAngle(c,f,Inferno_MaxTurnAngle(dat.l))
local Inferno_TriggerData td= Inferno_TriggerData( GetCSData(dat.trig) )
set td.f=x
set dat.c=x
if (ModuloReal(x,360)+0.0000001>ModuloReal(f,360)) and (ModuloReal(x,360)-0.0000001<ModuloReal(f,360)) then
set td.hasturntimer=false
call dat.destroy()
call ReleaseTimer(t)
endif
set t=null
endfunction
function Inferno_PrepareAngleTurning takes trigger t, real c, real f returns nothing
local timer x=NewTimer()
local Inferno_Attach at= Inferno_Attach.create()
local Inferno_TriggerData td
set at.trig=t
set at.f=f
set at.c=c
call SetCSData(x, integer(at) )
set td=Inferno_TriggerData( GetCSData(t) )
set td.turntimer=x
set td.f=c
set td.hasturntimer=true
call TimerStart(x,0.04,true,function Inferno_AngleTurning)
set x=null
endfunction
function Inferno_ResetFX takes nothing returns nothing
local timer x=GetExpiredTimer()
local Inferno_Attach at= Inferno_Attach( GetCSData(x) )
call AttachBoolean(at.u,"Inferno_IgnoreFX",false)
call at.destroy()
call ReleaseTimer(x)
set x=null
endfunction
function Inferno_BoltImpact takes nothing returns nothing
local unit m=GetTriggerCollisionMissile()
local Inferno_Attach at = Inferno_Attach( CollisionMissile_GetTag(m) ) //See how we are using CollisionMissile tags instead of CSData.
local unit o=at.u
local unit u=GetTriggerUnit()
local integer l
local integer s
local real f
local timer x
if (u!=null) then
set l=at.l
set f=GetDamageFactorByOptions(o,u,Inferno_DamageOptions(l))
if f!=0 then
set s=Inferno_SpellId()
if not(GetAttachedBoolean(u,"Inferno_IgnoreFX")) then
call DestroyEffect(AddSpellEffectTargetById(s,EFFECT_TYPE_TARGET,u,GetAbilityEffectById(s,EFFECT_TYPE_TARGET,1)))
call AttachBoolean(u,"Inferno_IgnoreFX",true)
set x=NewTimer()
set at=Inferno_Attach.create()
set at.u=u
call SetCSData(x, integer(at) )
call TimerStart(x,Inferno_TargetEffectCooldown(),false,function Inferno_ResetFX)
set x=null
endif
call UnitDamageTarget(o,u,Inferno_Damage(l)*f,true,false,null,null,null)
endif
else
call at.destroy() //cleanup now that the collision missile has died...
endif
set u=null
set m=null
set o=null
endfunction
function Inferno_Launch takes boolean cfc, unit u, integer l,real f returns nothing
local unit m
local real x=GetUnitX(u)
local real y=GetUnitY(u)
local real a
local real d
local integer s=Inferno_SpellId()
local Inferno_Attach at
if GetWidgetLife(u)>0 then
if GetRandomInt(0,100)<=10 then
call DestroyEffect(AddSpellEffectTargetById(s,EFFECT_TYPE_CASTER,u,GetAbilityEffectById(s,EFFECT_TYPE_CASTER,1)))
endif
if cfc then
call SetUnitFacing(u,f)
endif
set a=Inferno_MaxLaunchAngleVariation(l)
set d=GetRandomReal(0,Inferno_MaxLaunchDistVariation(l))
set a=(f + GetRandomReal(-a,a))*bj_DEGTORAD
set m=CollisionMissile_Create(GetAbilityEffectById(Inferno_SpellId(),EFFECT_TYPE_MISSILE,0),x+d*Cos(a),y+d*Sin(a),f,Inferno_Speed(l),0,Inferno_Area(l),60,false,Inferno_BoltCollisionSize(l),function Inferno_BoltImpact)
set at = Inferno_Attach.create()
call CollisionMissile_SetTag(m, integer(at) ) //Again, we are using collisionmissile tags here.
set f=Inferno_Scale(l)
call SetUnitScale(m,f,f,f)
call SetUnitOwner(m,GetOwningPlayer(u),true)
call SetUnitColor(m,GetPlayerColor(GetOwningPlayer(u)) )
set at.u=u
set at.l=l
endif
set m=null
endfunction
function Inferno_EndTurn takes nothing returns nothing
local timer t=GetExpiredTimer()
local Inferno_Attach at = Inferno_Attach( GetCSData(t) )
call AttachReal(at.u,"Inferno_Current",0)
call at.destroy()
call ReleaseTimer(t)
set t=null
endfunction
function Inferno_PrepareTurn takes unit u, real f returns nothing
local timer t=NewTimer()
local Inferno_Attach at = Inferno_Attach.create()
set at.u=u
if f==0 then
set f=0.1
endif
call AttachReal(u,"Inferno_Current",f)
call SetCSData(t, integer(at) )
call TimerStart(t,2,false,function Inferno_EndTurn)
set t=null
endfunction
function Inferno_DetectDirChangeT takes nothing returns nothing
local timer x=GetExpiredTimer()
local Inferno_Attach at = Inferno_Attach( GetCSData(x) )
local Inferno_TriggerData td
if GetUnitCurrentOrder(at.u)!=Inferno_Order() then
set td=Inferno_TriggerData( GetCSData( at.trig ) )
set td.b=40
endif
call ReleaseTimer(x)
call at.destroy()
set x=null
endfunction
function Inferno_DetectDirChange takes trigger t, unit u returns nothing
local timer x=NewTimer()
local Inferno_Attach at = Inferno_Attach.create()
set at.trig=t
set at.u=u
call SetCSData(x, integer(at) )
call TimerStart(x,0,false,function Inferno_DetectDirChangeT)
set x=null
endfunction
function Inferno_AI takes trigger t, unit u, integer l,real f returns boolean
local group g=CreateGroup()
local unit p
local player ow=GetOwningPlayer(u)
local unit tar=null
local real x=GetUnitX(u)
local real y=GetUnitY(u)
local integer d
local real gra
local integer b=0
local integer cl
local real max=Inferno_Area(l)
call GroupEnumUnitsInRange(g,x,y,max,null)
set max=max*max
loop
set p=FirstOfGroup(g)
exitwhen p==null
call GroupRemoveUnit(g,p)
if IsUnitEnemy(p,ow) and (GetWidgetLife(p)>0) and IsUnitVisible(p,ow) then
set d=R2I(1000.*(max-(Pow(GetUnitX(p)-x,2)+Pow(GetUnitY(p)-y,2))))
if IsUnitType(p,UNIT_TYPE_HERO) then
set cl=(GetHeroLevel(p)*2)*d
else
set cl=(GetUnitLevel(p))*d
endif
if (cl>b) then
set b=cl
set tar=p
endif
endif
endloop
call DestroyGroup(g)
if tar!=null then
set gra=Atan2BJ(GetUnitY(tar)-y,GetUnitX(tar)-x)
call Inferno_PrepareAngleTurning(t,f,gra)
endif
set ow=null
set p=null
set g=null
set tar=null
return (b!=0)
endfunction
function Inferno_Control takes nothing returns nothing
//
// Main control trigger
// A trigger is created by Trig_Inferno_Actions, that trigger uses this function, the trigger
// You may like to read Trig_Inferno_Actions first as it explains what data it is attaching to the trigger
// executes each (launch interval) seconds, or when the unit starts the effect of a spell or stops casting
//
local trigger t=GetTriggeringTrigger() //The trigger is run, you can use this function to know which trigger we are talking about
local Inferno_TriggerData d= Inferno_TriggerData( GetCSData(t) )
// Let me explain this mess:
// GetCSData(t) : With this, we recover that unique integer value we attached before, remember?
// Inferno_TriggerData() : This just converts that value to our struct.
local integer l= d.l //This was saved in the function below.
local real f
local integer b= d.b // "" "" ""
local unit u=d.u // '' '' ''
local eventid e=GetTriggerEventId()
// A trigger may have multiple events, so this works for letting us know which event caused
// the trigger to execute
if (b>=1) then
if (e==EVENT_UNIT_SPELL_EFFECT) then
set d.b=b+40
set b=40
else
set d.b=b+1
endif
endif
if (b==0) then
set f=GetUnitState(u,UNIT_STATE_MANA)
if (f<Inferno_BufferMana(l)) then
call PauseUnit(u,true)
call IssueImmediateOrder(u,"stop")
call PauseUnit(u,false)
set b=40
set d.b=40
else
call SetUnitState(u,UNIT_STATE_MANA,f-Inferno_ManaDrainPerLaunch(l))
if ((Inferno_EnableAI()) and (GetPlayerController(GetOwningPlayer(u))!=MAP_CONTROL_USER)) then
if not(d.hasturntimer) and (GetRandomInt(0,100)<=10) then
if not( Inferno_AI(t,u,l,d.f)) then
call PauseUnit(u,true)
call IssueImmediateOrder(u,"stop")
call PauseUnit(u,false)
set b=40
set d.b=b
endif
endif
endif
endif
endif
set f=d.f
if (b==0) and (e==EVENT_UNIT_SPELL_ENDCAST) then
call Inferno_DetectDirChange(t,u)
set d.b=b+1
if d.hasturntimer then
call Inferno_Attach( GetCSData(d.turntimer) ).destroy() //important cleanup here, easy to miss
call ReleaseTimer(d.turntimer)
set d.hasturntimer=false
endif
call Inferno_PrepareTurn(u, d.f )
endif
if (b<40) then
call Inferno_Launch((b==0),u, l,f)
endif
if (b>=40) then
// We are doing cleanup here. Please notice:
call TriggerRemoveAction(t,d.ac) //Removing the triggeraction (good that we stored it, right?)
call d.destroy() //Also recycle the object (quite important)
call DestroyTrigger(t) //Don't forget the trigger...
endif
set u=null
set t=null
set e=null
endfunction
function Trig_Inferno_Actions takes nothing returns nothing
//
//This is the first thing that runs whenever a hero starts casting the inferno spell
//
local unit u=GetTriggerUnit()
local integer s=GetSpellAbilityId()
local integer l=GetUnitAbilityLevel(u,s)
local location loc=GetSpellTargetLoc()
local real f=Atan2BJ(-GetUnitY(u)+GetLocationY(loc) ,-GetUnitX(u)+GetLocationX(loc))
local Inferno_TriggerData d =Inferno_TriggerData.create() //let's create our TriggerData object!
local trigger t=CreateTrigger()
// Creating a trigger in game is really important when you want to go for multinstance
// Spells, this trigger is used to detect whenever the unit starts or stops casting the spell
local real c
call SetCSData(t, integer(d) ) //We are linking our TriggerData object to the trigger t.
// You can get a unique integer value from a struct using integer()
// This value will allow us to later get the data.
set d.u=u //Save u at "u" for t
set d.l=l //Also save the level of the spell for t
set c=GetAttachedReal(u,"Inferno_Current") //If the unit was already casting inferno, the current direction should be saved
//On the attached variable "Inferno_Current"
if c!=0 then //If c is not 0, then inferno was being cast
call Inferno_PrepareAngleTurning(t,c,f) //So use inferno's turning function
else
set d.b = 0
set d.f = f //If c is 0 then the unit was not casting inferno and just use the current angle for f
endif
call TriggerRegisterUnitEvent(t,u,EVENT_UNIT_SPELL_ENDCAST) //Register the Stops casting an ability event for the trigger t
call TriggerRegisterUnitEvent(t,u,EVENT_UNIT_SPELL_EFFECT) //Register the Starts the effect of an ability event for the trigger t
set d.ac=TriggerAddAction(t,function Inferno_Control)
//Another application for Attacheable variables is fixing the TriggerAction leak, this
//is an evil leak, if you create a trigger in game and give it a trigger action, you have
//To remove it from the trigger, else it will leak.
// But if we find our way to link the trigger action to the trigger so we might destroy it later, we can fix it.
call TriggerRegisterTimerEvent(t,Inferno_LaunchInterval(l),true)
//Make a periodic event for the trigger, so it is execute each Inferno_LaunchInterval() seconds
call TriggerRegisterTimerEvent(t,0,false)
//Will make the Trigger execute instantly after this current trigger ends execution
call RemoveLocation(loc) //Remove point so it doesn't leak
set loc=null
set u=null
//weird bugs if you set t=null , yes, in very rare situations the return bug, and thereby
//attacheable variables get really weird bugs, this is related to how the stuff works, an
//depends on weird combinations of factors-
endfunction
//===========================================================================
function InitTrig_Inferno takes nothing returns nothing
call OnAbilityEffect(Inferno_SpellId(),"Trig_Inferno_Actions")
endfunction
[/jass] |
|