HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Mana Shot and Spirit Orb

06-21-2007, 02:47 AM#1
Pyrogasm
Mana Shot and Spirit Orb v1.30

Here are two spells I made as requests from people at TheHelper.net. As such, neither of the ideas for these spells was my own concoction; each person dictated how the spell was to function. Both spells comply with the JESP standard.



Spell Descriptions:
Mana Shot
Mana Cost: 100

The hero burns his mana, dealing damage to units in a cone.

Level 1 - Deals 5% of current mana as damage.
Level 2 - Deals 10% of current mana as damage.
Level 3 - Deals 15% of current mana as damage.
Level 4 - Deals 20% of current mana as damage.
Spirit Orb
Mana Cost: 110

Summons a spiritual orb that will transfer up to 80 mana between enemy units and allied units at regular intervals. More mana will be drained/given depending on how close each unit is to the orb.

Level 1 - Full mana transfer within 195, partial drain up to 325.
Level 2 - Full mana transfer within 230, partial drain up to 450.
Level 3 - Full mana transfer within 265, partial drain up to 575.
Level 4 - Full mana transfer within 300, partial drain up to 700.

Because I use Mac OS X, my code tends to get all funky when opened in the World Editor; to rectify this, simply use the attached text files in substitution of the code in the map. Alternatively, you can copy-paste the code below:
Collapse Mana Shot:
//*********************************************************************
//*                   Spell Name:  Mana Shot                          *
//*                   Spell Author: Pyrogasm                          *
//                  Follows the  JESP Standard                        *
//*********************************************************************

constant function ManaShot_AbilityId takes nothing returns integer
    return 'A001' //Rawcode of the main ability.
endfunction

constant function ManaShot_Damage takes integer Level, real CasterMana returns real
    return (0.05*Level)*CasterMana //How much damage the spell should deal; this function
endfunction                        //Takes the caster's mana as an additional argument

constant function ManaShot_Distance takes integer Level returns real
    return 700.00 //The total distance the cone should travel forward.
endfunction

constant function ManaShot_EndWidth takes integer Level returns real
    return 475.00 //The ending width of the cone.
endfunction

constant function ManaShot_CircleOffset takes integer Level returns real
    return 15.00 //How far in the direction of the caster's facing the circle will be offset.
endfunction      //The circle ensures that units that aren't directly in the way of the spell
                 //But are still near enough that they should get hit will get hit.

constant function ManaShot_CircleRadius takes integer Level returns real
    return 22.00 //Radius of the aforementioned circle.
endfunction

constant function ManaShot_MissileSpeed takes nothing returns real
    return 1100.00 //The speed of the cone missle.
endfunction        //This is to make sure units get damaged at the correct time.

constant function ManaShot_NumberOfDamageGroups takes integer Level returns integer
    return 8 //Number of groups that the damaged units will be divided in to estimate when
endfunction  //They should take damage. High number = more precise; low number = less precise.

constant function ManaShot_DamageEffectPath takes nothing returns string
    return "Abilities\\Spells\\Human\\ManaFlare\\ManaFlareBoltImpact.mdl"
endfunction //The effect to be played on damaged units.

constant function ManaShot_DamageEffectAttach takes nothing returns string
    return "origin"
endfunction //The attachment point for the above effect.


//**************************************************
//*************Start External Functions*************
//**************************************************

function AngleBetweenPointsXY takes real X1, real Y1, real X2, real Y2 returns real
    return Atan2((Y2-Y1),(X2-X1))*57.295827
endfunction

function Angles_IsAngleBetweenAngles takes real angle, real angle1, real angle2 returns boolean
    local real x

    set angle = ModuloReal(angle,360)
    set angle1 = ModuloReal(angle1,360)
    set angle2 = ModuloReal(angle2,360)
    if (angle1>angle2) then
        set x=angle1
        set angle1=angle2
        set angle2=x
    endif
    if (angle2-angle1)>(angle1 - (angle2-360)) then
        set angle2=angle2-360
        if angle > 180 then
            set angle=angle-360
        endif
        return angle>=angle2 and angle<=angle1
    endif

    return (angle>=angle1) and (angle<=angle2)
endfunction

//***************************************************
//*************End of External Functions*************
//***************************************************


function ManaShot_CastConditions takes nothing returns boolean
    return GetSpellAbilityId() == ManaShot_AbilityId()
endfunction

function ManaShot_AngleEnemyFilter takes nothing returns boolean
    return Angles_IsAngleBetweenAngles(AngleBetweenPointsXY(bj_meleeNearestMineDist, bj_randomSubGroupChance, GetUnitX(GetFilterUnit()), GetUnitY(GetFilterUnit())), bj_enumDestructableRadius, bj_lastTransmissionDuration) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false)
endfunction    

function ManaShot_EnemyFilter takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), bj_forceRandomCurrentPick) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false)
endfunction


function ManaShot_DamageCallbackNew takes nothing returns nothing
    local timer T = GetExpiredTimer()
    local unit Damager = GetAttachedUnit(T, "ManaShot Damager")
    local group DamageGroup = GetAttachedGroup(T, "ManaShot Damage Group")
    local real Damage = GetAttachedReal(T, "ManaShot Damage")
    local integer Countup = GetAttachedInt(T, "ManaShot Countup")
    local integer TotalIterations = GetAttachedInt(T, "ManaShot Total Iterations")
    local unit U
    local group G = CreateGroup()
    local real X = GetAttachedReal(T, "ManaShot X")
    local real Y = GetAttachedReal(T, "ManaShot Y")
    local real X2
    local real Y2
    local real DamageDistanceIncrement = GetAttachedReal(T, "ManaShot Damage Distance Increment")

    set Countup = Countup+1
    if Countup > TotalIterations then
        call DestroyGroup(DamageGroup)
        call PauseTimer(T)
        call CleanAttachedVars(T)
        call DestroyTimer(T)
    else
        call AttachInt(T, "ManaShot Countup", Countup)
        set DamageDistanceIncrement = (DamageDistanceIncrement*Countup)*(DamageDistanceIncrement*Countup)
        call GroupAddGroup(DamageGroup, G)
        loop
            set U = FirstOfGroup(G)
            exitwhen U == null
            set X2 = GetUnitX(U)
            set Y2 = GetUnitY(U)
            if (X-X2)*(X-X2)+(Y-Y2)*(Y-Y2) <= DamageDistanceIncrement then
                call UnitDamageTarget(Damager, U, Damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                call DestroyEffect(AddSpecialEffectTarget(ManaShot_DamageEffectPath(), U, ManaShot_DamageEffectAttach()))
                call GroupRemoveUnit(DamageGroup, U)
            endif
            call GroupRemoveUnit(G, U)
        endloop
    endif
    call DestroyGroup(G)

    set T = null
    set G = null
    set DamageGroup = null
    set Damager = null
endfunction

function ManaShot_Cast takes nothing returns nothing
    local unit U = GetTriggerUnit()
    local unit U2
    local integer Level = GetUnitAbilityLevel(U, ManaShot_AbilityId())
    local real X1 = GetUnitX(U)
    local real Y1 = GetUnitY(U)
    local real TX
    local real TY
    local location L = GetSpellTargetLoc()
    local real Distance = ManaShot_Distance(Level)
    local real Width = ManaShot_EndWidth(Level)
    local real TargAngle
    local real MaxAngle
    local real AngleAdjust = Atan((Width/2)/Distance)*57.295827
    local group TotalGroup = CreateGroup()
    local real Offset = ManaShot_CircleOffset(Level)
    local integer DamageDivisions = ManaShot_NumberOfDamageGroups(Level)
    local player P = GetOwningPlayer(U)
    local real DamageDistanceIncrement = Distance/DamageDivisions
    local real DamageDelay = DamageDistanceIncrement/ManaShot_MissileSpeed()
    local real Damage = ManaShot_Damage(Level, GetUnitState(U, UNIT_STATE_MANA))
    local timer T = CreateTimer()
    local group CircleGroup = CreateGroup()
    local boolexpr B = Condition(function ManaShot_AngleEnemyFilter)

    if L == null then
        set L = GetUnitLoc(GetSpellTargetUnit())
    endif
    set TX = GetLocationX(L)
    set TY = GetLocationY(L)
    call RemoveLocation(L)
    set TargAngle = AngleBetweenPointsXY(X1, Y1, TX, TY)

    set bj_enumDestructableRadius = TargAngle-AngleAdjust
    set bj_lastTransmissionDuration = TargAngle+AngleAdjust
    set bj_meleeNearestMineDist = X1
    set bj_randomSubGroupChance = Y1
    call GroupEnumUnitsInRange(TotalGroup, X1, Y1, Distance, B)
    call DestroyBoolExpr(B)

    set bj_forceRandomCurrentPick = P
    set TX = X1 + Offset * Cos(TargAngle*0.0174532)
    set TY = Y1 + Offset * Sin(TargAngle*0.0174532)
    set B = Condition(function ManaShot_EnemyFilter)
    call GroupEnumUnitsInRange(CircleGroup, TX, TY, ManaShot_CircleRadius(Level), B)
    call GroupAddGroup(CircleGroup, TotalGroup)
    call DestroyGroup(CircleGroup)
    call DestroyBoolExpr(B)
    set bj_forceRandomCurrentPick = null

    call AttachObject(T, "ManaShot Damage Group", TotalGroup)
    call AttachObject(T, "ManaShot Damager", U)
    call AttachReal(T, "ManaShot Damage", Damage)
    call AttachInt(T, "ManaShot Countup", 0)
    call AttachInt(T, "ManaShot Total Iterations", DamageDivisions)
    call AttachReal(T, "ManaShot X", X1)
    call AttachReal(T, "ManaShot Y", Y1)
    call AttachReal(T, "ManaShot Damage Distance Increment", DamageDistanceIncrement)
    call TimerStart(T, DamageDelay, true, function ManaShot_DamageCallbackNew)

    set B = null
    set U = null
    set U2 = null
    set L = null
    set TotalGroup = null
    set P = null
    set CircleGroup = null
endfunction

//===========================================================================
function InitTrig_Mana_Shot takes nothing returns nothing
    set gg_trg_Mana_Shot = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Mana_Shot, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_Mana_Shot, Condition(function ManaShot_CastConditions))
    call TriggerAddAction(gg_trg_Mana_Shot, function ManaShot_Cast)
endfunction
Collapse Spirit Orb:
//*********************************************************************
//*                   Spell Name: Spirit Orb                          *
//*                   Spell Author: Pyrogasm                          *
//                  Follows the  JESP Standard                        *
//*********************************************************************

constant function SO_AbilityId takes nothing returns integer
    return 'A000' //Rawcode of the main ability.
endfunction

constant function SO_SFXUnitId takes nothing returns integer
    return 'h002' //Rawcode of the unit that will be killed every interval.
endfunction       //To not destroy a unit, simply make this function return 0.

constant function SO_WardUnitId takes nothing returns integer
    return 'h002' //Rawcode of the unit that will be used as the ward;
endfunction       //This unit may be moveable/selectable.

constant function SO_DummyId takes nothing returns integer
    return 'h000' //Rawcode of your generic dummy unit; this
endfunction       //Unit MUST NOT have negative regeneration.

constant function SO_StealManaLightningAbilityId takes nothing returns integer
    return 'A004' //Rawcode of the "Spirit Orb Lightning (Drain Mana)" ability.
endfunction

constant function SO_StealBothLightningAbilityId takes nothing returns integer
    return 'A002' //Rawcode of the "Spirit Orb Lightning (Drain Both)" ability.
endfunction

constant function SO_GiveLightningAbilityId takes nothing returns integer
    return 'A003' //Rawcode of the "Spirit Orb Give Lightning" ability.
endfunction

constant function SO_GroundEffectPath takes nothing returns string
    return "Abilities\\Spells\\Human\\Flamestrike\\FlameStrikeTarget.mdl"
endfunction //The effect to be played on the ground during the duration of the spell.

constant function SO_StealEffectPath takes nothing returns string
    return "Abilities\\Spells\\NightElf\\ManaBurn\\ManaBurnTarget.mdl"
endfunction //The effect to be played on a unit that has its mana stolen by the orb.

constant function SO_StealEffectAttach takes nothing returns string
    return "chest" //The attachment point for the above effect.
endfunction

constant function SO_GiveEffectPath takes nothing returns string
    return "Abilities\\Spells\\Items\\AIma\\AImaTarget.mdl"
endfunction //The effect to be played on a unit that is given mana from the orb.

constant function SO_GiveEffectAttach takes nothing returns string
    return "chest"
endfunction //The attachment point for the above effect.

constant function SO_MinimumRange takes integer Level returns real
    return (160.00 + 35.00*Level) //The minimum range a unit must be in to receive
endfunction                       //Full effect of the spell.

constant function SO_MaxRange takes integer Level returns real
    return (200.00 + 125.00*Level) //The maximum range a unit must be in to
endfunction                        //Be targeted by the spell.

constant function SO_StealInterval takes integer Level returns real
    return 1.00 //The interval at which the orb will steal mana from units.
endfunction     //This is in seconds.

constant function SO_TotalDuration takes integer Level, real StealInterval returns real
    return StealInterval*5.00 //The total duration of the spell; this function takes
endfunction                   //The Steal Interval as an additional argument

constant function SO_MaxStolenMana takes integer Level returns real
    return 80.00 //The maximum amount of mana that can be stolen from a unit
endfunction

constant function SO_MinStolenMana takes integer Level returns real
    return 20.00 //The minimum amount of mana that can be stolen from a unit
endfunction


//****************************************************************************
//********************End of Configuration Functions*************************
//****************************************************************************


function SO_EnemyFilter takes nothing returns boolean
    return (IsUnitEnemy(GetFilterUnit(), bj_forceRandomCurrentPick) and GetUnitState(GetFilterUnit(), UNIT_STATE_MAX_MANA) > 0.00) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false)
endfunction

function SO_AllyFilter takes nothing returns boolean
    return (IsUnitAlly(GetFilterUnit(), bj_forceRandomCurrentPick) and GetUnitState(GetFilterUnit(), UNIT_STATE_MAX_MANA) > 0.00) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false)
endfunction

function SO_CastConditions takes nothing returns boolean
    return GetSpellAbilityId() == SO_AbilityId()
endfunction

function SO_ManaStealCallback takes nothing returns nothing
    local timer T = GetExpiredTimer()
    local unit Ward = GetAttachedUnit(T, "SO Ward")
    local real X = GetUnitX(Ward)
    local real Y = GetUnitY(Ward)
    local integer Level = GetAttachedInt(T, "SO Level")
    local group G
    local unit U
    local unit U2
    local real MaxStolenMana = SO_MaxStolenMana(Level)
    local real MinStolenMana = SO_MinStolenMana(Level)
    local real StolenMana
    local real X2
    local real Y2
    local real D
    local real MinRange = SO_MinimumRange(Level)
    local real MaxRange = SO_MaxRange(Level)
    local real Mana
    local real UnitMaxMana
    local real ManaGiven = 0.00
    local real BurnedMana = 0.00
    local real BurnedManaEx = 0.00
    local texttag ManaBurnText
    local real GivenFactor = (MaxStolenMana-MinStolenMana)/(MaxRange-MinRange)
    local integer DummyAbilityId = SO_StealManaLightningAbilityId()
    local unit Dummy
    local integer SFXUnitId

    if GetWidgetLife(Ward) < 0.406 then
        call DestroyEffect(GetAttachedEffect(T, "SO Ground Effect"))
        call PauseTimer(T)
        call CleanAttachedVars(T)
        call DestroyTimer(T)
    else
        set G = CreateGroup()
        set SFXUnitId = SO_SFXUnitId()
        if SFXUnitId > 0 then
            set U = CreateUnit(GetOwningPlayer(Ward), SFXUnitId, X, Y, 0) //*****OPTIONAL*****
            call SetUnitTimeScale(U, 1.50)
            call KillUnit(U)                                                  //*****END OPTIONAL*****
        endif
        set bj_forceRandomCurrentPick = GetOwningPlayer(Ward)
        call GroupEnumUnitsInRange(G, X, Y, MaxRange, Condition(function SO_EnemyFilter))
        set U = GroupPickRandomUnit(G)
        call GroupClear(G)

        if U != null then
            set Mana = GetUnitState(U, UNIT_STATE_MANA)
            set X2 = GetUnitX(U)
            set Y2 = GetUnitY(U)
            set D = (X-X2)*(X-X2)+(Y-Y2)*(Y-Y2)
            if D > MinRange*MinRange then
                set StolenMana = MinStolenMana + ((MaxStolenMana*(D-MinRange))/MaxRange)
            else
                set StolenMana = MaxStolenMana
            endif
            set Mana = Mana-StolenMana
            if Mana < 0.00 then
                set StolenMana = StolenMana+Mana
            endif
            call SetUnitState(U, UNIT_STATE_MANA, Mana)

            call GroupEnumUnitsInRange(G, X, Y, MaxRange, Condition(function SO_AllyFilter))
            set U2 = GroupPickRandomUnit(G)

            if U2 != null then
                set UnitMaxMana = GetUnitState(U2, UNIT_STATE_MAX_MANA)
                set Mana = GetUnitState(U2, UNIT_STATE_MANA)
                set ManaGiven = StolenMana
                set X2 = GetUnitX(U2)
                set Y2 = GetUnitY(U2)
                set D = (X-X2)*(X-X2)+(Y-Y2)*(Y-Y2)
                if D > MinRange*MinRange then
                    set D = (D-MinRange)*GivenFactor
                    if D < ManaGiven then
                        set BurnedManaEx = ManaGiven-D
                        set ManaGiven = D
                    endif
                endif                
                if Mana+ManaGiven > UnitMaxMana then
                    set ManaGiven = UnitMaxMana - Mana
                endif
                call SetUnitState(U2, UNIT_STATE_MANA, Mana+ManaGiven)
            else
                set BurnedManaEx = StolenMana
            endif

            set BurnedMana = StolenMana-ManaGiven+BurnedManaEx
            if BurnedMana > 0.00 then 
                set DummyAbilityId = SO_StealBothLightningAbilityId()
                call UnitDamageTarget(Ward, U, BurnedMana, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                set ManaBurnText = CreateTextTag()
                call SetTextTagPos(ManaBurnText, GetUnitX(U), GetUnitY(U), 0.00)
                call SetTextTagText(ManaBurnText, "-"+I2S(R2I(BurnedMana)), TextTagSize2Height(10.00))
                call SetTextTagVelocity(ManaBurnText, 0, 0.04)
                call SetTextTagColor(ManaBurnText, 82, 82, 255, 255)
                call SetTextTagPermanent(ManaBurnText, false)
                call SetTextTagLifespan(ManaBurnText, 5.00)
                call SetTextTagFadepoint(ManaBurnText, 2.00)
                call SetTextTagVisibility(ManaBurnText, true)

                if U2 != null then
                    set ManaBurnText = CreateTextTag()
                    call SetTextTagPos(ManaBurnText, X2, Y2, 0.00)
                    call SetTextTagText(ManaBurnText, "+"+I2S(R2I(ManaGiven)), TextTagSize2Height(10.00))
                    call SetTextTagVelocity(ManaBurnText, 0, 0.04)
                    call SetTextTagColor(ManaBurnText, 82, 82, 255, 255)
                    call SetTextTagPermanent(ManaBurnText, false)
                    call SetTextTagLifespan(ManaBurnText, 5.00)
                    call SetTextTagFadepoint(ManaBurnText, 2.00)
                    call SetTextTagVisibility(ManaBurnText, true)

                    call DestroyEffect(AddSpecialEffectTarget(SO_GiveEffectPath(), U2, SO_GiveEffectAttach()))
                    set Dummy = CreateUnit(GetOwningPlayer(Ward), SO_DummyId(), X, Y, 0.00)
                    call UnitAddAbility(Dummy, SO_GiveLightningAbilityId())
                    call IssueTargetOrder(Dummy, "fingerofdeath", U2)
                    call UnitApplyTimedLife(Dummy, 'BTLF', 2.00)
                    set U2 = null
                    set ManaBurnText = null
                endif
            endif

            call DestroyEffect(AddSpecialEffectTarget(SO_StealEffectPath(), U, SO_StealEffectAttach()))
            set Dummy = CreateUnit(GetOwningPlayer(Ward), SO_DummyId(), X, Y, 0.00)
            call UnitAddAbility(Dummy, DummyAbilityId)
            call IssueTargetOrder(Dummy, "fingerofdeath", U)
            call UnitApplyTimedLife(Dummy, 'BTLF', 2.00)
            set Dummy = null
            set U = null
        endif
        call DestroyGroup(G)
        set G = null
    endif

    set T = null
    set Ward = null
endfunction

function SO_Cast takes nothing returns nothing
    local unit U = GetTriggerUnit()
    local location L = GetSpellTargetLoc()
    local real X = GetLocationX(L)
    local real Y = GetLocationY(L)
    local player P = GetOwningPlayer(U)
    local unit Ward = CreateUnit(P, SO_WardUnitId(), X, Y, 0)
    local timer T = CreateTimer()
    local integer Level = GetUnitAbilityLevel(U, SO_AbilityId())
    local real Interval = SO_StealInterval(Level)

    call RemoveLocation(L)
    call UnitApplyTimedLife(Ward, 'BTLF', SO_TotalDuration(Level, Interval)+0.05) //To make sure everything runs on the last iteration
    call AttachObject(T, "SO Ward", Ward)
    call AttachInt(T, "SO Level", GetUnitAbilityLevel(U, SO_AbilityId()))
    call AttachObject(T, "SO Ground Effect", AddSpecialEffect(SO_GroundEffectPath(), X, Y))
    call TimerStart(T, Interval, true, function SO_ManaStealCallback)

    set L = null
    set Ward = null
    set T = null
    set U = null
endfunction

//===========================================================================
function InitTrig_Spirit_Orb takes nothing returns nothing
    set gg_trg_Spirit_Orb = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Spirit_Orb, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_Spirit_Orb, Condition(function SO_CastConditions))
    call TriggerAddAction(gg_trg_Spirit_Orb, function SO_Cast)
endfunction

Be warned, however: vB code has been known to screw with [jass] tags. To get the correct code, simply quote me and c 'n p it from there.
Attached Images
File type: jpgManaShotandSpiritOrb.jpg (688.8 KB)
Attached Files
File type: zipMana Shot and Spirit Orb v1.30.zip (130.0 KB)
06-23-2007, 08:15 AM#2
Pyrogasm
Erm... bump?
06-23-2007, 08:55 AM#3
blu_da_noob
It's been two days. The site has just come back. People are busy. The code is long and requires major review because a large portion appears useless.
06-23-2007, 09:36 AM#4
Pyrogasm
I wasn't bumping to get it reviewed; I was bumping to get it in the "New Posts" section to rouse some people who like to play around with stuff and find bugs/problems.

It wasn't getting much activity. Oh well.

EDIT: What part exactly "appears useless"?
06-23-2007, 10:24 AM#5
blu_da_noob
You do a whole lot of triangle stuff for grouping when you could simply get a sector (ie cone) by getting all units in X range of the caster and filter them by a limited angle range.
06-23-2007, 10:33 AM#6
Pyrogasm
Oh snap.

I'll get that fixed up as soon as possible, probably tomorrow evening.
07-01-2007, 10:46 PM#7
moyack
I took the freedom to modify the test map so Windows users can see the code without problem.

Some comments:
  • Screenshots!!
  • ManaShot uses several timers per casting, maximum 5, but.... it's not possible to try to use only one timer for all the spell? I'm afraid that I didn't understand why did you use several timers and several groups.
  • In Spirit Orb spell, you have two functions which don't have the spell prefix: AddLightningFromUnitToUnitTimed_Child and AddLightningFromUnitToUnitTimed

For other things, I think it's ok. Because you are using many timers, why not add CSSafety to your spells??, with that you can make your timer usage more efficient.

This is the code:

Collapse JASS:
//******************************************************************************************
//*
//* CSSafety 14.0
//* ¯¯¯¯¯¯¯¯
//*
//*  Utilities to make things safer. Currently this simply includes a timer recycling
//* Stack. Once you replace CreateTimer with NewTimer and DestroyTimer with ReleaseTimer
//* you no longer have to care about setting timers to null nor about timer related issues
//* with the handle index stack.
//*
//******************************************************************************************
//library CSSafety
//==========================================================================================
globals
    timer array cs_timers
    integer cs_timern = 0
endglobals

//==========================================================================================
function NewTimer takes nothing returns timer
    if (cs_timern==0) then
        return CreateTimer()
    endif
 set cs_timern=cs_timern-1
 return cs_timers[cs_timern]
endfunction

//==========================================================================================
function ReleaseTimer takes timer t returns nothing
    call PauseTimer(t)
    if (cs_timern==8191) then
        debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

        //stack is full, the map already has much more troubles than the chance of bug
        call DestroyTimer(t)
    else
        set cs_timers[cs_timern]=t
        set cs_timern=cs_timern+1
    endif    
endfunction

//endlibrary

You replace the command CreateTimer() by NewTimer() and you don't have destroy timers anymore, even pause them, just call the command ReleaseTimer(t) and that's all :)
07-02-2007, 06:56 AM#8
Pyrogasm
Ok; I'll add in the CSSaftey functions. I used multiple timers because I hadn't thought of a way to get an undetermined number of groups that are attached to a timer, but now I've figured it out. I'm going to have to prefix all the CSSafety functions beacause I must change the variable names (I can't use freeform globals); is that OK?

Screenshots will come... I'm still on dialup, but I think I can manage the upload.

About what blu said, what are your thoughts? If I use a sector like he said, then I have units that are technically outside of that triangle but still in the arc.

Thank you for converting the map to a windows-readable format; but you're going to have to do it again after I upload a new version.
07-11-2007, 01:43 PM#9
Rising_Dusk
I don't see the advantage to CSSafety in a spell submission.
Less things to add to your map for implementation the better, and anyone that uses CSSafety on their own and wants to use this spell would know very easily how to modify it to their desires.
I say leave it out.

Going through Spirit Orb first...
Collapse JASS:
constant function SO_MinimumRange takes integer Level returns real
    return (320.00 + 70.00*Level)/2 //Divide by 2 to get a radius
endfunction

constant function SO_MaxRange takes integer Level returns real
    return (400.00 + 250.00*Level)/2 //Divide by 2 to get a radius
endfunction
These divided by 2 things should be in the spell code itself, not the constant function.
It could get confusing for noob importers and they might remove it accidentally and screw things up.

Collapse JASS:
//****************************************************************************
//*****************Blade.dk's Mobile Lightning Functions**********************
//****************************************************************************
function AddLightningFromUnitToUnitTimed_Child takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit from = GetAttachedUnit(t, "from")
    local unit to = GetAttachedUnit(t, "to")
    local lightning bolt = GetAttachedLightning(t, "bolt")
    local real z1 = GetUnitFlyHeight(from)
    local real z2 = GetUnitFlyHeight(to)
    if z1 == 0 then
        set z1 = 60
    endif
    if z2 == 0 then
        set z2 = 60
    endif
    call MoveLightningEx(bolt, GetAttachedBoolean(t, "checkVisibility"), GetUnitX(from), GetUnitY(from), z1, GetUnitX(to), GetUnitY(to), z2)
    call AttachReal(t, "timeelapsed", GetAttachedReal(t, "timeelapsed")+0.01)
    if ((GetAttachedReal(t, "timeelapsed") > GetAttachedReal(t, "duration")) or (GetUnitState(from, UNIT_STATE_LIFE) <= 0) or (GetUnitState(to, UNIT_STATE_LIFE) <= 0)) then
        call CleanAttachedVars(t)
        call DestroyTimer(t)
        call DestroyLightning(bolt)
    endif
    set t = null
    set from = null
    set to = null
    set bolt = null
endfunction

function AddLightningFromUnitToUnitTimed takes string codeName, boolean checkVisibility, unit from, unit to, real duration returns lightning
    local timer t = CreateTimer()
    local lightning bolt
    local real z1 = GetUnitFlyHeight(from)
    local real z2 = GetUnitFlyHeight(to)
    if z1 == 0 then
        set z1 = 60
    endif
    if z2 == 0 then
        set z2 = 60
    endif
    set bolt = AddLightningEx(codeName, checkVisibility, GetUnitX(from), GetUnitY(from), z1, GetUnitX(to), GetUnitY(to), z2)
    call AttachReal(t, "duration", duration)
    call AttachObject(t, "from", from)
    call AttachObject(t, "to", to)
    call AttachBoolean(t, "checkVisibility", checkVisibility)
    call AttachObject(t, "bolt", bolt)
    call TimerStart(t, 0.01, true, function AddLightningFromUnitToUnitTimed_Child)
    return bolt
endfunction
//***********************************************************************************
//*****************End of Blade.dk's Mobile Lightning Functions**********************
//***********************************************************************************
These functions are BAD.
They are inaccurate and will never perfectly reflect the flying heights of flying units due to WC3 fly height smoothing.
I recommend changing the ENTIRE system to using a Finger of Death dummy ability between the units.
It works much better, is cleaner, and removes itself after the OE specified duration.
That means no need for attachment or timers or any of that stuff.

Collapse JASS:
        set U = CreateUnit(GetOwningPlayer(Ward), SO_SFXUnitId(), X, Y, 0) //*****OPTIONAL*****
        call SetUnitTimeScale(U, 1.50)
        call KillUnit(U)
        set U = null                                                       //*****END OPTIONAL*****
        set bj_forceRandomCurrentPick = GetOwningPlayer(Ward)
        call GroupEnumUnitsInRange(G, X, Y, MaxRange, Condition(function SO_EnemyFilter))
        set U = GroupPickRandomUnit(G)
        call DestroyGroup(G)
You don't need to nullify variables if you're just going to set them to a new value.
Setting them to a new value does the same, exact thing as nullifying.

Collapse JASS:
                set D = SquareRoot((X-X2)*(X-X2)+(Y-Y2)*(Y-Y2))
                if D > MinRange then
                    set D = (D-MinRange)*GivenFactor
                    if D < ManaGiven then
                        set BurnedManaEx = ManaGiven-D
                        set ManaGiven = D
                    endif
                endif  
Everywhere you do distance comparisons, don't square root the distance and just compare it to the square of the number you're using already.
Square root is a VERY slow function for any programming language, and in distance comparisons it's easily avoided.
Blu will appreciate me telling you to do this. :p

============================================================
And now Mana Shot...

All of Blu's notes about grouping the triangle stuff is true.
It's a lot less work, accurate enough, and saves like 40-60 LoC.
I say do it.

Collapse JASS:
    set X2 = X1 + Hypotenuse * Cos((TargAngle-Angle)*bj_DEGTORAD)
    set Y2 = Y1 + Hypotenuse * Sin((TargAngle-Angle)*bj_DEGTORAD)
    set X3 = X1 + Hypotenuse * Cos((TargAngle+Angle)*bj_DEGTORAD)
    set Y3 = Y1 + Hypotenuse * Sin((TargAngle+Angle)*bj_DEGTORAD)
Everywhere you use that variable, use the actual number.

You do a lot of excess work and use a hell of a lot of unnecessary timers to make it appear as if the energy is moving in a "wave" through the cone.
Why not use a single periodic timer for the entire thing instead of like 8-12 single shot timers?
That would improve performance tremendously.
You also would have to attach less.

Just attach the whole group of units in the cone to the single timer, then do the square of the distance check inside the timer callback to pick who gets hit.
Then remove units from the group that have already been hit, so they don't get hit again.
Simple, easy, saves like 30 LoC, and saves the use of 99% of your timers and groups.

And yeah, a screenshot.
One screenshot with both spells being shown would be preferable.
07-11-2007, 07:23 PM#10
Pyrogasm
Sorry, I've been lazy about fixing these up, but I'll get on it right away.

Thanks for the review, Rising_Dusk.
07-12-2007, 12:17 AM#11
Pyrogasm
Fixed and updated.

I did everything but use the CSSafety functions and remove the GroupAddUnitsInTriangle() stuff. The reason I kept the triangle is because I would have had to do trigonometry anyway to get the sector of the circle when given the length of the end of the triangle as input (in the constants); it didn't seem like the greatest tradeoff to me.
07-12-2007, 12:30 AM#12
Rising_Dusk
Quote:
...it didn't seem like the greatest tradeoff to me.
It would cut the geometry, calculations, and code length by nearly 75%.
I think that's plenty of a trade off.

I'll review it again later.
07-12-2007, 12:46 AM#13
Pyrogasm
Dur again. I was only thinking about my code/calculations, not the GroupAddUnitsInTriangle() one, as that's where most of the calculations lay.

In that case, hold out on reviewing this 'till I get back from seeing Harry Potter this evening and fix that code.

Sorry for being so stubborn ><
07-17-2007, 05:29 PM#14
blu_da_noob
Mana Shot still hasn't been updated to stop using the triangle group stuff.

Spirit Orb:
  • You forgot to null unit U in function SO_Cast
  • if GetWidgetLife(Ward) < 0.406 then //heh 0.405 :P
  • Still a place where you square root and could square both sides of the comparison instead (in the if U2 != null section).
  • Instead of creating and destroying the group each time in the callback function you can just clear it and reuse it. Avoid unnecessary creation/destruction of handles where possible. This also applies to your dummy lightning casting unit/s. Use one instead of creating one for each target. Actually this function can leak a group because you create it at decleration but if the if/then/else is false it won't be destroyed.
  • You load the sfx into a variable every time the function is called, but only use it once, and only if the spell has ended. Rather only load it at that time.
07-27-2007, 02:51 AM#15
Pyrogasm
Fix'd everything. No longer uses funky triangulation, and all the other mentioned things have been fixed.