| 06-14-2008, 08:32 AM | #1 | |
I'm having 2 really annoying problems with a spell I'm making. The spell is supposed to make a nova of lightning bolts appear at regular intervals, but the bolts are moving at incorrect angles (looks triangular, when it should be square/diamond-shaped) Here's a screenshot to show you just how messed up it is
Here's the whole code: Full code:scope Request initializer SpellInit //HAIL INITIALIZATION MACROS - DO NOT TOUCH //! runtextmacro HAIL_CreateProperty ("Data", "integer", "private") //! runtextmacro HAIL_CreateProperty ("Int", "integer", "private") //! runtextmacro HAIL_CreatePairProperty ("UnitAffected", "string", "private") //Constants and config functions globals private constant integer SPELLID = 'ACsh' //Spell rawcode private constant integer PROJID = 'h000' //Dummy unit rawcode private constant real TIMERINT = 0.03 //Frequency at which the timer will run private constant real DETECTRANGE = 75 private constant real CSPEED = 900 //Movement speed of the caster per second private constant real PSPEED = 700 //Speed of the projectiles per second private constant real CDIST = TIMERINT * CSPEED //Move distance per timer interval of caster private constant real PDIST = TIMERINT * PSPEED //Move distance per timer interval of projectile private constant boolean LIGHTON = true //Determines whether the ring of lightning will be created private constant string LIGHTID = "CLPB" //Lightning effect used for TimedLightning private constant real INITFADE = 1 //Initial alpha value for TimedLightning private constant real ENDFADE = 0 //Ending fade value for TimedLightning private constant boolean FADEON = true //Determines if TimedLightning fades or not private constant real LIGHTZ = 0 //Height of the lightning above ground private constant real INITRAD = 150 //Amount of distance away from caster that projectiles spawn at. Also determines TimedLightning initial radius private constant string EFFECTSTRING = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl" //Special effect created private constant real EFFECTINT = 0.15 //Frequency of effect spawning private constant integer PROJCOUNT = 4 //Number of projectiles spawned private constant real ANGLEINC = 360/I2R (PROJCOUNT) private constant string ENDFX = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" //The effect created when the projectiles die. Will also determine vertex count for TimedLightning private constant boolean TARGETFX = true //Determines whether ENDFX is displayed on units as well private constant string ATTACHPOINT = "origin" //Attachment point of ENDFX if TARGETFX is true private constant attacktype AT = ATTACK_TYPE_HERO //Attack type of damage dealt private constant damagetype DT = DAMAGE_TYPE_NORMAL //Damage type of damage dealt private constant weapontype WT = null //Weapon type of damage dealt (null or WEAPON_TYPE_WHOKNOWS will result in no sound played) private unit gcaster endglobals private function Damage takes unit u returns real local real base = 30 local real m1 = I2R (GetUnitAbilityLevel (u, SPELLID)) local real m2 = 15 return base + m1 * m2 endfunction private function ProjDist takes unit u returns real local real base = 300 local real m1 = I2R (GetUnitAbilityLevel (u, SPELLID)) local real m2 = 50 return 400.//base + m1 * m2 endfunction //END OF CONFIGURATION\\ //DONT TOUCH ANYTHING BELOW HERE\\ private function SetUnitXY takes unit u, real x, real y returns nothing if x<GetRectMaxX(bj_mapInitialPlayableArea) and x>GetRectMinX(bj_mapInitialPlayableArea) and y<GetRectMaxY(bj_mapInitialPlayableArea) and y>GetRectMinY(bj_mapInitialPlayableArea) then call SetUnitX(u,x) call SetUnitY(u,y) endif endfunction private struct SpellData unit caster real curdist = 0 real maxdist real timerticks = 0 timer movetimer boolean castermoving = true real angle group g = CreateGroup () unit array proj[PROJCOUNT] real array damage[PROJCOUNT] real array pcurdist[PROJCOUNT] real array pmaxdist[PROJCOUNT] real array pangle[PROJCOUNT] timer projtimer trigger projtrigger method onDestroy takes nothing returns nothing local integer i = 0 local real x local real y local unit u call ResetAnyUnitAffectedFor (.caster) call BJDebugMsg (I2S (CountUnitsInGroup (.g))) loop exitwhen i == PROJCOUNT set x = GetUnitX (.proj[i]) set y = GetUnitY (.proj[i]) call DestroyEffect (AddSpecialEffect (ENDFX, x, y)) call KillUnit (.proj[i]) set i = i + 1 endloop set u = FirstOfGroup (.g) loop exitwhen u == null call ResetInt (u) call BJDebugMsg (I2S (GetInt (u))) call GroupRemoveUnit (.g, u) set u = FirstOfGroup (.g) endloop call GroupClear (.g) call DestroyGroup (.g) call PauseTimer (.movetimer) call ResetData (.movetimer) call DestroyTimer (.movetimer) call PauseTimer (.projtimer) call ResetData (.projtimer) call DestroyTimer (.projtimer) call ResetData (.projtrigger) call DestroyTrigger (.projtrigger) endmethod endstruct private function ProjTimerCallback takes nothing returns nothing local SpellData a = GetData (GetExpiredTimer ()) local real x// = GetUnitX (a.proj) local real y// = GetUnitY (a.proj) local real x2 local real y2 local integer i = 0 local boolean tempbool = false loop exitwhen i == PROJCOUNT set x = GetUnitX (a.proj[i]) set y = GetUnitY (a.proj[i]) set x2 = x + Cos (a.pangle[i]) * PDIST set y2 = y + Sin (a.pangle[i]) * PDIST call SetUnitXY (a.proj[i], x2, y2) set a.pcurdist[i] = a.pcurdist[i] + PDIST if a.pcurdist[i] >= a.pmaxdist[i] then loop exitwhen tempbool == true call a.destroy () set tempbool = true endloop endif set i = i + 1 endloop endfunction private function ProjFunc takes nothing returns boolean local SpellData a = GetData (GetTriggeringTrigger ()) local unit u = GetTriggerUnit () if GetWidgetLife (u) > .405 and IsUnitEnemy (u, GetOwningPlayer (a.caster)) then if GetInt (u) != 1 then call UnitDamageTarget (a.caster, u, a.damage, false, true, AT, DT, WT) call GroupAddUnit (a.g, u) call SetInt (u, 1) if TARGETFX == true then call DestroyEffect (AddSpecialEffectTarget (ENDFX, u, ATTACHPOINT)) endif endif endif set u = null return true endfunction private function MoveTimerCallback takes nothing returns nothing local SpellData a = GetData (GetExpiredTimer ()) local real x1 = GetUnitX (a.caster) local real y1 = GetUnitY (a.caster) local real cx = x1 + Cos (a.angle) * CDIST local real cy = y1 + Sin (a.angle) * CDIST local integer i = 0 local real angle = 0//GetRandomReal (0, 360) local real range = ProjDist (a.caster) local real duration = range/PSPEED local real sx local real sy local real irad = INITRAD local real erad = INITRAD + range if a.castermoving == true then set a.curdist = a.curdist + CDIST set a.timerticks = a.timerticks + TIMERINT call SetUnitXY (a.caster, cx, cy) if a.timerticks >= EFFECTINT then call DestroyEffect (AddSpecialEffect (EFFECTSTRING, cx, cy)) set a.timerticks = a.timerticks - EFFECTINT endif if a.curdist >= a.maxdist then call PauseTimer (a.movetimer) //call PauseUnit (a.caster, false) set a.castermoving = false set a.projtimer = CreateTimer () call SetData (a.projtimer, a) call TimerStart (a.projtimer, TIMERINT, true, function ProjTimerCallback) set a.projtrigger = CreateTrigger () loop exitwhen i == PROJCOUNT set a.pcurdist[i] = 0 set a.damage[i] = Damage (a.caster) set a.pmaxdist[i] = ProjDist (a.caster) set angle = angle + ANGLEINC call BJDebugMsg (R2S (angle)) set a.pangle[i] = angle set sx = cx + Cos (angle) * INITRAD set sy = cy + Sin (angle) * INITRAD set a.proj[i] = CreateUnit (GetOwningPlayer (a.caster), PROJID, cx, cy, 0) call TriggerRegisterUnitInRange (a.projtrigger, a.proj[i], DETECTRANGE, Condition (function ProjFunc)) set i = i + 1 endloop endif endif endfunction private function SpellCond takes nothing returns boolean return GetSpellAbilityId () == SPELLID endfunction private function SpellActions takes nothing returns nothing local SpellData a = SpellData.create () local location l = GetSpellTargetLoc () local unit u = GetTriggerUnit () local real ux = GetUnitX (u) local real uy = GetUnitY (u) local real tx = GetLocationX (l) local real ty = GetLocationY (l) local real x = tx - ux local real y = ty - uy set a.maxdist = SquareRoot ((x*x) + (y*y)) set a.angle = Atan2 (y, x) set a.caster = u //call PauseUnit (u, true) set a.movetimer = CreateTimer () call SetData (a.movetimer, a) call TimerStart (a.movetimer, TIMERINT, true, function MoveTimerCallback) call RemoveLocation (l) set l = null set u = null call BJDebugMsg ("Trigger fired") endfunction private function SpellInit takes nothing returns nothing local trigger t = CreateTrigger () call TriggerRegisterAnyUnitEvent (t, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition (t, Condition (function SpellCond)) call TriggerAddAction (t, function SpellActions) endfunction endscope And here's the code associated with proj spawning and movement (to narrow the search for the problem down :P) Proj spawn and movement:private function ProjTimerCallback takes nothing returns nothing local SpellData a = GetData (GetExpiredTimer ()) local real x// = GetUnitX (a.proj) local real y// = GetUnitY (a.proj) local real x2 local real y2 local integer i = 0 local boolean tempbool = false loop exitwhen i == PROJCOUNT set x = GetUnitX (a.proj[i]) set y = GetUnitY (a.proj[i]) set x2 = x + Cos (a.pangle[i]) * PDIST set y2 = y + Sin (a.pangle[i]) * PDIST call SetUnitXY (a.proj[i], x2, y2) set a.pcurdist[i] = a.pcurdist[i] + PDIST if a.pcurdist[i] >= a.pmaxdist[i] then loop exitwhen tempbool == true call a.destroy () set tempbool = true endloop endif set i = i + 1 endloop endfunction private function MoveTimerCallback takes nothing returns nothing local SpellData a = GetData (GetExpiredTimer ()) local real x1 = GetUnitX (a.caster) local real y1 = GetUnitY (a.caster) local real cx = x1 + Cos (a.angle) * CDIST local real cy = y1 + Sin (a.angle) * CDIST local integer i = 0 local real angle = 0//GetRandomReal (0, 360) local real range = ProjDist (a.caster) local real duration = range/PSPEED local real sx local real sy local real irad = INITRAD local real erad = INITRAD + range if a.castermoving == true then set a.curdist = a.curdist + CDIST set a.timerticks = a.timerticks + TIMERINT call SetUnitXY (a.caster, cx, cy) if a.timerticks >= EFFECTINT then call DestroyEffect (AddSpecialEffect (EFFECTSTRING, cx, cy)) set a.timerticks = a.timerticks - EFFECTINT endif if a.curdist >= a.maxdist then call PauseTimer (a.movetimer) //call PauseUnit (a.caster, false) set a.castermoving = false set a.projtimer = CreateTimer () call SetData (a.projtimer, a) call TimerStart (a.projtimer, TIMERINT, true, function ProjTimerCallback) set a.projtrigger = CreateTrigger () loop exitwhen i == PROJCOUNT set a.pcurdist[i] = 0 set a.damage[i] = Damage (a.caster) set a.pmaxdist[i] = ProjDist (a.caster) set angle = angle + ANGLEINC call BJDebugMsg (R2S (angle)) set a.pangle[i] = angle set sx = cx + Cos (angle) * INITRAD set sy = cy + Sin (angle) * INITRAD set a.proj[i] = CreateUnit (GetOwningPlayer (a.caster), PROJID, cx, cy, 0) call TriggerRegisterUnitInRange (a.projtrigger, a.proj[i], DETECTRANGE, Condition (function ProjFunc)) set i = i + 1 endloop endif endif endfunction My second problem... I'm trying to prevent units being affected multiple times by different projectiles using HAIL's pair attachment, but units who are affected once can't be hit by the spell anymore. I'm fairly sure I cleared the pair data correctly. I've tried using groups (the units don't seem to be in the group, and I've tried 3 different types to attach to the unit (boolean, string and integer) and none of them seem to work :( Here's the code associated with projectile hit Proj hit:private struct SpellData unit caster real curdist = 0 real maxdist real timerticks = 0 timer movetimer boolean castermoving = true real angle group g = CreateGroup () unit array proj[PROJCOUNT] real array damage[PROJCOUNT] real array pcurdist[PROJCOUNT] real array pmaxdist[PROJCOUNT] real array pangle[PROJCOUNT] timer projtimer trigger projtrigger method onDestroy takes nothing returns nothing local integer i = 0 local real x local real y local unit u call ResetAnyUnitAffectedFor (.caster) call BJDebugMsg (I2S (CountUnitsInGroup (.g))) loop exitwhen i == PROJCOUNT set x = GetUnitX (.proj[i]) set y = GetUnitY (.proj[i]) call DestroyEffect (AddSpecialEffect (ENDFX, x, y)) call KillUnit (.proj[i]) set i = i + 1 endloop set u = FirstOfGroup (.g) loop exitwhen u == null call ResetInt (u) call BJDebugMsg (I2S (GetInt (u))) call GroupRemoveUnit (.g, u) set u = FirstOfGroup (.g) endloop call GroupClear (.g) call DestroyGroup (.g) call PauseTimer (.movetimer) call ResetData (.movetimer) call DestroyTimer (.movetimer) call PauseTimer (.projtimer) call ResetData (.projtimer) call DestroyTimer (.projtimer) call ResetData (.projtrigger) call DestroyTrigger (.projtrigger) endmethod endstruct private function ProjFunc takes nothing returns boolean local SpellData a = GetData (GetTriggeringTrigger ()) local unit u = GetTriggerUnit () if GetWidgetLife (u) > .405 and IsUnitEnemy (u, GetOwningPlayer (a.caster)) then if GetInt (u) != 1 then call UnitDamageTarget (a.caster, u, a.damage, false, true, AT, DT, WT) call GroupAddUnit (a.g, u) call SetInt (u, 1) if TARGETFX == true then call DestroyEffect (AddSpecialEffectTarget (ENDFX, u, ATTACHPOINT)) endif endif endif set u = null return true endfunction And I don't really want posts about "this could be better" and such, I know that the code isn't great at the moment. I just want to fix the spell, then I can clean it up and improve it. |
| 06-14-2008, 09:10 AM | #2 |
The problem is as much as i get it that you're using degree instead of Rad. There are 2 solutions for this problem. 1. Setting the variable ANGLEINC to 2*bj_PI/PROJCOUNT 2. Using a BJ_DEGTORAD in the MoveTimerCallback |
| 06-14-2008, 09:18 AM | #3 |
... that was a stupid mistake, thanks :D Now the projectile movement works perfectly, just need to fix the collision problem |
| 06-17-2008, 10:46 PM | #4 |
Bump Anyone have any idea why the attachment doesn't get cleared? |
| 06-18-2008, 02:20 AM | #5 | |
Quote:
From the HAIL thread, pair properties are currently bugged. Out of curiosity are you using HAIL just for pair properties? It is very easy and probably equally as fast to use GC for pair data. |
| 06-18-2008, 02:25 AM | #6 |
Furthermore, I can't think of a reason why this spell needs attaching... just store everything in a stack and loop through it. Uses only one Timer, Requires no outside Code, and Probably Faster. |
| 06-18-2008, 04:32 AM | #7 | |
Quote:
It should still work if you're only using one pair property, though. Turn on debug mode and see if it prints out created/destroyed at the correct times. |
| 06-18-2008, 10:56 AM | #8 | |||
Quote:
I'm using it for single attachment (if that's what you'd call it) as well i.e. struct to timer, struct to trigger. At first I thought it was a pair attachment issue so I tried to put the affected units into a group and attach a boolean only to the affected unit, then reset it in a ForGroup, but that didn't work either. Quote:
How exactly would I go about doing that? I'm not familiar with alot of that NewGen stuff (I assume it's a NewGen feature?) Quote:
I've heard that so many times, and here's the reason why: I don't know how to do it (correctly). |
| 06-18-2008, 11:06 AM | #9 | |
Quote:
http://www.wc3campaigns.net/showpost...5&postcount=11 |
| 06-18-2008, 11:37 AM | #10 |
Ah, I see :) Thanks for the info. I'll see what I can do. My only problem is how to check if a unit is already affected by that instance? Should I use a group within the struct? Or is there any other way that wouldn't cause MUI problems (I can see global arrays causing MUI problems :\) |
| 06-18-2008, 12:15 PM | #11 |
Using a group within the struct would be the easiest and fastest way to do it. |
| 06-18-2008, 12:26 PM | #12 |
Let's hope units are added to the group this time around ![]() |
| 06-18-2008, 12:49 PM | #13 |
Make sure you are creating the group with CreateGroup() before you add units to it with GroupAddUnit(). |
| 06-18-2008, 01:19 PM | #14 | |
Quote:
If I didn't do that, I would've got unitialized variable error anyway (which I didn't when I tried groups), right? |
| 06-18-2008, 01:54 PM | #15 |
If you are using grimore with wc3err on, then yes you would have. |
