HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

EquippedChaos Presents: Phoenix Circle

08-03-2008, 10:30 AM#1
EquippedChaos
Hey! This is currently Darkwulfv. Goldendercon and I formed a spellmaking duo called "EquippedChaos". We completed our first spell today, and I decided to come here for some last minute feedback before we moved into our next spell.

So, our first spell is an "omgwtfbbqhax" spell, in that it's insane with SFX and damage and stuff (I weakened it a bit, but making it more insane is easy). We decided to come in with a "BANG!", and this was the result.

Any script optimizations, etc. are appreciated.

NOTE: I've got a bit of a dilemma. This spell has a cooldown greater than its channel time, which is fine and dandy. But if, for some reason, someone were to lower the cooldown and cast the spell again while the first instance were active, the SFX do not die. I'd like to know the best way to accomplish this without ruining any MUI this spell has. Thanks!

Now, first off: The code:
Collapse JASS:
scope PhoenixCircle initializer Init

globals
//ID of the spell
  private constant integer ABILITY_ID = 'A000'
  
//ID of the dummy spell
  private constant integer DUMMY_ID = 'A003'
  
//ID of the unit that will move around the caster
  private constant integer DUMMY_UNIT = 'h000'
  
//ID of the dummy caster
  private constant integer DUMMY_CASTER = 'h001'
  
//Number of air dummies
  private constant integer AIR_DUMMIES = 6
  
//Number of ground dummies
  private constant integer GROUND_DUMMIES = 18  
  
//Number of Flame Strikes
  private constant integer FLAME_STRIKES = 6
  
//Distance from the caster air dummies will be  
  private constant real AIR_DISTANCE = 200.
  
//Distance from the caster ground dummies will be
  private constant real GROUND_DISTANCE = 300.  
  
//Interval of the movement timer
//Higher values = less chance of lag, choppier movement
//Lower values = more chance of lag, smoother movement
//.035 is usually very good.
  private constant real TIMER_INTERVAL = .035   
  
//The height the dummies will float to  
  private constant real DUMMY_HEIGHT = 450.
  
//The height the caster will float to
  private constant real CASTER_HEIGHT = 225.  
  
//How long the spell will last (maximum), in seconds
//Always make it about 1/2 second less, for accuracy with effects
  private constant real SPELL_DURATION = 15
  
//Order string for the casting spell
  private constant string CAST_STRING = "starfall"  
  
//Order string for the dummy spell
  private constant string ORDER_STRING = "flamestrike"
  
//Path the dummy uses for its model
//This is here for preloading purposes, to stop cast-time lag.
  private constant string DUMMY_MODEL = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl"
endglobals


//NO TOUCHY!!! (from here and below)
struct PhoenixCircleData
  unit caster = null
  unit array dummies[8191]
  unit array groundies[8191]
  real rotation = 0
  timer runtimer = null
endstruct  

globals 
  private group PhoenixCasters = CreateGroup()
endglobals

private function Callback takes nothing returns nothing
  local timer t = GetExpiredTimer()
  local PhoenixCircleData dat = GetHandleInt(t, "PhoenixData")
  local unit F = dat.caster
  local unit dummy
  local integer i = 1
  local real x = GetUnitX(F)
  local real y = GetUnitY(F)
  local real x2 = 0
  local real y2 = 0
  local real angle = 0
  local real rotation = dat.rotation
  
  loop
  exitwhen i > AIR_DUMMIES
    set dummy = dat.dummies[i]
    set angle = (Atan2(GetUnitY(dummy) - y, GetUnitX(dummy) - x)) + (bj_DEGTORAD *5.)
    set x2 = x + AIR_DISTANCE * Cos(angle)
    set y2 = y + AIR_DISTANCE * Sin(angle)
    call SetUnitPosition(dummy, x2, y2)
    set i = i + 1
  endloop

  set i = 1
  set rotation = rotation + 5. 
  set angle = 0
  
  loop
    exitwhen i > GROUND_DUMMIES
    set angle = rotation + ((360 / GROUND_DUMMIES) * i)
    set x2 = x + GROUND_DISTANCE * Cos(angle * bj_DEGTORAD)
    set y2 = y + GROUND_DISTANCE * Sin(angle * bj_DEGTORAD)
    call SetUnitPosition(dat.groundies[i], x2, y2)
    set i = i + 1
  endloop
  set dat.rotation = rotation    
    
  set F = null
  set dummy = null
  set t = null
endfunction  
  
private function EmergencyClean takes unit caster returns boolean
  local PhoenixCircleData C = GetHandleInt(caster, "EmergencyClean")
  local integer i = 1
  
  loop
    exitwhen i > AIR_DUMMIES
    call RemoveUnit(C.dummies[i])
    set i = i + 1
  endloop
  
  set i = 1
  
  loop
    exitwhen i > GROUND_DUMMIES
    call RemoveUnit(C.groundies[i])
    set i = i + 1
  endloop
  
  call PauseTimer(C.runtimer)
  call DestroyTimer(C.runtimer)
  
  call SetUnitFlyHeight(caster, 0, 99999.)
  
  return true
endfunction
  
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == ABILITY_ID
endfunction

private function Actions takes nothing returns nothing
  local unit dummy
  local unit C = GetTriggerUnit()
  local integer lvl = GetUnitAbilityLevel(C, ABILITY_ID)
  local integer i = 1
  local real X = GetUnitX(C)
  local real Y = GetUnitY(C)
  local real R = 360. / AIR_DUMMIES
  local real x2 = 0
  local real y2 = 0
  local player p = GetOwningPlayer(C)
  local timer t = CreateTimer()
  local boolean DoubleCast = false
  local PhoenixCircleData Data = PhoenixCircleData.create()
  set Data.caster = C
  set Data.runtimer = t
  
  if IsUnitInGroup(C, PhoenixCasters) then
    set DoubleCast = EmergencyClean(C)
    call GroupRemoveUnit(PhoenixCasters, C)
  endif
  call GroupAddUnit(PhoenixCasters, C)
  
  call UnitAddAbility(C, 'Amrf')
  call UnitRemoveAbility(C, 'Amrf')  
    
  call SetUnitFlyHeight(C, CASTER_HEIGHT, 75.)  
  if AIR_DUMMIES > 0 then
  loop
    exitwhen i > AIR_DUMMIES
    set x2 = X + AIR_DISTANCE * Cos((R * i) * bj_DEGTORAD)
    set y2 = Y + AIR_DISTANCE * Sin((R * i) * bj_DEGTORAD)
    set Data.dummies[i] = CreateUnit(p, DUMMY_UNIT, x2, y2, GetRandomReal(0, 360.))
    call SetUnitFlyHeight(Data.dummies[i], DUMMY_HEIGHT, 75.00 )
    set i = i + 1
  endloop
  endif
  
  set i = 1
  set x2 = 0
  set y2 = 0
  set R = 360. / GROUND_DUMMIES
  
  if GROUND_DUMMIES > 0 then
  loop
    exitwhen i > GROUND_DUMMIES
    set x2 = X + GROUND_DISTANCE * Cos((R * i) * bj_DEGTORAD) 
    set y2 = Y + GROUND_DISTANCE * Sin((R * i) * bj_DEGTORAD)
    set Data.groundies[i] = CreateUnit(p, DUMMY_UNIT, x2, y2, GetRandomReal(0, 360.))
    set i = i + 1
  endloop
  endif
  
  call SetHandleInt(C, "EmergencyClean", Data)
  call SetHandleInt(t, "PhoenixData", Data)
  call TimerStart(t, TIMER_INTERVAL, true, function Callback)
  
  loop
  exitwhen OrderId2String(GetUnitCurrentOrder(C)) != CAST_STRING
    call PolledWait(.2)
  endloop  
  
  call SetUnitFlyHeight(C, 0, 75.00 )
  
    set i = 1
    loop
      exitwhen i > AIR_DUMMIES
      call KillUnit(Data.dummies[i])
      set i = i + 1
    endloop
    set i = 1
    loop
    exitwhen i > GROUND_DUMMIES
      call KillUnit(Data.groundies[i])
      set i = i + 1
    endloop 
  
  call PauseTimer(t)
  call DestroyTimer(t)
  
  call PolledWait(1.)
  set i = 1
  set x2 = 0
  set y2 = 0
  set R = 0
  
  if FLAME_STRIKES > 0 and DoubleCast == false then
  loop
    exitwhen i > FLAME_STRIKES
    set dummy = CreateDummyUnit(p, DUMMY_CASTER, X, Y, DUMMY_ID, lvl, true)
    set x2 = X + (GROUND_DISTANCE * Cos(R * bj_DEGTORAD))
    set y2 = Y + (GROUND_DISTANCE * Sin(R * bj_DEGTORAD))
    call IssuePointOrder(dummy, ORDER_STRING, x2, y2)
    set R = ( R + ( 360.00 / FLAME_STRIKES ) )
    set i = i + 1
  endloop
  endif
  
  call GroupRemoveUnit(PhoenixCasters, C)
  call FlushHandleLocals(C)
  
  set dummy = null        
  set C = null
  set p = null
  set t = null
endfunction

//===========================================================================
private function Init takes nothing returns nothing
  local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction(t, function Actions )
    call Preload(DUMMY_MODEL)
  set t = null
endfunction
endscope

changelog

// v 1.0:
//First release

// v 1.1
//-Changed the code so the fire circles would die even if the spell had been double-casted; may cause spell to look strange
//-Changed the permanent immolation + Flame strike buffs to custom ones
//-Modified the instructions to make them easier to understand
//-Moved some functions to increase performance
//-Made dummies have random rotations when they're made to change look of spell



Just so you know, this spell is quite graphic intensive at the end, so lowering FLAME_STRIKES from 6 to 3-4 might be a good idea, since Flame Strike is a very graphic SFX.

Hope you guys enjoy our opening spell. More to come soon!
Attached Images
File type: jpgPhoenixCircle Screen.jpg (245.0 KB)
Attached Files
File type: w3xPhoenix Circle.w3x (26.8 KB)
08-04-2008, 02:36 AM#2
Vexorian
I wish there was an easier way to do teams that didn't require these dummy accounts, regarding your question, well, you don't really have to understand it, do you? Shouldn't you add a screenshot?
08-04-2008, 03:05 AM#3
EquippedChaos
Thank you Vex, much appreciated.

I don't get your reply to my question, but if it's not a big deal I won't worry about it.

Oh, and yeah, I probably should add a screenshot or two. I hope Wc3 doesn't F them up like usual.

EDIT: Sorry, no screenshots. They come up completely black, so nothing is seen. Can I ask someone to take a few for me?

Best shot: When the caster + air circle stops floating upwards. Phoenix Fire being shown is even better.
08-04-2008, 05:46 AM#4
Chuckle_Brother
Looks cool, was only able to take one as my net is being extra silly this morning.

http://i20.photobucket.com/albums/b2...Untitled-6.jpg
08-04-2008, 05:48 AM#5
Rising_Dusk
Quote:
Originally Posted by Vexorian
I wish there was an easier way to do teams that didn't require these dummy accounts
You could always make a way. :)
08-04-2008, 07:10 AM#6
Av3n
Why don't you just add the caster to a group of active casters and if it were to be cast again... you get the idea destroy or reset the spell or however you want to do it

-Av3n
08-04-2008, 03:45 PM#7
EquippedChaos
@Dusk: Yes, he should :D

@Chuckle Brother: Thanks a ton, you got a really good shot. Just gotta trim the edges.

@Av3n: That method could work, yes. I'd have to add the fires and stuff to groups and things and detect which instance their in... Hmm, that's a pain to think about. I mean there's no problem as long as the ability isn't cast by the same person at the same time...
Because it's a channeling spell, if you cast it again while channeling the code is set to run while channeling "starfall", and since you'd still be on that order, it starts the spell up but never ends the old one. I suppose giving them timed life would work, and rigging the flame strikes to a timer...

Off to work!
Oh btw, someone reported being able to select the fires. Has anyone else been able to do this? They have locust...
08-04-2008, 03:51 PM#8
Alexander244
Quote:
Oh btw, someone reported being able to select the fires. Has anyone else been able to do this? They have locust...
Yes.
Only the dummy caster has locust, not the Dummy (SFX).
08-04-2008, 04:11 PM#9
EquippedChaos
Really? Well I'll be damned. Thanks for finding that, it'll be fixed immediately
(can't believe I missed that)
08-04-2008, 05:09 PM#10
Veev
Very cool looking spell. I suggest making the immolation's damage the red fire (Abilities\Spells\Other\ImmolationRed\ImmolationRedDamage.mdl) and not the green fire.
08-04-2008, 05:27 PM#11
EquippedChaos
We were in a bit of a rush to put this out, so none of the buffs were changed. They will be in the final release, so yes, the fire will be red and not green.
08-04-2008, 06:22 PM#12
moyack
If one day I have to leave this community, I'll do it but first I'll teach to darkwulfv how to say good bye to polledwait function of doom and use the timer for that. The day you learn this, you will be a new man.
08-04-2008, 06:38 PM#13
EquippedChaos
Meh. I'm still not fully understanding how one goes about doing that... I mean, the whole reason I use polledwait functions in this particular spell is to WAIT for something without the use of timers (and thus lots of extra attaching and extracting everywhere), and timers don't stop the rest of the trigger from running, so...

I'm currently finetuning a way to make this spell work even if double-casted. Right now there are 3 ways (Timer, Timed life, and after-timer check) for the flames to die. I'm just making sure they'll all work.

Oh, and I'm (cohadar will shoot me) attaching a struct to the caster, which is apparently the devil's spawnchild because of handle corruption... or something stupid I don't understand. So uh, is this going to be an issue?
(I'm attaching the struct so I can access the flames if the caster tries double-casting).
08-04-2008, 07:38 PM#14
EquippedChaos
~Bumpdate~

Okay, I added a safety fallback in the event someone decides to change the cooldown so the spell will double cast. The flames will be immediately removed and replaced.
However, for some reason, the newly spawned flames will not die normally (with the epic burst of fire). This is fixed when casting normally. But you know, that's what people get for messing with things like that.

v1.1 is up. Changelog added to first post and code updated.
Thanks for all the kind words and advice we've been getting. Goldendercon and I will start our next spell as soon as he's back from school (we only really get to work together), and we'll put a pack out as soon as we can.

PS: If anyone wants to take more screenshots, they're welcome to!
08-05-2008, 07:23 AM#15
Pyrogasm
Fail at configuration.

Some things simply need config functions (such as spell duration) because they depend on the level and may have their own formulas.