• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Trigger] Relativistic Missiles help - Nova of Missiles

Status
Not open for further replies.
Level 7
Joined
Oct 20, 2010
Messages
182
I'm using Relativistic Missiles by Chopinski to attempt to code a nova spell. My goal is to create a spell that launches a nova of missiles from a caster unit in which an enemy can only be hit once per cast, despite multiple missiles of the cast colliding with them. In practice, it'd just behave as a nova spell that travels outwards.

I've done just that but the only problem is it isn't MUI. I'm not sure how to use dynamic indexing at all and I don't know if this is something I can use Unit Indexer for either. I have three triggers. The cast trigger, the hit trigger, and the finish trigger that clears the unit group. (I don't like the use of the finish trigger as I wonder if it'll cause problems when other missiles interact with these ones)

The help I need is in turning this into an MUI spell, and even suggestions for alternative ways or systems to accomplish this effect.

  • Nova Cast
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Missiles - F
    • Actions
      • For each (Integer A) from 0 to 10, do (Actions)
        • Loop - Actions
          • Set VariableSet MissileSource = (Triggering unit)
          • Set VariableSet MissileStart = (Position of (Triggering unit))
          • Set VariableSet MissileFinish = ((Position of (Triggering unit)) offset by 400.00 towards FAngle degrees.)
          • Set VariableSet FAngle = (FAngle + 36.00)
          • Set VariableSet MissileStartZ = 0.00
          • Set VariableSet MissileFinishZ = 0.00
          • Set VariableSet MissileSpeed = 500.00
          • Set VariableSet MissileCollision = 180.00
          • Set VariableSet Missile_onHit = Nova Hit <gen>
          • Set VariableSet Missile_onFinish = Nova Clear <gen>
          • Set VariableSet MissileModel = Abilities\Spells\Other\BreathOfFire\BreathOfFireMissile.mdl
          • Trigger - Run MissileCreate <gen> (ignoring conditions)
  • Nova Hit
    • Events
    • Conditions
      • And - All (Conditions) are true
        • Conditions
          • (MissileHitUnit is in F_UG.) Equal to False
          • MissileHitUnit Not equal to MissileSource
          • (MissileHitUnit is alive) Equal to True
    • Actions
      • Unit Group - Add MissileHitUnit to F_UG
      • Set VariableSet hitcount = (hitcount + 1)
      • Game - Display to (All players) the text: (String(hitcount))
  • Nova Clear
    • Events
    • Conditions
    • Actions
      • Unit Group - Remove all units from F_UG.
 
Level 7
Joined
Oct 20, 2010
Messages
182
It'd be incredibly helpful if you'd show me how to apply that using this missile system if you're familiar with it. I've checked the triggers of the spell and the code of missile management intertwining with what I'm looking for makes it all go over my head, haha.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
It's not too difficult, you need to use a Unit Group array, but in GUI these require an extra step.

Instead of using the standard GUI method of creating a Unit Group you need to do it like this:
  • Actions
  • Set Variable F_Index = F_Index + 1
  • Custom script: set udg_F_UG[F_Index] = CreateGroup()
  • For each (Integer A) from 0 to 10, do (Actions)
  • // do missile stuff

This index then needs to be stored to the missiles that you create. I assume Chopinski's system provides an Integer variable called Data or something along those lines that is meant for exactly this sort of thing.

EDIT: It looks like you can use the MissileType variable for this. As far as I can tell it's not used by the system for anything at all, so it's purely up to you to define it's use. In this case it can be used to link F_Index to the Missiles.

Now you can destroy the unit group in the Nova Finish trigger like so:
  • Custom script: call DestroyGroup(udg_F_UG[Data])
In this trigger you would get the [Index] from the missile's "Data" property.

In your Nova Hit trigger you would use the same logic, get the "Data" from the missile and use that as the [Index] of F_UG[].
  • (MissileHitUnit is in F_UG[Data].) Equal to False

Keep in mind that this has a limit of 32,768 casts per game before it breaks. This is because F_Index is always increasing by 1 and never gets reset back to 0. A simple fix would be to check if F_Index is Equal to "Data" when a Nova finishes, and if it is, set F_Index back to 0. Technically this fix isn't a 100% guarantee, but the only way things could go wrong here is if your players somehow managed to keep at least 1 Nova active at all times and do this 32,768 times in a row, thus reaching the Index limit. Highly unlikely if not impossible, it's stupid to even humor it.

Side note, this doesn't do anything in your Nova Hit trigger:
  • And - All (Conditions) are true
By default, ALL Conditions need to be met. Using And here is redundant.
 
Last edited:
Level 7
Joined
Oct 20, 2010
Messages
182
Thanks for the information on the AND statement.

There is no way to store data, at least that I can tell. I am just using MissileDamage to test what you've suggested, but strangely using this custom script to destroy/create a unit group that's an array and where the index is a variable, it considers the variable undeclared. I'm not sure why I'm getting this error. I can confirm I have these variables, they are integers, they are not arrays.
1640327569975.png

  • Nova Cast
    • Set VariableSet Findex = (Findex + 1)
    • Custom script: set udg_F_UG[Findex] = CreateGroup()
    • For each (Integer A) from 0 to 10, do (Actions)
      • Loop - Actions
        • -------- SETTING UP MEMBERS --------
        • Set VariableSet MissileDamage = (Real(Findex))
        • other missile stuff
        • Trigger - Run MissileCreate <gen> (ignoring conditions)
  • Nova Clear
    • Set VariableSet r2i = (Integer(MissileDamage))
    • Custom script: call DestroyGroup(udg_F_UG[r2i])


EDIT: I did get a workaround assuming the above won't work. Based on Devalut's teeth trigger, something like this should work... my only question is, are these unit groups actually leakless? They seem to work differently than I first thought.
  • NovaCast
    • Actions
      • Set VariableSet Findex = (Findex + 1)
      • Set VariableSet F_remainingmissile[Findex] = 0
      • Custom script: set udg_tempug = CreateGroup()
      • Set VariableSet F_UG[Findex] = tempug
      • For each (Integer A) from 0 to 18, do (Actions)
        • Loop - Actions
          • Set VariableSet F_remainingmissile[Findex] = (F_remainingmissile[Findex] + 1)
          • -------- Input other Missile stuff here too --------
          • Set VariableSet MissileDamage = (Real(Findex))
          • Set VariableSet Missile_onHit = Nova Hit <gen>
          • Set VariableSet Missile_onFinish = Nova Clear <gen>
          • Trigger - Run MissileCreate <gen> (ignoring conditions)
  • Nova Clear
    • Actions
      • Set VariableSet r2i = (Integer(MissileDamage))
      • Set VariableSet F_remainingmissile[r2i] = (F_remainingmissile[r2i] - 1)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • F_remainingmissile[r2i] Equal to 0
        • Then - Actions
          • Set VariableSet tempug = F_UG[r2i]
          • Custom script: call DestroyGroup(udg_tempug)
        • Else - Actions
 
Last edited:
Level 20
Joined
May 16, 2012
Messages
635
I created a version of your spell in vJASS based on your description. Doing what you want in jass is a brease, but if you are using the GUI version then i think @Uncle approach will do basically the same thing. I commented as much as i could to explain the process.

Note*: I'm at work so i could'nt test this code yet, but i think it should work.

vJASS:
library Nova requires SpellEffectEvent, PluginSpellEffect, Missiles
    /* -------------------------------------------------------------------------- */
    /*                                Configuration                               */
    /* -------------------------------------------------------------------------- */
    globals
        // The ability raw code
        private constant integer ABILITY    = 'A000'
        // The missile model
        private constant string  MODEL      = "Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireMissile.mdl"
        // The effect used when a missile hits a target
        private constant string  HIT_EFFECT = ""
        // The attachment point of the hit effect
        private constant string  ATTACH     = "origin"
        // The missile scale
        private constant real    SCALE      = 1.
        // The missile speed
        private constant real    SPEED      = 500.
    endglobals

    // The missile count
    private function GetMissileCount takes integer level returns integer
        return 10 + 0*level
    endfunction

    // The missile collision
    private function GetCollision takes integer level returns real
        return 180. + 0.*level
    endfunction

    // The missile travel distance
    private function GetDistance takes integer level returns real
        return 400. + 0.*level
    endfunction

    // The missile damage
    private function GetDamage takes integer level returns real
        return 50. + 50.*level
    endfunction

    // The Unit filter
    private function UnitFilter takes player owner, unit target returns boolean
        return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
    endfunction

    /* -------------------------------------------------------------------------- */
    /*                                   System                                   */
    /* -------------------------------------------------------------------------- */
    private struct NovaMissile extends Missiles
        Nova cast

        method onHit takes unit u returns boolean
            if UnitFilter(owner, u) then                        // Filter units when the hit event occurs.
                if not IsUnitInGroup(u, cast.group) then        // Checks if the hitted unit is already in the cast instance group.
                    if GroupAddUnit(cast.group, u) then         // Tries to add the hitted unit to the cast group.
                                                                // try to damage the unit
                        if UnitDamageTarget(source, u, damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null) then
                            call DestroyEffect(AddSpecialEffectTarget(HIT_EFFECT, u, ATTACH))   // Create the hit effect on the unit.
                        endif
                    endif
                endif
            endif

            return false            // Return false so the missile is not destroyed
        endmethod

        method onRemove takes nothing returns nothing
            call cast.decrement()   // When the missile finishes, call the decrement method from its parent.
        endmethod
    endstruct

    private struct Nova
        group group     // the unit group that will contain the units hit by a missile from a cast instance.
        integer count   // The total amount of missiles from a cast.

        // Destroys the cast instance, cleaning up leaks.
        method destroy takes nothing returns nothing
            call DestroyGroup(group)
            call deallocate()
            set group = null
        endmethod

        // This method is called when a missile finish its course. it decrement the missile count for the cast instance.
        // When count is 0 then there are no more missiles from one particular cast instance so we can destroy it.
        method decrement takes nothing returns nothing
            set count = count - 1
            if count <= 0 then
                call destroy()
            endif
        endmethod

        static method onCast takes nothing returns nothing
            local thistype this = thistype.allocate()                   // Create a cast instance
            local real d = GetDistance(Spell.level)                     // Get the missile travel distance
            local integer i = 0                                         // iterator
            local real angle = 0                                        // initial angle
            local NovaMissile missile                                   // the missile variable

            set count = GetMissileCount(Spell.level)                    // Get the amount of missiles.
            set group = CreateGroup()                                   // Create a unit group for the ability cast.

            loop
                exitwhen i == count
                    // Create a missile from caster position towards distance and angle.
                    set missile = NovaMissile.create(Spell.source.x, Spell.source.y, 50, Spell.source.x + d*Cos(angle), Spell.source.y + d*Sin(angle), 50)
                    set angle = angle + (360/count)*bj_DEGTORAD         // increment the angle.
                    set missile.model = MODEL                           // Set the missile model.
                    set missile.speed = SPEED                           // Set the missile speed.
                    set missile.scale = SCALE                           // Set the missile scale.
                    set missile.owner = Spell.source.player             // Set the missile owner.
                    set missile.source = Spell.source.unit              // Set the missile source unit.
                    set missile.damage = GetDamage(Spell.level)         // Set the missile damage.
                    set missile.collision = GetCollision(Spell.level)   // Set the missile collision.
                    set missile.cast = this                             // Set the missile reference to its creator (this cast instance).
                
                    call missile.launch()                               // Launches the missile
                set i = i + 1
            endloop
        endmethod

        private static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(ABILITY, function thistype.onCast) // When the ability is cast.
        endmethod
    endstruct
endlibrary

EDIT: It looks like you can use the MissileType variable for this. As far as I can tell it's not used by the system for anything at all, so it's purely up to you to define it's use. In this case it can be used to link F_Index to the Missiles.
In the next few days i'll be releasing version 2.5 of the system and it will include a gui variable to the DATA member. You can use the type member no problem, it is not used by the core system for nothing, but it is used by the missile group utility.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
In the next few days i'll be releasing version 2.5 of the system and it will include a gui variable to the DATA member. You can use the type member no problem, it is not used by the core system for nothing, but it is used by the missile group utility.
Great, thanks for the quick response.

@Bain_Rebirth
So I forgot the "_udg" prefix in my Custom scripts. When referencing a global variable in Custom script you need to write _udg before it's name.
Also, don't use the Damage variable as the Data, if anything use MissileType, and once Chopinski updates the system you can swap it with the new Data variable.

On Nova Cast:
  • Set VariableSet F_index = Findex + 1
  • Custom script: set udg_F_UG[udg_Findex] = CreateGroup()
  • For each (Integer A) from 0 to 10, do (Actions)
    • Loop - Actions
      • Set VariableSet MissileType = Findex
      • ///// setup the rest of the Missile variables
On Nova Finish:
  • Custom script: call DestroyGroup(udg_F_UG[udg_MissileType)
  • If (Findex Equal to MissileType) Then Set VariableSet Findex = 0 else Do nothing
On Nova Hit:
  • Conditions
  • (MissileHitUnit is in F_UG[MissileType].) Equal to False

That's if you don't end up using the code Chopinski wrote for you, which I imagine is the most efficient design.
 
Last edited:
Level 7
Joined
Oct 20, 2010
Messages
182
The switch to JASS or another language would have saved me so much headache this past year. It's a learning curve I haven't committed myself to just yet. @chopinski I attempted to use your code, there were a few syntax errors that I had to fix and it worked nicely. My only problem is not having the requisite knowledge of editing this for future use, but this was invaluable to read through and help understand so I thank you.

@Uncle I missed the udg prefix, I was scratching my head for an hour. I don't utilize missiletype or missiledamage in any of my other missiles, but I switched to Missiletype for the sake of avoiding conversion. I attempted a deindexer which has to take into account these missiles could be slowed down or paused by other effects, thus the last indexed spell finishing shouldn't necessarily restart.
  • Nova Clear
    • Events
    • Conditions
    • Actions
      • Set VariableSet r2i = MissileType
      • Set VariableSet F_remainingmissile[r2i] = (F_remainingmissile[r2i] - 1)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • F_remainingmissile[r2i] Equal to 0
        • Then - Actions
          • Custom script: call DestroyGroup(udg_F_UG[udg_r2i])
          • For each (Integer F_loopint) from 1 to (Findex - 1), do (Actions)
            • Loop - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • F_remainingmissile[F_loopint] Greater than 0
                • Then - Actions
                  • Set VariableSet F_deinxer = (F_deinxer + 1)
                • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • F_deinxer Equal to 0
              • Findex Equal to r2i
            • Then - Actions
              • Set VariableSet Findex = 0
            • Else - Actions
          • Set VariableSet F_deinxer = 0
        • Else - Actions
 
Status
Not open for further replies.
Top