| 10-31-2009, 06:24 AM | #1 |
I am trying to recreate "Spectral Dagger" ability from DotA. Anyway I decided to attempt to do this in vJass(first time I am attempting anything in vJass). Instead of using global variables, I want to use structs(just so I can practice using them). Here's what I am trying to do with the trigger: 1. Once a unit casts the spell at a target point, a dagger projectile (unit) is created. 2. Using timers, I want the dagger to move towards that target point(by moving the dagger using triggers). 3. After the dagger moves a total of <x> distance(I set the variable to 2000 for now), the timer stops and everything ends. That's it, well at least for what I am trying to do for the trigger. I am trying to use TimerUtils for Timer usage. Note: 1. I'm not even good with regular Jass. 2. A lot of the stuff written here is just stuff I tried to replicate by looking at triggers here. Again, I don't really know or understand much about vJass. Still trying to learn. 3. The code is a bit messy but I tried to add comments to show you what I am trying to do. 4. Also note, the spell doesn't work, it's just what I have currently. So basically, if anyone can help me get the code "to work", I will appreciate it. Here's the code: JASS:scope SpectralDagger private function Conditions takes nothing returns boolean if ( not ( GetSpellAbilityId() == 'A0GY' ) ) then return false endif return true endfunction private struct dagger_data unit Dagger_Caster = GetTriggerUnit() // Caster unit Dagger_Target = GetEventTargetUnit() // Target of Spell, the spell can either be casted at a point or a unit. unit Dagger_SpellUnit = null // The dagger projectile location CasterPoint = GetUnitLoc(GetTriggerUnit()) // Location of Caster location DaggerTarget = GetSpellTargetLoc() // Location of Target real Dagger_Distance = 2000.00 real Dagger_Counter = 0.00 // (This is used as a counter. Moves at 32 units per 0.04 seconds. Fires 25 times per second. Translates to 800 units per second. integer Shadow_Counter = 0 // This value goes up every 0.04 seconds, at 3 it creates a shadow path and resets back to 0 endstruct // The below function should happen once the spell(event) happens. // I want the below function to create and start the timer private function Actions takes nothing returns nothing local damagedata dd = 0 // I have no idea what I am doing, I simply attempted to replicate what was done in other triggers approved at wc3c.net local timer t = NewTimer() // No ideal what I am doing call CreateNUnitsAtLoc( 1, 'h026', GetOwningPlayer(GetTriggerUnit()), CasterPoint, bj_UNIT_FACING ) call UnitApplyTimedLifeBJ( 20.00, 'BTLF', GetLastCreatedUnit() ) // In case for some reason the projectile isn't removed later on, it has 20 second expiration time // Assigning stuff to structs? set dd = dagger_data.create() // No idea set dd.Dagger_SpellUnit = GetLastCreatedUnit() // No idea (again) call SetTimerData(d.t, d) // No idea again call TimerStart(d.t, 0.04, true, function Timer_Action) // No Idea endfunction // I want the below function to happen every 0.04 seconds private function Timer_Action takes nothing returns nothing local dagger_data dd = GetTimerData(GetExpiredTimer()) local location DaggerPoint local location DaggerOffset set dd = dagger_data.create() set dd.Shadow_Counter = ( dd.Shadow_Counter + 1 ) set dd.Dagger_Counter = ( dd.Dagger_Counter + 32.00 ) // Causes the counter to go up, once the counter is 2000+, the timer should stop set DaggerPoint = GetUnitLoc(dd.Dagger_SpellUnit) set DaggerOffset = PolarProjectionBJ(DaggerPoint, 32.00, AngleBetweenPoints(dd.Dagger_Caster, dd.Dagger_Target)) call SetUnitPositionLocFacingLocBJ( dd-Dagger_SpellUnit, DaggerOffset, dd.Dagger_Target ) if ( dd.Shadow_Counter >= 3 ) then // I want the shadows, left by the dagger to be made only once every 3 intervals call CreateNUnitsAtLoc( 1, 'h027', GetOwningPlayer(dd.Dagger_SpellUnit), dd.DaggerPoint, bj_UNIT_FACING ) call UnitApplyTimedLifeBJ( 9.00, 'BTLF', GetLastCreatedUnit() ) set dd.Shadow_Counter = 0 else endif call RemoveLocation(DaggerPoint) call RemoveLocation(DaggerOffset) set DaggerPoint = null set DaggerOffset = null if ( dd.Dagger_Counter >= dd.Dagger_Distance) // dd.Dagger_Distance is set to 2000.00 call ReleaseTimer(GetExpiredTimer()) call dd.destroy() call RemoveUnit( dd.Dagger_SpellUnit ) // Removes projectile from the game else endif endfunction //==================================================================== public function InitTrig takes nothing returns nothing local trigger trigger_dagger = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trigger_dagger, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition(trigger_dagger, Condition( function Conditions )) call TriggerAddAction(trigger_dagger, function Actions) endfunction endscope |
| 10-31-2009, 10:49 AM | #2 |
JASS:private function Conditions takes nothing returns boolean if ( not ( GetSpellAbilityId() == 'A0GY' ) ) then return false endif return true endfunction Can be merged to: JASS:private function Conditions takes nothing returns boolean return GetSpellAbilityId() == 'A0GY' // Will return true if the ability being cast is 'A0GY' :D endfunction JASS:private struct dagger_data unit Dagger_Caster = GetTriggerUnit() // Caster unit Dagger_Target = GetEventTargetUnit() // Target of Spell, the spell can either be casted at a point or a unit. unit Dagger_SpellUnit = null // The dagger projectile location CasterPoint = GetUnitLoc(GetTriggerUnit()) // Location of Caster location DaggerTarget = GetSpellTargetLoc() // Location of Target real Dagger_Distance = 2000.00 real Dagger_Counter = 0.00 // (This is used as a counter. Moves at 32 units per 0.04 seconds. Fires 25 times per second. Translates to 800 units per second. integer Shadow_Counter = 0 // This value goes up every 0.04 seconds, at 3 it creates a shadow path and resets back to 0 endstruct You can do it like this, you should try creating a custom "create" method (Which I'll tell you later when to use) which will take those members as arguments, and you can simply set them ;) I also strongly suggest you try using reals x and y instead of locations, as locations leak ;) Like this: JASS:private struct dagger_data unit Dagger_Caster unit Dagger_Target unit Dagger_SpellUnit location CasterPoint location DaggerTarget real Dagger_Distance real Dagger_Counter = 0 integer Shadow_Counter = 0 static method create takes unit caster, unit target, unit projectile, real distance, location targetloc returns thistype local thistype this = thistype.allocate() //Here's we're actually creating the struct, for further use ;) set this.Dagger_Caster = caster //Set the caster to a struct member set this.Dagger_Target = target //Set the target unit of the ability to a struct member set this.Dagger_SpellUnit = projectile set this.CasterPoint = GetUnitLoc( caster ) set this.DaggerTarget = targetloc set this.DaggerDistance = distance call UnitApplyTimedLife( projectile, 'BTLF', 20.0 ) return this endmethod endstruct That will do exactly what you wanted your struct to do, only this will work ;) Ok, so on to your Action function... Here you're using the wrong struct name: JASS:private function Actions takes nothing returns nothing local damagedata dd = 0 // I have no idea what I am doing, I simply attempted to replicate what was done in other triggers approved at wc3c.net local timer t = NewTimer() // No ideal what I am doing call CreateNUnitsAtLoc( 1, 'h026', GetOwningPlayer(GetTriggerUnit()), CasterPoint, bj_UNIT_FACING ) call UnitApplyTimedLifeBJ( 20.00, 'BTLF', GetLastCreatedUnit() ) // In case for some reason the projectile isn't removed later on, it has 20 second expiration time // Assigning stuff to structs? set dd = dagger_data.create() // No idea set dd.Dagger_SpellUnit = GetLastCreatedUnit() // No idea (again) call SetTimerData(d.t, d) // No idea again call TimerStart(d.t, 0.04, true, function Timer_Action) // No Idea endfunction Should at least be: JASS:private function Actions takes nothing returns nothing local unit u = GetTriggerUnit() local unit t = GetSpellTargetUnit() local player p = GetOwningPlayer ( u ) local location l local dagger_data dd local timer t = NewTimer() // Here you're using the TimerUtils system to efficiently create and use timers ;) if t == null then // Just so you know wether you're targetting a unit, or point ;) set l = GetSpellTargetLoc() set dd = dagger_data.create( u, null, CreateUnit( p, 'h026', GetLocationX( l ), GetLocationY( l ), bj_UNIT_FACING ), 2000.0, l ) // Using the right struct name, we create the struct and thereby triggering the custom "create" method ;) else set l = GetUnitLoc ( t ) set dd = dagger_data.create( u, t, CreateUnit( p, 'h026', GetLocationX( l ), GetLocationY( l ), bj_UNIT_FACING ), 2000.0, l ) call CreateNUnitsAtLoc( 1, 'h026', GetOwningPlayer(GetTriggerUnit()), CasterPoint, bj_UNIT_FACING ) // Assigning stuff to structs? //set dd = dagger_data.create() this isn't needed now ;) //set dd.Dagger_SpellUnit = GetLastCreatedUnit() // Not this either ;) call SetTimerData(t, dd) // Here's you're attaching the struct you just created to the timer, so you can later on get that value from another function call TimerStart(t, 0.04, true, function Timer_Action) // Here's you're simply starting the timer you just attached the struct to :D endfunction And now on to the callback function... This one is actually pretty good, just a few errors ;) JASS:private function Timer_Action takes nothing returns nothing local dagger_data dd = GetTimerData(GetExpiredTimer()) //local location DaggerPoint // Isn't needed because you have a structmember which stores this ;) local location DaggerOffset local unit shadow //set dd = dagger_data.create() You already have one, two lines above ;) set dd.Shadow_Counter = ( dd.Shadow_Counter + 1 ) set dd.Dagger_Counter = ( dd.Dagger_Counter + 32.00 ) // Causes the counter to go up, once the counter is 2000+, the timer should stop set DaggerOffset = PolarProjectionBJ(dd.DaggerTarget, 32.00, AngleBetweenPoints(dd.Dagger_Caster, dd.DaggerTarget)) call SetUnitPositionLocFacingLocBJ( dd.Dagger_SpellUnit, DaggerOffset, dd.Dagger_Target ) if ( dd.Shadow_Counter >= 3 ) then // I want the shadows, left by the dagger to be made only once every 3 intervals set shadow = CreateUnit(GetOwningPlayer(dd.Dagger_Caster), 'h027', GetLocationX( dd.DaggerTarget ), GetLocationY( dd.DaggerTarget ), bj_UNIT_FACING ) call UnitApplyTimedLife( shadow, 'BTLF', 9.00 ) set dd.Shadow_Counter = 0 endif call RemoveLocation(DaggerOffset) set DaggerOffset = null set shadow = null if ( dd.Dagger_Counter >= dd.Dagger_Distance) // dd.Dagger_Distance is set to 2000.00 call RemoveUnit( dd.Dagger_SpellUnit ) call ReleaseTimer(GetExpiredTimer()) call RemoveLocation( dd.DaggerTarget ) call RemoveLocation( dd.CasterPoint ) call dd.destroy() endif endfunction There you go, though it's all freehanded, so there could be some compile errors, if so then please post it here, and I can help you ;) Hope you learned something :D |
| 10-31-2009, 07:59 PM | #3 |
You could also use xecollider, it's perfect for what you are describing. |
| 11-01-2009, 12:42 AM | #4 |
But not if he wants to practice (v)Jass... |
| 11-02-2009, 05:36 PM | #5 | |
Quote:
xemissile gives a great opportunity to use features that are kind of unused but are very useful. |
| 11-09-2009, 12:56 AM | #6 |
Thanks for the help Komaqtion and everyone else. Reason why I took so long to reply was because I haven't really been mapping or anything until recently. Anyway, I used Komaqtion's suggestions and edited a few things because there were some things that did not work(just the syntax, suggestions worked fine). Despite how things look, it works but it seems to leak. After a certain amount of uses, an error message stating that there are no more timers available appears. I'm still a bit confused on how to use structs, timers, etc. Questions: 1. To use structs in a function, you have to start with something like JASS:local dagger_data dd 2. JASS:static method create takes unit caster, unit target, unit projectile, real distance, location targetloc returns thistype local thistype this = thistype.allocate() //Here's we're actually creating the struct, for further use ;) set this.Dagger_Caster = caster //Set the caster to a struct member set this.Dagger_Target = target //Set the target unit of the ability to a struct member set this.Dagger_SpellUnit = projectile set this.CasterPoint = GetUnitLoc( caster ) set this.DaggerTargetLoc = targetloc set this.Dagger_Distance = distance call UnitApplyTimedLife( projectile, 'BTLF', 20.0 ) return this endmethod This is Komaqtion's suggestions he made, which was placed inside the struct data. So the above is making the structs usable in other functions? JASS:set this.Dagger_Caster = caster For example, does that make anytime I do something like "set caster", it will set a value to the "caster"(variable name) in the struct? Sorry for all the questions but if anyone can explain some more stuff, I will appreciate it. Anyway here's my revised trigger(note that I haven't completely understand how to do things; the trigger works but it leaks it seems). JASS:scope SpectralDagger initializer InitDagger private function Conditions takes nothing returns boolean return GetSpellAbilityId() == 'A0GY' // Will return true if the ability being cast is 'A0GY' :D endfunction private struct dagger_data unit Dagger_Caster = GetTriggerUnit() // Caster unit Dagger_Target = GetEventTargetUnit() // Target of Spell, the spell can either be casted at a point or a unit. unit Dagger_SpellUnit = null // The dagger projectile location CasterPoint = GetUnitLoc(GetTriggerUnit()) // Location of Caster location DaggerTargetLoc = GetSpellTargetLoc() // Location of Target real Dagger_Distance = 2000.00 real Dagger_Counter = 0.00 // (This is used as a counter. Moves at 32 units per 0.04 seconds. Fires 25 times per second. Translates to 800 units per second. integer Shadow_Counter = 0 // This value goes up every 0.04 seconds, at 3 it creates a shadow path and resets back to 0 static method create takes unit caster, unit target, unit projectile, real distance, location targetloc returns thistype local thistype this = thistype.allocate() //Here's we're actually creating the struct, for further use ;) set this.Dagger_Caster = caster //Set the caster to a struct member set this.Dagger_Target = target //Set the target unit of the ability to a struct member set this.Dagger_SpellUnit = projectile set this.CasterPoint = GetUnitLoc( caster ) set this.DaggerTargetLoc = targetloc set this.Dagger_Distance = distance call UnitApplyTimedLife( projectile, 'BTLF', 20.0 ) return this endmethod endstruct // I want the below function to happen every 0.04 seconds private function TimerAction takes nothing returns nothing local dagger_data dd = GetTimerData(GetExpiredTimer()) local location DaggerPoint // Isn't needed because you have a structmember which stores this ;) local location DaggerOffset local unit shadow set dd.Shadow_Counter = ( dd.Shadow_Counter + 1 ) set dd.Dagger_Counter = ( dd.Dagger_Counter + 32.00 ) set DaggerPoint = GetUnitLoc(dd.Dagger_SpellUnit) set DaggerOffset = PolarProjectionBJ(DaggerPoint, 32.00, AngleBetweenPoints(dd.CasterPoint, dd.DaggerTargetLoc)) call SetUnitPositionLocFacingLocBJ( dd.Dagger_SpellUnit, DaggerOffset, dd.DaggerTargetLoc ) call SetUnitAbilityLevelSwapped( 'A0GW', dd.Dagger_SpellUnit, GetUnitAbilityLevelSwapped('A0GY', dd.Dagger_Caster) ) call SetUnitAbilityLevelSwapped( 'A0GX', dd.Dagger_SpellUnit, GetUnitAbilityLevelSwapped('A0GY', dd.Dagger_Caster) ) if ( dd.Shadow_Counter >= 2 ) then set shadow = CreateUnit(GetOwningPlayer(dd.Dagger_Caster), 'h027', GetLocationX( DaggerPoint ), GetLocationY( DaggerPoint ), bj_UNIT_FACING ) call UnitApplyTimedLife( shadow, 'BTLF', 9.00 ) set dd.Shadow_Counter = 0 endif call RemoveLocation(DaggerOffset) call RemoveLocation(DaggerPoint) call RemoveLocation(DaggerOffset) set DaggerPoint = null set DaggerOffset = null set shadow = null //Below is the part where the timer should be recycled and the struct destroyed //so it doesn't leak right? Not sure if I did it correctly. if ( dd.Dagger_Counter >= dd.Dagger_Distance ) then // dd.Dagger_Distance is set to 2000.00 call RemoveUnit( dd.Dagger_SpellUnit ) call ReleaseTimer(GetExpiredTimer()) call RemoveLocation( dd.DaggerTargetLoc ) call RemoveLocation( dd.CasterPoint ) call dd.destroy() endif endfunction // I want the below function to create and start the timer private function Actions takes nothing returns nothing local unit u = GetTriggerUnit() local unit t = GetSpellTargetUnit() local player p = GetOwningPlayer ( u ) local location l local location Caster local location SpellTarget local dagger_data dd local timer time = NewTimer() // Here you're using the TimerUtils system to efficiently create and use timers ;) local unit SomeUnit loop set SomeUnit = FirstOfGroup(udg_ShadowStopDamageGroup) exitwhen SomeUnit == null //Do stuff call GroupRemoveUnit(udg_ShadowStopDamageGroup, SomeUnit) endloop set Caster = GetUnitLoc (u) set SpellTarget = GetSpellTargetLoc() set dd = dagger_data.create( u, null, CreateUnit( p, 'h026', GetLocationX( Caster ), GetLocationY( Caster ), bj_UNIT_FACING ), 2000.0, SpellTarget ) dd.CasterPoint, bj_UNIT_FACING ) call SetTimerData(time, dd) // Here's you're attaching the struct you just created to the timer, so you can later on get that value from another function call TimerStart(time, 0.04, true, function TimerAction) // Here's you're simply starting the timer you just attached the struct to :D call TriggerSleepAction( 7.00 ) // I did this because for some reason, my edited //version of the trigger depends on the two local variables "Caster" and "SpellTarget". //If I were to remove the wait on removing the local points, the projectile would move to //the center to the map instead of the target point of cast. call RemoveLocation(Caster) call RemoveLocation(SpellTarget) set Caster = null set SpellTarget = null set u = null set t = null set p = null set l = null endfunction //==================================================================== private function InitDagger takes nothing returns nothing local trigger trigger_dagger = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trigger_dagger, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition(trigger_dagger, Condition( function Conditions )) call TriggerAddAction(trigger_dagger, function Actions) endfunction endscope |
| 11-09-2009, 11:58 AM | #7 |
It would be useful to know exactly which debug message you're getting from TimerUtils. Check the code in the resource thread to copy the exact text of the debug message (since you can't copy it from WC3). This spell would be greatly simplified if you used xecollider. The code for what you described in your first post would look something like this: JASS:scope dagger initializer Init globals private constant integer SPELL_ID = 'A000' private constant real DAGGER_SPEED = 1500.0 private constant real DAGGER_DISTANCE = 2000.0 private constant real DAGGER_COLLISION_SIZE = 50.0 private constant string DAGGER_MODEL = "Abilities\\Weapons\\..." private constant real DAGGER_MODEL_SCALE = 1.0 private constant real DAGGER_FLYHEIGHT = 60.0 endglobals private struct dagger extends xecollider integer level unit caster // The "extends" keyword automatically makes our struct an xecollider, // meaning it has all of the properties of xecollider and by extension xefx. // This includes the following method, which will run whenever a dagger hits a unit: method onUnitHit takes unit hitTarget returns nothing // You can do what you want with the unit here. // If you want to destroy the dagger when it hits a unit, just do: // call this.terminate() endmethod static method create takes real x, real y, real targetx, real targety returns dagger // When we allocate a new instance of dagger, we must use // the create arguments of xecollider: x, y and direction local real angle = Atan2(targety-y, targetx-x) local dagger this = dagger.allocate(x,y,angle) // Now we set the xefx properties: set this.fxpath = DAGGER_MODEL set this.scale = DAGGER_MODEL_SCALE set this.z = DAGGER_FLYHEIGHT // And the xecollider properties: set this.speed = DAGGER_SPEED set this.expirationTime = DAGGER_DISTANCE/DAGGER_SPEED set this.collisionSize = DAGGER_COLLISION_SIZE endmethod endstruct private function SpellCast takes nothing returns nothing local unit caster = SpellEvent.CastingUnit local dagger d = dagger.create(GetUnitX(caster), GetUnitY(caster), SpellEvent.TargetX, SpellEvent.TargetY) set d.level = GetUnitAbilityLevel(caster, SPELL_ID) set d.caster = caster endfunction private function Init takes nothing returns nothing call RegisterSpellEffectResponse(SPELL_ID, SpellCast) endfunction endscope Quick note: I'm also using SpellEvent. |
