WIP Unit Projectile Data

Grundy

Ultra Cool Member
Reaction score
35
I think this is getting close to being finished (I'm thinking about changing the target of UPD to widget instead of unit). I posted the UPD library & 3 example spells that use it. Screen shots of 1 of the spells, and attached the map with the spells. There are no comments for it yet - I hate writing comments.

Description: UPD is just a library I made for projectiles to match the way projectiles already work in wc3. Not completely identical but good enough. It's pretty basic but that's what I wanted. Standard projectiles don't have sine curve flight paths or circular flight paths.

How to use:
In your spell code make a struct that inherits UPD. It has operator methods for Speed (how fast unit target projectiles move), StartLoc (to set where the projectile starts), TargetLoc (to set where the projectile should fly to), Target (to set a unit target), HangTime (how long it takes for a point target projectile to hit its target), Arc (unit target projectile arc same ass all missile arcs in wc3), MaxHeight (how high a point target projectile should move up in the air before coming back down)

You can also directly access variables uOwner (should be the caster of the spell) uTarget (same as Target operator method but Target should be used to set the target and uTarget should be used to get the target), uProjectile (the actual projectile unit), and iUnitTypeId (the unit type id of the projectile unit)

You are welcome to add any variable you want to your struct. Most spells will probably want a level variable. And your struct should implement the OnMove and OnEnd functions. They both take nothing and return nothing. OnMove is called every time the projectile moves (when the TT timer expires) and OnEnd is called after the projectile hits the target unit or point. Your OnEnd should call this.destroy() and don't forget to clean up any variables you add to it.

UPD library:
This handles all the code for projectiles
JASS:
library UPD requires TT

    private interface IUPD
        method OnMove takes nothing returns nothing defaults nothing
        method OnEnd takes nothing returns nothing defaults nothing
    endinterface
    
    struct UPD extends IUPD //Unit Projectile Data
        //The unit should have flying movement & locust ability.
        integer iUnitTypeId             = 0
        private real rX                 = 0
        private real rY                 = 0
        unit uProjectile                = null
        unit uOwner                     = null
        unit uTarget                    = null
        private real rArcHeight         = 0
        private real rStartHeight       = 0
        private real rMax               = 0
        private real rPassed            = 0
        private real rInterval          = 0
        private real rRadAngle          = 0
        private real rDistance          = 0
        debug private boolean TargSet   = false

        //move to unit target 
        private static method MoveToUnit takes nothing returns boolean
            local UPD this = TT_GetData()
            local real rTX = GetUnitX(.uTarget)
            local real rTY = GetUnitY(.uTarget)
            set .rRadAngle = Atan2(rTY-.rY, rTX-.rX)
            call SetUnitFacing(.uProjectile, .rRadAngle * bj_RADTODEG)
            set .rX = .rX + Cos(.rRadAngle)*.rInterval
            set .rY = .rY + Sin(.rRadAngle)*.rInterval
            call SetUnitX(.uProjectile, .rX)
            call SetUnitY(.uProjectile, .rY)
            set .rPassed = .rPassed + .rInterval
            set .rDistance = SquareRoot((rTX-.rX)*(rTX-.rX)+(rTY-.rY)*(rTY-.rY))
            set .rMax = .rPassed + .rDistance
            call SetUnitFlyHeight(.uProjectile, .rPassed*.rDistance*.rArcHeight*4/.rMax + .rStartHeight, 0)
            if .rDistance < .rInterval then
                call .OnEnd()
                return true
            endif
            call .OnMove()
            return false
        endmethod

        //move to point target
        private static method MoveToPoint takes nothing returns boolean
            local UPD this = TT_GetData()
            set .rX = .rX + Cos(.rRadAngle) * .rInterval
            set .rY = .rY + Sin(.rRadAngle) * .rInterval
            set .rPassed = .rPassed+TT_PERIOD
            call SetUnitX(.uProjectile, .rX)
            call SetUnitY(.uProjectile, .rY)
            call SetUnitFlyHeight(.uProjectile, .rPassed*(.rMax - .rPassed)*.rArcHeight + .rStartHeight, 0)
            if .rPassed >= .rMax then
                call .OnEnd()
                return true
            endif
            call .OnMove()
            return false
        endmethod
            
        //Used for unit target
        method operator Speed= takes real rSpeed returns nothing
            set .rInterval = rSpeed*TT_PERIOD
        endmethod
            
        //Used for point or unit target
        method operator StartLoc= takes location whichLoc returns nothing
            set .rX = GetLocationX(whichLoc)
            set .rY = GetLocationY(whichLoc)
        endmethod
            
        //used for point target
        method operator TargetLoc= takes location whichLoc returns nothing
            local real rX = GetLocationX(whichLoc)
            local real rY = GetLocationY(whichLoc)
            set .rDistance = SquareRoot((rX-.rX)*(rX-.rX)+(rY-.rY)*(rY-.rY))
            set .rRadAngle = Atan2(rY-.rY, rX-.rX)
            debug set .TargSet = true
        endmethod

        //used for unit target
        method operator Target= takes unit whichUnit returns nothing
            set .uTarget = whichUnit
            set .rRadAngle = Atan2(GetUnitY(whichUnit)-.rY, GetUnitX(whichUnit)-.rX)
            debug set .TargSet = true
        endmethod

        //used for point target
        method operator HangTime= takes real rTime returns nothing
            if rTime - R2I(rTime/TT_PERIOD)*TT_PERIOD == 0 then
                set .rMax = rTime
            else
                set .rMax = (R2I(rTime/TT_PERIOD)+1)*TT_PERIOD
            endif
        endmethod

        //used for point target
        method operator MaxHeight= takes real rHeight returns nothing
            set .rArcHeight = rHeight
        endmethod

        //start the projectile
        method Start takes nothing returns nothing
            debug if .TargSet == false then
            debug   call BJDebugMsg("No target has been set.")
            debug   return
            debug endif
            debug if .iUnitTypeId == 0 then
            debug   call BJDebugMsg("No unit type specified.")
            debug   return
            debug endif
            debug if .uOwner == null then
            debug   call BJDebugMsg("No owner set.")
            debug   return
            debug endif
            set .uProjectile = CreateUnit(GetOwningPlayer(.uOwner), .iUnitTypeId, .rX, .rY, .rRadAngle * bj_RADTODEG)//CreateUnit(GetOwningPlayer(.uOwner), .iUnitTypeId, .rX, .rY, .rRadAngle * bj_RADTODEG)
            set .rStartHeight = GetUnitFlyHeight(.uProjectile)
            if .uTarget != null then
                call TT_Start(function UPD.MoveToUnit, this)
            else
                set .rInterval = .rDistance * TT_PERIOD / .rMax
                set .rArcHeight = (.rArcHeight * 4) / (.rMax * .rMax)
                call TT_Start(function UPD.MoveToPoint, this)
            endif
        endmethod

    endstruct

endlibrary

Hurl Boulder spell:
This is a spell trigger, it has 2 structs that extend UPD. The spell throws a boulder at a target location, when the boulder lands it deals damage and breaks apart into 6 smaller boulders that are thrown outwards, when each of them lands on the ground they break apart and deal a small amount of damage.


JASS:
scope HurlBoulder initializer Init

    globals
        private constant integer SPELL_ID                   = 'A000'
        private constant integer BOULDER_SPAWN_BASE         = 6
        private constant integer BOULDER_SPAWN_PER_LEVEL    = 0

        private constant integer BIG_BOULDER_ID             = 'h000'
        private constant real BIG_AOE_BASE                  = 75.0
        private constant real BIG_AOE_PER_LEVEL             = 75.0
        private constant real BIG_DAMAGE_BASE               = 0.0
        private constant real BIG_DAMAGE_PER_LEVEL          = 45.0
        private constant real BIG_HANG_TIME                 = 0.64
        private constant real BIG_MAX_HEIGHT                = 450.0

        private constant integer SMALL_BOULDER_ID           = 'h001'
        private constant real SMALL_AOE_BASE                = 35.0
        private constant real SMALL_AOE_PER_LEVEL           = 35.0
        private constant real SMALL_DAMAGE_BASE             = 0.0
        private constant real SMALL_DAMAGE_PER_LEVEL        = 15.0
        private constant real SMALL_HANG_TIME               = 0.36
        private constant real SMALL_MAX_HEIGHT              = 250.0
        private constant real SMALL_DISTANCE_BASE           = 60.0
        private constant real SMALL_DISTANCE_PER_LEVEL      = 40.0

        private constant attacktype ATTACK_TYPE             = ATTACK_TYPE_SIEGE
        private constant damagetype DAMAGE_TYPE             = DAMAGE_TYPE_DEMOLITION
        private constant weapontype WEAPON_TYPE             = WEAPON_TYPE_ROCK_HEAVY_BASH
    endglobals

    private struct SmallBoulder extends UPD
        integer iLevel = 0

        private method operator Damage takes nothing returns real
            return SMALL_DAMAGE_BASE + SMALL_DAMAGE_PER_LEVEL * .iLevel
        endmethod

        private method operator AoE takes nothing returns real
            return SMALL_AOE_BASE + SMALL_AOE_PER_LEVEL * .iLevel
        endmethod

        private method ValidTarget takes unit whichUnit returns boolean
            //Doesn't hit flying
            if IsUnitType(whichUnit, UNIT_TYPE_FLYING) then
                return false
            endif
            //Don't bother if it's already dead
            if GetUnitState(whichUnit, UNIT_STATE_LIFE) < 0.5 then
                return false
            endif
            //Only hits enemies
            return IsUnitEnemy(whichUnit, GetOwningPlayer(.uOwner))
        endmethod

        private method OnEnd takes nothing returns nothing
            local unit uTemp
            local group gTargets = NewGroup()
            call KillUnit(.uProjectile)
            call GroupEnumUnitsInRange(gTargets, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .AoE, null)
            loop
                set uTemp = FirstOfGroup(gTargets)
            exitwhen uTemp == null
                if .ValidTarget(uTemp) then
                    call UnitDamageTarget(.uOwner, uTemp, .Damage, false, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                endif
                call GroupRemoveUnit(gTargets, uTemp)
            endloop
            call ReleaseGroup(gTargets)
            set gTargets = null
            set uTemp = null
            call this.destroy()
        endmethod
    endstruct

    private struct BigBoulder extends UPD
        integer iLevel = 0

        private method operator Damage takes nothing returns real
            return BIG_DAMAGE_BASE + BIG_DAMAGE_PER_LEVEL * .iLevel
        endmethod

        private method operator AoE takes nothing returns real
            return BIG_AOE_BASE + BIG_AOE_PER_LEVEL * .iLevel
        endmethod

        private method operator BoulderCount takes nothing returns integer
            return BOULDER_SPAWN_BASE + BOULDER_SPAWN_PER_LEVEL * .iLevel
        endmethod

        private method operator Distance takes nothing returns real
            return SMALL_DISTANCE_BASE + SMALL_DISTANCE_PER_LEVEL * .iLevel
        endmethod

        private method ValidTarget takes unit whichUnit returns boolean
            //Doesn't hit flying
            if IsUnitType(whichUnit, UNIT_TYPE_FLYING) then
                return false
            endif
            //If it's already dead... Don't bother!
            if GetUnitState(whichUnit, UNIT_STATE_LIFE) < 0.5 then
                return false
            endif
            //Only hits enemies
            return IsUnitEnemy(whichUnit, GetOwningPlayer(.uOwner))
        endmethod

        private method OnEnd takes nothing returns nothing
            local unit uTemp
            local group gTargets = NewGroup()
            local SmallBoulder b
            local integer iCount = 0
            local location locStart = GetUnitLoc(.uProjectile)
            local location locTarget
            local real rAngle
            call KillUnit(.uProjectile)
            call GroupEnumUnitsInRange(gTargets, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .AoE, null)
            loop
                set uTemp = FirstOfGroup(gTargets)
            exitwhen uTemp == null
                if .ValidTarget(uTemp) then
                    call UnitDamageTarget(.uOwner, uTemp, .Damage, false, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                endif
                call GroupRemoveUnit(gTargets, uTemp)
            endloop
            loop
            exitwhen iCount >= .BoulderCount
                set rAngle = GetRandomReal(iCount, iCount+1)/.BoulderCount*2*bj_PI
                set b = SmallBoulder.create()
                set b.uOwner = .uOwner
                set b.iLevel = .iLevel
                set b.StartLoc = locStart
                set locTarget = Location(GetLocationX(locStart) + Cos(rAngle) * .Distance, GetLocationY(locStart) + Sin(rAngle) * .Distance)
                set b.TargetLoc = locTarget
                set b.HangTime = SMALL_HANG_TIME
                set b.MaxHeight = SMALL_MAX_HEIGHT
                set b.iUnitTypeId = SMALL_BOULDER_ID
                call b.Start()
                call RemoveLocation(locTarget)
                set iCount = iCount + 1
            endloop
            call ReleaseGroup(gTargets)
            call RemoveLocation(locStart)
            set gTargets = null
            set uTemp = null
            set locStart = null
            set locTarget = null
            call this.destroy()
        endmethod
    endstruct

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

    private function Actions takes nothing returns nothing
        local unit uCaster = GetSpellAbilityUnit()
        local location locCaster = GetUnitLoc(uCaster)
        local location locTarget = GetSpellTargetLoc()
        local BigBoulder b = BigBoulder.create()
        set b.uOwner = uCaster
        set b.iLevel = GetUnitAbilityLevel(uCaster, SPELL_ID)
        set b.StartLoc = locCaster
        set b.TargetLoc = locTarget
        set b.HangTime=BIG_HANG_TIME
        set b.MaxHeight=BIG_MAX_HEIGHT
        set b.iUnitTypeId = BIG_BOULDER_ID
        call b.Start()
        call RemoveLocation(locCaster)
        call RemoveLocation(locTarget)
        set uCaster = null
        set locCaster = null
        set locTarget = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger SpellTrigger = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(SpellTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(SpellTrigger, Condition(function Conditions))
        call TriggerAddAction(SpellTrigger, function Actions)
    endfunction
endscope

Wave of Despair spell:
This sends out a wave of despair (duh) that explodes at the end of its life dealing damage to units around it. It also does something else but it's hard to explain...
basically there are 2 kinds of projectiles, i'll call them "Wave Projectile" and "Heal Projectile"
Wave Projectile is the main projectile, the "wave of despair" if you will (and if you won't, too bad! because it's still true)
When the Wave Projectile comes in contact with an enemy unit it sends a "Heal Projectile" from the enemy unit back to the cast. The Heal Projectile deals damage to each enemy unit it passes. So this spell is *very* good against massive groups of enemy units, and it's pretty weak against single units.

This spell is kind of ridiculous but it's really just an example of the kind of shit i can make with UPD.
JASS:
scope WaveOfDespair initializer Init

    globals
        private constant integer SPELL_ID                   = 'A002'
        
        //=========================================================
        //WAVE PROJECTILE INFO
        //=========================================================
        private constant integer WAVE_ID                    = 'h002'
        
        private constant real WAVE_RANGE_BASE               = 800.0
        private constant real WAVE_RANGE_PER_LEVEL          = 50.0
        
        private constant real WAVE_SPEED_BASE               = 700.0
        private constant real WAVE_SPEED_PER_LEVEL          = 100.0
        
        private constant real WAVE_AOE_BASE                 = 150.0
        private constant real WAVE_AOE_PER_LEVEL            = 0.0
        
        private constant real BLAST_AOE_BASE                = 210.0
        private constant real BLAST_AOE_PER_LEVEL           = 0.0
        
        //Damage is dealt when the projectile poofs.
        private constant real BLAST_DAMAGE_BASE             = 0.0
        private constant real BLAST_DAMAGE_PER_LEVEL        = 50.0

        //=========================================================
        //HEAL PROJECTILE INFO
        //=========================================================
        private constant integer HEAL_ID                    = 'h003'
        
        private constant real HEAL_SPEED_BASE               = 450.0
        private constant real HEAL_SPEED_PER_LEVEL          = 0.0
        
        private constant real HEAL_DAMAGE_AOE_BASE          = 100.0
        private constant real HEAL_DAMAGE_AOE_PER_LEVEL     = 0.0
        
        //Heal projectile deals damage to each enemy it passes
        private constant real HEAL_DAMAGE_AMOUNT_BASE       = 0.0
        private constant real HEAL_DAMAGE_AMOUNT_PER_LEVEL  = 10.0
        
        //Heals the caster after it reaches the caster
        private constant real HEAL_AMOUNT_BASE              = 0.0
        private constant real HEAL_AMOUNT_PER_LEVEL         = 10.0
        
        //damage types
        private constant attacktype ATTACK                  = ATTACK_TYPE_CHAOS
        private constant damagetype DAMAGE                  = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WEAPON                  = WEAPON_TYPE_WHOKNOWS
        
        //Heal effect
        private constant string HEAL_EFFECT                 = "Abilities\\Spells\\Undead\\VampiricAura\\VampiricAuraTarget.mdl"
        private constant string DAMAGE_EFFECT               = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl"
        private constant string EFFECT_ATTACHMENT           = "origin"
    endglobals
        
    private function Range takes integer iLevel returns real
            return WAVE_RANGE_BASE + WAVE_RANGE_PER_LEVEL * iLevel
    endfunction
        
    private function WaveSpeed takes integer iLevel returns real
        return WAVE_SPEED_BASE + WAVE_SPEED_PER_LEVEL * iLevel
    endfunction
        
    private function HealSpeed takes integer iLevel returns real
        return HEAL_SPEED_BASE + HEAL_SPEED_PER_LEVEL * iLevel
    endfunction
    
    private struct HealProjectile extends UPD
        static unit uTemp       = null
        
        integer iLevel          = 0
        private group gHit      = null
        private group gInRange  = null
        
        static method create takes nothing returns HealProjectile
            local HealProjectile this = HealProjectile.allocate()
            set .gHit = NewGroup()
            set .gInRange = NewGroup()
            return this
        endmethod
        
        private method operator Damage takes nothing returns real
            return HEAL_DAMAGE_AMOUNT_BASE + HEAL_DAMAGE_AMOUNT_PER_LEVEL * .iLevel
        endmethod
        
        private method operator DamageAoE takes nothing returns real
            return HEAL_DAMAGE_AOE_BASE + HEAL_DAMAGE_AOE_PER_LEVEL * .iLevel
        endmethod
        
        private method operator Heal takes nothing returns real
            return HEAL_AMOUNT_BASE + HEAL_AMOUNT_PER_LEVEL * .iLevel
        endmethod
        
        private method ValidTarget takes nothing returns boolean
            if IsUnitInGroup(.uTemp, .gHit) then
                return false //Can only be hit once!
            endif
            if GetUnitState(.uTemp, UNIT_STATE_LIFE) < 0.5 then
                return false //Don't bother with corpses!
            endif
            if IsUnitType(.uTemp, UNIT_TYPE_STRUCTURE) then
                return false //Don't hurt buildings
            endif
            if IsUnitType(.uTemp, UNIT_TYPE_MAGIC_IMMUNE) then
                return false //Doesn't hurt fuckers that are immune to magic
            endif
            return IsUnitEnemy(.uTemp, GetOwningPlayer(.uOwner))
        endmethod
        
        private method OnMove takes nothing returns nothing
            call GroupEnumUnitsInRange(.gInRange, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .DamageAoE, null)
            loop
                set .uTemp = FirstOfGroup(.gInRange)
            exitwhen .uTemp == null
                if .ValidTarget() then
                    call UnitDamageTarget(.uOwner, .uTemp, .Damage, false, false, ATTACK, DAMAGE, WEAPON)
                    call GroupAddUnit(.gHit, .uTemp)
                endif
                call GroupRemoveUnit(.gInRange, .uTemp)
            endloop
        endmethod
        
        private method OnEnd takes nothing returns nothing
            call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, .uTarget, EFFECT_ATTACHMENT))
            call SetUnitState(.uTarget, UNIT_STATE_LIFE, GetUnitState(.uTarget, UNIT_STATE_LIFE) + .Heal)
            call KillUnit(.uProjectile)
            call ReleaseGroup(.gHit)
            call ReleaseGroup(.gInRange)
            call this.destroy()
        endmethod
    endstruct
    
    private struct WaveProjectile extends UPD
        static unit uTemp       = null
        
        integer iLevel          = 0
        private group gHit      = null
        private group gInRange  = null
        
        static method create takes nothing returns WaveProjectile
            local WaveProjectile this = WaveProjectile.allocate()
            set .gHit = NewGroup()
            set .gInRange = NewGroup()
            return this
        endmethod
        
        private method operator WaveArea takes nothing returns real
            return WAVE_AOE_BASE + WAVE_AOE_PER_LEVEL * .iLevel
        endmethod
        
        private method operator BlastArea takes nothing returns real
            return BLAST_AOE_BASE + BLAST_AOE_PER_LEVEL * .iLevel
        endmethod
        
        private method operator BlastDamage takes nothing returns real
            return BLAST_DAMAGE_BASE + BLAST_DAMAGE_PER_LEVEL * .iLevel
        endmethod
        
        private method ValidTarget takes nothing returns boolean
            if GetUnitState(.uTemp, UNIT_STATE_LIFE) < 0.5 then
                return false //Don't bother with corpses!
            endif
            if IsUnitType(.uTemp, UNIT_TYPE_STRUCTURE) then
                return false //Don't hurt buildings
            endif
            if IsUnitType(.uTemp, UNIT_TYPE_MAGIC_IMMUNE) then
                return false //Doesn't hurt fuckers that are immune to magic
            endif
            return IsUnitEnemy(.uTemp, GetOwningPlayer(.uOwner))
        endmethod
        
        private method WaveValidTarget takes nothing returns boolean
            if IsUnitInGroup(.uTemp, .gHit) then
                return false //Can only be hit once!
            endif
            return .ValidTarget()
        endmethod
        
        private method OnEnd takes nothing returns nothing
            call GroupEnumUnitsInRange(.gInRange, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .BlastArea, null)
            call KillUnit(.uProjectile)
            loop
                set .uTemp = FirstOfGroup(.gInRange)
            exitwhen .uTemp == null
                if .ValidTarget() then
                    call UnitDamageTarget(.uOwner, .uTemp, .BlastDamage, false, false, ATTACK, DAMAGE, WEAPON)
                    call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, .uTemp, EFFECT_ATTACHMENT))
                endif
                call GroupRemoveUnit(.gInRange, .uTemp)
            endloop
            call ReleaseGroup(.gHit)
            call ReleaseGroup(.gInRange)
            call this.destroy()
        endmethod
        
        private method OnMove takes nothing returns nothing
            local HealProjectile h
            local location locStart
            call GroupEnumUnitsInRange(.gInRange, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .WaveArea, null)
            loop
                set .uTemp = FirstOfGroup(.gInRange)
            exitwhen .uTemp == null
                call GroupRemoveUnit(.gInRange, .uTemp)
                if .WaveValidTarget() then
                    call GroupAddUnit(.gHit, .uTemp)
                    set h = HealProjectile.create()
                    set locStart = GetUnitLoc(.uTemp)
                    set h.StartLoc = locStart
                    set h.uOwner = .uOwner
                    set h.iLevel = .iLevel
                    set h.Target = .uOwner
                    set h.iUnitTypeId = HEAL_ID
                    set h.Speed = HealSpeed(.iLevel)
                    call h.Start()
                    call RemoveLocation(locStart)
                endif
            endloop
            set locStart = null
        endmethod

    endstruct
    
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ID
    endfunction

    private function Actions takes nothing returns nothing
        local unit uCaster = GetSpellAbilityUnit()
        local location locCaster = GetUnitLoc(uCaster)
        local location locTarget = GetSpellTargetLoc()
        local real rAngle = Atan2(GetLocationY(locTarget)-GetLocationY(locCaster), GetLocationX(locTarget)-GetLocationX(locCaster))
        local WaveProjectile w = WaveProjectile.create()
        local integer iLevel = GetUnitAbilityLevel(uCaster, SPELL_ID)
        call RemoveLocation(locTarget)
        set locTarget = Location(GetLocationX(locCaster) + Cos(rAngle)*Range(iLevel), GetLocationY(locCaster) + Sin(rAngle)*Range(iLevel))
        set w.uOwner = uCaster
        set w.iLevel = iLevel
        set w.StartLoc = locCaster
        set w.TargetLoc = locTarget
        set w.HangTime=Range(iLevel)/WaveSpeed(iLevel)
        set w.iUnitTypeId = WAVE_ID
        call w.Start()
        call RemoveLocation(locCaster)
        call RemoveLocation(locTarget)
        set uCaster = null
        set locCaster = null
        set locTarget = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger Trig = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(Trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(Trig, Condition(function Conditions))
        call TriggerAddAction(Trig, function Actions)
    endfunction

endscope

Venom Lance:
It's like a blink strike shadow strike. The caster vanishes, an envenomed lance is thrown from the caster to the target, when it hits the caster appears near the target and it's hit with a shadow strike.
JASS:
scope VenomLance initializer Init
    globals
        private constant integer VL_SPELL_ID        = 'A003'
        private constant integer SS_SPELL_ID        = 'A004'
        private constant integer INVULNERABLE       = 'AInv'

        private constant integer LANCE_ID           = 'h004'
        private constant integer VENOM_ID           = 'h005'
        private constant integer DUMMY_ID           = 'hZZZ'
        private constant integer TIMED_LIFE         = 'BTLF'

        private constant real SPEED_BASE            = 850.0
        private constant real SPEED_PER_LEVEL       = 0.0

        private constant real ARC                   = 0.1

        private constant real OFFSET                = 200.0

        private constant string START_EFFECT        = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
        private constant string END_EFFECT          = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"
        private constant string ATTACHMENT_POINT    = "origin"
    endglobals
    
    private function LanceSpeed takes integer iLevel returns real
        return SPEED_BASE + SPEED_PER_LEVEL * iLevel
    endfunction

    private struct Lance extends UPD
        integer iLevel = 0
        
        unit uVenom = null

        private method operator LanceSpeed takes nothing returns real
            return SPEED_BASE + SPEED_PER_LEVEL * .iLevel
        endmethod

        static method create takes integer level returns Lance
            local Lance l = Lance.allocate()
            set l.iLevel = level
            set l.Speed = l.LanceSpeed
            return l
        endmethod
        
        private method OnMove takes nothing returns nothing
            //Since the projectile model has no attachment points, a 2nd unit needs to be used
            //for the venom effect to work.
            call SetUnitX(.uVenom, GetUnitX(.uProjectile))
            call SetUnitY(.uVenom, GetUnitY(.uProjectile))
            call SetUnitFacing(.uVenom, GetUnitFacing(.uProjectile))
            call SetUnitFlyHeight(.uVenom, GetUnitFlyHeight(.uProjectile), 0)
        endmethod

        private method OnEnd takes nothing returns nothing
            local unit uDummy
            local real rTX = GetUnitX(.uTarget)
            local real rTY = GetUnitY(.uTarget)
            local real rAngle = Atan2(GetUnitY(.uProjectile)-rTY, GetUnitX(.uProjectile)-rTX)
            call SetUnitX(.uOwner, GetUnitX(.uTarget) + Cos(rAngle) * OFFSET)
            call SetUnitY(.uOwner, GetUnitY(.uTarget) + Sin(rAngle) * OFFSET)
            call ShowUnit(.uOwner, true)
            call UnitRemoveAbility(.uOwner, INVULNERABLE)
            call DestroyEffect(AddSpecialEffectTarget(END_EFFECT, .uOwner, ATTACHMENT_POINT))
            if GetLocalPlayer() == GetOwningPlayer(.uOwner) then
                call SelectUnit(.uOwner, true)
            endif
            //Need to make sure the target hasn't died since the spell was cast
            if GetUnitState(.uTarget, UNIT_STATE_LIFE) >= .5 then
                set uDummy = CreateUnit(GetOwningPlayer(.uOwner), DUMMY_ID, rTX, rTY, 0)
                call UnitAddAbility(uDummy, SS_SPELL_ID)
                call SetUnitAbilityLevel(uDummy, SS_SPELL_ID, .iLevel)
                call IssueTargetOrder(uDummy, "shadowstrike", .uTarget)
                call UnitApplyTimedLife(uDummy, TIMED_LIFE, 1.0)
                call IssueTargetOrder(.uOwner, "attack", .uTarget)
            endif
            call KillUnit(.uProjectile)
            call KillUnit(.uVenom)
            set uDummy = null
            call .destroy()
        endmethod
    endstruct

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

    private function Actions takes nothing returns nothing
        local unit uCaster = GetSpellAbilityUnit()
        local location locStart = GetUnitLoc(uCaster)
        local Lance l = Lance.create(GetUnitAbilityLevel(uCaster, VL_SPELL_ID))
        set l.StartLoc = locStart
        set l.uOwner = uCaster
        set l.Target = GetSpellTargetUnit()
        set l.iUnitTypeId = LANCE_ID
        call l.Start()
        set l.uVenom = CreateUnit(GetOwningPlayer(uCaster), VENOM_ID, GetLocationX(locStart), GetLocationY(locStart), GetUnitFacing(l.uProjectile))
        call SetUnitFlyHeight(l.uVenom, GetUnitFlyHeight(l.uProjectile), 0)
        call DestroyEffect(AddSpecialEffectLoc(START_EFFECT, locStart))
        call UnitAddAbility(uCaster, INVULNERABLE)
        call ShowUnit(uCaster, false)
        call RemoveLocation(locStart)
        set uCaster = null
        set locStart = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger Trig = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(Trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(Trig, Condition(function Conditions))
        call TriggerAddAction(Trig, function Actions)
    endfunction
endscope

Snooze:
Simple spell, just Sleep with a projectile. Using this method instead of the Sphere ability because sphere will make all spells have the same projectile. Using UPD I can give any instant target spell any projectile I want.
JASS:
scope Snooze initializer Init
    globals
        private constant integer SPELL_ID = 'A004'
        private constant integer SLEEP_SPELL = 'A005'

        private constant integer ZZ_ID = 'h006'
        private constant real DUMMY_ID = 'hZZZ'
        private constant integer TIMED_LIFE = 'BTLF'

        private constant real SPEED = 600
        private constant real ARC = 0.3
    endglobals

    private struct SnoozeData
        integer iLevel = 0

        private method TargetAlive takes nothing returns boolean
            return GetUnitState(.uTarget, UNIT_STATE_LIFE) >= 0.5
        endmethod

        private method OnEnd takes nothing returns nothing
            local unit uDummy
            if .TargetAlive() then
                set uDummy = CreateUnit(GetOwningPlayer(.uOwner), DUMMY_ID, GetUnitX(.uTarget), GetUnitY(.uTarget), 0)
                call UnitAddAbility(uDummy, SLEEP_SPELL)
                call SetUnitAbilityLevel(uDummy, SLEEP_SPELL, .iLevel)
                call IssueTargetOrder(uDummy, "sleep", .uTarget)
                call ApplyTimedLife(uDummy, TIMED_LIFE, 1.0)
            endif
            call KillUnit(.uProjectile)
            set uDummy = null
        endmethod
    endstruct

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

    private function Actions takes nothing returns nothing
        local unit uCaster = GetSpellAbilityUnit()
        local unit uTarget = GetSpellTargetUnit()
        local location locCaster = GetUnitLoc(uCaster)
        local SnoozeData s = SnoozeData.create()
        set s.iLevel = GetUnitAbilityLevel(uCaster, SPELL_ID)
        set s.uOwner = uCaster
        set s.StartLoc = locCaster
        set s.Target = uTarget
        set s.Speed = SPEED
        set s.Arc = ARC
        call s.Start()
        call RemoveLocation(locCaster)
        set locCaster = null
        set uCaster = null
        set uTarget = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger Trig = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(Trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(Trig, Condition(function Conditions))
        call TriggerAddAction(Trig, function Actions)
    endfunction
endscope

Diamond Dust: (no not shiva from Final Fantasy)
Hit 'em with the diamond dust
One breath and they're minds rust

Throws a cloud of diamond dust at a target area stunning enemy units and sapping their mana.
JASS:
scope DiamondDust initializer Init
//HIT 'EM WITH THE DIAMOND DUST
//ONE BREATH AND THEIR MINDS RUST

    globals
        private constant integer SPELL_ID           = 'A006'
        private constant integer STOMP_ID           = 'A007'

        private constant integer DUST_ID            = 'h006'
        private constant real DUMMY_ID              = 'hZZZ'
        private constant integer TIMED_LIFE         = 'BTLF'

        //Mana loss: 50/80/110/140
        private constant real MANA_LOSS_BASE        = 20.0
        private constant real MANA_LOSS_PER_LEVEL   = 30.0

        private constant real SPEED                 = 500
        private constant real HEIGHT                = 300
    endglobals

    private struct DiamondDustData
        integer iLevel = 0

        private method OnEnd takes nothing returns nothing
            local unit uDummy = CreateUnit(GetOwningPlayer(.uOwner), DUMMY_ID, GetUnitX(.uProjectile), GetUnitY(.uProjectile), 0)
            call UnitAddAbility(uDummy, STOMP_ID)
            call SetUnitAbilityLevel(uDummy, STOMP_ID, .iLevel)
            call 
    endstruct

    private function MainConditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ID
    endfunction

    private function MainActions takes nothing returns nothing
        local unit uCaster = GetSpellAbilityUnit()
        local location locCaster = GetUnitLoc(uCaster)
        local location locTarget = GetSpellTargetLoc()
        local DiamondDustData dd = DiamondDustData.create()
        set dd.iLevel = GetUnitAbilityLevel(uCaster, SPELL_ID)
        set dd.uOwner = uCaster
        set dd.StartLoc = locCaster
        set dd.TargetLoc = locTarget
        set dd.Speed = SPEED
        set dd.MaxHeight = HEIGHT
        call dd.Start()
        call RemoveLocation(locCaster)
        call RemoveLocation(locTarget)
        set locCaster = null
        set locTarget = null
        set uCaster = null
        set uTarget = null
    endfunction

    private function StompConditions takes nothing returns boolean
        return GetSpellAbilityId() == STOMP_ID
    endfunction

    private function StompActions takes nothing returns nothing
        local group gTarget = NewGroup()
        local unit uTemp = null
        local unit uCaster = GetSpellAbilityUnit()
        local integer iLevel = GetUnitAbilityLevel(uCaster, STOMP_ID)
        call GroupEnumUnitsInRange(gTarget, GetUnitX(uCaster), GetUnitY(uCaster), SpellAoE(iLevel), null)
        loop
            set uTemp = FirstOfGroup(gTarget)
        exitwhen uTemp == null
            if IsUnitEnemy(GetOwningPlayer(uCaster), uTemp) then
                call SetUnitState(uTemp, UNIT_STATE_MANA, GetUnitState(uTemp, UNIT_STATE_MANA) - ManaLoss(iLevel))
            endif
            call GroupRemoveUnit(gTarget, uTemp)
        endloop
        call ReleaseGroup(gTarget)
        set gTarget = null
        set uCaster = null
        set uTemp = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger MainTrig = CreateTrigger()
        local trigger StompTrig = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(MainTrig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(MainTrig, Condition(function MainConditions))
        call TriggerAddAction(MainTrig, function MainActions)
        call TriggerRegisterAnyUnitEventBJ(StompTrig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(StompTrig, Condition(function StompConditions))
        call TriggerAddAction(StompTrig, function StompActions)
    endfunction
endscope
 

Attachments

  • UDP and example spells.w3x
    60.9 KB · Views: 186

Viikuna

No Marlo no game.
Reaction score
265
Vexorians xecollider is pretty cool, you should check it out. Also you dont really have to use TT, struct array work just fine.

You should make an interface for missile hit, so you can extend that missile struct and have different missile hit methods for different missiles. ( Like in xecollider )
 

Grundy

Ultra Cool Member
Reaction score
35
i looked at that xe thing i didn't really like it. and I don't mean any offense to vex at all - i know lot of people like it and use it and that's awesome. just a matter of personal preference. I don't need crazy flight patterns and i'm hoping this one will just be really easy to use and I'm going to try to add some spellblock checking in it too for unit targets

and i am using an interface. I just didn't get to it yet.... but now it's there.

I'm just using TT for the 1 timer thing. I know I could probably do this with 1 timer if i just make a global timer or a static timer in the struct or whatever but TT is simple enough to use and it would basically do the same exact thing anyway.

I also added an example of how I could use this UPD thing.
 

Romek

Super Moderator
Reaction score
963
A better version of this has been made before.
(Vexorians xecollider)

So, I really see no uses for this. (Although I'm glad I helped you with the coding indirectly - With UAC)
 

Grundy

Ultra Cool Member
Reaction score
35
A better version of this has been made before.
(Vexorians xecollider)

So, I really see no uses for this.

I already said why i'm making this and don't want to use xe. I'm really making this for personal use. If you don't want to use it that wont bother none.

and to everyone else:
this is supposed to be like basic stuff like imitating the way projectiles currently work in wc3. I don't want it to be super fancy because normal projectiles from attacks and spells in wc3 aren't super fancy. I don't want sin cuves flight pathes and i don't want everything to chop down trees. That would just be out of place compared to how stuff works in wc3 right now.

For some things I do want to detect collision in certain ways.... like creating a shockwave type of spell i would make my struct look like this:

JASS:

  private struct Shockwave extends UPD
    integer iLevel = 0
    private group gHit = CreateGroup() //NewGroup()
    private group gTargets = CreateGroup() //NewGroup()
    private unit uTemp

    private method ValidTarget takes nothing returns boolean
      if IsUnitInGroup(.uTemp, .gHit) then
        return false
      endif
      if GetUnitState(.uTemp, UNIT_STATE_LIFE) < 0.5 then
        return false
      endif
      return IsUnitEnemy(.uTemp, GetOwningPlayer(.uOwner))
    endmethod

    method OnMove takes nothing returns nothing
      call GroupEnumUnitsInRange(.gTargets, GetUnitX(.uProjectile), GetUnitY(.uProjectile), SpellArea(.iLevel))
      loop
        set .uTemp = FirstOfGroup(.gTargets)
      exitwhen .uTemp == null
        if ValidTarget() then
          call GroupAddUnit(.gHit, .uTemp)
          call UnitDamageTarget(.uOwner, .uTemp, SpellDamage(.iLevel), false, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
        endif
        call GroupRemoveUnit(.gTargets, .uTemp)
      endloop
    endmethod
      
  endstruct


that's not totally finished obviously i would add stuff to clean up and destroy it afterwards.
 

Flare

Stops copies me!
Reaction score
662
Ah, so a OnCollision type method is out of the question then? :(

So, I really see no uses for this
Well, there's a load of different attachment systems (many working in much the same way), but that didn't stop the authors from making their own systems, did it?

(Although I'm glad I helped you with the coding indirectly - With UAC)
:confused: Elaborate?
 

Grundy

Ultra Cool Member
Reaction score
35
I can elaborate on that. I started making this before and it worked pretty good but I really didn't like the way I was doing it at all. I saw his UAC and I liked how the code looked. I decided to rewrite my projectile thing and the basic structure is pretty close to his UAC. I never used "interface" or "extends" anywhere in any of my jass code ever but after i saw his UAC i decided to use interface and inhertence.

And I edited my previous post, it has an example of how I would use this for "collision" (ie. shockwave projectile colliding with enemy units to deal damage)
 

Viikuna

No Marlo no game.
Reaction score
265
Ye, interfaces do pwn. My new buff/condition system uses them too.

Im actually making a system like xecollider for my map, and I had this idea of making speed, acceleration, max/min speed and homing mode members public.

This way it would be possible to easily make a spell which slows all missiles in area ( Like TimeBomb in SC2 trailer ), or maybe a spell which changes all missiles in X range to homing missiles, and makes them chase the target unit.

Projectiles systems do make these things easier, since you dont have to write dozens of if/then/elses for every projectile spell.
 

Grundy

Ultra Cool Member
Reaction score
35
I fixed the code in the 1st post. It actually works now (well i still havent tried unit targets, but i know point targets work). And the example spell code works too.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top