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

[v]Jass - Splitter Wave v0.1

  • Like
Reactions: Anachron
BTNCrushingWave.gif
Splitter Wave
Description

Sends out a wave of water which explodes when it hits an obstacle or enemy. Every near enemy gets damaged upon explosion and 3 new waves are send out, but the damage dealt by the new waves is less than of te old ones. All waves will disappear when they traveled a maximal distance or heading towards the map borders. Damage upon impact, the amount of maximal splits and the maximal distance increase per level, damage reduction decreases per level.

Level Info:
Level 1Level 2Level 3

Damage: 40
Maximal Splits: 3
Maximal Distance: 1800
Damage Reduction: 3.5

Damage: 70
Maximal Splits: 7
Maximal Distance: 3050
Damage Reduction: 2.5

Damage: 100
Maximal Splits: 11
Maximal Distance: 4300
Damage Reduction: 2.16

This spell requires JassNewGet Pack. Download it from here

The spell is using IsTerrainWalkable by Antiarf so i give credits to him. Furthermore this spell uses 2 of my own (new) libraries.

Spellcode:
JASS:
library SplitterWave initializer Init uses IsTerrainWalkable,TNTK

    globals
        private     constant    integer     SID                = 'spws'
        //The spell rawcode
        
        private     constant    integer     DID                = 'spld'
        //The dummy unit rawcode
                                    
        private     constant    real        MOVE_STEP          = 25
        //The moving speed of a wave
        
        private     constant    real        COLLISION_RADIUS   = 80.
        //The collisions radius of a wave
        
        private     constant    real        MIN_DAMAGE         = 10.0
        //The minimal damage dealt on impact. Because of the damage
        //reduction feature this is necessary.
        
        private     constant    real     TIME_OUT           = 0.04
        //Time buffer which prevents perma multi-splits
        
        private     constant    string      SPLIT_SFX          = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
        //Special effect being displayed when a wave *dies*
        
        private     constant    string      IMPACT_SFX         = ""
        //Special effect being displayed on a unit which got hit by a wave
        
        //Damage options... should be selfexplaining
        private     constant    attacktype  ATTACK = ATTACK_TYPE_MAGIC
        private     constant    damagetype  DAMAGE = DAMAGE_TYPE_UNIVERSAL
        private     constant    weapontype  WEAPON = WEAPON_TYPE_WHOKNOWS
        
    endglobals
        
    //_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
    //The spell privates. PLEASE DO NOT TOUCH !
    //_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
    
    private keyword Control
    
    globals
        private     Control                 TEMP
        private     group                   TEMP_GROUP
        private     filterfunc              CollisionFilter  //Filter callback for the enum group
    endglobals
    //_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
    //_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
    
    //The maximal travel distance for one spell instance....Well if you don´t understand here´s an example:
    //The maximal travel distance is 5000. The first wave has already travelled 3000. If it splits the new
    //waves can reach a maximal distance of 2000 (5000-3000). This is valid for every split
    private constant function GetMaxDistance takes integer level returns integer
        return level * 1250 + 550
    endfunction
    
    //The maximal amount of splits for one spell instance
    private constant function GetSplits takes integer level returns integer
        return level * 4 - 1
    endfunction
    
    //The amount of new waves which spawn when a wave is splitting
    private constant function GetNewWaveAmount takes integer level returns integer
        return level * 0 + 3
    endfunction
    
    //The damage dealt to the nearby enemies
    private constant function GetImpactDamage takes integer level returns integer
        return level * 30 + 10
    endfunction
    
    //A small part of the damage which is decreasing on every split.
    private constant function GetDamageReduction takes real level returns real
        return 2 / level  + 1.5
    endfunction
                
    //A single wave instance
    private struct Wave extends TNTKnock
                        
        //Destructor
        method onDestroy takes nothing returns nothing
        
            local Control m = .data
        
            set m.waves  = m.waves -1
            set m.damage = m.damage - m.reduct
            
            if m.damage < MIN_DAMAGE then
                set m.damage = MIN_DAMAGE
            endif
            
            call DestroyEffect(AddSpecialEffect(SPLIT_SFX,.x,.y))
            call RemoveUnit(.knocker)
            
        endmethod
        
        //Selfexplaining
        method OnSplit takes nothing returns nothing
        
            local Control m = .data
            local real start = Acos(.velx/MOVE_STEP)
            local real end   = start + bj_PI
            local real step  = bj_PI / m.new
            local unit u
                            
            //Checks the timer buffer, the remaining distance and splits
            if m.splits > 0 and .distance > 0 and m.cansplit then
                        
                set m.cansplit = false
                set m.splits = m.splits - 1 //If this becomes 0 the spell is over
                                
                //Creating new waves
                loop
                    exitwhen start > end
                    set u = CreateUnit(Player(15),DID,.0,.0,start*bj_RADTODEG)
                    call Wave.create(m,u,.x + 2 * .speed * Cos(start),.y + 2 * .speed * Cos(start),start,.distance - 2* .speed)
                    set m.waves = m.waves + 1
                    set start = start + step
                endloop
                
            endif
            
            call .destroy()
            set u = null
                
        endmethod
                
        //Redefining the onBreak method
        method onBreak takes nothing returns boolean
        
            local Control m = .data
                    
            //Is the instance about to be destroyed normally
            if super.onBreak() then
                set m.waves = m.waves -1
                return true
            endif
            
            set TEMP = m
            call GroupClear(TEMP_GROUP)
            call GroupEnumUnitsInRange(TEMP_GROUP,.x,.y,COLLISION_RADIUS,CollisionFilter)
            
            if not IsTerrainWalkable(.x,.y) or FirstOfGroup(TEMP_GROUP) != null then
                call .OnSplit()
                return true
            endif 
                                    
            return false
            
        endmethod
        
        //Constructor
        static method create takes Control m, unit dummy, real x, real y, real rad, real dist returns thistype
        
            local thistype this = thistype.allocate()
            
            call .StartLinear(null,x,y,rad,dist,MOVE_STEP)
            call SetUnitPosition(dummy,.x + .velx,.y + .vely)
            call SetUnitFacing(dummy,rad*bj_RADTODEG)
            set .knocker  = dummy
            set .data     = m
                                    
            return this
        
        endmethod
                                                
    endstruct
            
    //This struct controls the splits and holds some coherent data
    private struct Control extends Indexable
    
        real runtime = TIME_OUT
        
        unit    caster
        //real    timeout //The time buffer
        real    damage
        real    reduct
        integer splits
        integer waves
        integer new
        boolean cansplit

        //Checks if there are on splits/waves left
        method onBreak takes nothing returns boolean
            return .splits <= 0 or .waves <= 0
        endmethod
        
        //Updates the the time buffer
        method onLoop takes nothing returns nothing
        
            set .cansplit = true
                        
        endmethod        
        
        //Creator
        static method create takes unit ca, unit dummy, location loc returns thistype
        
            local thistype this = thistype.allocate()
            local real rad = Atan2(GetLocationY(loc) - GetWidgetY(ca),GetLocationX(loc) - GetWidgetX(ca))
            local integer lv = GetUnitAbilityLevel(ca,SID)
            
            call Wave.create(this,dummy,GetWidgetX(ca),GetWidgetY(ca),rad,GetMaxDistance(GetUnitAbilityLevel(ca,SID)))            
            set .caster  = ca
            set .new     = GetNewWaveAmount(lv)
            set .splits  = GetSplits(lv)
            set .damage  = GetImpactDamage(lv)
            set .reduct  = GetDamageReduction(lv)
            set .waves   = 1
            set .cansplit = true
                                                
            return this
            
        endmethod
        
    endstruct
    
    //A filter which checks if there are valid-enemy units near a wave
    private function CollisionFilterFunc takes nothing returns boolean
    
        local Control m = TEMP
        local unit u = GetFilterUnit()
                
        //Checks if a wave has to split
        if IsUnitEnemy(u,GetOwningPlayer(m.caster)) and not ( /*
      */   GetUnitTypeId(u) == DID               or /*
      */   IsUnitType(u, UNIT_TYPE_DEAD)         or /*
      */   IsUnitType(u, UNIT_TYPE_STRUCTURE)    or /*
      */   IsUnitType(u, UNIT_TYPE_MECHANICAL)   or /*
      */   IsUnitType(u, UNIT_TYPE_FLYING)       or /*
      */   IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) or /*
      */   IsUnitType(u, UNIT_TYPE_ANCIENT)      )  /*
      */then
                
            call UnitDamageTarget(m.caster,u,m.damage,false,false,ATTACK,DAMAGE,WEAPON)
            call DestroyEffect(AddSpecialEffect(IMPACT_SFX,GetWidgetX(u),GetWidgetY(u)))
                    
            set u = null
            return true

        endif
        
        set u = null
        return false
        
    endfunction
                                
    //Starts the spell
    private function onCast takes nothing returns nothing
        call Control.create(GetTriggerUnit(),CreateUnit(Player(15),DID,.0,.0,.0),GetSpellTargetLoc())
        //I know its a bad idea to create the dummy unit at this point of the script
        //but this will prevent a weird bug
    endfunction

    //Checks the rawcode
    private function onCheck takes nothing returns boolean
        return GetSpellAbilityId() == SID
    endfunction

    //Initializer
    private function Init takes nothing returns nothing
    
        local trigger trig = CreateTrigger()
                
        set TEMP_GROUP = CreateGroup()
        set CollisionFilter = Filter( function CollisionFilterFunc )

        call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_CAST)
        call TriggerAddAction(trig, function onCast)
        call TriggerAddCondition(trig, Filter( function onCheck))
        
        call Preload(SPLIT_SFX)
        call Preload(IMPACT_SFX)
        call PreloadStart()
        
        set trig = null
                
    endfunction
    
endlibrary
TNTI:
JASS:
library TNTI

    private interface Ext
    
        static integer Total   = 0
        static integer Current = 0
        static constant real Intervall = .01
        static timer TIM = null
        
        real runtime = .01
        
        method onBreak    takes nothing returns boolean defaults true
        method onLoop     takes nothing returns nothing defaults nothing
        
    endinterface
        
    struct Indexable extends Ext
    
        private static Ext array Index
        private real timing = .0
        
        /*stub method onBreak takes nothing returns boolean
            call BJDebugMsg(SCOPE_PREFIX+": |c00FF0000onBreak() from struct with id: "+I2S(.getType())+" is not defined !")
            return true
        endmethod
        
        stub method onLoop takes nothing returns nothing
            call BJDebugMsg(SCOPE_PREFIX+": |c00FF0000onLoop() from struct with id: "+I2S(.getType())+" is not defined !")
        endmethod*/
                
        private method onDestroy takes nothing returns nothing
        
            set Ext.Total = Ext.Total -1
            set thistype.Index[Ext.Current] = thistype.Index[Ext.Total]
            set Ext.Current = Ext.Current-1
                        
            if Ext.Total == 0 then
                call PauseTimer(Ext.TIM)
            endif
                        
        endmethod
    
        private static method Loop takes nothing returns nothing
        
            local Ext this
            set Ext.Current = 0
            
            loop
                exitwhen Ext.Current == Ext.Total
                set this = thistype.Index[Ext.Current]
                
                set .timing = .timing + thistype.Intervall
                
                if .timing >= .runtime then
                    if .onBreak() then
                        call .destroy()
                    else
                        call .onLoop()
                        set .timing = 0
                    endif
                endif
                                                
                set Ext.Current = Ext.Current+1
            endloop
                        
        endmethod
        
        static method create takes nothing returns thistype
        
            local Ext this = thistype.allocate()
            set thistype.Index[Ext.Total] = this
            set Ext.Total = Ext.Total +1
                        
            if Ext.Total == 1 then
                call TimerStart(Ext.TIM,Ext.Intervall,true, function thistype.Loop)
            endif
                       
            return this
            
        endmethod
            
        private static method onInit takes nothing returns nothing
            set Ext.TIM = CreateTimer()
        endmethod
        
    endstruct
endlibrary
TNTK:
JASS:
library TNTK requires TNTI

    globals
        private constant real OFFSET = 64.
        private real MAX
        private real MAY
        private real MIX
        private real MIY
    endglobals
    
    struct TNTKnock extends Indexable
    
        unit knocker = null
        unit target  = null
        integer data = 0x0
            
        readonly real velx = .0
        readonly real vely = .0
        readonly boolean inuse = false
        readonly real runtime  = 0.02
        readonly real x = .0
        readonly real y = .0
        readonly real distance = 10.
        readonly real radiants = .0
        readonly real time  = .0
        readonly real speed = .0
        
        method IsInMap takes nothing returns boolean
            return .x <= MAX and .x >= MIX and .y <= MAY and .y >= MIY
        endmethod
                
        stub method onKnock takes nothing returns nothing
            call SetUnitPosition(.knocker,.x,.y)
            
            //Sorry but for some unknown reason SetUnitX/Y bugs
            //call SetUnitX(.knocker,.x)
            //call SetUnitY(.knocker,.y)
        endmethod
                    
        method onBreak takes nothing returns boolean
            return not .IsInMap() or .distance <= .speed+1. /*
             */ or ( .target  != null and IsUnitType(.target ,UNIT_TYPE_DEAD))  /*
             */ or ( .knocker != null and IsUnitType(.knocker,UNIT_TYPE_DEAD))
        endmethod
                
        method onLoop takes nothing returns nothing
                                        
            if .inuse then
            
                if .target == null then
                    set .x = .x + velx
                    set .y = .y + vely
                    set .time = .time - .runtime
                    set .distance = .distance - .speed
                else
                    set .velx = GetWidgetX(.target) - GetWidgetX(.knocker)
                    set .vely = GetWidgetY(.target) - GetWidgetY(.knocker)
                    set .radiants = Atan2(.vely,.velx)
                    set .distance = SquareRoot(.velx*.velx+.vely*.vely) - .speed
                    set .x = .x + Cos(.radiants) * .speed
                    set .y = .y + Sin(.radiants) * .speed
                    set .time = .time + .runtime
                endif
                            
                call .onKnock()
            
            endif
            
        endmethod
        
        method onDestroy takes nothing returns nothing
            set .knocker = null
            set .target  = null
            set .inuse   = false
        endmethod
                
        method ChangeSpeed takes real newspeed returns nothing
        
            set velx = Cos(.radiants) * newspeed
            set vely = Sin(.radiants) * newspeed
            set .speed = newspeed
            set .time = (.distance*.runtime)/newspeed
            
        endmethod
        
        method ChangeDirection takes real newrad returns nothing
        
            set velx = Cos(newrad) * .speed
            set vely = Sin(newrad) * .speed
            set .radiants = newrad
            
        endmethod
        
        method Stop takes nothing returns nothing
            set .inuse = true
            set .distance = -1.
        endmethod
        
        method StartLinear takes unit u, real sx, real sy, real rad, real dis, real speed returns nothing
        
            if speed > 1 and not .inuse then
            
                set .knocker = u
                set .target = null
                set .x = sx
                set .y = sy
                set .distance = dis
                set .radiants = rad
                set .time = (dis*.runtime) / speed
                set .speed = speed
                set .velx = Cos(rad)*speed
                set .vely = Sin(rad)*speed
                set .inuse = true
                
            endif
            
        endmethod
                
        method StartLinearTimed takes unit u, real sx, real sy, real rad, real dis, real time returns nothing
            
            if time > .runtime and not .inuse then
                        
                set .knocker = u
                set .target = null
                set .x = sx
                set .y = sy
                set .distance = dis
                set .radiants = rad
                set .time = time
                set .speed = (dis*.runtime) / time
                set velx = Cos(rad) * .speed
                set vely = Sin(rad) * .speed
                set .inuse = true
                
            endif
                    
        endmethod
        
        method StartLinearTimedLoc takes unit u, location from, location to, real time, boolean noleak returns nothing
        
            local real x = GetLocationX(to) - GetLocationX(from)
            local real y = GetLocationY(to) - GetLocationY(from)
            local real rad = Atan2(y,x)
            local real dis = SquareRoot(x*x+y*y)
            
            call .StartLinearTimed(u,GetLocationX(from),GetLocationY(from),rad,dis,time)
            
            if noleak then
                call RemoveLocation(from)
                call RemoveLocation(to)
            endif
                        
        endmethod
        
        method StartHomingTimed takes unit u, unit targ, real sx, real sy, real speed returns nothing
                    
            if IsUnitType(targ,UNIT_TYPE_DEAD) != true and targ != null and speed > 0. then
            
                set .knocker = u
                set .target = targ
                set .x = sx
                set .y = sy
                set .speed = speed
                set .time = 0.
                set .distance = speed+3.
                set .inuse = true
                                
            endif
                        
        endmethod
                
        private static method onInit takes nothing returns nothing
        
            set MAX = GetRectMaxX(bj_mapInitialPlayableArea) - OFFSET
            set MAY = GetRectMaxY(bj_mapInitialPlayableArea) - OFFSET
            set MIX = GetRectMinX(bj_mapInitialPlayableArea) + OFFSET
            set MIY = GetRectMinY(bj_mapInitialPlayableArea) + OFFSET
                        
        endmethod
        
    endstruct   
    
endlibrary
VersionChanges

v.01
  • Initial Release

Keywords:
Shockwave, Water, Split, Splitterwave, exponentially, Blue
Contents

Splitterwave Spell Test Map (Map)

Reviews
14:26, 28th Feb 2010 The_Reborn_Devil: The code looks really good, there are no leaks and the spell is unique. Status: Approved Rating: Recommended

Moderator

M

Moderator

14:26, 28th Feb 2010
The_Reborn_Devil:
The code looks really good, there are no leaks and the spell is unique.


Status: Approved
Rating: Recommended
 
Top