| 02-09-2007, 01:39 PM | #1 | ||||||||||||||||||||||||
This tutorial is about using new technologies... vJass is a set of extensions to Jass it specially adds structs and global declaration freedom, what are these features and how to use it are things that are included in this tutorial. If you are a handle vars user you would like this tutorial since I am gonna update a spell from old, rusty handle vars to structs and will also specify exactly why you should stop using handle vars (it is also valid for attacheable vars, which are actually the same thing) Requirements - JassHelper is a proof of concept compiler for the vJass extension that works inside grimoire or WEHelper (although wehelper currently is having issues with patch 1.21) An easy way to get it is from the Jass NewGen pack. In order to use the extensions you need to save the map using (grimoire or wehelper)+JassHelper, so it processes the script and translates it to valid Jass code. The spell The spell we will port to vJass is optical flare, an starcraft inspired spell that reduces sight radius of units. It is a very old spell I made in my beginning days... It is convenient for this tutorial because the complex is simple: Just remove the unit's sight range and then periodically move a dummy unit with some minimal sight range to the position of the unit. It is also an special spell that may also need some global variables. JASS://********************************************************************************************** //* //* Optical Flare //* ¯¯¯¯¯¯¯¯¯¯¯¯¯ //* Requires: //* - The Pool Class functions //* - The Handle Variables functions //* - The Optical Flare Ability //* - The Optical Flare Buff //* - The Optical Flare Sight Destruction Ability //* - The Flare Vision unit type //* - This Trigger (make sure it points to the right rawcodes) //* Note: //* - If you have any custom detector ability in your map, you have to add it to the //* SetupDetectionPool function at the end of this configuration section. //* //********************************************************************************************** //============================================================================================== // Optical Flare Configuration // constant function OpticalFlare_SpellId takes nothing returns integer return 'A007' //// The Rawcode of the Optical Flare Ability In your map endfunction constant function OpticalFlare_BuffId takes nothing returns integer return 'B000' //// The Rawcode of the Optical Flare Buff In your map endfunction constant function OpticalFlare_SightDestructorSpellId takes nothing returns integer return 'A006' //// The Rawcode of the Optical Flare Sight Destructor ability In your map endfunction constant function OpticalFlare_FakeSightUnit takes nothing returns integer return 'n000' //// The Rawcode of the Optical Flare Vision unit type In your map endfunction constant function OpticalFlare_MissileSpeed takes nothing returns integer return 1500 //// The missile speed, should be exactly the same as the one used by the ability endfunction function SetupDetectionPool takes integer spells returns nothing call PoolAddItem(spells,'Agyv') //True Sight (Flying Machine) call PoolAddItem(spells,'Atru') //True Sight (Shade) call PoolAddItem(spells,'Adtg') //True Sight (Neutral 1) call PoolAddItem(spells,'ANtr') //True Sight (Neutral 2) call PoolAddItem(spells,'Adts') //Magic Sentry call PoolAddItem(spells,'Adt1') //Detector (Sentry Ward) call PoolAddItem(spells,'Abdt') //Burrow Detection (fliers) //If you have any custom ability that allows passive detection, you must add a line for it // Example: call PoolAddItem(spells,'A008') //Creep Detection Ability // Remove the example from your map, or this ability would remove an ability it is not // supposed to remove. endfunction //=================================================================================================== function OpticalFlareDetectDetector takes unit u, integer p returns integer local integer n=CountItemsInPool(p) local integer i loop exitwhen n==0 set i=PoolGetItem(p,n) if GetUnitAbilityLevel(u,i)>0 then return i endif set n=n-1 endloop return 0 endfunction function OpticalFlare_GetUnit takes timer t, string s returns unit return GetHandleHandle(t,s) endfunction function OpticalFlare_Timer takes nothing returns nothing local timer t=GetExpiredTimer() local unit b=OpticalFlare_GetUnit(t,"b") local unit sh=OpticalFlare_GetUnit(t,"sh") local real x=GetHandleReal(t,"x") local real y=GetHandleReal(t,"y") local real nx=GetUnitX(b) local real ny=GetUnitY(b) local real a=ModuloReal( Atan2(ny-y,nx-x) , 2*bj_PI) local real d= SquareRoot(Pow(x-nx,2) +Pow(y-ny,2)) if ModuloReal(a+bj_PI/4,bj_PI*2)<=bj_PI then set d=d*40 endif call SetUnitPosition(sh,nx+d*Cos(a),ny+d*Sin(a) ) call SetHandleReal(t,"x",nx) call SetHandleReal(t,"y",ny) call SetUnitFacing(sh,GetUnitFacing(b)) call SetUnitFlyHeight(sh, GetUnitFlyHeight(b)+120,0) set t=null set b=null set sh=null endfunction function OpticalFlare_Effect takes unit b, integer l returns nothing local real ac=GetUnitAcquireRange(b) local unit sh=CreateUnit( GetOwningPlayer(b), OpticalFlare_FakeSightUnit(), GetUnitX(b), GetUnitY(b), 0) local integer abi=OpticalFlareDetectDetector(b, GetStoredInteger(InitGameCache("opticalflare"),"opt","pool") ) local timer t=CreateTimer() call SetUnitPathing(sh,false) call UnitRemoveAbility(b, abi ) call UnitAddAbility(b, OpticalFlare_SightDestructorSpellId() ) call UnitMakeAbilityPermanent(b,true,OpticalFlare_SightDestructorSpellId()) call UnitMakeAbilityPermanent(b,true,OpticalFlare_BuffId()) call SetHandleHandle(t,"b",b) call SetHandleHandle(t,"sh",sh) call TimerStart(t,0.01,true, function OpticalFlare_Timer ) loop exitwhen IsUnitDeadBJ(b) call TriggerSleepAction(0) exitwhen not UnitHasBuffBJ(b, OpticalFlare_BuffId() ) endloop call UnitRemoveAbility(b, OpticalFlare_SightDestructorSpellId() ) call UnitAddAbility(b, abi ) call RemoveUnit(sh) call FlushHandleLocals(t) call DestroyTimer(t) set t=null set sh=null endfunction function Trig_Optical_Flare_Actions takes nothing returns nothing local unit u=GetTriggerUnit() local unit blind=GetSpellTargetUnit() local integer l=GetUnitAbilityLevel(u,GetSpellAbilityId() ) call PolledWait( SquareRoot(Pow(GetUnitX(u)-GetUnitX(blind),2) + Pow(GetUnitY(u)-GetUnitY(blind),2) ) / OpticalFlare_MissileSpeed() ) call OpticalFlare_Effect(blind,l) set u=null set blind=null endfunction function Trig_Optical_Flare_Conditions takes nothing returns boolean return GetSpellAbilityId() == OpticalFlare_SpellId() endfunction function InitTrig_Optical_Flare takes nothing returns nothing local integer i=CreatePool() call StoreInteger(InitGameCache("opticalflare"),"opt","pool",i) call SetupDetectionPool(i) set gg_trg_Optical_Flare = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( gg_trg_Optical_Flare, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( gg_trg_Optical_Flare, Condition( function Trig_Optical_Flare_Conditions ) ) call TriggerAddAction( gg_trg_Optical_Flare, function Trig_Optical_Flare_Actions ) endfunction I will take some moments to explain what the spell is doing. The first part are some constant functions a pre JESP header:
Then the actual code:
The first update Now that we analized this spell we notice what is the priority: fix the leak. We could go the easy way and make a gamecache variable to prevent the InitGameCache, but it seems that it is not the real problem, aren't we doing too much effort for an integer? (pool id) In fact, do we really need the pool? Isn't the dynamism of pools better for things that need to be dynamic? So I would guess that a better thing to do is to replace all the pool stuff with an array. Besides we also need an integer variable. And these things are configuration stuff used by all the instances of optical flare, so it would be convenient that they were global variables. But then how do you add global variables? Long ago the only way was with the variable dialog in GUI, which took some time and was limited, besides of making the spell harder to copy since copying a Jass trigger to another map does not copy the global variables it uses. But vJass allows declaration of globals by globals block in a way similar to common.j/blizzard.j/war3map.j globals. It is like this: JASS:globals type name type2 name2 = value endglobals type works in the same way it works in local variable declarations, and the rest is the same as well.... So, we can be happy now since we are gonna add a global array to replace that whole pool and gamecache stuff that was leaky. We need these globals: JASS:globals integer array DetectorSpells integer array DetectorSpellsN=0 endglobals This requires modiffications of 3 functions, the setup one in which the user adds the detector spells, the one that returns the id of a detector ability if it is found and the init function that calls the setup one. JASS:function SetupDetectionAbilities returns nothing set DetectorSpells[1]='Agyv' //True Sight (Flying Machine) set DetectorSpells[2]='Atru' //True Sight (Shade) set DetectorSpells[3]='Adtg' //True Sight (Neutral 1) set DetectorSpells[4]='ANtr' //True Sight (Neutral 2) set DetectorSpells[5]='Adts' //Magic Sentry set DetectorSpells[6]='Adt1' //Detector (Sentry Ward) set DetectorSpells[7]='Abdt' //Burrow Detection (fliers) // set DetectorSpellsN=7 //normally these 7 spells are needed, uncomment this line if they are just 7 //If you have any custom ability that allows passive detection, you must add a line for it // Example: set DetectorSpells[8]='A008' //Creep Detection Ability set DetectorSpellsN=8 //We added 8 detector spells to the array, so let's specify that number here. // Remove the example for your map (also update the count), or this ability would remove an ability it is not // supposed to remove. endfunction A typo was fixed. Notice that this time we are using the arrays, it might be a little less intuitive for the user but this has got a work around I will specify later. This function no longer needs any arguments, so we removed the argument as well. JASS:function OpticalFlareDetectDetector takes unit u returns integer local integer i local integer n=DetectorSpellsN loop exitwhen n==0 set i=DetectorSpells[n] if GetUnitAbilityLevel(u,i)>0 then return i endif set n=n-1 endloop return 0 endfunction We do not need that p argument either. Also updated the part of OpticalFlare_Effect that called it: local integer abi=OpticalFlareDetectDetector(b ) Finally the Init function looks much cleaner: JASS:function InitTrig_Optical_Flare takes nothing returns nothing call SetupDetectionAbilities() set gg_trg_Optical_Flare = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( gg_trg_Optical_Flare, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( gg_trg_Optical_Flare, Condition( function Trig_Optical_Flare_Conditions ) ) call TriggerAddAction( gg_trg_Optical_Flare, function Trig_Optical_Flare_Actions ) endfunction Replacing the handle vars The obvious update was done, but now we have an emeny left, the HandleVars calls. We just have to take a look at OpticalFlare_Timer and OpticalFlare_Effect to see how HandleVars intensive this spell currently is. What's wrong with handle vars? you may ask.
The alternative chosen by this tutorial are structs why?
Are structs the ultimate medicine? No, they aren't:
In the case of spells, structs are fairly perfect as long as you figure out a way to attach an struct to a handle. The limit is not meaningful since you would hardly need 8190 instances of an spell, in fact it is hard already to have 100 instances of an spell... So, what exactly are structs? Do you know what an array is? An array is a group of variables with the same name that are indexed. Now think of an alternative to an array, instead of grouping the variables as indexes of an array, you group them by name. So you have some kind of variable that holds a group of variables, subvariables, and they got their own name. For instance take a point, a point in the 2D world is a pair of values, x and y. If you had a variable of type point it would have 2 parts: x and y. In a perfect world you would be able to tell Jass that you want to have variables of type point, and be able to operate their x or y values separatedly. Like: JASS:function DoSomething takes nothing returns point local point p=point.create() set p.x=4.5 set p.y=12.3 return p endfunction And you easily made a function that returns 2 values x and y, inside the point "variable", from now we will call the super variable an struct. Of course, Jass does not allow you to declare these new types. But vJass does... But how would you declare it? What's the best way? You need to tell the compiler 2 things: name of the new type and the "subvariables" it holds. The syntax we chose is like this: JASS:struct point real x real y endstruct You may have noticed this if you read the JassHelper readme after downloading it. So these struct types may hold any combination of "subvariables" inside! We will from now call these "subvariables" members or fields from now. The final note is that in the example, the p variable is not actually holding a point but a reference to a point. Structs, like handles are always pointer-like. In this case, what do we need? An inspection to _Effect and _Timer brings some conclussions: The Handlevars used are 4: A unit b (unit affected by the buff), A unit sh (the dummy), and reals x,y (the previous position of the unit). When we are moving from handlevars to structs we would expect to have the same functionality, what we would like here is to attach certain struct to the timer, this struct should hold those 4 values. JASS:struct flaredata unit b //Unit affected by the buff unit sh //The dummy unit real x //] real y ///Previous Position endstruct Creating an struct Creating an object of an struct type is really easy, you just need to use (objectname).create(), for example for this flaredata object we use flaredata.create() How do you attach and struct to a handle? This is the tricky part is it? It really isn't. references to structs are interchangeable with integers (in a similar way that handle references are... ...just easier) For instance, will take the easiest way here and just use GetHandleInt / SetHandleInt , HandleVars aren't really a big performance hit when used once, nor they are a threat when used to store native types that are not typecasted references, of course, there are plenty of alternatives to attach a single integer to a handle, and there are some that are specialized towards the handle type. Plenty of the alternatives are much faster than HandleVars, but this is just an example so we'll use HandleVars. To store an struct reference you just need to use the single store function that takes an integer but use the struct. You can then assign an integer to an struct variable to load it. It is also recommended you use the structname() typecast operator since it is a more valid coding... It has becamo hard to explain with vague words, so I'll just post code, it should be really easy to notice how this works: JASS:struct flaredata unit b //Unit affected by the buff unit sh //The dummy unit real x //] real y ///Previous Position endstruct function OpticalFlare_Timer takes nothing returns nothing local timer t=GetExpiredTimer() local flaredata dat= flaredata( GetHandleInt(t,"dat") ) //notice the typecast operator, //in this case it is not necessary but it just makes the //code look better local real nx=GetUnitX(dat.b) local real ny=GetUnitY(dat.b) local real a=ModuloReal( Atan2(ny-dat.y , nx-dat.x ) , 2*bj_PI) //we do not need them as locals anymore local real d= SquareRoot(Pow(dat.x-nx,2) +Pow(dat.y-ny,2)) if ModuloReal(a+bj_PI/4,bj_PI*2)<=bj_PI then set d=d*40 endif call SetUnitPosition(dat.sh,nx+d*Cos(a),ny+d*Sin(a) ) set dat.x=nx //notice how setting thing actually uses the usual set syntax set dat.y=ny call SetUnitFacing( dat.sh,GetUnitFacing( dat.b )) call SetUnitFlyHeight( dat.sh, GetUnitFlyHeight( dat.b )+120,0) set t=null endfunction function OpticalFlare_Effect takes unit b, integer l returns nothing local real ac=GetUnitAcquireRange(b) local unit sh=CreateUnit( GetOwningPlayer(b), OpticalFlare_FakeSightUnit(), GetUnitX(b), GetUnitY(b), 0) local integer abi=OpticalFlareDetectDetector(b ) local timer t=CreateTimer() local flaredata dat = flaredata.create() call SetUnitPathing(sh,false) call UnitRemoveAbility(b, abi ) call UnitAddAbility(b, OpticalFlare_SightDestructorSpellId() ) call UnitMakeAbilityPermanent(b,true,OpticalFlare_SightDestructorSpellId()) call UnitMakeAbilityPermanent(b,true,OpticalFlare_BuffId()) set dat.x=0. //struct fields aren't automatically initialized to 0, unlike handle vars set dat.y=0. // It is also possible to make them initialize to a default value inside the struct block set dat.b=b set dat.sh=sh call SetHandleInt(t,"dat",dat) //Here we are attaching the struct object to the handle call TimerStart(t,0.01,true, function OpticalFlare_Timer ) loop exitwhen IsUnitDeadBJ(b) call TriggerSleepAction(0) exitwhen not UnitHasBuffBJ(b, OpticalFlare_BuffId() ) endloop call UnitRemoveAbility(b, OpticalFlare_SightDestructorSpellId() ) call UnitAddAbility(b, abi ) call RemoveUnit(sh) call FlushHandleLocals(t) call DestroyTimer(t) call flaredata.destroy(dat) //We need to dispose struct objects once they are not used anymore set t=null set sh=null endfunction Yes, you need to destroy structs, just like you need to flush handle vars. But don't think structs leak, they just use space that is ready for them, destroy is simply allowing the id to be recycled. If you just created and created struct types and forgot to destroy you would easily get to the 8190 instances limit since instances you are not using are not freed. You must accept that the code looks better without all those GetHandleHandle and etc, if it doesn't look better to you at least be glad to know that this is much faster, if anything we reduced the GetHandle** calls done every 0.01s per flared unit to 1 (down from 4). Shouldn't a better version get rid of all gamecache usage? We could use an alternative method to store the struct's integer id for the handle. But this spell allows us to use another method, one that eliminates the need of attaching a timer, we just need to abuse globals again... A single timer What we are currently doing (and is always done, a lot) Is, for each instance of the spell, create a timer, attach info and then make the timer expire each 0.01 seconds, then when the buff ends, destroy the timer and the info. This means that there are as many timers for as many instances of the spell. Do we really need them? In the case of this spell, the order in which each timer expires does not matter, the only thing that matters is to process the active buffs each 0.01 seconds. Instead of making 100 timers to process 100 active instances, since what really matters is to have 100 instances and having 100 timers is not actually needed, we could just use a single timer and force it to process the 100 instances. It is easier than it sounds, we should only add each active instance to a global array and then when this unique timer expires go through the whole array and process each of the indexes. Adding stuff to an array is not difficult, you just keep some element number variable and just increase it after assigning last position to the new element. Take a look to the expire event, it just processes the data, besides of getting the data from the expired timer. JASS:globals flaredata array OpticalFlare_Ar integer OpticalFlare_total = 0 timer OpticalFlare_timer=CreateTimer() endglobals WE got all we need, an array of flaredata objects, an integer that tells us the total and a timer that will expire when necessary. Now, we need to handle this timer with some intelligence, it should effectively be paused when there are no active instances. And periodically expire each 0.01 seconds whenever there is at least once. Starting the timer when necessary is easy, just check at the moment of adding an element to the array, if the number of elements was 0 then the timer was innactive and it is necessary to enable it. However pausing the timer when there are no active buffs anymore is a little harder. First of all, we need to remove flaredata objects from the array at the time they are destroyed. Else we would have an array full of invalid objects. We could go to the moment in which flaredata.destroy() gets called, and remove the element from the array there. But it is a little problematic to find the index of the currently being destroyed object. Something better would be to stop destroying the flaredata objects from that function, and instead do it by request in the expiration event function. OpticalFlare_Effect requires the right to request destruction (and removal) of a flaredata object. The destruction request should probably just be a field in the flaredata struct, we could add a destroyplease boolean field to it: JASS:struct flaredata unit b //Unit affected by the buff unit sh //The dummy unit real x //] real y ///Previous Position boolean destroyplease = false endstruct Notice the = false, it determines a default value automatically assigned to the new object when you use create(). Function OpticalFlare_Effect, should now just set that field to true at the moment it wants an object destroyed. Instead of calling destroy on it. The expiration event would loop through all the supposedly active buffs, if it finds that for any of them destroyplease is true it should destroy the object and remove it it from the array. In case the number of elements is 0 after processing the elements in the array, it should pause the timer again. The current function that processes each flaredata object is OpticalFlare_Timer we should modiffy it to do the processing inside a loop and also handle the destruction. JASS:function OpticalFlare_Timer takes nothing returns nothing local integer i=0 local flaredata dat local real nx local real ny local real a local real d loop exitwhen i==OpticalFlare_total set dat= OpticalFlare_Ar[i] if (dat.destroyplease) then //This is tricky. // We already have the ith flaredata referenced by dat, so we // will first remove it from the array, since the order does not matter // we can simply move the last flaredata to this position and decrease // the total set OpticalFlare_Ar[i]= OpticalFlare_Ar[ OpticalFlare_total - 1] set OpticalFlare_total=OpticalFlare_total-1 //now we are free to destroy the dat call dat.destroy() //another way to destroy struct objects. //If we don't substract i, it will skip the new object we just moved // to the ith position (we are increasing i later) set i=i-1 else //Notice how this ressembles the original expire function set nx=GetUnitX(dat.b) set ny=GetUnitY(dat.b) set a=ModuloReal( Atan2(ny-dat.y , nx-dat.x ) , 2*bj_PI) set d= SquareRoot(Pow(dat.x-nx,2) +Pow(dat.y-ny,2)) if ModuloReal(a+bj_PI/4,bj_PI*2)<=bj_PI then set d=d*40 endif call SetUnitPosition(dat.sh,nx+d*Cos(a),ny+d*Sin(a) ) set dat.x=nx set dat.y=ny call SetUnitFacing( dat.sh,GetUnitFacing( dat.b )) call SetUnitFlyHeight( dat.sh, GetUnitFlyHeight( dat.b )+120,0) endif set i=i+1 endloop if (OpticalFlare_total==0) then // let's pause the timer, it is not needed anymore call PauseTimer(OpticalFlare_timer) endif endfunction WE now have to modiffy the _Effect function, since it is currently creating a timer and attaching stuff to it. We need to make it just add the new flaredata object to the array, start the timer if necessary and then set destroyplease to true instead of destroying the flaredata. JASS:function OpticalFlare_Effect takes unit b, integer l returns nothing local real ac=GetUnitAcquireRange(b) local unit sh=CreateUnit( GetOwningPlayer(b), OpticalFlare_FakeSightUnit(), GetUnitX(b), GetUnitY(b), 0) local integer abi=OpticalFlareDetectDetector(b ) local flaredata dat = flaredata.create() call SetUnitPathing(sh,false) call UnitRemoveAbility(b, abi ) call UnitAddAbility(b, OpticalFlare_SightDestructorSpellId() ) call UnitMakeAbilityPermanent(b,true,OpticalFlare_SightDestructorSpellId()) call UnitMakeAbilityPermanent(b,true,OpticalFlare_BuffId()) set dat.x=0. //struct fields aren't automatically initialized to 0, unlike handle vars set dat.y=0. // It is also possible to make them initialize to a default value inside the struct block set dat.b=b set dat.sh=sh if(OpticalFlare_total==0) then //There are no elements in the array so the timer is inactive call TimerStart(OpticalFlare_timer,0.01,true,function OpticalFlare_Timer) //restart it endif set OpticalFlare_total=OpticalFlare_total+1 //increase the number of elements set OpticalFlare_Ar[ OpticalFlare_total-1 ] = dat //add the flaredata to the array, notice that these are arrays //that begin with the [0] index... loop exitwhen IsUnitDeadBJ(b) call TriggerSleepAction(0) exitwhen not UnitHasBuffBJ(b, OpticalFlare_BuffId() ) endloop call UnitRemoveAbility(b, OpticalFlare_SightDestructorSpellId() ) call UnitAddAbility(b, abi ) call RemoveUnit(sh) set dat.destroyplease = true //Will send a signal to the timer function so it destroys this object... set sh=null endfunction We just got rid of the creation of timers, attaching stuff, destroying timers, flushing handle vars and all those annoyances , in exchange of some array operations. And that's it! The spell has been modiffied to use structs in a clean way instead of HandleVars, there is a huge speed gain in the 0.01 seconds timer. This spell used to cause the game a heart attack in my computer when there were 11 units affected by optical flare. Now you wouldn't notice a drop in performance even with 24 units affected with it, imagine how good it would be if we increased the timer to 0.04seconds from 0.01? That would allow a lot of units to be affected by this buff. One thing is left to do: change the header to requires JassHelper, instead of requires PoolClass and HandleVars... There is one thing pending and it is that the configuration function that sets the detector abilities is not as friendly as before. A second part of this tutorial in which we take the spell one step further by using scopes and methods will come soon. |
| 02-13-2007, 06:12 PM | #2 |
Great tutorial, sounds interesting, i'm gonna go try some stuff out with this now. |
| 02-13-2007, 06:31 PM | #3 |
Okay... Well I forced myself to read it. I think I'll give it a go... Nice job Vex. It kinda sorta made sense to me. I had an issue following at one point -- JASS://This is tricky. // We already have the ith flaredata referenced by dat, so we // will first remove it from the array, since the order does not matter // we can simply move the last flaredata to this position and decrease // the total set OpticalFlare_Ar[i]= OpticalFlare_Ar[ OpticalFlare_total - 1] set OpticalFlare_total=OpticalFlare_total-1 What are you doing at this point? The comments don't really register with my mind. |
| 02-13-2007, 06:47 PM | #4 | |||||||||||||
i = 1 we want to remove ith element. Easiest way so far is to place the last element in that position and then just reduce the number of elements
|
| 02-16-2007, 03:14 AM | #5 |
OK if I understand this right this right here JASS:
set nx=GetUnitX(dat.b)
set ny=GetUnitY(dat.b)
set a=ModuloReal( Atan2(ny-dat.y , nx-dat.x ) , 2*bj_PI)
set d= SquareRoot(Pow(dat.x-nx,2) +Pow(dat.y-ny,2))
if ModuloReal(a+bj_PI/4,bj_PI*2)<=bj_PI then
set d=d*40
endif
call SetUnitPosition(dat.sh,nx+d*Cos(a),ny+d*Sin(a) )Is a complex way to move dat.sh(The dummy unit) to the position of the unit that it is affecting (dat.b) now I would never claim to be a better programmer than you (Not even close) but why not just do this? JASS:
call SetUnitX(dat.sh, GetUnitX(dat.b))
call SetUnitY(dat.sh, GetUnitY(dat.b))
and then you could just ignore saving the last position of the unit. Obviously you know of such methods so I think I can safely assume I'm wrong, I guess I'm just asking to be taught. Maybe there is something about optical flare I don't understand. but all I can see that really affects the game is SetUnitPostion. Is it trying to set the 2 units to the same spot? Very nice tutorial btw. The rest of it was very clear and helpful to me and explained in a way that made perfect sense and taught me some things that are very useful. |
| 02-16-2007, 12:09 PM | #6 |
you should have tested yourself, there is some delay in the update of fog of war, if you move the dummy directly the flared unit will become invisible while moving. So it is better to try to move the dummy where the unit would be after some time, kind of predicting where it is going . Nevermind the math there , it is terrible, there is a better way using vector theory. |
| 02-16-2007, 02:37 PM | #7 |
Oh yeah i knew there was a .40 update interval, I just didn't know the math well enough to know it was going infront of the unit. I wonder if you could just run a timer every .4 seconds and have the timer start at .38 seconds so it changes the unit position .02 seconds before the vision change? but I guess that is not for this thread. but I understand now thanks. |
| 02-17-2007, 01:39 AM | #8 |
Excellent stuff. My only issue with it is the decided lack of arrays within a struct, but still exceedingly useful. Good stuff. |
| 02-17-2007, 10:29 AM | #9 |
Making structs have arrays directly would ruin polymorphism, I don't think it is worth it. With dynamic array members it is possible to "have arrays" anyways JASS:library test private type intarray extends integer array [100] struct bbbb intarray ar int top=0 method new takes nothing returns nothing set ar=intarray.create() endmethod method onDestroy takes nothing returns nothing call ar.destroy() endmethod endstruct endlibrary function errs takes nothing returns nothing local bbbb a=bbbb.create() set bbbb.ar[5]=34 endfunction Eventually, in a next version declaring JASS:struct bbb integer array ar [100] //this syntax may be changed integer top=0 endstruct Will do all of above automatically (except the library thing) |
| 02-17-2007, 01:36 PM | #10 |
That is just purely awesome. But something I didn't get from here, but I assume to be the case, will all members of a struct object automatically be destroyed/nulled/removed/etc as fits the requirements for that object? |
| 02-17-2007, 01:41 PM | #11 |
I would have to make a garbage collector or a ref counter for that, and I seriously wouldn't like to do it. Most of the destroying is easy when you have an onDestroy method. Nulling members is not really necessary, the struct's id is recycled and eventually the members will get assigned again. |
| 02-17-2007, 03:06 PM | #12 |
Excellent. |
| 02-17-2007, 08:57 PM | #13 |
The structs aren't inlined (at least conceptually) in vjass, it's always a pointer-they never live constrained inside a scope. I think you only need three things for the desired behavior: something to stop assignment to that var (but reading is of course ok, if dangerous), a constructor syntax with ": varname(params)" and automatic entry in onDestroy. Hopefully the same syntax would let you constrain the struct instance to a function scope. It could make things a little cleaner-one less line where you use it, of course structs get even more verbose and the language more complex so it might be a bad trade. |
| 02-19-2007, 08:40 PM | #14 |
Hey all, I'm afraid that I'm still somewhat in the dark about vJass and structs. vJass is an extension to Jass I understand. But how does this work? Wc3 maps can only consist of jass right? So maybe you guys have made a vJass compiler that compiles vJass into Jass so that it can be inserted into a wc3 map. Then vJass only makes the programming easier but the code not more efficient. Or does vJass work in another way? Also I couldn't gather what functionality vJass adds. For one it adds structs, but what are structs exactly? I know it is a variable that can contain other variables. But is that the full functionality of structs? The code I read seemed to have some functions contained inside a struct: "bbbb.Create(); bbbb.onDestroy()", so I'm curious about that. Besides the structs I don't know what functionality vJass adds. Thanks for answering. I don't mind if you just link me to some thread that I don't know about that contains the right information. Lordy. |
| 02-19-2007, 10:46 PM | #15 |
vJass cannot be faster than Jass, fact is that for the dynamic stuff that we used to use handle vars, arrays are a good alternative, but writing everything in terms of arrays and pointers (thus acquiring the level of dynamism we want) is a really low level way of doing it and generates some of the most horrible and repetitive Jass code. vJass simply saves the hassle of doing so. It is not faster than Jass but it is surely faster than gamecache, and the way it works makes it easier to use. Unlike handling the stuff manually which would be more difficult than gamecache, specially considering the fact that it is a huge annoyance to actually declare a global variable without a preprocessor. |
