HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Spell help (and stuff) plox.

07-12-2008, 09:18 AM#1
darkwulfv
So, I've seen a person or two ask for continued help on making a spell work/coded better, and I thought "hell, why not try it?"

BEFORE WE BEGIN: Can channeling spells be picked up by "Unit starts the effect of an ability"? (I'm almost 100% sure they can, I've used it for other channeling spells before)

So, I went about making a (IMO) cool spell. Problem is, something must be crashing the thread before it even starts (wut?), because nothing happens when I cast it, and the BJDebugMsg's don't appear (not even the first one). So, a little spell background first:

Breath of the Dragons
Spell is based on Starfall (for the channeling). What the spell is supposed to do is summon a large dragon over the caster's head (just for show). After that happens, all the units along an area (line segment, actually) are damaged over and over (thanks Ammorth for the GroupEnumUnitsInRangeOfSeg scripts), and... stuff. I use Acid Bomb for a missile (thanks to Callahan for his dragon head missile models), and also a detection; it leaves a buff on the target(s) long enough to be detected so the unit can be damaged (I have to trigger all spell damage for my map).

Yes, I use constant functions. It's because they're easy to edit and get inlined by Vex's optimizer anyways.

BotD_Type is the "element" for this spell. You don't need to worry about it for any reason.

I just started going a little farther in-depth with structs and stuff, so if you see ways I might be messing up or ways to work it better, don't hesitate to let me know.

Finally, any optimizations (besides inlining the constants) you find, point them out to me.


Well, with that aside, here's the code:
Collapse JASS:
constant function BotD_Ability_ID takes nothing returns integer
  return 'A001'
endfunction

constant function BotD_Dummy_ID takes nothing returns integer
  return 'A005'
endfunction

constant function BotD_Buff_ID takes nothing returns integer
  return 'B003'
endfunction

constant function BotD_Dragon_ID takes nothing returns integer
  return 'e001'
endfunction

constant function BotD_DummyCaster_ID takes nothing returns integer
  return 'e002'
endfunction

constant function BotD_Range takes nothing returns real
  return 500. //+ (level * 50)
endfunction

constant function BotD_Width takes nothing returns real
  return 300.
endfunction

constant function BotD_Damage takes nothing returns real
  return 30. //+ (level * 15)
endfunction

constant function BotD_Type takes nothing returns integer
  return 1 //Fire
endfunction

constant function BotD_Interval takes nothing returns real
  return .5 //Interval between waves
endfunction

constant function BotD_Check_Interval takes nothing returns real
  return .25 //Interval between checking units in the major group
endfunction

constant function BotD_Effect takes nothing returns string
  return "Abilities\\Spells\\Other\\Awaken\\Awaken.mdl"
endfunction

struct BotDDamage
  unit caster
  unit dragon
  integer level
  real facing
  real endpointX
  real endpointY
  
  static method create takes unit caster, integer lvl, real endX, real endY returns BotDDamage
    local BotDDamage data = BotDDamage.allocate()
    
    set data.caster = caster
    set data.level = lvl
    set data.endpointX = endX
    set data.endpointY = endY
    
    return data
  endmethod  
  
endstruct

globals
  group BotD_Targets = CreateGroup()
endglobals

function Breath_of_the_Dragons_Filter takes nothing returns boolean
  local unit u = GetFilterUnit()
  local boolean b1 = IsUnitEnemy(u, GetOwningPlayer(GetTriggerUnit()))
  local boolean b2 = GetWidgetLife(u) > .405
  local boolean b3 = GetUnitAbilityLevel(u, 'Avul') == 0
  local boolean b4 = IsUnitInGroup(u, BotD_Targets) == false
  
  set u = null
  return b1 and b2 and b3 and b4
endfunction

function Breath_of_the_Dragons_Grouping takes nothing returns nothing
  local BotDDamage data = GetHandleInt(GetExpiredTimer(), "BotDWave")
  local unit u = data.caster
  local real x = data.endpointX
  local real y = data.endpointY
  local group g = CreateGroup()
  local unit f
  local real x2 = GetUnitX(u)
  local real y2 = GetUnitY(u)
call BJDebugMsg("Grouping enemies")
  if OrderId2String(GetUnitCurrentOrder(u)) == "starfall" then
    call GroupEnumUnitsInRangeOfSegNoCurveA(g, x2, y2, x, y, BotD_Width(), Condition(function Breath_of_the_Dragons_Filter))
    call SetUnitAnimation(data.dragon, "attack" )
    
    loop
    set f = FirstOfGroup(g)
      exitwhen f == null
      call IssueTargetOrder(CreateDummyUnit(GetOwningPlayer(u), BotD_DummyCaster_ID(), x2, y2, BotD_Dummy_ID(), data.level, true), "acidbomb", f)
      call GroupAddUnit(BotD_Targets, f)
      call GroupRemoveUnit(g, f)
    endloop
  else
    call DestroyEffect(AddSpecialEffect(BotD_Effect(), x2, y2))
    call PauseTimer(GetExpiredTimer())
    call DestroyTimer(GetExpiredTimer())
    call KillUnit(data.dragon)
  endif
call BJDebugMsg("Enemies Grouped: " + I2S(CountUnitsInGroup(g))) 
  call DestroyGroup(g)  
  set u = null
  set g = null
  set f = null
  call data.destroy()
endfunction

function Breath_of_the_Dragons_Filter2 takes nothing returns boolean
  local unit u = GetFilterUnit()
  local boolean b1 = GetUnitAbilityLevel(u, BotD_Buff_ID()) > 0
  local boolean b2 = GetWidgetLife(u) > .405
  local boolean b3 = GetUnitAbilityLevel(u, 'Avul') == 0
  
  set u = null
  return b1 and b2 and b3
endfunction
  
function Breath_of_the_Dragons_Damage takes nothing returns nothing
  local timer t = GetExpiredTimer()
  local BotDDamage data = GetHandleInt(t, "BotDInter")
  local unit u = data.caster
  local integer lvl = data.level
  local group g = CreateGroup()
  local unit f 
call BJDebugMsg("Checking for damagable targets")  
  if CountUnitsInGroup(BotD_Targets) > 0 then
    call GroupAddGroup(BotD_Targets, g)
    loop
    set f = FirstOfGroup(g)
      exitwhen f == null
      call UnitDamageTarget(u, f, ArmorDamage(f, BotD_Damage() + (lvl * 15), BotD_Type()), false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
      call GroupRemoveUnit(g, f)
      call GroupRemoveUnit(BotD_Targets, f)
    endloop
  endif
  
  call DestroyGroup(g)
  call data.destroy()
  set t = null
  set u = null
  set g = null
  set f = null  
endfunction

function Breath_of_the_Dragons_Conditions takes nothing returns boolean
    return ( GetSpellAbilityId() == BotD_Ability_ID() )
endfunction

function Breath_of_the_Dragons_Actions takes nothing returns nothing
  local unit u = GetTriggerUnit()
  local real x = GetUnitX(u)
  local real y = GetUnitY(u)
  local real facing = GetUnitFacing(u)
  local unit u2 
  local integer lvl = GetUnitAbilityLevel(u, BotD_Ability_ID())
  local real distance = BotD_Range() + (lvl * 50)
  local timer DamageT = CreateTimer()
  local timer WaveT = CreateTimer()
  local real endX = x +(distance*Cos(facing))
  local real endY = y +(distance*Sin(facing))
  local BotDDamage Dam = BotDDamage.create(u, lvl, endX, endY)
  
call BJDebugMsg("Casted... Summoning Dragon.")  
  call DestroyEffect(AddSpecialEffect(BotD_Effect(), x, y))
  call PolledWait(1.7)
  set u2 = CreateUnit(GetOwningPlayer(u), BotD_Dragon_ID(), x, y, facing)
  set Dam.dragon = u2
call BJDebugMsg("Dragon Summoned. Starting timers.")
  call SetUnitTimeScale(u2, 166.00 * .01 )
  
  call TimerStart(WaveT, BotD_Interval(), true, function Breath_of_the_Dragons_Grouping)
  call TimerStart(DamageT, BotD_Check_Interval(), true, function Breath_of_the_Dragons_Damage)
call BJDebugMsg("Timers Started.")
  call SetHandleInt(WaveT, "BotDWave", Dam)
  call SetHandleInt(DamageT, "BotDInter", Dam)
call BJDebugMsg("Structs Applied.")  
  call PolledWait(15.)
call BJDebugMsg("Cleaning up")
  call PauseTimer(WaveT)
  call DestroyTimer(WaveT)
  call PauseTimer(DamageT)
  call DestroyTimer(DamageT)
  call Dam.destroy()
  call KillUnit(u2)
  
  set u = null
  set u2 = null
  set DamageT = null
endfunction

//===========================================================================
function InitTrig_Breath_of_the_Dragons takes nothing returns nothing
  local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(t, Condition( function Breath_of_the_Dragons_Conditions ) )
    call TriggerAddAction(t, function Breath_of_the_Dragons_Actions )
  set t = null
endfunction


Thanks to any and all who help. Knowing my luck, this'll be some stupid error right under my nose.
07-12-2008, 12:42 PM#2
Castlemaster
Try this:
Instead of "EVENT_PLAYER_UNIT_SPELL_EFFECT" use "EVENT_PLAYER_UNIT_SPELL_CHANNEL". Channel spells use a different event. Also, yes you can use "unit begins casting a spell" on any spell. If you are also wanting to make the channel spell be able to be interrupted let me know and I can show you how it's done.
07-12-2008, 01:03 PM#3
Alexander244
Collapse JASS:
function Breath_of_the_Dragons_Actions takes nothing returns nothing
  local unit u = GetTriggerUnit()
  local real x = GetUnitX(u)
  local real y = GetUnitY(u)
  local real facing = GetUnitFacing(u)
  local unit u2 
  local integer lvl = GetUnitAbilityLevel(u, BotD_Ability_ID())
  local real distance = BotD_Range() + (lvl * 50)
  local timer DamageT = CreateTimer()
  local real endX = x +(distance*Cos(facing))
  local real endY = y +(distance*Sin(facing))
  local BotDDamage Dam = BotDDamage.create(u, u2, lvl, endX, endY)
  //...

Using an uninitialized variable is killing the thread.

---
EVENT_PLAYER_UNIT_SPELL_EFFECT works fine for channeling spells.

Please use a scope instead of prefixing, and use constant globals for values that won't need config calculations/similar (mainly Id's and model paths). It makes it easier to read.
07-12-2008, 01:21 PM#4
darkwulfv
Quote:
Using an uninitialized variable is killing the thread.
...Oh for fuck's sake. I KNEW it would be something stupid.

Quote:
Please use a scope instead of prefixing, and use constant globals for values that won't need config calculations/similar (mainly Id's and model paths). It makes it easier to read.
I wasn't gonna rewrite the code for my spell simply to ask for help; if I planned on submitting it I would, and in the event I do, I will. This is how I write my code for my map. But thanks anyways for spotting that. Isn't JASShelper supposed to spot that? (unintialized variable u2 would be the error). Dunno why it never came up.
07-12-2008, 01:23 PM#5
Alexander244
war3err from Grimoire spots that sort of stuff.
07-12-2008, 01:25 PM#6
darkwulfv
Well, we can't use war3err cause of patch 1.22. Would've fixed this ages ago if we could =(
07-12-2008, 01:48 PM#7
darkwulfv
Okay, so the spell is still crashing somewhere. I think I've pinpointed where, but I can't determine why. Here's the segments of code I believe may have something to do with it (full code still @ top)

The BJDebugMsgs do not show from the first timer callback. The BJDebugMsg that displays after the timers (Timers Started) does show up, but the PolledWait never seems to happen, because "Cleaning Up" is never displayed.

Collapse JASS:
function Breath_of_the_Dragons_Grouping takes nothing returns nothing
  local BotDDamage data = GetHandleInt(BotD_WaveT, "BotDWave")
  local unit u = data.caster
  local real x = data.endpointX
  local real y = data.endpointY
  local group g = CreateGroup()
  local unit f
  local real x2 = GetUnitX(u)
  local real y2 = GetUnitY(u)
call BJDebugMsg("Grouping enemies")
  if OrderId2String(GetUnitCurrentOrder(u)) == "starfall" then
    call GroupEnumUnitsInRangeOfSegNoCurveA(g, x2, y2, x, y, BotD_Width(), Condition(function Breath_of_the_Dragons_Filter))
    call SetUnitAnimation(data.dragon, "attack" )
    
    loop
    set f = FirstOfGroup(g)
      exitwhen f == null
      call IssueTargetOrder(CreateDummyUnit(GetOwningPlayer(u), BotD_DummyCaster_ID(), x2, y2, BotD_Dummy_ID(), data.level, true), "acidbomb", f)
      call GroupAddUnit(BotD_Targets, f)
      call GroupRemoveUnit(g, f)
    endloop
  else
    call DestroyEffect(AddSpecialEffect(BotD_Effect(), x2, y2))
    call PauseTimer(BotD_WaveT)
    call KillUnit(data.dragon)
  endif
call BJDebugMsg("Enemies Grouped: " + I2S(CountUnitsInGroup(g))) 
  call DestroyGroup(g)  
  set u = null
  set g = null
  set f = null
  call data.destroy()
endfunction

function Breath_of_the_Dragons_Damage takes nothing returns nothing
  local timer t = GetExpiredTimer()
  local BotDDamage data = GetHandleInt(t, "BotDInter")
  local unit u = data.caster
  local integer lvl = data.level
  local group g = CreateGroup()
  local unit f 
  
  if CountUnitsInGroup(BotD_Targets) > 0 then
    call GroupAddGroup(BotD_Targets, g)
    loop
    set f = FirstOfGroup(g)
      exitwhen f == null
      call UnitDamageTarget(u, f, ArmorDamage(f, BotD_Damage() + (lvl * 15), BotD_Type()), false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
      call GroupRemoveUnit(g, f)
      call GroupRemoveUnit(BotD_Targets, f)
    endloop
  endif
  
  call DestroyGroup(g)
  call data.destroy()
  set t = null
  set u = null
  set g = null
  set f = null  
endfunction

  call TimerStart(BotD_WaveT, BotD_Interval(), true, function Breath_of_the_Dragons_Grouping)
  call TimerStart(DamageT, BotD_Check_Interval(), true, function Breath_of_the_Dragons_Damage)
call BJDebugMsg("Timers Started.")
  call SetHandleInt(BotD_WaveT, "BotDWave", Dam)
  call SetHandleInt(DamageT, "BotDInter", Dam)
  
  call PolledWait(15.)
call BJDebugMsg("Cleaning up")
  call PauseTimer(BotD_WaveT)
  call PauseTimer(DamageT)
  call DestroyTimer(DamageT)
  call Dam.destroy()
  call KillUnit(u2)
07-12-2008, 02:19 PM#8
Alexander244
Is the gamecache variable you're using pointing to something? I can't see any other reason why that script would fail.
07-12-2008, 04:43 PM#9
darkwulfv
It's pointing to gamecache, like normal. My Get/SetHandleInt functions work, I've used them elsewhere without a problem.