Spell Arcane Magic

Sooda

Diversity enchants
Reaction score
319
I have done my second MUI JASS ability what is leakless (I hope at least so.) and pretty optimized.

Description: Magic spell which damages enemy units in a line. Each unit after first target takes additional damage.
Level 1: Deals 50 base damage. Each target after takes additional 15 damage. Damage limit is 100 damage.
Level 2: Deals 100 base damage. Each target after takes additional 30 damage. Damage limit is 200 damage.
Level 3: Deals 150 base damage. Each target after takes additional 45 damage. Damage limit is 300 damage.

Links to screenshots:
arcanemagicandtrollsjw5.jpg


arcanemagicanddivineshiun3.jpg


You can find demo map below my post (See attachments.).

Read map' s README how to implement it.
JASS:
JASS:
constant function AbilityRawId takes nothing returns integer
    // default is 'A000'
    return 'A000'
endfunction


constant function MissileEffect takes nothing returns string
    // default is "Abilities\\Spells\\Undead\\OrbOfDeath\\AnnihilationMissile.mdl"
    return "Abilities\\Spells\\Undead\\OrbOfDeath\\AnnihilationMissile.mdl"
endfunction

constant function AbilityBaseDistance takes nothing returns real
    // default is 500.
    return 500.
endfunction


constant function DistanceAddedPerLevel takes nothing returns real
    // default is 100.
    return 100.
endfunction


constant function DamageCapPerLevel takes nothing returns real
    // DamageCapPerLevel() * abilityLevel
    // default is 100.
    return 100.
endfunction

constant function AbilityBaseDamage takes nothing returns real
    // formulae is AbilityBaseDamage() * abilityLevel
    // default is 50.
    return 50.
endfunction


constant function DamageAddedPerTarget takes nothing returns real
    // formulae is DamageAddedPerTarget() * abilityLevel
    // default is 15.
    return 15.
endfunction

constant function AbilityRadius takes nothing returns real
    // default is 100.
    return 100.
endfunction


constant function ShowText takes nothing returns boolean
    // default is true
    return true
endfunction


constant function TextColor takes nothing returns string
    // default is "|CFFE55BB0"
    return "|CFFE55BB0"
endfunction

constant function AbilityAttackType takes nothing returns attacktype
    // default is ATTACK_TYPE_NORMAL
    return ATTACK_TYPE_NORMAL
endfunction


constant function AbilityDamageType takes nothing returns damagetype
    // default is DAMAGE_TYPE_LIGHTNING
    return DAMAGE_TYPE_LIGHTNING
endfunction

function ValidUnit takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(GetTriggerUnit())) and GetUnitState(GetFilterUnit(),UNIT_STATE_LIFE) >= .405
endfunction

function ValidAbility takes nothing returns boolean
	return GetSpellAbilityId() == AbilityRawId()
endfunction

function ShowFloatingTextTag takes string msg, real locationX, real locationY returns texttag
    local texttag damageMsgTag = CreateTextTag()
    
    call SetTextTagText(damageMsgTag,msg,.024)
    call SetTextTagPos(damageMsgTag,locationX,locationY,0.)
    call SetTextTagColor(damageMsgTag,100,100,100,0)
    
    call SetTextTagVelocity(damageMsgTag,0.,.035)
    call SetTextTagFadepoint(damageMsgTag,0.)
    call SetTextTagLifespan(damageMsgTag,3.)
    call SetTextTagPermanent(damageMsgTag,false)
    
    call SetTextTagVisibility(damageMsgTag,true)
    
    return damageMsgTag   
endfunction

function DamageUnitsInLine takes nothing returns nothing
    local unit caster = GetTriggerUnit()
    local unit enumUnit
    
    local integer abilityLevel = GetUnitAbilityLevel(caster,AbilityRawId())
    local integer textTagCounter = 0
    local integer textTagCounterMask = 0
    
    local real counter = 0.
    local real damage = AbilityBaseDamage() * abilityLevel
    local real damageCap = DamageCapPerLevel() * abilityLevel
    local real addedDamage = DamageAddedPerTarget() * abilityLevel
    local real radius = AbilityRadius()
    
    local boolean showString = ShowText()
    local string stringColor = TextColor()
    local string damageString
    
    local texttag array enumUnitDamageTag
    
    local attacktype whichAttack = AbilityAttackType()
    local damagetype whichDamage = AbilityDamageType()
    
    local location abilityLoc = GetSpellTargetLoc()
    local real locationX = GetLocationX(abilityLoc)
    local real locationY = GetLocationY(abilityLoc)
    
    local real casterX = GetUnitX(caster)
    local real casterY = GetUnitY(caster)
    
    local real enumUnitX
    local real enumUnitY
    
    local real angleBetweenPoints = bj_RADTODEG * Atan2(locationY - casterY, locationX - casterX)
    local real polarProjectionXCos = Cos(angleBetweenPoints * bj_DEGTORAD)
    local real polarProjectionYSin = Sin(angleBetweenPoints * bj_DEGTORAD)
    local real locationPolarProjectionY = 0.
    local real locationPolarProjectionX = 0.
    
    local real distance = AbilityBaseDistance()+(DistanceAddedPerLevel()*abilityLevel)
    
    local effect missileEffect = null
    
    local group damagedUnits = CreateGroup()
    local group tempUnits = CreateGroup()
    
    local boolexpr filterUnits = Condition (function ValidUnit)
    
    
	loop
		exitwhen counter > distance
			set locationPolarProjectionX = casterX + counter * polarProjectionXCos
			set locationPolarProjectionY = casterY + counter * polarProjectionYSin
            
			set missileEffect = AddSpecialEffect(MissileEffect(),locationPolarProjectionX,locationPolarProjectionY)

			call GroupEnumUnitsInRange(tempUnits,locationPolarProjectionX,locationPolarProjectionY,radius,filterUnits)
            
            
				loop
					set enumUnit = FirstOfGroup(tempUnits)
					exitwhen enumUnit == null
						if (IsUnitInGroup(enumUnit,damagedUnits) == false) then
							call GroupAddUnit(damagedUnits,enumUnit)
                            
							set enumUnitX = GetUnitX(enumUnit)
							set enumUnitY = GetUnitY(enumUnit)
							set damageString = stringColor + I2S(R2I(damage)) + "!"
            
							if showString then
								set enumUnitDamageTag[textTagCounter] = ShowFloatingTextTag(damageString,enumUnitX,enumUnitY)
								set textTagCounter = textTagCounter + 1
							endif
            
							call UnitDamageTarget(caster,enumUnit,damage,true,false,whichAttack,whichDamage,WEAPON_TYPE_WHOKNOWS)
							call GroupRemoveUnit(tempUnits,enumUnit)
            
							if damage + addedDamage >= damageCap then
									set damage = damageCap
								else
									set damage = damage + addedDamage 
							endif
						endif
                        
						call GroupRemoveUnit(tempUnits,enumUnit)
                        
						set enumUnit = null 
				endloop
        
		call DestroyEffect(missileEffect)
		set missileEffect = null
             
		set counter = counter + radius
	endloop
    
	call DestroyBoolExpr(filterUnits)
	set filterUnits = null
    
	call DestroyGroup(damagedUnits)
	call DestroyGroup(tempUnits)
	set damagedUnits = null
	set tempUnits = null
    
	call RemoveLocation(abilityLoc)
	set abilityLoc = null
    
	set caster = null
	set enumUnit = null
        
	if showString then
		call TriggerSleepAction(3.)
        
		loop
			exitwhen textTagCounterMask > textTagCounter
				call DestroyTextTag(enumUnitDamageTag[textTagCounterMask])
				set enumUnitDamageTag[textTagCounterMask] = null 
			set textTagCounterMask = textTagCounterMask + 1
		endloop
	endif
endfunction

function InitTrig_ArcaneMagic takes nothing returns nothing
    local trigger arcaneMagicTrig = CreateTrigger()
    local effect preLoadEffect = AddSpecialEffect(MissileEffect(),0.,0.)
    local integer counter = 0
    
    call DestroyEffect(preLoadEffect)
    set preLoadEffect = null
    
    call TriggerAddCondition(arcaneMagicTrig,Condition (function ValidAbility))
    call TriggerAddAction(arcaneMagicTrig, function DamageUnitsInLine)
	
    loop
        exitwhen counter > 15
            call TriggerRegisterPlayerUnitEvent(arcaneMagicTrig,Player(counter),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set counter = counter + 1
    endloop
    
    set arcaneMagicTrig = null 
endfunction

Any suggestions or feedback ? I gladly hear them.
 

Attachments

  • Arcane_Magic_Demo_Map_V1.03.w3x
    53.6 KB · Views: 500
I read it briefly, can't give any comments at the moment, although it looks longer then it should. Why do you use so many functions? constants / locals should do perfectly.
 
It dosn' t use any "CS Cache" system. You can modify almost every aspect of ability from constant functions ( 1/ 3 takes constant functions.) I could maybe shorten it. Going to deal with it next weekend (Hopefuly).
 
I like this spell, but it also bears a few flaws.

Just want to point one little thing before I start:

>local real locationX = GetLocationX(abilityLoc)
>local real locationY = GetLocationY(abilityLoc)

You don't use those. But don't delete them, I have a use for them...

Ok, first and foremost, you calculate the angle wrong. The spell looks weird because of this.

> local real facingDegrees = GetUnitFacing(caster)

If you target a point (using the spell), the line will spawn correctly. Problem is, if you then target another point close to that one, since the caster won't change his facing angle, it will go out the same way, which isn't what I want as the caster. I want it to go towards where I click, every time.

Here's what it should be.

You keep the 2 "locationX" and "locationY", but since we want the spell to aim correctly, we will calculate the angle between the position of your caster and your spelltargetloc.

Code:
local real facingDegrees = bj_RADTODEG * Atan2(locationY - casterY, locationX - casterX)

------------------------------

Also, when I cast Divine Shield it throws a line of your spell. Unfortunate bug, but there's a solution for it, especially when I see this:

Code:
function InitTrig_A000 takes nothing returns nothing
    local trigger arcaneCannonTrig = CreateTrigger()
    local boolexpr validAbility = Condition (function AbilityCheck)
    local integer counter = 0
    
    call TriggerAddCondition(arcaneCannonTrig,validAbility)
    
    loop
        exitwhen counter > 15
            call TriggerRegisterPlayerUnitEvent(arcaneCannonTrig,Player(counter),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set counter = counter + 1
    endloop

    call DestroyBoolExpr(validAbility)
    set validAbility = null    
    
    call TriggerAddAction(arcaneCannonTrig, function DamageUnitsInLine)
endfunction

Don't do that with trigger conditions. By "that" I mean use and destroy the local boolexpr. I never knew why, but it bugs.

Should be:

Code:
function InitTrig_A000 takes nothing returns nothing
    local trigger arcaneCannonTrig = CreateTrigger()
    local integer counter = 0
    
    call TriggerAddCondition(arcaneCannonTrig,Condition (function AbilityCheck))
    
    loop
        exitwhen counter > 15
            call TriggerRegisterPlayerUnitEvent(arcaneCannonTrig,Player(counter),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set counter = counter + 1
    endloop  
    
    call TriggerAddAction(arcaneCannonTrig, function DamageUnitsInLine)
endfunction

I know, it leaks, but it's not the end of the world. It's only 1 leak, not 1 in a loop which in the end means countless of leaks.

Now that I fixed those 2 things in your map (I won't upload it here, you can do it yourself), it works perfectly. The line go towards exactly where you clicked, and when you cast a spell it doesn't throw a line.

Also, you might want to fix that tooltip. Some syntax mistakes in there, such as typing "magic what damages..." when you should type "Magic spell which damages...", etc. Makes it hard to read.

Also (again), you might want to change the name of the trigger. "A000" isn't a good trigger name.

Overall, nice spell! Fix those flaws and it will be ok :)
 
>Ok, first and foremost, you calculate the angle wrong. The spell looks weird because of this.

> local real facingDegrees = GetUnitFacing(caster)

Its technically not 'wrong', but, more inaccurate, in a sense.

And Sooda: its probably better to make those screenshots fully visible in this thread, not in another website (just easier to see).

I'm going to try out the spell now :shades:.
 
@ Daxtreme
Thanks for awsome feedback, I also noticed odd angle when casting it sometimes but also forgot it after that. All your suggestions are really helpful. Going to fix them in upcoming weekend. Glad that somebody liked spell idea :eek:

> Its probably better to make those screenshots fully visible in this thread, not in another website (just easier to see).

Oky.

> "A00" sin' t good trigger name.
I was trying to aim for JASP standard.
 
> Its technically not 'wrong', but, more inaccurate, in a sense.

Quoted for Truth.

> I was trying to aim for JASP standard.

Actually, the JESP standard states that your spell's trigger must be named exactly "ArcaneMagic", and every function inside the trigger must have "ArcaneMagic" before its name, followed by an underline. Also, the function names must have no "Trig" in them.

I suggest that you should read it! ;)
 
JESP Standard isn't a requirement on this site. Just don't claim your spells to meet it if they don't.
 
Ok I have updated map and JASS to latest version. I fixed mistakes what Daxtreme pointed out. I used Vexorian idea to do actions in condition function. They told something about to not use waits in there. But I used one and didn' t noticed any drastic changes.
Can someone confirm is it safe to use waits there or not ? If not then what to do ?
 
I must say I love how this spell works :p Great job.

You forgot to null the local trigger though. Just add the line

set arcaneMagicTrig = null

at the end of the InitTrig function.

Approved, but fix this small leak ;)

P.S. It seems the wait along with Condition func doesn't bug, unless I missed what the part with the wait really does.
 
> You forgot to null the local trigger though. Just add the line

Fixed it and updated map, jass. I have noticed you are always so positive. It makes our days brighter.
 
> You forgot to null the local trigger though. Just add the line

Fixed it and updated map, jass. I have noticed you are always so positive. It makes our days brighter.

You think? :p

Make more spells!!!1
 
Oh I just fixed major bug in my JASS, TriggerSleepAction always halts condition functions (Even when waits are in functions what will be called from condition function.) I had to use actionFunc to finally clean leaks (Before it didn' t removed any leak because of TriggerSleepAction.) Sorry fo such a mistake and please re-download ability if you had version 1.02 or older :eek: .

EDIT: Vex told I can also call functions with waits with ExecuteFunc... oh is it worth to change back ?
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top