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

Gravity Spellpack v1.2

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: MortAr
Just a gravity-themed spell that I made out of boredom.

Requirements
All below requirements are included in the test map.
- JNGP
- Damage
* AIDS
* Event
- Timer32
- GTrigger
- GroupUtils

Overview
  • Inversion Field [Active]
  • Gravity Matrix [Passive]
  • Gravity Flux [Active]
  • Graviton Acceleration [Active]


Details
- The spells are vJASS
- They should be leak-less and lag-less
- It is MUI, meaning can be cast many times at the same instance


Implementation
JASS:
//==============================================================================
//                          GRAVITY SPELLPACK v1.2                         
//                              BY Ayanami                             
//==============================================================================

//==============================================================================
//                            REQUIREMENTS                                      
//==============================================================================
// - JNGP                                                                                                                                   
// - Damage  
//    * Event
//    * AIDS          
// - Timer32
// - GTrigger
// - GroupUtils                                                                
//==============================================================================

//==============================================================================
//                           IMPLEMENTATION                                     
//==============================================================================
// 1) Copy the whole "Required Systems" Trigger folder 
// 2) Save the map, it will take a bit longer than usual
// 3) Close the map and re-open it, then disable or delete the trigger "Objects"
// 4) Go to the Import Manager and export all resources, then import into map
// 5) Copy all 4 abilities under "Night Elf"      
// 6) Copy the whole "Gravity Spellpack" Trigger folder                                         
//==============================================================================


Spell Codes



Creates an inverse gravity field at the target point. The inverse field causes enemy units to be trapped within the gravity matrix. The gravity field rejects any allied units, causing them to be forced outside of the field. Due to the gravitational differences between the 2 mediums, no units can enter or leave the field at any time and any damage taken from different mediums are reduced. Target area of 500 at all levels. Lasts 4 seconds.

Level 1 - Field area of 500. Reduces damage taken by 80%.
Level 2 - Field area of 450. Reduces damage taken by 70%.
Level 3 - Field area of 400. Reduces damage taken by 60%.
Level 4 - Field area of 350. Reduces damage taken by 50%.

Cast Range: 600
Target Type: Point Area
Cooldown: 22/20/18/16 seconds

JASS:
scope InversionField // requires Damage, Timer32, GTrigger, GroupUtils

//===========================================================================
//                          CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABIF' // raw code of ability "Inversion Field"
    private constant integer ABILDUMID = 'AIF0' // raw code of ability "Inversion Field (Buff)"
    private constant integer BUFFID = 'BIF0' // raw code of buff "Inversion Field"
    private constant integer PRELOADID = 'prel' // raw code of unit "Preloader"
    
    private constant string WALLART = "Abilities\\Spells\\Undead\\DarkSummoning\\DarkSummonMissile.mdl" // model used as field wall
    
    private constant string DAMAGEART = "Abilities\\Weapons\\WingedSerpentMissile\\WingedSerpentMissile.mdl" // model used when damaged
    private constant string DAMAGEATTACH = "chest" // attachment point of model
    
    private constant real THICKNESS = 100.0 // thickness of the field wall
    
    private constant boolean GFBOOLEAN = true // set to true if Gravity Flux will be used
    private constant boolean GABOOLEAN = true // set to true if Graviton Acceleration will be used
endglobals

private function GetCastArea takes integer level returns real
    return 500.0 // area of cast
endfunction

private function GetArea takes integer level returns real
    return 550.0 - (50.0 * level) // area of field
endfunction

private function GetDuration takes integer level returns real
    return 4.0 // duration of spell
endfunction

private function GetReduction takes integer level returns real
    return 0.9 - (0.1 * level) // reduction in damage taken where, 1.0 == 100%
endfunction

private function GetWallCount takes integer level returns integer
    return 30 - (3 * level) // number of effect created
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================
// Do not touch below unless you know what you're doing

native UnitAlive takes unit id returns boolean

private struct Data
    real duration
    real aoe
    real castaoe
    real x
    real y
    integer level
    group tgroup
    static thistype tempData
    static unit tempCaster
    static hashtable Hash = InitHashtable()
    
    private static method staticCheck takes unit u returns boolean
        local boolean check = true
    
        static if GFBOOLEAN then
            set check = GetUnitTypeId(u) != GravityFlux_DUMMYID
        endif
    
        static if GABOOLEAN then
            if not check then
                set check = not IsUnitInGroup(u, GravitonAcceleration_Checkgroup)
            endif
        endif
    
        return check
    endmethod
    
    private static method groupFilter takes nothing returns boolean
        local thistype this = tempData
        local unit u = GetFilterUnit()
        local real a
        local real dx
        local real dy

        if IsUnitEnemy(u, GetOwningPlayer(tempCaster)) and UnitAlive(u) then
            if GetUnitAbilityLevel(u, ABILDUMID) == 0 then
                call UnitAddAbility(u, ABILDUMID)
            endif
            call SetUnitAbilityLevel(u, ABILDUMID, this.level)
            set u = null
            return true
        else
            set dx = GetUnitX(u) - this.x
            set dy = GetUnitY(u) - this.y
            if dx * dx + dy * dy < this.aoe * this.aoe then
                set a = Atan2(dy, dx)
                set dx = this.x + (this.aoe + THICKNESS) * Cos(a)
                set dy = this.y + (this.aoe + THICKNESS) * Sin(a)
                call SetUnitX(u, dx)
                call SetUnitY(u, dy)
            endif
            set u = null
        endif
        return false
    endmethod
    
    private static method groupFunc takes nothing returns nothing
        local thistype this = tempData
        local unit u = GetEnumUnit()
        local real dx = GetUnitX(u) - this.x
        local real dy = GetUnitY(u) - this.y
        local real a
        
        if GetUnitAbilityLevel(u, ABILDUMID) == 0 then
            call UnitAddAbility(u, ABILDUMID)
            call SetUnitAbilityLevel(u, ABILDUMID, this.level)
        endif
        if SquareRoot(dx * dx + dy * dy) > this.aoe - THICKNESS and thistype.staticCheck(u) then
            set a = Atan2(dy, dx)
            set dx = this.x + (this.aoe - THICKNESS - 50) * Cos(a)
            set dy = this.y + (this.aoe - THICKNESS - 50) * Sin(a)
            call SetUnitX(u, dx)
            call SetUnitY(u, dy)
        endif
        set u = null
    endmethod
    
    private static method groupAction takes nothing returns boolean
        local thistype this = tempData
        local unit u = GetFilterUnit()
        local real dx
        local real dy
        local real a
        
        if not IsUnitInGroup(u, this.tgroup) and thistype.staticCheck(u) then
            set a = Atan2(GetUnitY(u) - this.y, GetUnitX(u) - this.x)
            set dx = this.x + (this.aoe + THICKNESS + 50) * Cos(a)
            set dy = this.y + (this.aoe + THICKNESS + 50) * Sin(a)
            call SetUnitX(u, dx)
            call SetUnitY(u, dy)
        endif
        set u = null
        return false
    endmethod
    
    private static method removeGroup takes nothing returns nothing
        call UnitRemoveAbility(GetEnumUnit(), ABILDUMID)
        call UnitRemoveAbility(GetEnumUnit(), BUFFID)
    endmethod
    
    private method periodic takes nothing returns nothing
        local integer i
        local real dx
        local real dy
        
        if this.duration <= 0 then
            set i = LoadInteger(Hash, this, StringHash("count"))
            loop
                exitwhen i < 0
                call DestroyEffect(LoadEffectHandle(Hash, this, i))
                set i = i - 1
            endloop
        
            call ForGroup(this.tgroup, function thistype.removeGroup)
            call ReleaseGroup(this.tgroup)
            call FlushChildHashtable(Hash, this)
            call this.stopPeriodic()
            call this.deallocate()
        else
            set tempData = this
            call ForGroup(this.tgroup, function thistype.groupFunc)
            call GroupEnumUnitsInArea(ENUM_GROUP, this.x, this.y, this.aoe + THICKNESS, Filter(function thistype.groupAction))
            set this.duration = this.duration - T32_PERIOD
        endif
    endmethod
    implement T32x

    static method actions takes nothing returns nothing
        local thistype this = thistype.allocate()
        local unit u = GetTriggerUnit()
        local integer i
        local integer count
        local real a
        local real x
        local real y
        
        set this.level = GetUnitAbilityLevel(u, ABILID)
        set this.duration = GetDuration(this.level)
        set this.aoe = GetArea(this.level)
        set this.castaoe = GetCastArea(this.level)
        set this.x = GetSpellTargetX()
        set this.y = GetSpellTargetY()
        set this.tgroup = NewGroup()
        
        set tempCaster = u
        set tempData = this
        call GroupEnumUnitsInArea(this.tgroup, this.x, this.y, this.castaoe, Filter(function thistype.groupFilter))
        
        set count = GetWallCount(this.level)
        set i = 0
        loop
            exitwhen i == count
            set a = ((bj_PI * 2) / count) * i
            set x = this.x + this.aoe * Cos(a)
            set y = this.y + this.aoe * Sin(a)
            call SaveEffectHandle(Hash, this, i, AddSpecialEffect(WALLART, x, y))
            set i = i + 1
        endloop
        
        call SaveInteger(Hash, this, StringHash("count"), i - 1)
        call this.startPeriodic()
        set u = null
    endmethod
    
    private static method damageReduction takes nothing returns boolean
        local unit u = GetEventDamageSource()
        local unit t = GetTriggerUnit()
        local integer sourcelevel = GetUnitAbilityLevel(u, ABILDUMID)
        local integer targetlevel = GetUnitAbilityLevel(t, ABILDUMID)
    
        if sourcelevel > 0 and targetlevel > 0 then
        elseif sourcelevel > 0 then
            call Damage_Block(GetEventDamage() * GetReduction(sourcelevel))
            call DestroyEffect(AddSpecialEffectTarget(DAMAGEART, t, DAMAGEATTACH))
        elseif targetlevel > 0 then
            call Damage_Block(GetEventDamage() * GetReduction(targetlevel))
            call DestroyEffect(AddSpecialEffectTarget(DAMAGEART, t, DAMAGEATTACH))
        endif
        set u = null
        set t = null
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        local unit u = CreateUnit(Player(13), PRELOADID, 0, 0, 0)
        
        call GT_RegisterStartsEffectEvent(t, ABILID)
        call TriggerAddAction(t, function thistype.actions)
        
        set t = CreateTrigger()
        call Damage_RegisterEvent(t)
        call TriggerAddCondition(t, Condition(function thistype.damageReduction))
        set t = null
        
        //preload
        call UnitAddAbility(u, ABILDUMID)
        call RemoveUnit(u)
        set u = null
    endmethod
endstruct

endscope




Constantly generates an unstable gravity field in an area of 1000. In this unstable gravitized zone, all damage loses its power if the damage source is further away from the hero. Further away the damage source is, less damage this unit takes, with a maximum reduction at above 1000 distance. The blocked damage is converted into energy, and absorbed. For every 100 mana absorbed, an automatic gravity force is unleashed, dealing 100 damage to all units within 500 range. Insufficient mana results in a reset in mana counter.

Level 1 - Maximum reduction of 17%.
Level 2 - Maximum reduction of 28%.
Level 3 - Maximum reduction of 39%.
Level 4 - Maximum reduction of 50%.

Passive

JASS:
scope GravityMatrix initializer OnInit // requires Damage, GTrigger, GroupUtils

//===========================================================================
//                          CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABGM' // raw code of ability "Gravity Matrix"
    
    private constant string DAMAGEART = "Abilities\\Weapons\\WingedSerpentMissile\\WingedSerpentMissile.mdl" // model used when damaged
    private constant string DAMAGEATTACH = "chest" // attachment point of model
    
    private constant string MANAART = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" // model used when damaging enemy units
    private constant string MANAATTACH = "origin" // attachment point of model
    
    private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type of damage
    private constant damagetype DMG = DAMAGE_TYPE_MAGIC // damage type of damage
    private constant weapontype WEP = WEAPON_TYPE_WHOKNOWS // weapon type of damage
endglobals

private function GetDistance takes integer level returns real
    return 1000.0 // distance where damage reduction becomes maximum
endfunction

private function GetReduction takes integer level returns real
    return 0.06 + (0.11 * level) // maximum damage reduction where, 1.00 = 100%
endfunction

private function GetManaInterval takes integer level returns real
    return 100.0 // every interval of mana where damage is unleashed
endfunction

private function GetArea takes integer level returns real
    return 500.0 // area of damage
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================
// Do not touch below unless you know what you're doing

native UnitAlive takes unit id returns boolean

globals
    private hashtable Hash = InitHashtable()
    private trigger Trig
    private unit Tempunit
    private real Tempreal
endglobals

private function GroupFunc takes nothing returns boolean
    local unit u = GetFilterUnit()
    
    if IsUnitEnemy(u, GetOwningPlayer(Tempunit)) and UnitAlive(u) then
        call DisableTrigger(Trig)
        call UnitDamageTargetEx(Tempunit, u, Tempreal, true, false, ATK, DMG, WEP)
        call DestroyEffect(AddSpecialEffectTarget(MANAART, u, MANAATTACH))
        call EnableTrigger(Trig)
    endif
    set u = null
    return false
endfunction

private function Learn takes nothing returns boolean
    local unit u = GetTriggerUnit()
    if GetUnitAbilityLevel(u, ABILID) == 1 then
        call SaveReal(Hash, GetHandleId(u), 0, 0.0)
    endif
    set u = null
    return false
endfunction

private function Actions takes nothing returns boolean
    local unit u
    local integer i
    local integer count
    local integer id
    local real r
    local real x
    local real y
    local real dist
    local real maxdist
    
    set Tempunit = GetTriggerUnit()
    set i = GetUnitAbilityLevel(Tempunit, ABILID)
    set id = GetHandleId(Tempunit)
    set maxdist = GetDistance(i)
    if i > 0 then
        set u = GetEventDamageSource()
        set x = GetUnitX(u) - GetUnitX(Tempunit)
        set y = GetUnitY(u) - GetUnitY(Tempunit)
        set dist = SquareRoot(x * x + y * y)
        if dist > maxdist then
            set dist = maxdist
        endif
        set r = GetEventDamage() * GetReduction(i) * (dist / maxdist)
        call Damage_Block(r)
        call DestroyEffect(AddSpecialEffectTarget(DAMAGEART, Tempunit, DAMAGEATTACH))
        call SetUnitState(Tempunit, UNIT_STATE_MANA, GetUnitState(Tempunit, UNIT_STATE_MANA) + r)
        set r = r + LoadReal(Hash, id, 0)
        if r >= GetManaInterval(i) then
            set count = R2I(r / GetManaInterval(i))
            call SaveReal(Hash, id, 0, r - (GetManaInterval(i) * count))
            if GetUnitState(Tempunit, UNIT_STATE_MANA) >= GetManaInterval(i) then
                set Tempreal = GetManaInterval(i)
                loop
                    exitwhen count == 0
                    call GroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(Tempunit), GetUnitY(Tempunit), GetArea(i), Filter(function GroupFunc))
                    call SetUnitState(Tempunit, UNIT_STATE_MANA, GetUnitState(Tempunit, UNIT_STATE_MANA) - GetManaInterval(i))
                    set count = count - 1
                endloop
            endif
        else
            call SaveReal(Hash, id, 0, r)
        endif
    endif
    set u = null
    return false
endfunction

private function OnInit takes nothing returns nothing
    local trigger t = CreateTrigger()
    
    call GT_RegisterLearnsAbilityEvent(t, ABILID)
    call TriggerAddCondition(t, Condition(function Learn))
    
    set Trig = CreateTrigger()
    call Damage_RegisterEvent(Trig)
    call TriggerAddCondition(Trig, Condition(function Actions))
    set t = null
endfunction

endscope




Creates a heavy gravity current at target point. The sudden change in gravity causes a leakage of nearby enemy units' mana. The mana materializes in a ball form, which then travels towards the target point. Upon arrival at the target point, the volatile energy balls violently explodes, unleashing a devastating gravity force, dealing damage equal to the total mana absrobed to all units within the area. Damage is distributed evenly among all enemy units. Area of 400 at all levels.

Level 1 - 50 mana absorption.
Level 2 - 75 mana absorption.
Level 3 - 100 mana absorption.
Level 4 - 125 mana absorption.

Cast Range: 700
Target Type: Point
Cooldown: 12 seconds

JASS:
scope GravityFlux // requires Damage, Timer32, GTrigger, GroupUtils

native UnitAlive takes unit id returns boolean

//===========================================================================
//                          CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABGF' // raw code of ability "Gravity Flux"
    public constant integer DUMMYID = 'grFl' // raw code of unit "Gravity Flux Dummy"
    
    private constant boolean SELF = true // true if includes mana cost
    
    private constant real TIME = 1.0 // time taken for balls to reach target point
    
    private constant string ART = "Objects\\Spawnmodels\\NightElf\\NEDeathMedium\\NEDeath.mdl" // effect upon enemy damage
    private constant string ARTPOINT = "origin" // attachment point of damage effect
    private constant string DEATHART = "Objects\\Spawnmodels\\NightElf\\NEDeathMedium\\NEDeath.mdl" // effect upon projectile death
    
    private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type of damage
    private constant damagetype DMG = DAMAGE_TYPE_MAGIC // damage type of damage
    private constant weapontype WEP = WEAPON_TYPE_WHOKNOWS // weapon type of damage
endglobals

private function GetMana takes integer level returns real
    return 25.0 + (25.0 * level) // mana absorbed from unit
endfunction

private function GetTargetAoe takes integer level returns real
    return 400.0 // target area of effect
endfunction

private function GetAoe takes integer level returns real
    return 400.0 // area of effect of damage
endfunction

private function FilterTarget takes unit owner, unit target returns boolean
    return UnitAlive(target) or IsUnitEnemy(target, GetOwningPlayer(owner)) // targets allowed for damage
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================

private struct Data
    real duration
    real mana
    real x
    real y
    integer level
    integer count = 0
    group ballgroup
    group damagegroup
    unit caster
    static Data tempData
    static hashtable Hash = InitHashtable()
    static unit tempUnit
    static real tempX
    static real tempY
    static real tempReal
    static integer tempInt
    
    private static method damageFunc takes nothing returns nothing
        local thistype this = tempData
        local unit u = GetEnumUnit()
        
        call UnitDamageTargetEx(this.caster, u, this.mana, true, false, ATK, DMG, WEP)
        call DestroyEffect(AddSpecialEffectTarget(ART, u, ARTPOINT))
        set u = null
    endmethod
    
    private static method countFunc takes nothing returns boolean
        local thistype this = tempData
        local unit u = GetFilterUnit()
        
        if IsUnitEnemy(u, GetOwningPlayer(this.caster)) and UnitAlive(u) then
            set this.count = this.count + 1
            call GroupAddUnit(this.damagegroup, u)
        endif
        set u = null
        return false
    endmethod
    
    private static method groupFunc takes nothing returns nothing
        local thistype this = tempData
        local unit u = GetEnumUnit()
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        local real a = Atan2(this.y - y, this.x - x)
        local real dist = LoadReal(Hash, GetHandleId(u), 0)
        
        set x = x + dist * Cos(a)
        set y = y + dist * Sin(a)
        call SetUnitPosition(u, x, y)
        set u = null
    endmethod
    
    private static method filterFunc takes nothing returns boolean
        local thistype this = tempData
        local unit u = GetFilterUnit()
        local real x
        local real y
        local real mana
        local real unitmana
        
        if FilterTarget(this.caster, u) then
            set x = GetUnitX(u)
            set y = GetUnitY(u)
            set mana = GetMana(this.level)
            set unitmana = GetUnitState(u, UNIT_STATE_MANA)
            if unitmana < mana then
                set mana = unitmana
            endif
            set this.mana = this.mana + mana
            set tempUnit = CreateUnit(GetOwningPlayer(this.caster), DUMMYID, x, y, Atan2(this.y - y, this.x - x) * bj_RADTODEG)
            call GroupAddUnit(this.ballgroup, tempUnit)
            call SetUnitState(u, UNIT_STATE_MANA, unitmana - mana)
            set x = x - this.x
            set y = y - this.y
            call SaveReal(Hash, GetHandleId(tempUnit), 0, (SquareRoot(x * x + y * y) / TIME) * T32_PERIOD)
        endif
        set u = null
        return false
    endmethod
    
    private static method GroupFlush takes nothing returns nothing
        local unit u = GetEnumUnit()
        local thistype this = tempData
        
        call FlushChildHashtable(Hash, GetHandleId(u))
        call GroupRemoveUnit(this.ballgroup, u)
        call RemoveUnit(u)
        set u = null
    endmethod
    
    private method periodic takes nothing returns nothing
        if this.duration <= 0 then
            set tempData = this
            call GroupEnumUnitsInArea(ENUM_GROUP, this.x, this.y, GetAoe(this.level), Filter(function thistype.countFunc))
            if this.count > 0 then
                set this.mana = this.mana / this.count
                call ForGroup(this.damagegroup, function thistype.damageFunc)
            endif
            call DestroyEffect(AddSpecialEffect(DEATHART, this.x, this.y))
            call ForGroup(this.ballgroup, function thistype.GroupFlush)
            call ReleaseGroup(this.ballgroup)
            call this.stopPeriodic()
            call this.deallocate()
        else
            set tempData = this
            call ForGroup(this.ballgroup, function thistype.groupFunc)
            set this.duration = this.duration - T32_PERIOD
        endif
    endmethod
    implement T32x
    
    private static method create takes unit caster, integer level, real x, real y, real mana returns thistype
        local thistype this = thistype.allocate()

        set this.duration = TIME
        set this.mana = mana
        set this.x = x
        set this.y = y
        set this.level = level
        set this.count = 0
        set this.ballgroup = NewGroup()
        set this.damagegroup = NewGroup()
        set this.caster = caster
        
        static if SELF then
            set tempX = GetUnitX(this.caster)
            set tempY = GetUnitY(this.caster)
            set tempUnit = CreateUnit(GetOwningPlayer(this.caster), DUMMYID, tempX, tempY, Atan2(y - tempY, x - tempX) * bj_RADTODEG)
            set tempX = tempX - this.x
            set tempY = tempY - this.y
            call GroupAddUnit(this.ballgroup, tempUnit)
            call SaveReal(Hash, GetHandleId(tempUnit), 0, (SquareRoot(tempX * tempX + tempY * tempY) / TIME) * T32_PERIOD)
        endif
        
        set tempData = this
        call GroupEnumUnitsInArea(ENUM_GROUP, this.x, this.y, GetTargetAoe(this.level), Filter(function thistype.filterFunc))
        call this.startPeriodic()
        return this
    endmethod
    
    private static method manaTimeOut takes nothing returns nothing
        local timer t = GetExpiredTimer()
    
        call thistype.create(tempUnit, tempInt, tempX, tempY, tempReal - GetUnitState(tempUnit, UNIT_STATE_MANA))
        call PauseTimer(t)
        call DestroyTimer(t)
        set t = null
    endmethod
    
    private static method actions takes nothing returns nothing
        local timer t
    
        set tempUnit = GetTriggerUnit()
        set tempInt = GetUnitAbilityLevel(tempUnit, ABILID)
        set tempX = GetSpellTargetX()
        set tempY = GetSpellTargetY()
        
        static if SELF then
            set t = CreateTimer()
            set tempReal = GetUnitState(tempUnit, UNIT_STATE_MANA)
            call TimerStart(t, 0.00, false, function thistype.manaTimeOut)
        else
            call thistype.create(tempUnit, tempInt, tempX, tempY, 0)
        endif
        set t = null
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call GT_RegisterStartsEffectEvent(t, ABILID)
        call TriggerAddAction(t, function thistype.actions)
        set t = null
        
        //preload
        call RemoveUnit(CreateUnit(Player(13), DUMMYID, 0, 0, 0))
    endmethod
endstruct

endscope




Causes a huge amount of gravitons to accelerate towards the target point, creating a black hole. The black hole causes all enemy units within 600 range to have their mana drained periodically and dragged towards the target point. The less mana enemies have, the stronger the dragging force. At 0 mana, enemies cannot resist the dragging force and is pulled towards the center and receives damage instead of mana drain. Lasts 5 seconds.

Level 1 - 20 mana per second.
Level 2 - 30 mana per second.
Level 3 - 40 mana per second.

Cast Range: 300
Target Type: Point
Cooldown: 75 seconds

JASS:
scope GravitonAcceleration // requires Damage, Timer32, GTrigger, GroupUtils

//===========================================================================
//                          CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABGA' // raw code of ability "Graviton Acceleration"
    private constant integer DUMMYID = 'grAc' // raw code of unit "Graviton Acceleration Dummy"
    
    private constant real MAXSPEED = 200.0 // maximum drag speed
    private constant real SPINRATE = 2.0 // number of spins in the center area per second
    
    private constant string LIGHTNING = "DRAM" // lightning effect used, small note that lightning effect
                                               // can generate lag when dealing with mass units
    private constant boolean ALLOWLIGHT = true // true if lightning effect is used
    
    private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type of damage
    private constant damagetype DMG = DAMAGE_TYPE_MAGIC // damage type of damage
    private constant weapontype WEP = WEAPON_TYPE_WHOKNOWS // weapon type of damage
endglobals

private function GetCenterAoe takes integer level returns real
    return 100.0 // radius of center area
endfunction

private function GetAoe takes integer level returns real
    return 600.0 // area of effect of spell
endfunction

private function GetAbsorbAmount takes integer level returns real
    return 10.0 + (10.0 * level) // amount of mana absorbed per second
endfunction

private function GetDuration takes integer level returns real
    return 5.0 // duration of spell
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================

native UnitAlive takes unit id returns boolean

globals
    public group Checkgroup = CreateGroup()
endglobals

private struct Data
    integer level
    real x
    real y
    real duration
    real amount
    real angle
    real center
    real aoe
    group track
    unit caster
    unit dummy
    static Data tempData
    static hashtable Hash = InitHashtable()
    
    private static method groupFunc takes nothing returns nothing
        local thistype this = tempData
        local unit u = GetEnumUnit()
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        local real dx = x - this.x
        local real dy = y - this.y
        local real a
        local real mana
        local real dist = SquareRoot(dx * dx + dy * dy)
        local integer id = GetHandleId(u)
        local boolean check
        
        if dist > this.aoe or not UnitAlive(u) then
            call DestroyLightning(LoadLightningHandle(Hash, this, id))
            call GroupRemoveUnit(this.track, u)
            call GroupRemoveUnit(Checkgroup, u)
        else
            set mana = GetUnitState(u, UNIT_STATE_MANA)
            if mana >= this.amount then
                call SetUnitState(u, UNIT_STATE_MANA, mana - this.amount)
            else
                call UnitDamageTargetEx(this.caster, u, this.amount, true, false, ATK, DMG, WEP)
                call SetUnitState(u, UNIT_STATE_MANA, 0)
            endif
            set check = mana >= 1
            set a = LoadReal(Hash, id, 0) + (this.angle)
            set dx = this.x + this.center * Cos(a)
            set dy = this.y + this.center * Sin(a)
            call MoveLightningEx(LoadLightningHandle(Hash, this, id), true, dx, dy, 0, x, y, GetUnitFlyHeight(u) + 50.0)
            call SaveReal(Hash, id, 0, a)
            if dist > this.center then
                set a = Atan2(this.y - y, this.x - x)
                if check then
                    set dist = MAXSPEED * T32_PERIOD
                else
                    set dist = MAXSPEED * (1.0 - (mana / GetUnitState(u, UNIT_STATE_MAX_MANA))) * T32_PERIOD
                endif
                set dx = x + dist * Cos(a)
                set dy = y + dist * Sin(a)
            endif
            if check then
                call SetUnitX(u, dx)
                call SetUnitY(u, dy)
            else
                call SetUnitPosition(u, dx, dy)
            endif
        endif
        set u = null
    endmethod
    
    private static method filterFunc takes nothing returns boolean
        local thistype this = tempData
        local unit u = GetFilterUnit()
        local real a
        local real x
        local real y
        
        if IsUnitEnemy(u, GetOwningPlayer(this.caster)) and UnitAlive(u) and not IsUnitInGroup(u, this.track) then
            set x = GetUnitX(u)
            set y = GetUnitY(u)
            set a = Atan2(y - this.y, x - this.x)
            static if ALLOWLIGHT then
                call SaveLightningHandle(Hash, this, GetHandleId(u), AddLightning(LIGHTNING, true, this.x + this.center + Cos(a), this.y + this.center + Sin(a), x, y))
            endif
            call SaveReal(Hash, GetHandleId(u), 0, a)
            call GroupAddUnit(this.track, u)
            call GroupAddUnit(Checkgroup, u)
        endif
        set u = null
        return false
    endmethod
    
    private static method groupFlush takes nothing returns nothing
        local thistype this = tempData
        local unit u = GetEnumUnit()
        local integer id = GetHandleId(u)
        
        static if ALLOWLIGHT then
            call DestroyLightning(LoadLightningHandle(Hash, this, id))
        endif
        call GroupRemoveUnit(Checkgroup, u)
        set u = null
    endmethod
    
    private method periodic takes nothing returns nothing
        set tempData = this
        if this.duration <= 0 then
            call KillUnit(this.dummy)
            call ForGroup(this.track, function thistype.groupFlush)
            call FlushChildHashtable(Hash, this)
            call ReleaseGroup(this.track)
            call this.stopPeriodic()
            call this.deallocate()
        else
            call GroupEnumUnitsInArea(ENUM_GROUP, this.x, this.y, this.aoe, Filter(function thistype.filterFunc))
            call ForGroup(this.track, function thistype.groupFunc)
            set this.duration = this.duration - T32_PERIOD
        endif
    endmethod
    implement T32x
    
    static method actions takes nothing returns nothing
        local thistype this = thistype.allocate()
        
        set this.caster = GetTriggerUnit()
        set this.level = GetUnitAbilityLevel(this.caster, ABILID)
        set this.x = GetSpellTargetX()
        set this.y = GetSpellTargetY()
        set this.duration = GetDuration(this.level)
        set this.amount = GetAbsorbAmount(this.level) * T32_PERIOD
        set this.angle = bj_PI * 2 * (T32_PERIOD / SPINRATE)
        set this.center = GetCenterAoe(this.level)
        set this.aoe = GetAoe(this.level)
        set this.track = NewGroup()
        set this.dummy = CreateUnit(GetOwningPlayer(this.caster), DUMMYID, this.x, this.y, 0)
        
        call this.startPeriodic()
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call GT_RegisterStartsEffectEvent(t, ABILID)
        call TriggerAddAction(t, function thistype.actions)
        set t = null
        
        //preload
        call RemoveUnit(CreateUnit(Player(13), DUMMYID, 0, 0, 0))
    endmethod
endstruct

endscope



Credits

Jesus4Lyf
- Damage
- Timer32
- GTrigger
Rising_Dusk - GroupUtils
WILL THE ALMIGHTY - Graviton Acceleration Model



Changelogs

- Initial relase


Inversion Field
- Made number of special effects to be configurable
- Optimized trigger
Gravity Matrix
- Optimized trigger
Gravity Flux
- Made targets allowed to be configurable
- Optimized trigger
Graviton Acceleration
- Added a line to preload dummy unit
- Fixed a bug where lightning effects were not destroyed when 2 or more instances of the skill were running
- Optimized trigger


- Reworked code using Timer32, GTrigger and GroupUtils, instead of KeyTimers2 and Recycle


Feedback will be appreciated.

Keywords:
gravity, graviton, flux, matrix, inversion, field
Contents

Gravity Spellpack v1.2 (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 11 Nov 2011 Bribe: Instead of StringHash("count") you could use the child key "-1" for example. FirstOfGroup loop here instead of group filtering. You should use pathing checks to...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.


11 Nov 2011
Bribe: Instead of StringHash("count") you could use the child key "-1" for example.

FirstOfGroup loop here instead of group filtering.

You should use pathing checks to make sure that units don't get knocked out of bounds and crash the game.

What's the point in saving 0.0 in a hashtable? It is already 0.0 by default.

Since you only use "key 0" in the hashtable you should use Table, or even better a unit indexer for this. Wait you have AIDS in your map, so why are you using a hashtable and getting the handle Id in the first place? You do this in both "GravityFlux" and "GravityMatrix".

Just use "null" for weapon type for spells.

For global groups just use "bj_lastCreatedGroup".
 
Level 3
Joined
Aug 21, 2009
Messages
73
I tested it there where no test enemies so i added and then i tryed to launch again then evrything just went wrong all triggers disabled and such.... think it needs a fix there
EDIT: Didn't see that i could press down arrow for spawns but anyways u should be able to put out unit's without the spell crashing
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Code Check:

Inversion Field

  • There's really no need to use a local variable to refer to tempData.
    This applies to other group enumerations.
  • In static method GroupFilter:
    JASS:
    set dx = GetUnitX(u) - this.x
    set dy = GetUnitY(u) - this.y
    That way, you can do this
    JASS:
    set a = Atan2(dy, dx)
    This applies to other instances like GroupFunc.
  • I think the number of special effects that are spawned should be configurable.
  • There is no need for a when you loop to create the special effects.
    dx and dy can be set like this:
    JASS:
    set dx = this.x + this.aoe * Cos(bj_PI*2/count * i)
    set dy = this.y + this.aoe * Sin(bj_PI*2/count * i)
  • JASS:
    if GetUnitAbilityLevel(u, ABILDUMID) > 0 and GetUnitAbilityLevel(t, ABILDUMID) > 0 then
    elseif GetUnitAbilityLevel(u, ABILDUMID) > 0 then
    ?
  • You could use local integer variables to store the ability level in DamageReduction.
  • There is no need for the local effect variable e.
    JASS:
    call DestroyEffect(AddSpecialEffectTarget(DAMAGEART, t, DAMAGEATTACH))
  • You don't have to use another local trigger variable for the damage detection. You could just do something like this:
    JASS:
    set t = CreateTrigger()
    call Damage_RegisterEvent(t)
    call TriggerAddAction(t, function DamageReduction)
    This applies to Gravity Matrix as well.
Gravity Matrix

  • Attacktype, damagetype, and weapontype could be configurable.
  • There is no need for the local effect variable e.
    JASS:
    call DestroyEffect(AddSpecialEffectTarget(MANAART, u, MANAATTACH))
  • You could have used Tempunit all along to refer to GetTriggerUnit instead of having to declare the local unit variable t.
  • 1000.0 should be configurable.
Gravity Flux

  • Yes, you destroyed special effects correctly! ^o^
  • When you set check in FilterFunc, you can do something like this:
    JASS:
    set check = IsUnitAlly(u, GetOwningPlayer(this.caster)) and GetUnitState(u, UNIT_STATE_MANA) > 0
  • Using static ifs to determine if an ally or an enemy unit can be affected is a bit too much. I would provide a configuration function at the top to allow the user to customize the targets. (Know that your current method can bug though if both ALLY and ENEMY are set to true.)
  • unitmana could have been set earlier since it's going to be used to determine the check in FilterFunc.
  • In the Actions method, you could null t after TimerStart
Graviton Acceleration

  • Use radians directly.
    JASS:
    set this.angle = bj_PI*2* (INTERVAL / SPINRATE)
    That way, you don't need to convert from degrees in GroupFunc.
  • No need for local unit u in Actions.
  • You could preload the dummy unit.
 
Level 9
Joined
Dec 3, 2010
Messages
162
Updated the codes.

Code Check:

Inversion Field

  • There's really no need to use a local variable to refer to tempData.
    This applies to other group enumerations.

True. But I just like using the "this" keyword. I don't really want to name everything tempData.caster, etc.

I smell a BlackRose apprentice.

o_O?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Don't use KT2... use http://www.hiveworkshop.com/forums/submissions-414/snippet-timerqueue-187502/ instead ; |. It makes a world of difference.

JASS:
private static method expire takes nothing returns nothing
//this only runs once, but it can be set to repeat by adding the timer back into the queue
            local thistype this = thistype(0).nextTimer
            local real offset = timeOffset
            local thistype next
            local integer prev = currentTimer
            loop
                set next = nextTimer
                set nextTimer = recycle
                set recycle = this
                set expired = true
                set currentTimer = this
                //code
                set this = nextTimer
                exitwhen timeOffset != offset or this == 0
            endloop
            set currentTimer = prev
            set thistype(0).nextTimer = this
            if (this != 0) then
                call TimerStart(expireTimer, timeOffset-recycle.timeOffset, false, function thistype.expire)
            endif
endmethod

Add in code
JASS:
if (lastTimer == 0) then
    call TimerStart(expireTimer, INTERVAL, false, function thistype.expire)
endif
set timeOffset = INTERVAL-TimerGetRemaining(expireTimer)
set lastTimer.nextTimer = this
set lastTimer = this
set nextTimer = 0

->AIDS

Try Unit Indexer instead (linked in sig). Read the top of the thread to read a compare/contrast on the three indexing systems.

->Event

Event linked in my sig is a lot faster.

->Recycle

Recycling shouldn't really be done to minimize memory. Speed is good, but so is minimal memory. This is why I recycle very rarely now (only recycle units).

->Damage
Check out AdvDamageEvent ;o.
 
Last edited:
Level 6
Joined
Aug 20, 2009
Messages
95
Don't use KT2... use http://www.hiveworkshop.com/forums/submissions-414/snippet-timerqueue-187502/ instead ; |. It makes a world of difference.

JASS:
private static method expire takes nothing returns nothing
//this only runs once, but it can be set to repeat by adding the timer back into the queue
            local thistype this = thistype(0).nextTimer
            local real offset = timeOffset
            local thistype next
            local integer prev = currentTimer
            loop
                set next = nextTimer
                set nextTimer = recycle
                set recycle = this
                set expired = true
                set currentTimer = this
                //code
                set this = nextTimer
                exitwhen timeOffset != offset or this == 0
            endloop
            set currentTimer = prev
            set thistype(0).nextTimer = this
            if (this != 0) then
                call TimerStart(expireTimer, timeOffset-recycle.timeOffset, false, function thistype.expire)
            endif
endmethod

Add in code
JASS:
if (lastTimer == 0) then
    call TimerStart(expireTimer, INTERVAL, false, function thistype.expire)
endif
set timeOffset = INTERVAL-TimerGetRemaining(expireTimer)
set lastTimer.nextTimer = this
set lastTimer = this
set nextTimer = 0

->AIDS

Try Unit Indexer instead (linked in sig). Read the top of the thread to read a compare/contrast on the three indexing systems.

->Event

Event linked in my sig is a lot faster.

->Recycle

Recycling shouldn't really be done to minimize memory. Speed is good, but so is minimal memory. This is why I recycle very rarely now (only recycle units).

->Damage
Check out AdvDamageEvent ;o.

Don't advertise your shit here. All I need to say.


@iAyanami Edit: I like the spells, only a few comments.
Graviton Acceleration: I don't like how the movements are so choppy, it's like they're blinking around the sphere, not moving around it, it ruins that spell. Other than that, good job, only other thing I could suggest is that you add some more special effects on say the mana shield.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Don't advertise your shit here. All I need to say.

I'll ignore that statement and ignore any future refs as if they were never posted ; ).

So yea, update to TimerQueue asap ;D. The rest were just suggestions, but not much of a deal, just the TimerQueue one. You shouldn't be using KT2 for this, and in fact, using it for this is wrong ;P.
 
Level 9
Joined
Dec 3, 2010
Messages
162
I'll ignore that statement and ignore any future refs as if they were never posted ; ).

So yea, update to TimerQueue asap ;D. The rest were just suggestions, but not much of a deal, just the TimerQueue one. You shouldn't be using KT2 for this, and in fact, using it for this is wrong ;P.

Yeah, actually, I've stopped using KT myself. This was made before I actually stopped using KT. Will update it soon. I'm now currently using TimerUtils, T32 or TimerQueue for timer systems.

Also, I'm using AIDS because it's needed for Damage. And I'm using Damage because I want it to support the Damage system. I might add support for just the AddDamageEvent though.

EDIT
Updated the Spellpack to version 1.2

Changelogs

- Reworked code using Timer32, GTrigger and GroupUtils, instead of KeyTimers2 and Recycle


More feedback will be appreciated. Thanks :D
 
Last edited:
Level 22
Joined
Nov 14, 2008
Messages
3,256
2PI should be stored into a global constant.

In the Graviton Acc spell you still use squareroot while in all the other spells you avoided it. this.aoe * this.aoe will fix it
This line

if dist > this.aoe or not UnitAlive(u) then

Why don't you just use Damage's SpellWrapper instead of UnitDamageTargetEx? Seems like a spell to me :p

That was just a quick check, haven't tested it in-game yet.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I might add support for just the AddDamageEvent though.

Well, currently AdvDamageEvent has 2 bugs, one having to do with the wc3 engine itself and the other having to do with the fact that nobody's found a good way to determine whether or not a unit is flagged to explode. At the moment, no units explode (think back to artillery attacks like canon where the unit explodes). Some maps do depend on that flag, but not the end of the world. I'll eventually get it working tho >: ). The last post on the thread actually discusses a way to find out whether a unit will make another unit explode or not (yes, me n Bribe finally figured it out), but there are issues with it ;P.

The other bug has to do with the fact that damage is applied from chains of expiring attacks, but this one can be fixed with attack indexing, which I'll eventually get back to.

I guess AdvDamageEvent is safe to use on most maps (most maps don't rely on the explode flag) =). The API for it won't change, just the back code, so if you do add support for it, you won't have to worry about it later on when I implement the explosion fix (once I solve it).

TimerUtils, T32 or TimerQueue for timer systems

Good suite ; )

Also, Event by j4l does run a lot slower than the one at THW (it's been proven), so just a thought ;P. There's no way to support both libs.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I like this one a lot better than the previous spellpack I reviewed from you. It's more dynamic and gives players some extra mechanics in-game.

Improvements:

- When units start to be damaged by the black hole, if you changed the lightning's alpha color to red that would really help the effect.
- The effect for Inversion Field is really bad. You can only see half of the dark summoning missile because it's on the ground. Also, when units reach the edge, they just look like they're glitching the whole time making it look really weird. If you made some kind of a constant "push/pull" effect on the respective units (moving them over time instead of instantly) this would look better.
 
Top