Spell Exploding Runes

Cohadar

master of fugue
Reaction score
209
Exloding Runes
(vJASSified)
By moyack und cohadar. 2009.

attachment.php



A long time ago, I(moyack) did by request a spell for the project Tides of Darkness. Now I decided to vJASSify it and this is the result.
This spell resembles the one in WC2, used by the ogre magi units.


Requirements:

Spell Description:
It is an exploding thingy. :D


How to install:

  1. Open the test map.
  2. Copy the trigger and the ability used in this spell into you map.
  3. Copy ABC library to your map.
  4. Copy all rune units to your map and make sure their ID's are in sequential order like in demo map.
  5. Voila!! spell installed.


JASS:

//***************************************************************************************************************
//*                                                                                                             *
//*                                          Runes Spell (vJASSified).                                          *
//*                                           By Moyack and Cohadar                                             *
//*                                                  V.3.1                                                     *
//*                                                                                                             *
//***************************************************************************************************************
//*

library Runes initializer init uses ABC
//***************************************************************************************************************
//* The constant data where you can modify the ability properties
//*
globals
    // Returns the spell ID. Spell based on Channel
    private constant integer SpellID = 'A000' 
    
    // These effects are loaded from Art-Special field on spell
    private string Effect_Ground  
    private string Effect_Explosion
endglobals

//===========================================================================
//  Rune dummy unit id's must be in sequential order, for example:
//  'o0R1', 'o0R2', 'o0R3', 'o0R4', 'o0R5', 'o0R6'
//  Check object editor data for details
//===========================================================================
private constant function GetRuneId takes integer variation returns integer
    return 'o0R0' + variation
endfunction

private constant function DetectionRange takes integer lvl returns real
    return 120. + 50. * (lvl - 1) // Returns the range where the runes will detect units
endfunction

private constant function ExplosionRange takes real detection_range returns real
    return 1.2 * detection_range // explosion range is a bit bigger than detection range
endfunction

private constant function Radius takes integer lvl returns real
    return 2 * DetectionRange(lvl) // Returns the radius where the runes will be placed
endfunction

private constant function Amount takes integer lvl returns integer
    return 4 + 1 * (lvl - 1) // Returns the number of runes at the perimeter
endfunction

private constant function Damage takes integer lvl returns real
    return 350. + 50. * (lvl - 1) // Returns the damage dealt to units near to the runes
endfunction

private constant function Duration takes integer lvl returns real
    return 40. + 5. * (lvl - 1) // Returns the runes timed life
endfunction

private constant function Scale takes integer lvl returns real
    return 1.2 + 0.3 * (lvl - 1) // Returns the rune scale
endfunction

//***************************************************************************************************************
//* end constant data...
//*

globals
    private group G = CreateGroup()
endglobals

//===========================================================================
private function DisplayDamage takes real x, real y, real dmg returns nothing
    local texttag tt = CreateTextTag()
    local string text = I2S(R2I(dmg)) + "!"
    call SetTextTagText(tt, text, 0.024)
    call SetTextTagPos(tt, x, y, 0.0)
    call SetTextTagColor(tt, 255, 0, 0, 255)
    call SetTextTagVelocity(tt, 0.0, 0.04)
    call SetTextTagVisibility(tt, true)
    call SetTextTagFadepoint(tt, 2.0)
    call SetTextTagLifespan(tt, 5.0)
    call SetTextTagPermanent(tt, false)
    //set text = null
    set tt = null    
endfunction

//===========================================================================
struct RuneData 
    unit caster
    unit rune
    trigger proximity_trig
    triggeraction action // used for cleaning up later
    real damage
    real detection_range
    real x
    real y
    
    static RuneData THIS // for passing data to ExplosionEnum
    
    //===========================================================================
    private static method ExplosionEnum takes nothing returns nothing
        if GetWidgetLife(RuneData.THIS.caster) > 0.405 then
            call UnitDamageTarget(RuneData.THIS.caster, GetEnumUnit(), RuneData.THIS.damage, false, false, ATTACK_TYPE_SIEGE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_ROCK_HEAVY_BASH)
        else
            call UnitDamageTarget(RuneData.THIS.rune, GetEnumUnit(), RuneData.THIS.damage, false, false, ATTACK_TYPE_SIEGE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_ROCK_HEAVY_BASH)
        endif
    endmethod
    
    //===========================================================================
    //  When ground unit walks over rune it is time for action
    //===========================================================================
    private static method RuneUnitFilter takes nothing returns boolean
        if GetWidgetLife(GetFilterUnit()) > 0.405 then
            if IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING) == false  then
                return true
            endif
        endif
        return false
    endmethod    
    
    //===========================================================================
    private static method RuneActions takes nothing returns nothing
        local RuneData data = GetTriggerStructA(GetTriggeringTrigger()) // ABC
        set RuneData.THIS = data
        call GroupEnumUnitsInRange(G, data.x, data.y, 2*ExplosionRange(data.detection_range), Condition(function RuneData.RuneUnitFilter))
        call ForGroup(G, function RuneData.ExplosionEnum)
        call GroupClear(G)
        call DisplayDamage(data.x, data.y, data.damage)
        call data.destroy()
    endmethod
    
    //===========================================================================
    static method create takes unit caster, real x, real y , integer level, boolean central returns RuneData
        local RuneData data = RuneData.allocate()
        local real scale = Scale(level)
        set  data.x = x
        set  data.y = y
        set  data.caster = caster
        set  data.proximity_trig = CreateTrigger()
        set  data.detection_range = DetectionRange(level)
        set  data.action = TriggerAddAction(data.proximity_trig, function RuneData.RuneActions)
        call SetTriggerStructA(data.proximity_trig, data) // ABC  trigger->data  
        // runes 1-2 are central runes
        // runes 3-6 are perimiter runes
        // Central runes have bigger model and deal double damage        
        if central then 
            set data.rune = CreateUnit(GetOwningPlayer(caster), GetRuneId(GetRandomInt(1,2)), x, y, 0)
            set data.damage = Damage(level)*2
        else
            set data.rune = CreateUnit(GetOwningPlayer(caster), GetRuneId(GetRandomInt(3,6)), x, y, 0)
            set data.damage = Damage(level)
        endif
        call SetUnitScale(data.rune, scale, scale, scale)
        call UnitAddAbility(data.rune, 'Agho') // ghost (invisibility)
        call UnitAddAbility(data.rune, 'Avul') // invulnerable
        call UnitApplyTimedLife(data.rune, 'BTLF', Duration(level))
        call TriggerRegisterUnitInRange(data.proximity_trig, data.rune, data.detection_range, Filter( function RuneData.RuneUnitFilter ))
        return data
    endmethod
    
    //===========================================================================
    method onDestroy takes nothing returns nothing
        call DestroyEffect(AddSpecialEffect(Effect_Ground, .x, .y))
        call DestroyEffect(AddSpecialEffect(Effect_Explosion, .x, .y))
        call DisableTrigger(this.proximity_trig)
        call TriggerRemoveAction(this.proximity_trig, this.action)
        call ClearTriggerStructA(this.proximity_trig) // ABC
        call DestroyTrigger(this.proximity_trig)
        call KillUnit(this.rune)
        call ShowUnit(this.rune, false)
    endmethod
endstruct


//***************************************************************************************************************
//*                                                                                                             *
//*                                         Runes Casting Functions                                             *
//*                                                                                                             *
//***************************************************************************************************************

//===========================================================================
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

//===========================================================================
private function Actions takes nothing returns nothing
    local location targetLoc = GetSpellTargetLoc()
    local real lx = GetLocationX(targetLoc)
    local real ly = GetLocationY(targetLoc) 
    local integer level = GetUnitAbilityLevel(GetTriggerUnit(), SpellID)
    local real fc = GetUnitFacing(GetTriggerUnit()) * bj_DEGTORAD
    local real a = Amount(level)
    local real R = Radius(level)
    local real angle = 2 * bj_PI / a
    local integer count = 0
    local real x
    local real y
    call RemoveLocation(targetLoc)
    set targetLoc = null
    call RuneData.create(GetTriggerUnit(), lx, ly , level, true)
    loop
        exitwhen count == a
        set x = lx + R * Cos(fc + count * angle)
        set y = ly + R * Sin(fc + count * angle)
        call RuneData.create(GetTriggerUnit(), x, y , level, false)
        set count = count + 1
    endloop
endfunction

//===========================================================================
private function GetFX takes integer id returns string 
    return GetAbilityEffectById(SpellID, EFFECT_TYPE_SPECIAL, id)
endfunction

//===========================================================================
private function init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( trig, Condition( function Conditions ) )
    call TriggerAddAction( trig, function Actions )
    
    set Effect_Ground = GetFX(0)
    set Effect_Explosion = GetFX(1)
    call Preload(Effect_Ground)
    call Preload(Effect_Explosion)
endfunction

endlibrary



Changelog:
  • 2.0: Initial Release
  • 2.1: Improved the killing credits and performance a little bit more.
  • 2.2: Simplified the code a little bit in the Grouping units, and if the caster is alive in that moment, the killing credit goes to him. Added to the resurrection some eyecandy :D
  • 3.0: (Cohadar) Added new rune models. Central rune now deals double damage. Runes are now invisible and can be detected by true sight. No more perma timers but spell now requires ABC.
  • 3.1: Added timed life to runes.

As to avoid confusion:
Moyack ran out of time so I(Cohadar) offered him to complete this spell according to my suggestions and resubmit it.
He agreed.
 

Attachments

  • (1)Runes Spell.w3x
    61.3 KB · Views: 243

Cohadar

master of fugue
Reaction score
209
is there a way to make it so the runes only dmg enemy units? Jass atm makes my brain melt.

Basically it is much more complicated to make it detect/damage only enemies becayse than unit owner would have to be passed as parameter to RuneUnitFilter, and it would have to be done in 2 places because this filter is used as both detection filter and damage filter.

So it would require a lot of code changes and it would also have some performance cost.

So the answer is: Yes there is a way, but I am not gonna do it.

I'll test it this night.

Last time you said that you were gone for a month :p
 

Romek

Super Moderator
Reaction score
963
Strings don't need nulling.
And dynamic triggers are bad, they can't be completely destroyed (the actions, in particular). You could use a static trigger and PUI, to get the data attached to the triggering unit.

And what does the spell do? I guess it's like some sort of landmine? :)
 

Cohadar

master of fugue
Reaction score
209
Strings don't need nulling.
Irrelevant

And dynamic triggers are bad,
Only if you are bad coder.

they can't be completely destroyed (the actions, in particular).
Wanna bet? (read code carefully)

You could use a static trigger and PUI, to get the data attached to the triggering unit.
I could? I don't get how you imagine that to be done, so please post some code.

And what does the spell do? I guess it's like some sort of landmine? :)
Exploding runes? no way, it is probably a healing spell. :)
 

moyack

Cool Member
Reaction score
9
One thing I noticed is that the runes are not dying. 2 minutes and they don't die.

You should check that.
 

Attachments

  • LastReplay.w3g
    17.9 KB · Views: 239

Romek

Super Moderator
Reaction score
963
> Irrelevant
You do it.

> Only if you are bad coder.
Does that make a difference?

> Wanna bet? (read code carefully)
I'm pretty sure TriggerRemoveAction doesn't actually destroy the handle. It just removes the action from the trigger, so it leaks.

> I could? I don't get how you imagine that to be done, so please post some code.
See my Heroic Guard spell.

> Exploding runes? no way, it is probably a healing spell. :)
It heals -300 HP to units that step within range. I guess. :D
 

Cohadar

master of fugue
Reaction score
209
One thing I noticed is that the runes are not dying. 2 minutes and they don't die.

You should check that.
The creep revival trigger in the demo map is reviving them, it has nothing to do with spell, spell works fine. Will fix the demo map...
EDIT:
Nope I was wrong, seems I forgot this line somewhere while I was coding:
JASS:

call UnitApplyTimedLife(data.rune, 'BTLF', Duration(level))

spell updated.


==================================================================================
> Irrelevant
You do it.
Actually I think I stole that texttag code from emjlr3 or something so go hussle him about it

> Only if you are bad coder.
Does that make a difference?
Of course it does, for example my dynamic trigger spells don't have/cause bugs :p

> Wanna bet? (read code carefully)
I'm pretty sure TriggerRemoveAction doesn't actually destroy the handle. It just removes the action from the trigger, so it leaks.
You are talking about trigger events. In case you haven't noticed the trigger is also destroyed.

> I could? I don't get how you imagine that to be done, so please post some code.
See my Heroic Guard spell.
That spell has nothing to do with Unit Enters Range
 

Viikuna

No Marlo no game.
Reaction score
265
Of course it does, for example my dynamic trigger spells don't have/cause bugs

Because there is so many different truths about dynamic triggers, I think there is nothing wrong with using dynamic triggers in spell, unless someone can prove that the spell has/causes bugs because of dynamic triggers.
 
Reaction score
91
> I think there is nothing wrong with using dynamic triggers in spell
True, there isn't. There isn't anything wrong with dynamic triggers in other spells either, unless you're trying to do that handle stack bug on purpouse. :p
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top