• 🏆 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!

Devastation Envoy V1.01

Thought I'd do a little thing, something you could spam a lot out of and not worry about your computer frying - while it's possible for all of my previous spell works to be reduced to tiny little versions, it never suited most of them, well this little number will hopefully change that, the Level 1 version in the test map is what I imagine to be best for spam-tasms though the level 3 does well to show what it's possible of being if you scale it up a bit, it has a 0 cooldown (yes that's intentional) so you can drain your mana very very quickly to cast this spell a few tens of times, and (at least on my computer) not experience a significant framrerate drop.


- Spammable
- Involves Celestial physics (woo orbits)
- Effects Galore (how surprising)
- Randomises the cast every time (takes a long long time to get two the same)
- Heavily configurable
- Extensive Readme (READ IT!)
- Uses a linked list
- comes with a variable creator to help with importing



The caster calls upon the light and creates a portal of Holy energies which release Shards of light to fall upon and deal damage to the land.
Level 1 - Shards deal 100 Damage, Portal lasts 1.50 seconds.
Level 2 - Shards deal 125 Damage, Portal lasts 2.00 seconds.
Level 3 - Shards deal 150 Damage, Portal lasts 2.50 seconds.


JASS:
////////////////////////////////////////////////////////////////////
//                    DEVASTATION ENVOY V1.00                     //
//                                                                //
//  Author: Tank-Commander                                        //
//  Requires: Dummy.mdl                                           //
//  Purpose: Spam-cast spell                                      //
//                                                                //
//  Notes:                                                        //
//    -  Read the readme before you try modifying the config      //
//    -  Use the "Helpful files" to help you import the spell     //
//                                                                //
//  Credits:                                                      //
//    -  (Dummy.mdl) Vexorian                                     //
//                                                                //
//                                                                //
//  If you have used this spell in your map, you are required     //
//  to give credits to Tank-Commander for the creation of it      //
//  If you take a snippet from this code for physics reasons      //
//  or whatever else, if possible, also give credit for its       //
//  source origin                                                 //
//                                                                //
//  Importing: Remember to import Dummy.mdl and possibly the      //
//  object data when importing this spell (just the ability and   //
//  dummy unit) note that importing the dummy unit object should  //
//  be done AFTER importing Dummy.mdl, if not, then you will      //
//  need to set the model of the dummy to Dummy.mdl yourself, if  //
//  you would like to change what your dummy unit is you can,     //
//  but it will still need Dummy.mdl for this spell to work       //
//  If you have problems, make sure you read the readme first     //
//  and follow it to the best of your abilities before posting    //
//  comments                                                      //
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
//  README:                                                       //
//    Before modifying this spell a few things need to be         //
//    understood and read, this is one of those things, while     //
//    most modification can be considered intuitive, it still     //
//    helps to read through these intstructions, as they will     //
//    inform you about how to configure this spell to your        //
//    desire.                                                     //
//----------------------------------------------------------------//
//  Initial importing: The variable creator trigger can be        //
//  imported first and if you have the correct settings (file,    //
//  preferences, General, automatically create unknown variables  //
//  checked, then when you pasta in the variable creator it       //
//  will automatically give you all the variables you need for    //
//  this spell                                                    //
//----------------------------------------------------------------//
//  This configuration is ordered by category of use, and not     //
//  alphabetically, the ones you're most likely to want to        //
//  change, are earlier in the readme, the most essential being   //
//  first. These categories are:                                  //
//                                                                //
//  - Data Values: The most essential to change, otherwise the    //
//                 spell will simply not function                 //
//                                                                //
//  - ChronoKinetic: Fancy word for affecting the flow of time    //
//                 Contains things like the duration of the       //
//                 spell, the Timer speed and Shard Spawn rate    //
//                                                                //
//  - Power: Controls the aspects of the speed of the shards      //
//                 based on the Mass, and Radius of the Portal    //
//                                                                //
//  - Damage: Controls the AOE and damage (health and mana)       //
//                 values each individual Shard has               //
//                                                                //
//  - Aesthetics: Controls the Appearences of the ability - the   //
//                 models used, the scales of those models and    //
//                 so on                                          //
//                                                                //
//  - Damage Information: Contains things like weapontypes and    //
//                 damage types, etc.                             //
//                                                                //
//  - Other Attributes: Contains the world gravity, death timer   //
//                 Height let and the celestial Gravitational     //
//                 constant.                                      //
//----------------------------------------------------------------//
//                        DATA VALUES                             //
//----------------------------------------------------------------//
//  Dummy ID: This is the raw data of the dummy unit, to see      //
//  raw data in the object editor, press Ctrl + D, doing this     //
//  again will switch it back, if you want to change this dummy   //
//  unit, follow as displayed (use the first 4 characters in      //
//  the raw data and put them in ' markers)                       //
constant function DE_DummyID takes nothing returns integer
    return 'u000'
endfunction
//----------------------------------------------------------------//
//  Spell ID: This is done in the same manner as the Dummy ID     //
//  except that this time, you're doing it with ability raw       //
//  data, see the dummy unit if you do not know already how to    //
//  view raw data                                                 //
constant function DE_SpellID takes nothing returns integer
    return 'A000'
endfunction
//----------------------------------------------------------------//
//                        CHRONOKINETIC                           //
//----------------------------------------------------------------//
//  Timer Speed: The default for this is 0.03, it determines how  //
//  many times per second these triggers are ran, normally you    //
//  want to leave this at 0.03, but 0.04 and prehaps 0.05         //
//  would be good options if you computer lags a bit.             //
constant function DE_TimerSpeed takes nothing returns real
    return 0.03
endfunction
//----------------------------------------------------------------//
//  Duration Base: Determines how long each cast will last as a   //
//  base value - this is to help give better control when         //
//  scaling abilities while not having to change values for each  //
//  level, cutting out a lot of configuration time, the value is  //
//  in seconds (1.00 = 1second)                                   //
constant function DE_DurationBase takes nothing returns real
    return 1.00
endfunction
//----------------------------------------------------------------//
//  Duration Per Level: Deter,omes jpw ;pmg eacj casy wo;; as a   //
//  per level value - at level one this will be applied once,     //
//  twice at level two and so forth, it is added on to the base   //
//  value to come to the actual duration of the spell instance    //
constant function DE_DurationPerLevel takes nothing returns real
    return 0.50
endfunction
//----------------------------------------------------------------//
//  Spawn Rate Base: A base value, this controls how many         //
//  seconds there are between each Shard being created, if this   //
//  is the same or lower (the sum total of the Base and Per       //
//  Level) is lower or equal to the timer speed, then one Shard   //
//  will be made every Timer Speed seconds (Time is a decimal     //
//  value, 0.5 is half a second)                                  //
constant function DE_SpawnRateBase takes nothing returns real
    return 0.15
endfunction
//----------------------------------------------------------------//
//  Spawn Rate Per Level: The other part to the spawn rate        //
//  remember that it's a decimal value and the lower it is, the   //
//  faster Shards are created, so for this per level you'll       //
//  normally either want a negative value, or 0.00, though you    //
//  are not limited to such values                                //
constant function DE_SpawnRatePerLevel takes nothing returns real
    return -0.03
endfunction
//----------------------------------------------------------------//
//                            POWER                               //
//----------------------------------------------------------------//
//  Radius Base: This Base value determines how far out from the  //
//  center the shards are created, it also affects the gravity    //
//  strength of the Portal - the smaller the radius in relation   //
//  to it's Mass, the higher the gravitational effect and the     //
//  more volatile the Shards will be                              //
constant function DE_RadiusBase takes nothing returns real
    return 100.00
endfunction
//----------------------------------------------------------------//
//  Radius Per Level: The per level component to the Radius,      //
//  works the same all the other per level factors, giving this   //
//  a negative value will make your Portal smaller but more       //
//  volatile each level, and a positive value will make it large  //
//  and more stable                                               //
constant function DE_RadiusPerLevel takes nothing returns real
    return 0.00
endfunction
//----------------------------------------------------------------//
//  Mass Base: the Mass within the Portal, the main strength of   //
//  the gravitational pull, larger numbers will decrease          //
//  stability and increase the speed the Shards move, lowering    //
//  it makes it more stable, but unlike the Radius, it does not   //
//  affect the size of your Portal, works well for a baseline     //
//  when testing different levels of instability or stability     //
//  when getting your preferences for the spell                   //
constant function DE_MassBase takes nothing returns real
    return 800.00
endfunction
//----------------------------------------------------------------//
//  Mass Per Level: the Per level component of the Mass can make  //
//  the spell more stable or volatile as it levels up without     //
//  changing it's size, remember that it is applied once at level //
//  one, so the values 800 and 400 (the defaults) yeild 1200      //
//  Mass at level one                                             //
constant function DE_MassPerLevel takes nothing returns real
    return 400.00
endfunction
//----------------------------------------------------------------//
//                            DAMAGE                              //
//----------------------------------------------------------------//
//  Shard AOE Base: Determines the area of effect of the shards   //
//  90 is melee range, 40 is directly on top, 20 is probably too  //
//  small an area to work and 400 to large, try different values  //
//  and see what you like                                         //
constant function DE_ShardAOEBase takes nothing returns real
    return 90.00
endfunction
//----------------------------------------------------------------//
//  Shard AEO Per Level: Normally not used, but here for your     //
//  preferences, allows you to adjust the area of effect of your  //
//  shards as they level up, normally only making the area larger //
//  makes logical sense, or not changing it at all                //
constant function DE_ShardAOEPerLevel takes nothing returns real
    return 0.00
endfunction
//----------------------------------------------------------------//
//  Health Damage Base: This is the damage the spell deals to     //
//  all the units within the AOE on impact with a shard, you'll   //
//  want to make this fairly relative to the number of shards     //
//  you have, 75 shards with 100 damage each is 7500 damage in    //
//  one cast of the spell - that's a lot of potential damage in   //
//  a spell which has no cooldown by default, note that this      //
//  damage is not true damage - enemies will have their armour    //
//  applied and reduce this damage based on their armour type     //
//  vs your damage type                                           //
constant function DE_HealthDamageBase takes nothing returns real
    return 75.00
endfunction
//----------------------------------------------------------------//
//  Health Damage Per Level: For changing the amount of damage    //
//  you want to deal with each shard as you level up, normally    //
//  this is your main source of increased damage as the spell     //
//  levels up rather than the other factors, it makes little      //
//  sense to not have this as a positive value of some number     //
//  (25% of your total level 1 damage seems like a good number,   //
//  i.e. if your combined level one damage is 100, roughly 25 of  //
//  that damage should probably be coming from this per level     //
//  value                                                         //
constant function DE_HealthDamagePerLevel takes nothing returns real
    return 25.00
endfunction
//----------------------------------------------------------------//
//  Mana Damage Base: Unlike Health damage the mana damage is     //
//  true damage - no matter how much armour they have, exactly    //
//  these values will be subtracted from their mana, normally     //
//  this is 0 as mana damage is mostly useless except vs certain  //
//  enemies, but it's still good to have the option to be able    //
//  to have it if you want                                        //
constant function DE_ManaDamageBase takes nothing returns real
    return 0.00
endfunction
//----------------------------------------------------------------//
//  Mana Damage Per Level: Per level component of the normally    //
//  disused mana damage, not any real sense in having a below 0   //
//  amount here, since it'll only make it weaker, like the        //
//  health damage I suggest this to be 25% of your total mana     //
//  damage of your level 1 version                                //
constant function DE_ManaDamagePerLevel takes nothing returns real
    return 0.00
endfunction
//----------------------------------------------------------------//
//                        AESTHETICS                              //
//----------------------------------------------------------------//
//  Portal Model:  This determines the model used for your        //
//  main portal, you want to use the model path and paste it      //
//  into the double quotes or " markers, if the path has only     //
//  single slashes (\) you'll need to changeb it to double slash  //
//  (\\) before you save, the spell will not work if you do not   //
//  do this (if you acidentally save with the single slash (\)    //
//  it will still save but you may find yourself having a         //
//  lingering Progress bar at full percentage, you can ignore it  //
//  for the most part, but if you have it, you probably haven't   //
//  entered this field correctly. Get the model paths from the    //
//  Object editor - find the model you want (the model, not the   //
//  unit, in the models list, select it and hit enter twice,      //
//  then select the path (will look similar to this default       //
//  value, and paste in it here) I suggest doing this with a      //
//  unit of no value (changing the model to get the model path)   //
//  and then reset the field afterwards to get the unit back to   //
//  normal                                                        //
constant function DE_PortalModel takes nothing returns string
    return "Abilities\\Weapons\\WitchDoctorMissile\\WitchDoctorMissile.mdl"
endfunction
//----------------------------------------------------------------//
//  Shard Model: The model used for your individual shards,       //
//  follow the steps in the "Portal Model" section if you need    //
//  information on how to change these values successfully        //
constant function DE_ShardModel takes nothing returns string
    return "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl"
endfunction
//----------------------------------------------------------------//
//  Impact Model: The model used for your individual shards,      //
//  follow the steps in the "Portal Model" section if you need    //
//  information on how to change these values successfully        //
constant function DE_ImpactModel takes nothing returns string
    return "Abilities\\Weapons\\SteamTank\\SteamTankImpact.mdl"
endfunction
//----------------------------------------------------------------//
//  Attachment Point: This determines where on the unit the       //
//  model is placed, "origin", "chest", "head", "overhead" are    //
//  all good places to put the model, switch them around to see   //
//  which you prefer the most, "foot" and "hand" are less so as   //
//  they're offsetted from the center                             //
constant function DE_AttachmentPoint takes nothing returns string
    return  "origin"
endfunction
//----------------------------------------------------------------//
//  Portal Height Base: This is how height the portal is off the  //
//  floor (it is added onto the terrain Z) you'll want it a good  //
//  distance off the ground (unles you want Shards to smack into  //
//  the ground while the spell is still going off, which is a     //
//  viable thing to do for this spell) but not so high as to      //
//  send the Shards halfway across the map, 400 is default        //
constant function DE_PortalHeightBase takes nothing returns real
    return 400.00
endfunction
//----------------------------------------------------------------//
//  Portal Height Per Level: The per level for the height of the  //
//  portal, generally this'll be set at 0 as the spell changes    //
//  more on functionality, than on effectiveness depending on     //
//  height - so changing this value as it levels up will not      //
//  necessarily make it any stronger or weaker                    //
constant function DE_PortalHeightPerLevel takes nothing returns real
    return 0.00
endfunction
//----------------------------------------------------------------//
//  Portal Scale Base: this is the scale of the portal model      //
//  it's a decimal percentage (1.00 = 100%) as the largest part   //
//  of the spell you'll normally want this at, at least 100%      //
//  depending on the model, generally a good idea to try to line  //
//  it up with your portal radius as to get the best effect out   //
//  of it                                                         //
constant function DE_PortalScaleBase takes nothing returns real
    return 2.00
endfunction
//----------------------------------------------------------------//
//  Portal Scale Per Level: Used for changing the portal scale    //
//  as it levels up - good in combination with radius change per  //
//  level as to stay correct relatively                           //
constant function DE_PortalScalePerLevel takes nothing returns real
    return 1.00
endfunction
//----------------------------------------------------------------//
//  Shard Scale Base: Scales the Shards that are created by the   //
//  portal, normally you'll want them significantly smaller that  //
//  the portal 10% (0.10) is a good size, but for some models a   //
//  bit bigger might be good, be sure to play around with this    //
//  to get the size you want and looks best in your opinion       //
constant function DE_ShardScaleBase takes nothing returns real
    return 0.10
endfunction
//----------------------------------------------------------------//
//  Shard Scale Per Level: Also for use with making the portal    //
//  seem relatively the same size as it levels up - or simply     //
//  to make them bigger to show increased AOE, Damage, more or    //
//  less to show anything about the spell being stronger as it    //
//  levels up and becomes more powerful                           //
constant function DE_ShardScalePerLevel takes nothing returns real
    return 0.00
endfunction
//----------------------------------------------------------------//
//                    DAMAGE INFORMATION                          //
//----------------------------------------------------------------//
//  Damage Type: These determine the damagetypes, changing this   //
//  will modify the damage multiplyers vs certain enemies         //
//  the standard is DAMAGE_TYPE_MAGIC, note that this spell       //
//  automatically discludes magic immunes, so changing this       //
//  damage type will not make them start taking damage            //
constant function DE_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_MAGIC
endfunction
//----------------------------------------------------------------//
//  Attack Type: This is very much so basically the same as       //
//  Damage Type, generally you'll want this to match with it      //
//  as such the default is ATTACK_TYPE_MAGIC, though Damagetype   //
//  is a key factor for determining bonuses rather than this      //
//  but unlike weapontype, you cannot have null as a setting      //
constant function DE_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_MAGIC
endfunction
//----------------------------------------------------------------//
//  Weapon Type: This alters what kind of weapon type is used by  //
//  the spell, those without knowledge of weapontypes don't       //
//  worry, you're not missing much, this spell doesn't really     //
//  use it, hence the default of null, but if you want to use     //
//  them, no reason to not.                                       //
constant function DE_WeaponType takes nothing returns weapontype
    return null
endfunction
//----------------------------------------------------------------//
//                     OTHER ATTRIBUTES                           //
//----------------------------------------------------------------//
//  Gravity: Determines the worldly gravity strength used to      //
//  pull the shards back to the ground after they have been       //
//  freed from the portal, having a lower gravity increases the   //
//  spread range, higher lowers it, the default is 1/8 of the     //
//  earthly gravity of 9.81, beng 1.22625                         //
constant function DE_Gravity takes nothing returns real
    return 1.22625
endfunction
//----------------------------------------------------------------//
//  Gravitational Constant: This is the constant reprisented by   //
//  G in Celestial Physics and Mechanics. Normally it's           //
//  6.67384 x 10^-11 N (m/kg)^2 but for Warcraft purposes I       //
//  cranked it up a bit, (x 10^11 to be exact) changing it may    //
//  yeild interesting results, but I take no responcibility if    //
//  you do something silly and crash the game by making it        //
//  really high                                                   //
constant function DE_GravitationalConstant takes nothing returns real
    return 6.67384
endfunction
//----------------------------------------------------------------//
//  Height Let: This is a small let so that projectiles nearly    //
//  touching the floor, will be treated as actually touching it   //
//  This is to prevent graphical errors because models cannot     //
//  sink into the floor by reducing their fly height              //
//  5 is default 10 is probably the max you can reasonably give   //
//  this                                                          //
constant function DE_HeightLet takes nothing returns real
    return 5.00
endfunction
//----------------------------------------------------------------//
//  Death Timer: Determines how long after the death of a dummy   //
//  will it be removed from the game completely, this is so       //
//  death effects, if any can finish playing before the unit is   //
//  removed, 1.8 is default, 2 is probably excessive and in some  //
//  cases it's possible that 1 could be too little                //
constant function DE_DeathTimer takes nothing returns real
    return 1.80
endfunction
//----------------------------------------------------------------//
//  You have now reached the end of the configuration, below are  //
//  the functions used to run the spell, beyond this point if     //
//  you find any constant values (indicated in Blue like the      //
//  other constants here if you have standard syntax highlighting //
//  they're there for a reason and don't fiddle with them, to     //
//  actually make any modifications I hope you're an experienced  //
//  programmer, though do not hassle for help because I don't     //
//  take responcibility for other people's programming skills     //
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
//  Function used for finding the Z height of a location, since   //
//  it cannot be done with co-ordinates, a recycled location is   //
//  used constantly.                                              //
////////////////////////////////////////////////////////////////////
function DE_GetZ takes real x, real y returns real

    //Gets the location Z of the selected location
    call MoveLocation(udg_DE_ZLoc, x, y)
    return GetLocationZ(udg_DE_ZLoc)
    
endfunction

////////////////////////////////////////////////////////////////////
//  Target filter function - passed units and players and checks  //
//  if the unit is allowed to be targetted by this spell          //
////////////////////////////////////////////////////////////////////
function DE_TargetFilter takes unit u, player pl returns boolean

    //Checks if the unit can be used as a target
    if (IsUnitType(u, UNIT_TYPE_GROUND)) and (not IsUnitType(u, UNIT_TYPE_STRUCTURE)) and (not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and (IsUnitEnemy(u, pl)) and (GetUnitTypeId(u) != DE_DummyID()) and (not IsUnitType(u, UNIT_TYPE_DEAD)) then
        return true
    endif
    
    return false

endfunction

////////////////////////////////////////////////////////////////////
//  Function for running the main parts of the spell, creates     //
//  new shards and destroys old portals, and does all the         //
//  recycling and damaging of targets, as well as shard movement  //
////////////////////////////////////////////////////////////////////
function DE_Loop takes nothing returns nothing

    //Sets up all the locals needed for this section
    local integer TempInt = 0
    local integer TempInt2 = 0
    local integer TempNode = 0
    local integer Node = 0
    local real Angle
    local real Angle2
    local real Distance
    local real x
    local real x2
    local real dx
    local real y
    local real y2
    local real dy
    local real z
    local unit u
    local player pl
    
    loop
        //Moves to the next node
        set TempInt = TempInt + 1
        exitwhen TempInt > udg_DE_SpellCounter
        set Node = udg_DE_NextNode[Node]
        
        if (udg_DE_StageID[Node] == 2) then
            
            //Initialises data required for movement
            set x = GetUnitX(udg_DE_Unit[Node])
            set y = GetUnitY(udg_DE_Unit[Node])
            set udg_DE_CurrentZ[Node] = udg_DE_CurrentZ[Node] + udg_DE_ZVelocity[Node]
            set z = DE_GetZ(x, y)
            set dy = udg_DE_PortalY[Node] - y
            set dx = udg_DE_PortalX[Node] - x 
            set Angle = Atan2(dy, dx)
            set Angle2 = Atan2(udg_DE_PortalZ[Node] - udg_DE_CurrentZ[Node], SquareRoot(dx * dx + dy * dy))
            
            //Checks if the projectile has crashed
            if (udg_DE_CurrentZ[Node] - z < DE_HeightLet()) then
                //Yes it crashed, deassociating the unit
                set udg_DE_StageID[Node] = 3
                set pl = GetOwningPlayer(udg_DE_OriginalCaster[Node])
               
                call DestroyEffect(udg_DE_CurrentEffect[Node])
                call DestroyEffect(AddSpecialEffect(DE_ImpactModel(), x, y))
                //Select Units to damage
                call GroupEnumUnitsInRange(udg_DE_TempGroup, x, y, udg_DE_ShardAOE[Node], null)              
                       
                loop
                    //Scanning through
                    set u = FirstOfGroup(udg_DE_TempGroup)
                    exitwhen u == null
                           
                    //Select all the units which are to be damaged
                    if DE_TargetFilter(u, pl) then
                        //Dealing health and mana damage
                        call UnitDamageTarget(udg_DE_OriginalCaster[Node], u, udg_DE_HealthDamage[Node], true, false, DE_AttackType(), DE_DamageType(), DE_WeaponType())
                        call SetUnitState(u, UNIT_STATE_MANA, (GetUnitState(u,UNIT_STATE_MANA) - udg_DE_ManaDamage[Node]))
                    endif
                    
                    //Remove the unit from the unit group
                    call GroupRemoveUnit(udg_DE_TempGroup, u)
                           
                endloop
                           
                //Removes the projectile
                call KillUnit(udg_DE_Unit[Node])                
               
                //Nulls variables
                set u = null
                set pl = null
            else
                //Setting the new locations X and Y for this instance
                set x2 = x + udg_DE_XVelocity[Node]
                set y2 = y + udg_DE_YVelocity[Node]
                
                if (IsUnitInGroup(udg_DE_Unit[Node], udg_DE_GravitatedShard)) then
                    //Calculating the new velocities (will be used next time this runs)
                    set udg_DE_ZVelocity[Node] = udg_DE_ZVelocity[Node] + udg_DE_BaseVelocity[Node] * Sin(Angle2)
                    set udg_DE_XVelocity[Node] = udg_DE_XVelocity[Node] + udg_DE_BaseVelocity[Node] * Cos(Angle) * Cos(Angle2)
                    set udg_DE_YVelocity[Node] = udg_DE_YVelocity[Node] + udg_DE_BaseVelocity[Node] * Sin(Angle) * Cos(Angle2)
                else
                    set udg_DE_ZVelocity[Node] = udg_DE_ZVelocity[Node] - DE_Gravity()
                endif
                
                //Makes sure the new location is within the map bounds
                if ((udg_DE_MapMinX <= x2) and (x2 <= udg_DE_MapMaxX) and (udg_DE_MapMinY <= y2)and (y2 <= udg_DE_MapMaxY)) then
                    call SetUnitX(udg_DE_Unit[Node], x2)
                    call SetUnitY(udg_DE_Unit[Node], y2)
                endif
                
                //Sets the correct fly height
                call SetUnitFlyHeight(udg_DE_Unit[Node], udg_DE_CurrentZ[Node] - z, 0.00)            
            endif
      
        elseif (udg_DE_StageID[Node] == 1) then
            //Increases the duration of both artificial wait timers
            set udg_DE_CurrentShardDelay[Node] = udg_DE_CurrentShardDelay[Node] + DE_TimerSpeed()
            set udg_DE_CurrentDuration[Node] = udg_DE_CurrentDuration[Node] + DE_TimerSpeed()
            
            //Checks if the spell has ran out of duration
            if(udg_DE_CurrentDuration[Node] >= udg_DE_Duration[Node]) then
                set udg_DE_StageID[Node] = 3
                call DestroyEffect(udg_DE_CurrentEffect[Node])
                
                //Finds all the affected Shards and releases them
                loop
                    set TempInt2 = TempInt2 + 1
                    exitwhen TempInt2 > udg_DE_SpellCounter
                    set TempNode = udg_DE_NextNode[TempNode]

                    if (udg_DE_Portal[TempNode] == udg_DE_Unit[Node]) then
                        call GroupRemoveUnit (udg_DE_GravitatedShard, udg_DE_Unit[TempNode])
                    endif

                endloop

            //Checks if it's time to make a new shard
            elseif (udg_DE_CurrentShardDelay[Node] >=  udg_DE_SpawnRate[Node]) then
                //Resets the timer
                set udg_DE_CurrentShardDelay[Node] = 0.00
                
                //Sets up a random place to put the new Shard
                set Angle = GetRandomReal(0, 360)
                set Angle2 = GetRandomReal(0, 180)
                set Distance = Cos(Angle2) * udg_DE_Radius[Node]
                
                if (Distance < 0) then
                    set Distance = Distance * -1
                endif
            
                set udg_DE_SpellCounter = udg_DE_SpellCounter + 1
                set x = udg_DE_PortalX[Node] + Distance * Cos(Angle * bj_DEGTORAD)
                set y = udg_DE_PortalY[Node] + Distance * Sin(Angle * bj_DEGTORAD)
                set z = Sin(Angle2) * udg_DE_Radius[Node] + udg_DE_PortalZ[Node]
                
                //Checking for recycleable Nodes
                if (udg_DE_RecycleableNodes == 0) then
                    set udg_DE_NodeNumber = udg_DE_NodeNumber + 1
                    set TempNode = udg_DE_NodeNumber
                else
                    set udg_DE_RecycleableNodes = udg_DE_RecycleableNodes - 1
                    set TempNode = udg_DE_RecycleNodes[udg_DE_RecycleableNodes]
                endif

                //Sets up this Node
                set udg_DE_NextNode[TempNode] = 0
                set udg_DE_NextNode[udg_DE_LastNode] = TempNode
                set udg_DE_PrevNode[TempNode] = udg_DE_LastNode
                set udg_DE_LastNode = TempNode
        
                //Sets up the data for the Shard
                set udg_DE_HealthDamage[TempNode] = udg_DE_HealthDamage[Node]
                set udg_DE_ManaDamage[TempNode] = udg_DE_ManaDamage[Node]
                set udg_DE_Portal[TempNode] = udg_DE_Unit[Node]
                set udg_DE_PortalX[TempNode] = udg_DE_PortalX[Node]
                set udg_DE_PortalY[TempNode] = udg_DE_PortalY[Node]
                set udg_DE_PortalZ[TempNode] = udg_DE_PortalZ[Node]
                set udg_DE_ShardAOE[TempNode] = udg_DE_ShardAOE[Node]
                set udg_DE_OriginalCaster[TempNode] = udg_DE_OriginalCaster[Node]
                set udg_DE_BaseVelocity[TempNode] = udg_DE_BaseVelocity[Node]
                set udg_DE_CurrentZ[TempNode] = z
                set udg_DE_StageID[TempNode] = 2
                
                //Sets up initialisation Velocities (this is to give the shards a bit of a kick of instability in their orbit)
                set udg_DE_ZVelocity[TempNode] = udg_DE_BaseVelocity[TempNode] * Sin(Angle2)
                set udg_DE_XVelocity[TempNode] = udg_DE_BaseVelocity[TempNode] * Cos(Angle) * Cos(Angle2)
                set udg_DE_YVelocity[TempNode] = udg_DE_BaseVelocity[TempNode] * Sin(Angle) * Cos(Angle2)
                
                //Creates the unit and applies Aesthetics
                set udg_DE_Unit[TempNode] = CreateUnit(Player(14), DE_DummyID(), x, y, 0.00)
                if UnitAddAbility(udg_DE_Unit[TempNode], 'Amrf') and UnitRemoveAbility(udg_DE_Unit[TempNode], 'Amrf') then
                endif
                set udg_DE_CurrentEffect[TempNode] = AddSpecialEffectTarget(DE_ShardModel(), udg_DE_Unit[TempNode], DE_AttachmentPoint())
                call SetUnitScale(udg_DE_Unit[TempNode], udg_DE_ShardScale[Node], 0.00, 0.00)
                call SetUnitFlyHeight(udg_DE_Unit[TempNode], z, 0.00)
                
                //Adds them to the group of Gravitated Shards
                call GroupAddUnit(udg_DE_GravitatedShard, udg_DE_Unit[TempNode])
                
            endif
                
        elseif (udg_DE_CurrentDeathTimer[Node] < DE_DeathTimer()) then
            set udg_DE_CurrentDeathTimer[Node] = 0.00
            
            //Removes the projectile
            call RemoveUnit(udg_DE_Unit[Node])
            
            if (udg_DE_LastNode == Node) then
                set udg_DE_LastNode = udg_DE_PrevNode[Node]
            endif
               
            //Recycles the node
            set udg_DE_RecycleNodes[udg_DE_RecycleableNodes] = Node
            set udg_DE_RecycleableNodes = udg_DE_RecycleableNodes + 1
            set udg_DE_NextNode[udg_DE_PrevNode[Node]] = udg_DE_NextNode[Node]
            set udg_DE_PrevNode[udg_DE_NextNode[Node]] = udg_DE_PrevNode[Node]
            set udg_DE_SpellCounter = udg_DE_SpellCounter - 1
            set TempInt = TempInt - 1

            //Destroys the timer when not in use
            if (udg_DE_SpellCounter == 0) then
                call DestroyTimer(GetExpiredTimer())
            endif
            
        else
            set udg_DE_CurrentDeathTimer[Node] = udg_DE_CurrentDeathTimer[Node] + DE_TimerSpeed()
        endif
        
    endloop
    
endfunction

////////////////////////////////////////////////////////////////////
//  Function runs when a new instance is to be created - runs     //
//  as a condition but always returns false, creates a new        //
//  portal if the correct spell was cast                          //
////////////////////////////////////////////////////////////////////
function DE_NewInstance takes nothing returns boolean

    //Sets up locals
    local unit u
    local integer Node
    local real rLevel
    local real x
    local real y
    
    //Checks if the spell cast is the correct spell
    if (GetSpellAbilityId() == DE_SpellID()) then
        set u = GetTriggerUnit()
        set x = GetSpellTargetX()
        set y = GetSpellTargetY()
        set rLevel = I2R(GetUnitAbilityLevel(u, DE_SpellID()))
        
        //Checking for recycleable Nodes
        if (udg_DE_RecycleableNodes == 0) then
            set udg_DE_NodeNumber = udg_DE_NodeNumber + 1
            set Node = udg_DE_NodeNumber
        else
            set udg_DE_RecycleableNodes = udg_DE_RecycleableNodes - 1
            set Node = udg_DE_RecycleNodes[udg_DE_RecycleableNodes]
        endif

        //Sets up this Node
        set udg_DE_NextNode[Node] = 0
        set udg_DE_NextNode[udg_DE_LastNode] = Node
        set udg_DE_PrevNode[Node] = udg_DE_LastNode
        set udg_DE_LastNode = Node

        //Sets up the portal data
        set udg_DE_PortalHeight[Node] = DE_PortalHeightBase() + (DE_PortalHeightPerLevel() * rLevel)
        set udg_DE_Mass[Node] = DE_MassBase() + (DE_MassPerLevel() * rLevel)
        set udg_DE_Radius[Node] = DE_RadiusBase() + (DE_RadiusPerLevel() * rLevel)
        set udg_DE_BaseVelocity[Node] = ((DE_GravitationalConstant() * udg_DE_Mass[Node]) / udg_DE_Radius[Node]) * DE_TimerSpeed() 
        set udg_DE_HealthDamage[Node] = DE_HealthDamageBase() + (DE_HealthDamagePerLevel() * rLevel)
        set udg_DE_ManaDamage[Node] = DE_ManaDamageBase() + (DE_ManaDamagePerLevel() * rLevel)
        set udg_DE_SpawnRate[Node] = DE_SpawnRateBase() + (DE_SpawnRatePerLevel() * rLevel)
        set udg_DE_Duration[Node] = DE_DurationBase() + (DE_DurationPerLevel() * rLevel)
        set udg_DE_ShardScale[Node] = DE_ShardScaleBase() + (DE_ShardScalePerLevel() * rLevel)
        set udg_DE_ShardAOE[Node] = DE_ShardAOEBase() + (DE_ShardAOEPerLevel() * rLevel)
        set udg_DE_PortalX[Node] = x
        set udg_DE_PortalY[Node] = y
        set udg_DE_PortalZ[Node] = udg_DE_PortalHeight[Node] + DE_GetZ(x, y)
        set udg_DE_CurrentShardDelay[Node] = 0.00
        set udg_DE_CurrentDuration[Node] = 0.00
        set udg_DE_OriginalCaster[Node] = u
        set udg_DE_StageID[Node] = 1
        set udg_DE_SpellCounter = udg_DE_SpellCounter + 1
        
        //Creates the unit and applies Aesthetics
        set udg_DE_Unit[Node] = CreateUnit(Player(14), DE_DummyID(), x, y, 0.00)
        if UnitAddAbility(udg_DE_Unit[Node], 'Amrf') and UnitRemoveAbility(udg_DE_Unit[Node], 'Amrf') then
        endif
        set udg_DE_CurrentEffect[Node] = AddSpecialEffectTarget(DE_PortalModel(), udg_DE_Unit[Node], DE_AttachmentPoint())
        call SetUnitScale(udg_DE_Unit[Node], DE_PortalScaleBase() + (DE_PortalScalePerLevel() * rLevel), 0.00, 0.00)
        call SetUnitFlyHeight(udg_DE_Unit[Node], udg_DE_PortalHeight[Node], 0.00)
        
        //Checks if it's the only portal on the map
        if (udg_DE_SpellCounter == 1) then
            call TimerStart(CreateTimer(), DE_TimerSpeed(), true, function DE_Loop)
        endif
        
        //Nulls variables
        set u = null
        
    endif
    
    return false

endfunction
////////////////////////////////////////////////////////////////////
//  Function for setting up the other functions and initialising  //
//  the map bounds variables and Z finder for locations           //
////////////////////////////////////////////////////////////////////
function InitTrig_DE_DevastationEnvoy takes nothing returns nothing

    //Sets up locals
    local trigger DE = CreateTrigger()
    local integer index = 0

    //Initialise the event for every player
    loop
        call TriggerRegisterPlayerUnitEvent(DE, Player(index), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
   
    call TriggerAddCondition(DE, Condition(function DE_NewInstance))
   
    //Sets up the map bounds the spell with use
    set udg_DE_MapMaxX = GetRectMaxX(bj_mapInitialPlayableArea)
    set udg_DE_MapMinX = GetRectMinX(bj_mapInitialPlayableArea)
    set udg_DE_MapMaxY = GetRectMaxY(bj_mapInitialPlayableArea)
    set udg_DE_MapMinY = GetRectMinY(bj_mapInitialPlayableArea)

    //Sets up the Z location finder
    set udg_DE_ZLoc = Location(0,0)
    //Nulls variables
    set DE = null
   
endfunction

////////////////////////////////////////////////////////////////////
//  End of the spell                                              //
////////////////////////////////////////////////////////////////////

  • Variable Creator
    • Events
    • Conditions
    • Actions
      • Set DE_AOE[0] = 0.00
      • Set DE_BaseVelocity[0] = 0.00
      • Set DE_CurrentDeathTimer[0] = 0.00
      • Set DE_CurrentDuration[0] = 0.00
      • Set DE_CurrentEffect[0] = DE_CurrentEffect[0]
      • Set DE_CurrentShardDelay[0] = 0.00
      • Set DE_CurrentZ[0] = 0.00
      • Set DE_Duration[0] = 0.00
      • Set DE_GravitatedShard = DE_GravitatedShard
      • Set DE_HealthDamage[0] = 0.00
      • Set DE_LastNode = 0
      • Set DE_ManaDamage[0] = 0.00
      • Set DE_MapMaxX = 0.00
      • Set DE_MapMaxY = 0.00
      • Set DE_MapMinX = 0.00
      • Set DE_MapMinY = 0.00
      • Set DE_Mass[0] = 0.00
      • Set DE_NextNode[0] = 0
      • Set DE_NodeNumber = 0
      • Set DE_OriginalCaster[0] = DE_OriginalCaster[0]
      • Set DE_Portal[0] = DE_Portal[0]
      • Set DE_PortalHeight[0] = 0.00
      • Set DE_PortalX[0] = 0.00
      • Set DE_PortalY[0] = 0.00
      • Set DE_PortalZ[0] = 0.00
      • Set DE_PrevNode[0] = 0
      • Set DE_Radius[0] = 0.00
      • Set DE_RecycleNodes[0] = 0
      • Set DE_RecycleableNodes = 0
      • Set DE_ShardAOE[0] = 0.00
      • Set DE_ShardScale[0] = 0.00
      • Set DE_SpawnRate[0] = 0.00
      • Set DE_SpellCounter = 0
      • Set DE_StageID[0] = 0
      • Set DE_TempGroup = DE_TempGroup
      • Set DE_Unit[0] = No unit
      • Set DE_XVelocity[0] = 0.00
      • Set DE_YVelocity[0] = 0.00
      • Set DE_ZLoc = DE_ZLoc
      • Set DE_ZVelocity[0] = 0.00

-=V1.00=-
- Initial Upload (excuse any herp derps in the code)
-=V1.01=-
- Modified the targetting filter to be a bit more efficient


Give credits if you use this spell in your map and as always, Enjoy.

Keywords:
Star, Portal, Flare, Explosion, Sun, Devastation, Destuction, Celestial, Gravity, Magnetism, Spam, Spamtasm, Shard, Space, Light, Holy, Envoy, Short,
Contents

Devastation Envoy (Map)

Reviews
19:57, 25th Sep 2013 PurgeandFire: Looks good. This spell is fantastic--and is such a beautiful combination of math and particles. It is coded very well and easy to implement for all. (Old) Review...

Moderator

M

Moderator

19:57, 25th Sep 2013
PurgeandFire: Looks good. This spell is fantastic--and is such a beautiful combination of math and particles. It is coded very well and easy to implement for all.

(Old) Review:
http://www.hiveworkshop.com/forums/2422053-post12.html
If you have a question about my review, please PM me.
 
Great that you think so - I'll be trying to scale things down a little bit more in future and get more smaller ones like this - given the large ones are well, large, hard to use for most maps unless they're scaled down a lot more. Of course, I have full intentions on getting all my physics-goodness and effects into those small spells to keep them unique and interesting.
 
The eye-candy of this spell is fantastic, and the code is very nice.

Code review:
  • You use two groups: udg_DE_TempGroup and udg_DE_TempGroup2. The former is for the "filtered units", in here:
    JASS:
    if (IsUnitType(u, UNIT_TYPE_GROUND)) and (not IsUnitType(u, UNIT_TYPE_STRUCTURE)) and (not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and (IsUnitEnemy(u, pl)) and (GetUnitTypeId(u) != DE_DummyID()) and (not IsUnitType(u, UNIT_TYPE_DEAD)) then
            call GroupAddUnit(udg_DE_TempGroup, u)
        endif
    Is there a particular reason why you wouldn't want to just use "return ..." and return a boolean? Then you can just check if DE_Filter(...) then or w/e.

    The reason why I say this is because using two groups seems a bit wasteful since they are both going to be empty in the end. I imagine that IsUnitInGroup() isn't O(1) (probably reads a list of the unit's groups), so it may be better to do the direct "if (cond) then".
  • Around this:
    JASS:
    //Sets up a random place to put the new Shard
    Why don't you use radians? Also, you cast Cos() and Sin() on it without converting deg2rad. I suppose it doesn't matter since blizz accounts for angles outside the standard range (-pi to pi/0 to 2pi), and the angles are random anyway so it doesn't matter (they are just going to be random in one more way :p)

Other than that it is fine.
 
Attempts to recreate the bug you speak of have yielded nothing for me, this was even when I had the highest level being cast non-stop (i.e. 10 or so instances running simultaneously) for 5 minutes.

Could you prehaps provide a more detailed explanation of how this occured and provide a screenshot? It's difficult to fix bugs I cannot cause to happen. Though a part of me wonders if it was a freak glitch with warcraft, though I don't like to attribute bugs to it.
 
Level 5
Joined
Jul 17, 2013
Messages
125
i imported your spell into my map and now spell is in height about 700 and it looks like stick of atoms, not ball like on your map, where could be problem?

EDIT: HEY! i found the problem, if my hero (casting unit) is on terrain level 3 it is weird and if on level 1 it is totally normal, can you fix that?

EDIT_2:Oh, why you have in editor in dummy Move - Type -> Swimming? i changet it on Flying and now is everything ok ;)
 
That'd be due to prevention of projectiles leaving the map, the two which fling out are because of it being partially inside (and them spawning outside, then being unable to reach the centre) that effect is for the most part, intentional, the flinging out can't really be avoided (the normaly solution would be to check the location of the cast point to make sure it's in the map bounds, if I get some time I can add that, though in most maps you woudn't be able to reach that far out anyway)
 
Top