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: 247

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
964
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: 242

Romek

Super Moderator
Reaction score
964
> 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.
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1
  • The Helper The Helper:
    New recipe is another summer dessert Berry and Peach Cheesecake - https://www.thehelper.net/threads/recipe-berry-and-peach-cheesecake.194169/

      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