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

[vJASS] Modules and thistype.onThing.exists

Status
Not open for further replies.
I've never used modules before. I'm trying to make a system that can be used like Missile or Buff System, that is to say, you can do something like call Thing.dothething(instance) and then it will run the various methods inside of the struct Thing. So, for example:
JASS:
    struct MovingPoint
     
        static method onPeriod takes Beam b returns nothing
            local real a = GetBeamAngle(b) // returns angle in radians
            local real dist = GetBeamLength(b)
            local real x = GetPointX(b.impact) + Cos(a) * dist + 10.
            local real y = GetPointY(b.impact) + Sin(a) * dist + 10.
            call Point.update(b.impact, x, y, 0.)
            call BJDebugMsg("test")
        endmethod
     
        implement BeamControl
     
    endstruct
The above example isn't working, and I can't say I know why because I don't really get any of this. I've been trying to read the code behind Missile or Buff System, and the only thing I've been able to identify is static if thistype.onPeriod.exists then inside the module that is then implemented into your custom struct that has Implement MyModule.

I guess my question is... how do you make this work? I'm not even sure I'm explaining my problem correctly. I'm making a lightning-effect handler that behaves slightly differently that the others. I'll just post my code below:

JASS:
library Beam
 
    globals
 
        private constant real TIMEOUT = .03125
        private constant location LOC = Location(0., 0.)
     
        private timer IntervalTimer = null
        private integer BeamCount = 0
        private Beam array ActiveBeams
    endglobals
 
    function GetLocalUnitZ takes unit u returns real
        call MoveLocation(LOC, GetUnitX(u), GetUnitY(u))
        return GetUnitFlyHeight(u) + GetLocationZ(LOC)
    endfunction
 
 
    // Point getters
    function GetPointX takes Point point returns real
        return point.x
    endfunction
 
    function GetPointY takes Point point returns real
        return point.y
    endfunction
 
    function GetPointZ takes Point point returns real
        return point.z
    endfunction
 
    // Beam getters
    function GetBeamAngle takes Beam b returns real
        return Atan2(b.impact.y - b.launch.y, b.impact.x - b.launch.x)
    endfunction
 
    function GetBeamLength takes Beam b returns real
        return SquareRoot( (b.impact.x - b.launch.x)*(b.impact.x - b.launch.x) + (b.impact.y - b.launch.y)*(b.impact.y - b.launch.y) )
    endfunction
 
 
    module BeamControl
     
        private static method remove takes integer starterInt returns nothing
            local integer i = starterInt
            loop
                set ActiveBeams[i] = ActiveBeams[i + 1]
                set i = i + 1
                exitwhen i > BeamCount
            endloop
            set BeamCount = BeamCount - 1
            call ActiveBeams[starterInt].destroy()
        endmethod
     
        private static method interval takes nothing returns nothing
         
            local integer i = 1
            local Beam b
            local boolean moveBeam = false
         
            local real x
            local real y
            local real z
            local real a
         
            loop
             
                set i = i + 1
                set b = ActiveBeams[i]
                exitwhen i > BeamCount
             
                // runs every TIMEOUT seconds
                static if thistype.onPeriod.exists then
                    call thistype.onPeriod(b)
                endif
             
                // runs if the
                static if thistype.onEnd.exists then
                 
                endif
             
                if b.source != null then
                    if b.sourceFacing then
                        set a = b.sourceAngle + GetUnitFacing(b.source) * bj_DEGTORAD
                    else
                        set a = b.sourceAngle
                    endif
                    set x = GetUnitX(b.source) + Cos(a) * b.sourceDistance
                    set y = GetUnitY(b.source) + Sin(a) * b.sourceDistance
                    set z = GetLocalUnitZ(b.source) + b.sourceZOffset
                    call Point.update(b.launch, x, y, z)
                endif
             
                if b.target != null then
                    if b.targetFacing then
                        set a = b.targetAngle + GetUnitFacing(b.target) * bj_DEGTORAD
                    else
                        set a = b.targetAngle
                    endif
                    set x = GetUnitX(b.target) + Cos(a) * b.targetDistance
                    set y = GetUnitY(b.target) + Sin(a) * b.targetDistance
                    set z = GetLocalUnitZ(b.target) + b.targetZOffset
                    call Point.update(b.launch, x, y, z)
                endif
             
                if Point.hasMoved(b.launch) or Point.hasMoved(b.impact) then
                    call MoveLightningEx(b.beam, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
                endif
                // if not in the fade phase
                if not b.isFading then
                    set b.beamTime = b.beamTime - TIMEOUT
                    if b.beamTime <= 0. then
                        set b.isFading = true
                    endif
                // if in the fade phase
                else
                    set b.fadeTime = b.fadeTime - TIMEOUT
                    if b.fadeTime <= 0. then
                        call thistype.remove(i)
                    endif
                endif
             
            endloop
         
            if BeamCount <= 0 then
                call PauseTimer(IntervalTimer)
            endif
         
        endmethod
     
        static method cast takes Beam b returns nothing
         
            local real xSource
            local real ySource
            local real xTarget
            local real yTarget
            local real x
            local real y
            local real z
         
            if not b.isCast then
         
                set b.isCast = true
             
                if b.source != null then
                    // if you have a source, calculate the angle and distance between the launch coordinates and the source.
                    // these values will then recalculate every interval to determine if the Point has moved or not.
                    set xSource = GetUnitX(b.source)
                    set ySource = GetUnitY(b.source)
                    set b.sourceAngle = Atan2(b.yLaunch - ySource, b.xLaunch - xSource)
                    set b.sourceDistance = SquareRoot( (b.xLaunch - xSource)*(b.xLaunch - xSource) + (b.yLaunch - ySource)*(b.yLaunch - ySource) )
                    set b.sourceZOffset = b.zLaunch - GetLocalUnitZ(b.source)
                endif
                set b.launch = Point.create(b.xLaunch, b.yLaunch, b.zLaunch)
             
                if b.target != null then
                    // same as the above, but for targets
                    set xTarget = GetUnitX(b.target)
                    set yTarget = GetUnitY(b.target)
                    set b.targetAngle = Atan2(b.yImpact - yTarget, b.xImpact - xTarget)
                    set b.targetDistance = SquareRoot( (b.xImpact - xTarget)*(b.xImpact - xTarget) + (b.yImpact - yTarget)*(b.yImpact - yTarget) )
                    set b.targetZOffset = b.zImpact - GetLocalUnitZ(b.target)
                endif
                set b.impact = Point.create(b.xImpact, b.yImpact, b.zImpact)
             
                set b.beam = AddLightningEx(b.beamFX, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
                if b.beamTime < 0. then
                    set b.isFading = true
                else
                    set b.isFading = false
                endif
             
                set BeamCount = BeamCount + 1
                if BeamCount == 1 then //if BeamCount is equal to 1 then that means it was previously zero, which means the timer was inactive.
                    call TimerStart(IntervalTimer, TIMEOUT, true, function thistype.interval)
                endif
             
                //  do I need to create these triggers?
                /*static if thistype.onPeriod.exists then
                 
                endif
                static if thistype.onFade.exists then
                 
                endif*/
            endif
         
        endmethod
     
    endmodule
 
    struct Point
     
        readonly real x
        readonly real y
        readonly real z
        private real xOld
        private real yOld
        private real zOld
     
        static method remove takes thistype point returns nothing
            set point.x = 0.
            set point.y = 0.
            set point.z = 0.
            set point.xOld = 0.
            set point.yOld = 0.
            set point.zOld = 0.
            call point.deallocate()
        endmethod
     
        static method hasMoved takes thistype point returns boolean
            if point.x != point.xOld or point.y != point.yOld or point.z != point.zOld then
                return true
            else
                return false
            endif
        endmethod
     
        static method update takes thistype point, real X, real Y, real Z returns nothing
            set point.xOld = point.x
            set point.yOld = point.y
            set point.zOld = point.z
            set point.x = X
            set point.y = Y
            set point.z = Z
        endmethod
     
        static method create takes real X, real Y, real Z returns thistype
            local thistype point = allocate()
            set point.x = X
            set point.y = Y
            set point.z = Z
            set point.xOld = X
            set point.yOld = Y
            set point.zOld = Z
            return point
        endmethod
     
    endstruct
 
    struct Beam
     
        unit source
        unit target
     
        // these reals will be used to recalculate the new coordiates of Points should the source or target move
        real sourceAngle
        real sourceDistance
        real sourceZOffset
        real targetAngle
        real targetDistance
        real targetZOffset
     
        // these booleans will move the point around the source/target with relation to their facing angle
        boolean sourceFacing
        boolean targetFacing
     
        // these values will either be the launch and impact coordinates, or offset the source and
        // target location by these values. Refer to the demo for a exmaple of how to use those as offsets.
        real xLaunch
        real yLaunch
        real zLaunch
        real xImpact
        real yImpact
        real zImpact
     
        real beamTime
        real fadeTime
     
        // these variables store the launch and impact Points of the beam instance.
        Point impact
        Point launch
     
        lightning beam
        string beamFX
        real red
        real blue
        real green
        real alpha
     
        boolean isFading
        boolean isCast
     
        method destroy takes nothing returns nothing
            if this.beam != null then
                call DestroyLightning(this.beam)
                set this.beam = null
            endif
            set this.source = null
            set this.target = null
            set this.isCast = false
            call this.deallocate()
        endmethod
     
        static method create takes nothing returns thistype
            local thistype b = allocate()
            //initialise all variables to their default value
            set b.source = null
            set b.target = null
            set b.sourceAngle = 0.
            set b.sourceDistance = 0.
            set b.sourceZOffset = 0.
            set b.targetAngle = 0.
            set b.targetDistance = 0.
            set b.targetZOffset = 0.
            set b.sourceFacing = false
            set b.targetFacing = false
            set b.xLaunch = 0.
            set b.yLaunch = 0.
            set b.zLaunch = 0.
            set b.xImpact = 0.
            set b.yImpact = 0.
            set b.zImpact = 0.
            set b.beamTime = 0.
            set b.fadeTime = 0.
            set b.impact = 0
            set b.launch = 0
            set b.beam = null
            set b.beamFX = null
            set b.red = 1.
            set b.blue = 1.
            set b.green = 1.
            set b.alpha = 1.
            set b.isFading = false
            set b.isCast = false
            return b
        endmethod
     
    endstruct

endlibrary

JASS:
scope MovingPointDemo initializer init

    struct MovingPoint
     
        // this method is never called :(
        // how do I make it work?
        static method onPeriod takes Beam b returns nothing
            local real a = GetBeamAngle(b) // returns angle in radians
            local real dist = GetBeamLength(b)
            local real x = GetPointX(b.impact) + Cos(a) * dist + 10.
            local real y = GetPointY(b.impact) + Sin(a) * dist + 10.
            call Point.update(b.impact, x, y, 0.)
            call BJDebugMsg("test")
        endmethod
     
        implement BeamControl
     
    endstruct
 
    private function Actions takes nothing returns nothing
     
        local unit Source = GetTriggerUnit()
        local real xSource = GetUnitX(Source)
        local real ySource = GetUnitY(Source)
        local real xTarget = GetSpellTargetX()
        local real yTarget = GetSpellTargetY()
     
        local real a = Atan2(yTarget - ySource, xTarget - xSource)
        local real dist = 80.//GetUnitCollision(Source)
     
        local Beam b = Beam.create()
     
        set b.source = Source
        set b.beamFX = "BLNL"
        set b.xLaunch = xSource + Cos(a) * dist
        set b.yLaunch = ySource + Sin(a) * dist
        set b.zLaunch = GetUnitFlyHeight(Source) + 80.
        set b.xImpact = xTarget
        set b.yImpact = yTarget
        set b.zImpact = 0.
        set b.beamTime = 2.
     
        call MovingPoint.cast(b)
     
        set Source = null
    endfunction
 
    private function init takes nothing returns nothing
        call RegisterSpellEffectEvent('A000', function Actions)
    endfunction

endscope
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
If I understood correctly, what you aim to do is like a Buff/Missile system that allows you to configure/edit (via code) what each instance (or group of instance) will do?

You could think that modules are like macros that gets copied to each struct that implements it. Therefore all structs that implement BeamControl will produce a copy of private static method interval takes nothing returns nothing which is something you don't wanna do because you'll end up with a very lengthy script. So instead of that, you could use TimerUtils instead which is something like
JASS:
//In constructor method
static if thistype.onPeriod.exist
  set this.t = NewTimerEx(this)
  call TimerStart(this.t, TIMEOUT, false, function thistype.onPeriod)
endif

As for your code, I don't know why it doesn't work. My guess is thistype.onPeriod should takes nothing?
 
@Flux Hmm, maybe I can move the interval outside the module, though I'm not sure how it will know to run onPeriod and others like it... Is a lengthy script really that bad? I'm a bit of a psycho for performance, so if I can avoid have a timer for every single lightning instance, that would be preferable.

Also if thistype.onPeriod takes nothing, how will the demo know what instance it's modifying? :/

@Chaosy pffft xD
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
@Flux Hmm, maybe I can move the interval outside the module, though I'm not sure how it will know to run onPeriod and others like it... Is a lengthy script really that bad? It's a bit of a psycho for performance, so if I can avoid have a timer for every single lightning instance, that would be preferable.

Also if thistype.onPeriod takes nothing, how will the demo know what instance it's modifying? :/
Putting the private static method interval in the module is really bad in my opinion because each struct that implements the module will get its own copy of private static method interval which is pointless and will cause a bug (I think?) because if you have 2 structs that implements the module, you will have 2 interval running simultaneously causing the onPeriod to run two times (I think).

The performance difference between one timer for all vs one timer per instance is really miniscule (you can test if yourself using debugging timing libraries I used in DummyRecycler but if were talking about hundreds of instances running at the same time then I think it matters a bit). Sometimes it's better to have better readability than slight/negligible performance. Furthermore, TimerUtils allows you to get the instance via ReleaseTimer or GetTimerData if i recall correctly the name.
 
You're right about the interval being inside the module as it was repeating ad infinitum and crashing the game. The other reason why it wasn't working was because I never created the timer :p

I moved the timer method outside the module, but now it's ignoring the .onPeriod methods :(
JASS:
static if BeamControl.onPeriod.exists then
    call BeamControl.onPeriod(b)
endif
Doesn't seem to work. Any idea how to work around this? Do I need to create a trigger and run it? A bit confused about all this :p
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
Need to see the newly edited code. Also, it's not a good idea to put a long script inside a module. If you look at this example, the module only has a few lines because it's like a macro that get's copied everytime it is implemented. What exactly are you trying to achieve? Perhaps there's another way so that you don't have to use a module.
 
hmm, I would use code arrays, but JASS doesn't allow code arrays, sadly xD
Essentially, I want to give the user the ability to customise that happens to their lightning effects. For example, if they want to add special effects at the start and end of the bolt or move the lightning effect dynamically, etc. Basically I'd like to offer the same level of control as you do in Buff System.

anyway, updated code:
JASS:
library Beam
   
    globals
   
        private constant real TIMEOUT = .03125
        private constant location LOC = Location(0., 0.)
       
        private timer IntervalTimer = CreateTimer()
        private integer BeamCount = 0
        private Beam array ActiveBeams
    endglobals
   
    function GetLocalUnitZ takes unit u returns real
        call MoveLocation(LOC, GetUnitX(u), GetUnitY(u))
        return GetUnitFlyHeight(u) + GetLocationZ(LOC)
    endfunction
   
   
   
    // Point getters
    function GetPointX takes Point point returns real
        return point.x
    endfunction
   
    function GetPointY takes Point point returns real
        return point.y
    endfunction
   
    function GetPointZ takes Point point returns real
        return point.z
    endfunction
   
    // Beam getters
    function GetBeamAngle takes Beam b returns real
        return Atan2(b.impact.y - b.launch.y, b.impact.x - b.launch.x)
    endfunction
   
    function GetBeamLength takes Beam b returns real
        return SquareRoot( (b.impact.x - b.launch.x)*(b.impact.x - b.launch.x) + (b.impact.y - b.launch.y)*(b.impact.y - b.launch.y) )
    endfunction
   
    // Beam setters
    function SetBeamTime takes Beam b, real newTime returns nothing
        set b.beamTime = newTime
    endfunction
   
    function FadeBeam takes Beam b returns nothing
        set b.isFading = true
    endfunction
   
    private function RemoveInstance takes integer starterInt returns nothing
        local integer i = starterInt
        call ActiveBeams[starterInt].destroy()
        loop
            set ActiveBeams[i] = ActiveBeams[i + 1]
            set i = i + 1
            exitwhen i > BeamCount
        endloop
        set BeamCount = BeamCount - 1
    endfunction
       
    function Interval takes nothing returns nothing
           
        local integer i = 0
        local Beam b
       
        local real x
        local real y
        local real z
        local real a
       
        loop
           
            set i = i + 1
            set b = ActiveBeams[i]
            exitwhen i > BeamCount
           
            // runs every TIMEOUT seconds
            static if BeamControl.onPeriod.exists then //<-- this no work :(
                call BeamControl.onPeriod(b)
            endif
           
            if b.source != null then
                if b.sourceFacing then
                    set a = b.sourceAngle + (GetUnitFacing(b.source) + 180.) * bj_DEGTORAD
                else
                    set a = b.sourceAngle
                endif
                set x = GetUnitX(b.source) + Cos(a) * b.sourceDistance
                set y = GetUnitY(b.source) + Sin(a) * b.sourceDistance
                set z = GetLocalUnitZ(b.source) + b.sourceZOffset
                call Point.update(b.launch, x, y, z)
            endif
           
            if b.target != null then
                if b.targetFacing then
                    set a = b.targetAngle + (GetUnitFacing(b.target) + 180.) * bj_DEGTORAD
                else
                    set a = b.targetAngle
                endif
                set x = GetUnitX(b.target) + Cos(a) * b.targetDistance
                set y = GetUnitY(b.target) + Sin(a) * b.targetDistance
                set z = GetLocalUnitZ(b.target) + b.targetZOffset
                call Point.update(b.launch, x, y, z)
            endif
           
            if Point.hasMoved(b.launch) or Point.hasMoved(b.impact) then
                call MoveLightningEx(b.beam, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
            endif
            // if not in the fade phase
            if not b.isFading then
                if b.beamTime != 0 then // if zero, then beam lasts forever.
                    set b.beamTime = b.beamTime - TIMEOUT
                    if b.beamTime <= 0. then
                        set b.isFading = true
                    endif
                endif
            // if in the fade phase
            else
                set b.fadeTime = b.fadeTime - TIMEOUT
                if b.fadeTime <= 0. then
                    call RemoveInstance(i)
                endif
            endif
           
        endloop
       
        if BeamCount <= 0 then
            call PauseTimer(IntervalTimer)
        endif
       
    endfunction
   
    module BeamControl
       
        static method cast takes Beam b returns nothing
           
            local real xSource
            local real ySource
            local real xTarget
            local real yTarget
            local real x
            local real y
            local real z
           
            if not b.isCast then
           
                set b.isCast = true
               
                if b.source != null then
                    // if you have a source, calculate the angle and distance between the launch coordinates and the source.
                    // these values will then recalculate every interval to determine if the Point has moved or not.
                    set xSource = GetUnitX(b.source)
                    set ySource = GetUnitY(b.source)
                    set b.sourceAngle = Atan2(b.yLaunch - ySource, b.xLaunch - xSource)
                    set b.sourceDistance = SquareRoot( (b.xLaunch - xSource)*(b.xLaunch - xSource) + (b.yLaunch - ySource)*(b.yLaunch - ySource) )
                    set b.sourceZOffset = b.zLaunch - GetLocalUnitZ(b.source)
                endif
                set b.launch = Point.create(b.xLaunch, b.yLaunch, b.zLaunch)
               
                if b.target != null then
                    // same as the above, but for targets
                    set xTarget = GetUnitX(b.target)
                    set yTarget = GetUnitY(b.target)
                    set b.targetAngle = Atan2(b.yImpact - yTarget, b.xImpact - xTarget)
                    set b.targetDistance = SquareRoot( (b.xImpact - xTarget)*(b.xImpact - xTarget) + (b.yImpact - yTarget)*(b.yImpact - yTarget) )
                    set b.targetZOffset = b.zImpact - GetLocalUnitZ(b.target)
                endif
                set b.impact = Point.create(b.xImpact, b.yImpact, b.zImpact)
               
                set b.beam = AddLightningEx(b.beamFX, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
                if b.beamTime < 0. then
                    set b.isFading = true
                else
                    set b.isFading = false
                endif
               
                set BeamCount = BeamCount + 1
                set ActiveBeams[BeamCount] = b
                if BeamCount == 1 then //if BeamCount is equal to 1 then that means it was previously zero, which means the timer was inactive.
                    call TimerStart(IntervalTimer, TIMEOUT, true, function Interval)
                endif
               
                /*static if thistype.onPeriod.exists then
               
                endif
                static if thistype.onFade.exists then
                   
                endif*/
            endif
           
        endmethod
       
    endmodule
   
    struct Point
       
        readonly real x
        readonly real y
        readonly real z
        private real xOld
        private real yOld
        private real zOld
       
        static method remove takes thistype point returns nothing
            set point.x = 0.
            set point.y = 0.
            set point.z = 0.
            set point.xOld = 0.
            set point.yOld = 0.
            set point.zOld = 0.
            call point.deallocate()
        endmethod
       
        static method hasMoved takes thistype point returns boolean
            if point.x != point.xOld or point.y != point.yOld or point.z != point.zOld then
                return true
            else
                return false
            endif
        endmethod
       
        static method update takes thistype point, real X, real Y, real Z returns nothing
            set point.xOld = point.x
            set point.yOld = point.y
            set point.zOld = point.z
            set point.x = X
            set point.y = Y
            set point.z = Z
        endmethod
       
        static method create takes real X, real Y, real Z returns thistype
            local thistype point = allocate()
            set point.x = X
            set point.y = Y
            set point.z = Z
            set point.xOld = X
            set point.yOld = Y
            set point.zOld = Z
            return point
        endmethod
       
    endstruct
   
    struct Beam
       
        unit source
        unit target
       
        // these reals will be used to recalculate the new coordiates of Points should the source or target move
        real sourceAngle
        real sourceDistance
        real sourceZOffset
        real targetAngle
        real targetDistance
        real targetZOffset
       
        // these booleans will move the point around the source/target with relation to their facing angle
        boolean sourceFacing
        boolean targetFacing
       
        // these values will either be the launch and impact coordinates, or offset the source and 
        // target location by these values. Refer to the demo for a exmaple of how to use those as offsets.
        real xLaunch
        real yLaunch
        real zLaunch
        real xImpact
        real yImpact
        real zImpact
       
        real beamTime
        real fadeTime
       
        // these variables store the launch and impact Points of the beam instance.
        Point impact
        Point launch
       
        lightning beam
        string beamFX
        real red
        real blue
        real green
        real alpha
       
        boolean isFading
        boolean isCast
       
        method destroy takes nothing returns nothing
            if this.beam != null then
                call DestroyLightning(this.beam)
                set this.beam = null
            endif
            set this.source = null
            set this.target = null
            set this.isCast = false
            call this.deallocate()
        endmethod
       
        static method create takes nothing returns thistype
            local thistype b = allocate()
            //initialise all variables to their default value
            set b.source = null
            set b.target = null
            set b.sourceAngle = 0.
            set b.sourceDistance = 0.
            set b.sourceZOffset = 0.
            set b.targetAngle = 0.
            set b.targetDistance = 0.
            set b.targetZOffset = 0.
            set b.sourceFacing = false
            set b.targetFacing = false
            set b.xLaunch = 0.
            set b.yLaunch = 0.
            set b.zLaunch = 0.
            set b.xImpact = 0.
            set b.yImpact = 0.
            set b.zImpact = 0.
            set b.beamTime = 0.
            set b.fadeTime = 0.
            set b.impact = 0
            set b.launch = 0
            set b.beam = null
            set b.beamFX = null
            set b.red = 1.
            set b.blue = 1.
            set b.green = 1.
            set b.alpha = 1.
            set b.isFading = false
            set b.isCast = false
            return b
        endmethod
       
    endstruct

endlibrary
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
It doesn't work because BeamControl.onPeriod.exist checks the struct BeamControl (not the custom struct implementing the module) if it has a static method onPeriod. To debug/imagine it easier, just think of it as copying the contents of the module ti whatever struct you are implementing it to.

As for the solution, if you don't want a separate timer per instance, then you have to use trigger to make it work. Basically, there is a single loop that iterates all the instances and for it to run the "custom" function, you have to do a TriggerEvaluate. You cannot however make the onPeriod take a parameter (because it is a code tied to a trigger) but you could store that parameter instead to a struct attribute within the module and just refer to it when needed.

This is not your everyday simple question so I won't doubt if you did not fully understood what I mean here. Anyway, if you really want to, you could just study how others do it (e.g. Missile System or Buff System) and ask specific parts how it works.
 
Hmm, well I've been looking at Buff System and I've noticed you create a trigger with onApply.exitsts:

JASS:
        private static method onApplyInit takes nothing returns boolean
            call thistype(s__Buff_callback).onApply()
            return false
        endmethod
     
        private static method onRemoveInit takes nothing returns boolean
            call thistype(s__Buff_callback).onRemove()
            return false
        endmethod
     
        static method initialize takes nothing returns nothing
            static if thistype.onApply.exists then
                set s__Buff_onApply[thistype.typeid] = CreateTrigger()
                call TriggerAddCondition(s__Buff_onApply[thistype.typeid], function thistype.onApplyInit)
            endif
            static if thistype.onRemove.exists then
                set s__Buff_onRemove[thistype.typeid] = CreateTrigger()
                call TriggerAddCondition(s__Buff_onRemove[thistype.typeid], function thistype.onRemoveInit)
            endif
        endmethod

Then in method check takes unit source, unit target returns thistype there is this line call TriggerEvaluate(thistype.onApply[this.getType()])

I guess what I don't understand is what this.getType() is? It that some struct-specific function that retrieves an instance or smthng? I can't seem to find it declared anywhere else.

Additionally, the onApply trigger is a static. I've only ever seen this used in Table, and tbh idk what it does xd

When I look at the example code, the onApply and onRemove are both non-static, but when I do TriggerAddCondition, it looks for a static code. If the code is static, it won't know what the value of this is. I'm thinking of storing the value of the instance in a hashtable and just doing local Beam b = LoadInteger(Storage, GetHandleId of GetTriggeringTrigger(), etc), but I'm wondering if you did it some other way?

EDIT: So I just tested the above and it still has the exponential loop thing which crashes the game, even though the interval trigger is outside the module :/

JASS:
library Beam
   
    globals
   
        private constant real TIMEOUT = .03125
        private constant location LOC = Location(0., 0.)
       
        private timer IntervalTimer = CreateTimer()
        private hashtable InstanceStorage = InitHashtable()
        private integer BeamCount = 0
        private Beam array ActiveBeams
    endglobals
   
    function GetLocalUnitZ takes unit u returns real
        call MoveLocation(LOC, GetUnitX(u), GetUnitY(u))
        return GetUnitFlyHeight(u) + GetLocationZ(LOC)
    endfunction
   
   
   
    // Point getters
    function GetPointX takes Point point returns real
        return point.x
    endfunction
   
    function GetPointY takes Point point returns real
        return point.y
    endfunction
   
    function GetPointZ takes Point point returns real
        return point.z
    endfunction
   
    // Beam getters
    function GetBeamAngle takes Beam b returns real
        return Atan2(b.impact.y - b.launch.y, b.impact.x - b.launch.x)
    endfunction
   
    function GetBeamLength takes Beam b returns real
        return SquareRoot( (b.impact.x - b.launch.x)*(b.impact.x - b.launch.x) + (b.impact.y - b.launch.y)*(b.impact.y - b.launch.y) )
    endfunction
   
    function GetBeamInstance takes trigger trig returns integer
        return LoadInteger(InstanceStorage, GetHandleId(trig), 0)
    endfunction
   
    // Beam setters
    function SetBeamTime takes Beam b, real newTime returns nothing
        set b.beamTime = newTime
    endfunction
   
    function FadeBeam takes Beam b returns nothing
        set b.isFading = true
    endfunction
   
   
   
    private function RemoveInstance takes integer starterInt returns nothing
        local integer i = starterInt
        call ActiveBeams[starterInt].destroy()
        loop
            set ActiveBeams[i] = ActiveBeams[i + 1]
            set i = i + 1
            exitwhen i > BeamCount
        endloop
        set BeamCount = BeamCount - 1
    endfunction
       
    function Interval takes nothing returns nothing
           
        local integer i = 0
        local Beam b
       
        local real x
        local real y
        local real z
        local real a
       
        loop
           
            set i = i + 1
            set b = ActiveBeams[i]
            exitwhen i > BeamCount
           
            // runs every TIMEOUT seconds
            if b.onPeriodTrig != null then
                call TriggerEvaluate(b.onPeriodTrig)
            endif
           
            if b.source != null then
                if b.sourceFacing then
                    set a = b.sourceAngle + (GetUnitFacing(b.source) + 180.) * bj_DEGTORAD
                else
                    set a = b.sourceAngle
                endif
                set x = GetUnitX(b.source) + Cos(a) * b.sourceDistance
                set y = GetUnitY(b.source) + Sin(a) * b.sourceDistance
                set z = GetLocalUnitZ(b.source) + b.sourceZOffset
                call Point.update(b.launch, x, y, z)
            endif
           
            if b.target != null then
                if b.targetFacing then
                    set a = b.targetAngle + (GetUnitFacing(b.target) + 180.) * bj_DEGTORAD
                else
                    set a = b.targetAngle
                endif
                set x = GetUnitX(b.target) + Cos(a) * b.targetDistance
                set y = GetUnitY(b.target) + Sin(a) * b.targetDistance
                set z = GetLocalUnitZ(b.target) + b.targetZOffset
                call Point.update(b.launch, x, y, z)
            endif
           
            if Point.hasMoved(b.launch) or Point.hasMoved(b.impact) then
                call MoveLightningEx(b.beam, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
            endif
           
            // if not in the fade phase
            if not b.isFading then
                if b.beamTime != 0 then // if zero, then beam lasts forever.
                    set b.beamTime = b.beamTime - TIMEOUT
                    if b.beamTime <= 0. then
                        set b.isFading = true
                    endif
                endif
            // if in the fade phase
            else
                set b.fadeTime = b.fadeTime - TIMEOUT
                if b.fadeTime <= 0. then
                    call RemoveInstance(i)
                endif
            endif
           
        endloop
       
        if BeamCount <= 0 then
            call PauseTimer(IntervalTimer)
        endif
       
    endfunction
   
    module BeamControl
       
        static method cast takes Beam b returns nothing
           
            local real xSource
            local real ySource
            local real xTarget
            local real yTarget
            local real x
            local real y
            local real z
           
            if not b.isCast then
           
                set b.isCast = true
               
                if b.source != null then
                    // if you have a source, calculate the angle and distance between the launch coordinates and the source.
                    // these values will then recalculate every interval to determine if the Point has moved or not.
                    set xSource = GetUnitX(b.source)
                    set ySource = GetUnitY(b.source)
                    set b.sourceAngle = Atan2(b.yLaunch - ySource, b.xLaunch - xSource)
                    set b.sourceDistance = SquareRoot( (b.xLaunch - xSource)*(b.xLaunch - xSource) + (b.yLaunch - ySource)*(b.yLaunch - ySource) )
                    set b.sourceZOffset = b.zLaunch - GetLocalUnitZ(b.source)
                endif
                set b.launch = Point.create(b.xLaunch, b.yLaunch, b.zLaunch)
               
                if b.target != null then
                    // same as the above, but for targets
                    set xTarget = GetUnitX(b.target)
                    set yTarget = GetUnitY(b.target)
                    set b.targetAngle = Atan2(b.yImpact - yTarget, b.xImpact - xTarget)
                    set b.targetDistance = SquareRoot( (b.xImpact - xTarget)*(b.xImpact - xTarget) + (b.yImpact - yTarget)*(b.yImpact - yTarget) )
                    set b.targetZOffset = b.zImpact - GetLocalUnitZ(b.target)
                endif
                set b.impact = Point.create(b.xImpact, b.yImpact, b.zImpact)
               
                set b.beam = AddLightningEx(b.beamFX, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
                if b.beamTime < 0. then
                    set b.isFading = true
                else
                    set b.isFading = false
                endif
               
                set BeamCount = BeamCount + 1
                set ActiveBeams[BeamCount] = b
                if BeamCount == 1 then //if BeamCount is equal to 1 then that means it was previously zero, which means the timer was inactive.
                    call TimerStart(IntervalTimer, TIMEOUT, true, function Interval)
                endif
               
                static if thistype.onPeriod.exists then
                    set b.onPeriodTrig = CreateTrigger()
                    call TriggerAddCondition(b.onPeriodTrig, Condition(function thistype.onPeriod))
                    call SaveInteger(InstanceStorage, GetHandleId(b.onPeriodTrig), 0, b)
                endif
                static if thistype.onEnd.exists then
                   
                endif
            endif
           
        endmethod
       
    endmodule
   
    struct Point
       
        readonly real x
        readonly real y
        readonly real z
        private real xOld
        private real yOld
        private real zOld
       
        static method remove takes thistype point returns nothing
            set point.x = 0.
            set point.y = 0.
            set point.z = 0.
            set point.xOld = 0.
            set point.yOld = 0.
            set point.zOld = 0.
            call point.deallocate()
        endmethod
       
        static method hasMoved takes thistype point returns boolean
            if point.x != point.xOld or point.y != point.yOld or point.z != point.zOld then
                return true
            else
                return false
            endif
        endmethod
       
        static method update takes thistype point, real X, real Y, real Z returns nothing
            set point.xOld = point.x
            set point.yOld = point.y
            set point.zOld = point.z
            set point.x = X
            set point.y = Y
            set point.z = Z
        endmethod
       
        static method create takes real X, real Y, real Z returns thistype
            local thistype point = allocate()
            set point.x = X
            set point.y = Y
            set point.z = Z
            set point.xOld = X
            set point.yOld = Y
            set point.zOld = Z
            return point
        endmethod
       
    endstruct
   
    struct Beam
       
        unit source
        unit target
       
        // these reals will be used to recalculate the new coordiates of Points should the source or target move
        real sourceAngle
        real sourceDistance
        real sourceZOffset
        real targetAngle
        real targetDistance
        real targetZOffset
       
        // these booleans will move the point around the source/target with relation to their facing angle
        boolean sourceFacing
        boolean targetFacing
       
        // these values will either be the launch and impact coordinates, or offset the source and 
        // target location by these values. Refer to the demo for a exmaple of how to use those as offsets.
        real xLaunch
        real yLaunch
        real zLaunch
        real xImpact
        real yImpact
        real zImpact
       
        real beamTime
        real fadeTime
       
        // these variables store the launch and impact Points of the beam instance.
        Point impact
        Point launch
       
        lightning beam
        string beamFX
        real red
        real blue
        real green
        real alpha
       
        boolean isFading
        boolean isCast
       
        trigger onPeriodTrig
        trigger onEndTrig
       
        method destroy takes nothing returns nothing
            if this.beam != null then
                call DestroyLightning(this.beam)
                set this.beam = null
            endif
            set this.source = null
            set this.target = null
            set this.isCast = false
            if this.onPeriodTrig != null then
                call FlushChildHashtable(InstanceStorage, GetHandleId(this.onPeriodTrig))
                call DestroyTrigger(this.onPeriodTrig)
                set this.onPeriodTrig = null
            endif
            call this.deallocate()
        endmethod
       
        static method create takes nothing returns thistype
            local thistype b = allocate()
            //initialise all variables to their default value
            set b.source = null
            set b.target = null
            set b.sourceAngle = 0.
            set b.sourceDistance = 0.
            set b.sourceZOffset = 0.
            set b.targetAngle = 0.
            set b.targetDistance = 0.
            set b.targetZOffset = 0.
            set b.sourceFacing = false
            set b.targetFacing = false
            set b.xLaunch = 0.
            set b.yLaunch = 0.
            set b.zLaunch = 0.
            set b.xImpact = 0.
            set b.yImpact = 0.
            set b.zImpact = 0.
            set b.beamTime = 0.
            set b.fadeTime = 0.
            set b.impact = 0
            set b.launch = 0
            set b.beam = null
            set b.beamFX = null
            set b.red = 1.
            set b.blue = 1.
            set b.green = 1.
            set b.alpha = 1.
            set b.isFading = false
            set b.isCast = false
            return b
        endmethod
       
    endstruct

endlibrary
 
Last edited:
Level 8
Joined
Oct 4, 2016
Messages
208
JASS:
library Beam
 
    globals
 
        private constant real TIMEOUT = .03125
        private constant location LOC = Location(0., 0.)
   
        private timer IntervalTimer = CreateTimer()
        private trigger array EventTrigger
        private integer EventOption
        private integer EventInstance
        private integer BeamCount = 0
        private Beam array ActiveBeams
    endglobals
 
    function GetLocalUnitZ takes unit u returns real
        call MoveLocation(LOC, GetUnitX(u), GetUnitY(u))
        return GetUnitFlyHeight(u) + GetLocationZ(LOC)
    endfunction
 
    // Point getters
    function GetPointX takes Point point returns real
        return point.x
    endfunction
 
    function GetPointY takes Point point returns real
        return point.y
    endfunction
 
    function GetPointZ takes Point point returns real
        return point.z
    endfunction
 
    // Beam getters
    function GetBeamAngle takes Beam b returns real
        return Atan2(b.impact.y - b.launch.y, b.impact.x - b.launch.x)
    endfunction
 
    function GetBeamLength takes Beam b returns real
        return SquareRoot( (b.impact.x - b.launch.x)*(b.impact.x - b.launch.x) + (b.impact.y - b.launch.y)*(b.impact.y - b.launch.y) )
    endfunction
 
    // Beam setters
    function SetBeamTime takes Beam b, real newTime returns nothing
        set b.beamTime = newTime
    endfunction
 
    function FadeBeam takes Beam b returns nothing
        set b.isFading = true
    endfunction
 
    private function RemoveInstance takes integer starterInt returns nothing
        local integer i = starterInt
        call ActiveBeams[starterInt].destroy()
        loop
            set ActiveBeams[i] = ActiveBeams[i + 1]
            set i = i + 1
            exitwhen i > BeamCount
        endloop
        set BeamCount = BeamCount - 1
    endfunction
   
    function Interval takes nothing returns nothing
       
        local integer i = 0
        local Beam b
   
        local real x
        local real y
        local real z
        local real a
   
        loop
       
            set i = i + 1
            set b = ActiveBeams[i]
            exitwhen i > BeamCount
       
            // runs every TIMEOUT seconds
            set EventOption = 1
            set EventInstance = b
            call TriggerEvaluate(EventTrigger[b.id])
       
            if b.source != null then
                if b.sourceFacing then
                    set a = b.sourceAngle + (GetUnitFacing(b.source) + 180.) * bj_DEGTORAD
                else
                    set a = b.sourceAngle
                endif
                set x = GetUnitX(b.source) + Cos(a) * b.sourceDistance
                set y = GetUnitY(b.source) + Sin(a) * b.sourceDistance
                set z = GetLocalUnitZ(b.source) + b.sourceZOffset
                call Point.update(b.launch, x, y, z)
            endif
       
            if b.target != null then
                if b.targetFacing then
                    set a = b.targetAngle + (GetUnitFacing(b.target) + 180.) * bj_DEGTORAD
                else
                    set a = b.targetAngle
                endif
                set x = GetUnitX(b.target) + Cos(a) * b.targetDistance
                set y = GetUnitY(b.target) + Sin(a) * b.targetDistance
                set z = GetLocalUnitZ(b.target) + b.targetZOffset
                call Point.update(b.launch, x, y, z)
            endif
       
            if Point.hasMoved(b.launch) or Point.hasMoved(b.impact) then
                call MoveLightningEx(b.beam, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
            endif
       
            // if not in the fade phase
            if not b.isFading then
                if b.beamTime != 0 then // if zero, then beam lasts forever.
                    set b.beamTime = b.beamTime - TIMEOUT
                    if b.beamTime <= 0. then
                        set b.isFading = true
                    endif
                endif
            // if in the fade phase
            else
                set b.fadeTime = b.fadeTime - TIMEOUT
                if b.fadeTime <= 0. then
                    set EventOption = 2
                    set EventInstance = b
                    call TriggerEvaluate(EventTrigger[b.id])
                    call RemoveInstance(i)
                endif
            endif
       
        endloop
   
        if BeamCount <= 0 then
            call PauseTimer(IntervalTimer)
        endif
   
    endfunction
 
    module BeamControl
   
        static method cast takes Beam b returns nothing
       
            local real xSource
            local real ySource
            local real xTarget
            local real yTarget
            local real x
            local real y
            local real z
       
            if not b.isCast then
       
                set b.isCast = true
           
                if b.source != null then
                    // if you have a source, calculate the angle and distance between the launch coordinates and the source.
                    // these values will then recalculate every interval to determine if the Point has moved or not.
                    set xSource = GetUnitX(b.source)
                    set ySource = GetUnitY(b.source)
                    set b.sourceAngle = Atan2(b.yLaunch - ySource, b.xLaunch - xSource)
                    set b.sourceDistance = SquareRoot( (b.xLaunch - xSource)*(b.xLaunch - xSource) + (b.yLaunch - ySource)*(b.yLaunch - ySource) )
                    set b.sourceZOffset = b.zLaunch - GetLocalUnitZ(b.source)
                endif
                set b.launch = Point.create(b.xLaunch, b.yLaunch, b.zLaunch)
           
                if b.target != null then
                    // same as the above, but for targets
                    set xTarget = GetUnitX(b.target)
                    set yTarget = GetUnitY(b.target)
                    set b.targetAngle = Atan2(b.yImpact - yTarget, b.xImpact - xTarget)
                    set b.targetDistance = SquareRoot( (b.xImpact - xTarget)*(b.xImpact - xTarget) + (b.yImpact - yTarget)*(b.yImpact - yTarget) )
                    set b.targetZOffset = b.zImpact - GetLocalUnitZ(b.target)
                endif
                set b.impact = Point.create(b.xImpact, b.yImpact, b.zImpact)
           
                set b.beam = AddLightningEx(b.beamFX, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
                if b.beamTime < 0. then
                    set b.isFading = true
                else
                    set b.isFading = false
                endif
           
                set b.id = thistype.typeid
                set BeamCount = BeamCount + 1
                set ActiveBeams[BeamCount] = b
                if BeamCount == 1 then //if BeamCount is equal to 1 then that means it was previously zero, which means the timer was inactive.
                    call TimerStart(IntervalTimer, TIMEOUT, true, function Interval)
                endif
            endif
       
        endmethod
   
        public static method actions takes nothing returns nothing
            local Beam b = EventInstance
            if EventOption == 1 then
                static if thistype.onPeriod.exists then
                    call thistype.onPeriod(b)
                endif
            elseif EventOption == 2 then
                static if thistype.onEnd.exists then
                    call thistype.onEnd(b)
                endif
            endif
        endmethod
   
        private static method onInit takes nothing returns nothing
            set EventTrigger[thistype.typeid] = CreateTrigger()
            call TriggerAddCondition(EventTrigger[thistype.typeid],Condition(function thistype.actions))
        endmethod
    endmodule
 
    struct Point
   
        readonly real x
        readonly real y
        readonly real z
        private real xOld
        private real yOld
        private real zOld
   
        static method remove takes thistype point returns nothing
            set point.x = 0.
            set point.y = 0.
            set point.z = 0.
            set point.xOld = 0.
            set point.yOld = 0.
            set point.zOld = 0.
            call point.deallocate()
        endmethod
   
        static method hasMoved takes thistype point returns boolean
            if point.x != point.xOld or point.y != point.yOld or point.z != point.zOld then
                return true
            else
                return false
            endif
        endmethod
   
        static method update takes thistype point, real X, real Y, real Z returns nothing
            set point.xOld = point.x
            set point.yOld = point.y
            set point.zOld = point.z
            set point.x = X
            set point.y = Y
            set point.z = Z
        endmethod
   
        static method create takes real X, real Y, real Z returns thistype
            local thistype point = allocate()
            set point.x = X
            set point.y = Y
            set point.z = Z
            set point.xOld = X
            set point.yOld = Y
            set point.zOld = Z
            return point
        endmethod
   
    endstruct
 
    struct Beam
   
        unit source
        unit target
   
        // these reals will be used to recalculate the new coordiates of Points should the source or target move
        real sourceAngle
        real sourceDistance
        real sourceZOffset
        real targetAngle
        real targetDistance
        real targetZOffset
   
        // these booleans will move the point around the source/target with relation to their facing angle
        boolean sourceFacing
        boolean targetFacing
   
        // these values will either be the launch and impact coordinates, or offset the source and
        // target location by these values. Refer to the demo for a exmaple of how to use those as offsets.
        real xLaunch
        real yLaunch
        real zLaunch
        real xImpact
        real yImpact
        real zImpact
   
        real beamTime
        real fadeTime
   
        // these variables store the launch and impact Points of the beam instance.
        Point impact
        Point launch
   
        lightning beam
        string beamFX
        real red
        real blue
        real green
        real alpha
   
        boolean isFading
        boolean isCast
   
        trigger onPeriodTrig
        trigger onEndTrig
   
        integer id
   
        method destroy takes nothing returns nothing
            if this.beam != null then
                call DestroyLightning(this.beam)
                set this.beam = null
            endif
            set this.source = null
            set this.target = null
            set this.isCast = false
            if this.onPeriodTrig != null then
                call DestroyTrigger(this.onPeriodTrig)
                set this.onPeriodTrig = null
            endif
            call this.deallocate()
        endmethod
   
        static method create takes nothing returns thistype
            local thistype b = allocate()
            //initialise all variables to their default value
            set b.source = null
            set b.target = null
            set b.sourceAngle = 0.
            set b.sourceDistance = 0.
            set b.sourceZOffset = 0.
            set b.targetAngle = 0.
            set b.targetDistance = 0.
            set b.targetZOffset = 0.
            set b.sourceFacing = false
            set b.targetFacing = false
            set b.xLaunch = 0.
            set b.yLaunch = 0.
            set b.zLaunch = 0.
            set b.xImpact = 0.
            set b.yImpact = 0.
            set b.zImpact = 0.
            set b.beamTime = 0.
            set b.fadeTime = 0.
            set b.impact = 0
            set b.launch = 0
            set b.beam = null
            set b.beamFX = null
            set b.red = 1.
            set b.blue = 1.
            set b.green = 1.
            set b.alpha = 1.
            set b.isFading = false
            set b.isCast = false
            set b.id = 0
            return b
        endmethod
   
    endstruct
endlibrary

don't need a hashtable, simple use a trigger array and attach a special method that calls methods in the module (view method actions).
It works as an event with event responses, each struct that implements ControlBeam will create its trigger in its onInit method taking as array id its struct ID.
In Beam struct i add a index variable called id, this variable works to easily find the trigger belonging to a Beam.
since the Interval method is outside the module, you will need

getType() is a method created in every Struct that contains a unique id that represent the struct. Read this for more information.
https://www.hiveworkshop.com/threads/vjass-meet-vjass-extending-structs
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
Hmm, well I've been looking at Buff System and I've noticed you create a trigger with onApply.exitsts:
JASS:
        private static method onApplyInit takes nothing returns boolean
            call thistype(s__Buff_callback).onApply()
            return false
        endmethod
   
        private static method onRemoveInit takes nothing returns boolean
            call thistype(s__Buff_callback).onRemove()
            return false
        endmethod
   
        static method initialize takes nothing returns nothing
            static if thistype.onApply.exists then
                set s__Buff_onApply[thistype.typeid] = CreateTrigger()
                call TriggerAddCondition(s__Buff_onApply[thistype.typeid], function thistype.onApplyInit)
            endif
            static if thistype.onRemove.exists then
                set s__Buff_onRemove[thistype.typeid] = CreateTrigger()
                call TriggerAddCondition(s__Buff_onRemove[thistype.typeid], function thistype.onRemoveInit)
            endif
        endmethod
Then in method check takes unit source, unit target returns thistype there is this line call TriggerEvaluate(thistype.onApply[this.getType()])
Creating a trigger allows the call to method onApply anywhere. As you can see, calling TriggerEvaluate(thistype.onApply[this.getType()]) will call the trigger causing private static method onApplyInit takes nothing returns boolean to run which then calls method onApply. Also notice that I did not put the core functionality inside the module (I am referring to method check) because that would mean that each struct implementing the module will have a copy of that very lengthy function. What I did is I call the function inside the module so we will end up with only 1 copy of static method check.
I guess what I don't understand is what this.getType() is? It that some struct-specific function that retrieves an instance or smthng? I can't seem to find it declared anywhere else.
this.getType() returns a unique id of a struct extending another struct. Example, struct S1 extends Buff assigns a unique id to S1. If we have another struct S2 extends Buff, S2 also gets assigned a unique id different to S1. We can therefore use this.getType() (reference to an actual instance) or S1.typeid (reference to struct name) to differentiate between different structs extending from a parent struct.
Additionally, the onApply trigger is a static. I've only ever seen this used in Table, and tbh idk what it does xd
It is a static array though. The index of the array is the unique id of the struct extending Buff. Therefore each struct extending Buff will have a single trigger to allow the call to onApply everywhere.
When I look at the example code, the onApply and onRemove are both non-static, but when I do TriggerAddCondition, it looks for a static code. If the code is static, it won't know what the value of this is. I'm thinking of storing the value of the instance in a hashtable and just doing local Beam b = LoadInteger(Storage, GetHandleId of GetTriggeringTrigger(), etc), but I'm wondering if you did it some other way?
They are both should be non-static for the user to refer to the actual instance this inside method onApply. TriggerAddCondition needs a static code so what I did is I call the non-static method inside the static method.
JASS:
    private static method onApplyInit takes nothing returns boolean //The static method tied to the trigger
        call thistype(s__Buff_callback).onApply()                   //The non-static method
        return false
    endmethod
Also, if you wanted to go for one timer per instance, this is how you should do it (I don't have an editor at the moment)
JASS:
struct S1
    private static method onPeriod takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        //Refer to your beam via this.beam
    endmethod
    implement BeamControl
endstruct
//Your module
module BeamControl
    readonly Beam beam
    private timer t
    //Your other stuffs
    //...
    //Your destructor
    //...
        //...
        call ReleaseTimer(this.t)
    //...
    //Your constructor
    //...
        //...
        set this.beam = <YourBeam>
        //...
        static if thistype.onPeriod.exist
            set this.t = NewTimerEx(this)
            call TimerStart(this.t, TIMEOUT, false, function thistype.onPeriod)
        endif
    //...
endmodule
If you wanted 1 timer for all instance, create a single iterator function that picks all current instance and call each of their onPeriod each via TriggerEvaluate which I think won't save you some performance and may even be slower due to TriggerEvaluations.
 
Hmm, but see, how would I call thistype.onEnd or onRemove or w/e if I don't have a loop inside the module? I'm tempted to go with -Manuel-'s suggestion but since you told me TriggerEvaluations are slow, I'm already unwilling to use them now lol. Missile has a loop inside of MissileActions, which seems to be used to see when the missile instance should end, but I'm not too sure. Furthermore, with Missile you can have a static method onPeriod takes Missile m returns boolean and I guess I wanted to make something like that without having the user retrieve the instance manually with something like GetTimerData().

TBH I'm still kinda flabbergasted why the onPeriod seems to duplicate itself endlessly when I run it one way or another. I'm gonna try running one timer per instance and see if it still crashes my wc3.

EDIT: So loop inside the module still kills my wc3 xD
@-Manuel- so I tried your way and it fires onPeriod alright, but my existing bug of endlessly-repeating loops is still present :(

I've uploaded the map itself so if anyone cares to bug-test this in-editor that would be tremendously helpful.


JASS:
library Beam
 
    globals
 
        private constant real TIMEOUT = .03125
        private constant location LOC = Location(0., 0.)
    
        private timer IntervalTimer = CreateTimer()
        private hashtable InstanceStorage = InitHashtable()
        private trigger array EventTrigger
        private integer EventOption
        private integer EventInstance
        private integer BeamCount = 0
        private Beam array ActiveBeams
    endglobals
 
    function GetLocalUnitZ takes unit u returns real
        call MoveLocation(LOC, GetUnitX(u), GetUnitY(u))
        return GetUnitFlyHeight(u) + GetLocationZ(LOC)
    endfunction
 
 
 
    // Point getters
    function GetPointX takes Point point returns real
        return point.x
    endfunction
 
    function GetPointY takes Point point returns real
        return point.y
    endfunction
 
    function GetPointZ takes Point point returns real
        return point.z
    endfunction
 
    // Beam getters
    function GetBeamAngle takes Beam b returns real
        return Atan2(b.impact.y - b.launch.y, b.impact.x - b.launch.x)
    endfunction
 
    function GetBeamLength takes Beam b returns real
        return SquareRoot( (b.impact.x - b.launch.x)*(b.impact.x - b.launch.x) + (b.impact.y - b.launch.y)*(b.impact.y - b.launch.y) )
    endfunction
 
    function GetBeamInstance takes trigger trig returns integer
        return LoadInteger(InstanceStorage, GetHandleId(trig), 0)
    endfunction
 
    // Beam setters
    function SetBeamTime takes Beam b, real newTime returns nothing
        set b.beamTime = newTime
    endfunction
 
    function FadeBeam takes Beam b returns nothing
        set b.isFading = true
    endfunction
 
 
 
    private function RemoveInstance takes integer starterInt returns nothing
        local integer i = starterInt
        call ActiveBeams[starterInt].destroy()
        loop
            set ActiveBeams[i] = ActiveBeams[i + 1]
            set i = i + 1
            exitwhen i > BeamCount
        endloop
        set BeamCount = BeamCount - 1
    endfunction
    
    function Interval takes nothing returns nothing
        
        local integer i = 0
        local Beam b
    
        local real x
        local real y
        local real z
        local real a
    
        loop
        
            set i = i + 1
            set b = ActiveBeams[i]
            exitwhen i > BeamCount
        
            // runs every TIMEOUT seconds
            set EventOption = 1
            set EventInstance = b
            call TriggerEvaluate(EventTrigger[b.id])
        
            if b.source != null then
                if b.sourceFacing then
                    set a = b.sourceAngle + (GetUnitFacing(b.source) + 180.) * bj_DEGTORAD
                else
                    set a = b.sourceAngle
                endif
                set x = GetUnitX(b.source) + Cos(a) * b.sourceDistance
                set y = GetUnitY(b.source) + Sin(a) * b.sourceDistance
                set z = GetLocalUnitZ(b.source) + b.sourceZOffset
                call Point.update(b.launch, x, y, z)
            endif
        
            if b.target != null then
                if b.targetFacing then
                    set a = b.targetAngle + (GetUnitFacing(b.target) + 180.) * bj_DEGTORAD
                else
                    set a = b.targetAngle
                endif
                set x = GetUnitX(b.target) + Cos(a) * b.targetDistance
                set y = GetUnitY(b.target) + Sin(a) * b.targetDistance
                set z = GetLocalUnitZ(b.target) + b.targetZOffset
                call Point.update(b.launch, x, y, z)
            endif
        
            if Point.hasMoved(b.launch) or Point.hasMoved(b.impact) then
                call MoveLightningEx(b.beam, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
            endif
        
            // if not in the fade phase
            if not b.isFading then
                if b.beamTime != 0 then // if zero, then beam lasts forever.
                    set b.beamTime = b.beamTime - TIMEOUT
                    if b.beamTime <= 0. then
                        set b.isFading = true
                    endif
                endif
            // if in the fade phase
            else
                set b.fadeTime = b.fadeTime - TIMEOUT
                if b.fadeTime <= 0. then
                    set EventOption = 2
                    set EventInstance = b
                    call TriggerEvaluate(EventTrigger[b.id])
                    call RemoveInstance(i)
                    set i = i - 1
                endif
            endif
        
        endloop
    
        if BeamCount <= 0 then
            call PauseTimer(IntervalTimer)
        endif
    
    endfunction
 
    module BeamControl
    
        static method cast takes Beam b returns nothing
        
            local real xSource
            local real ySource
            local real xTarget
            local real yTarget
            local real x
            local real y
            local real z
        
            if not b.isCast then
        
                set b.isCast = true
            
                if b.source != null then
                    // if you have a source, calculate the angle and distance between the launch coordinates and the source.
                    // these values will then recalculate every interval to determine if the Point has moved or not.
                    set xSource = GetUnitX(b.source)
                    set ySource = GetUnitY(b.source)
                    set b.sourceAngle = Atan2(b.yLaunch - ySource, b.xLaunch - xSource)
                    set b.sourceDistance = SquareRoot( (b.xLaunch - xSource)*(b.xLaunch - xSource) + (b.yLaunch - ySource)*(b.yLaunch - ySource) )
                    set b.sourceZOffset = b.zLaunch - GetLocalUnitZ(b.source)
                endif
                set b.launch = Point.create(b.xLaunch, b.yLaunch, b.zLaunch)
            
                if b.target != null then
                    // same as the above, but for targets
                    set xTarget = GetUnitX(b.target)
                    set yTarget = GetUnitY(b.target)
                    set b.targetAngle = Atan2(b.yImpact - yTarget, b.xImpact - xTarget)
                    set b.targetDistance = SquareRoot( (b.xImpact - xTarget)*(b.xImpact - xTarget) + (b.yImpact - yTarget)*(b.yImpact - yTarget) )
                    set b.targetZOffset = b.zImpact - GetLocalUnitZ(b.target)
                endif
                set b.impact = Point.create(b.xImpact, b.yImpact, b.zImpact)
            
                set b.beam = AddLightningEx(b.beamFX, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
                if b.beamTime < 0. then
                    set b.isFading = true
                else
                    set b.isFading = false
                endif
            
                set b.id = thistype.typeid
                set BeamCount = BeamCount + 1
                set ActiveBeams[BeamCount] = b
                if BeamCount == 1 then //if BeamCount is equal to 1 then that means it was previously zero, which means the timer was inactive.
                    call TimerStart(IntervalTimer, TIMEOUT, true, function Interval)
                endif
            endif
        
        endmethod
    
        private static method actions takes nothing returns nothing
            local Beam b = EventInstance
            if EventOption == 1 then
                static if thistype.onPeriod.exists then
                    call thistype.onPeriod(b)
                endif
            elseif EventOption == 2 then
                static if thistype.onEnd.exists then
                    call thistype.onEnd(b)
                endif
            endif
        endmethod
 
        private static method onInit takes nothing returns nothing
            set EventTrigger[thistype.typeid] = CreateTrigger()
            call TriggerAddCondition(EventTrigger[thistype.typeid], Condition(function thistype.actions))
        endmethod
    
    endmodule
 
    struct Point
    
        readonly real x
        readonly real y
        readonly real z
        private real xOld
        private real yOld
        private real zOld
    
        static method remove takes thistype point returns nothing
            set point.x = 0.
            set point.y = 0.
            set point.z = 0.
            set point.xOld = 0.
            set point.yOld = 0.
            set point.zOld = 0.
            call point.deallocate()
        endmethod
    
        static method hasMoved takes thistype point returns boolean
            if point.x != point.xOld or point.y != point.yOld or point.z != point.zOld then
                return true
            else
                return false
            endif
        endmethod
    
        static method update takes thistype point, real X, real Y, real Z returns nothing
            set point.xOld = point.x
            set point.yOld = point.y
            set point.zOld = point.z
            set point.x = X
            set point.y = Y
            set point.z = Z
        endmethod
    
        static method create takes real X, real Y, real Z returns thistype
            local thistype point = allocate()
            set point.x = X
            set point.y = Y
            set point.z = Z
            set point.xOld = X
            set point.yOld = Y
            set point.zOld = Z
            return point
        endmethod
    
    endstruct
 
    struct Beam
    
        unit source
        unit target
    
        // these reals will be used to recalculate the new coordiates of Points should the source or target move
        real sourceAngle
        real sourceDistance
        real sourceZOffset
        real targetAngle
        real targetDistance
        real targetZOffset
    
        // these booleans will move the point around the source/target with relation to their facing angle
        boolean sourceFacing
        boolean targetFacing
    
        // these values will either be the launch and impact coordinates, or offset the source and
        // target location by these values. Refer to the demo for a exmaple of how to use those as offsets.
        real xLaunch
        real yLaunch
        real zLaunch
        real xImpact
        real yImpact
        real zImpact
    
        real beamTime
        real fadeTime
    
        // these variables store the launch and impact Points of the beam instance.
        Point impact
        Point launch
    
        lightning beam
        string beamFX
        real red
        real blue
        real green
        real alpha
    
        boolean isFading
        boolean isCast
    
        integer id
    
        method destroy takes nothing returns nothing
            if this.beam != null then
                call DestroyLightning(this.beam)
                set this.beam = null
            endif
            set this.source = null
            set this.target = null
            set this.isCast = false
            call this.deallocate()
        endmethod
    
        static method create takes nothing returns thistype
            local thistype b = allocate()
            //initialise all variables to their default value
            set b.source = null
            set b.target = null
            set b.sourceAngle = 0.
            set b.sourceDistance = 0.
            set b.sourceZOffset = 0.
            set b.targetAngle = 0.
            set b.targetDistance = 0.
            set b.targetZOffset = 0.
            set b.sourceFacing = false
            set b.targetFacing = false
            set b.xLaunch = 0.
            set b.yLaunch = 0.
            set b.zLaunch = 0.
            set b.xImpact = 0.
            set b.yImpact = 0.
            set b.zImpact = 0.
            set b.beamTime = 0.
            set b.fadeTime = 0.
            set b.impact = 0
            set b.launch = 0
            set b.beam = null
            set b.beamFX = null
            set b.red = 1.
            set b.blue = 1.
            set b.green = 1.
            set b.alpha = 1.
            set b.isFading = false
            set b.isCast = false
            set b.id = 0
            return b
        endmethod
    
    endstruct

endlibrary

EDIT 2: OKAY WOW I'M A DUMBASS. There was no exponential loop. It was crashing bcz the lightning effect was going out of bounds in the demo. I can't believe this it what was happening @_@

How do I stop a Point from going out of bounds now? T_T
 

Attachments

  • Beam v1.00.w3x
    53.8 KB · Views: 52
Last edited:
Level 8
Joined
Oct 4, 2016
Messages
208
This code could help you, to verify if a point is out of map bounds.

World Bounds v1.3

Also you can create a boolean variable in Beam for every custom method that check if onPeriod or onRemove exists, to avoid unnecessary trigger evaluations if a event method not exists

If you want custom members in the custom Beam struct, simply use the Beam instance in struct array.
 
The problem seemed generic at first. So, your problem is with the usage of modules? As said beforehand, modules are just macros, only without parameters.

Modules are somewhat behaviorally different from textmacros, but I will not elaborate further on this.

JASS:
module SomeMod
    ...
    
    static method foor takes nothing returns nothing
        local thistype this = allocate()

        static if thistype.ryder_fool.exists then
            call ryder_fool()
        endif
    endmethod
endmodule

struct SomeModder
    implement SomeMod
endstruct

As can be seen above, module SomeMod has a static method foor. Notice the keyword thistype. This means that it will refer to the individual struct either implementing it or the struct itself.

As for thistype.ryder_fool.exists, it checks if a method with the same name exists. If not, the following block of code will be ignored.
Originally, thistype was to be exclusively used by modules (convention) but with the flashy highlighter, who could resist? As a result, the usage of thistype instead of the proper struct name became prevalent.

Do note, that this might turn out problematic for you

JASS:
struct SomeModder
    implement SomeMod
endstruct

module SomeMod
    ...
   
    static method foor takes nothing returns nothing
        local thistype this = allocate()

        static if thistype.ryder_fool.exists then
            call ryder_fool()
        endif
    endmethod
endmodule

To resolve that, you can use keyword


JASS:
keyword SomeMod

struct SomeModder
    implement SomeMod
endstruct

module SomeMod
    ...
   
    static method foor takes nothing returns nothing
        local thistype this = allocate()

        static if thistype.ryder_fool.exists then
            call ryder_fool()
        endif
    endmethod
endmodule

As of the standing right now, I do not really know enough on the nature of the problem to explain well what the problem really is.
 
EDIT: NEVERMIND, I made another dumb mistake again.
Okay, so the system is sorta working. I made 3 demo spells with it and 2 of them are working without a hitch, except for the third one, 'Bola'. I tried to make a Bola-type spell, but for some reason sometimes it casts and sometimes it doesn't. There doesn't appear to be anything wrong with it, so I'm wondering whether someone here might be able to see something I'm missing. I've attached the demo map in case you want to see the bug for yourself. Cast Bola with a Shaman repeatedly to test. The numbers that print on screen are just there to indicate that the onPeriod method in the Bola scope is indeed firing, even if the lightning effect or special effect trail are not visible.

PS: I'm hoping the issue isn't because of the loop is inside the Module. I'm unwilling to have TriggerEvalaute fire all the time.

JASS:
library Beam requires TimerUtils
  
    /*
  
        Beam v1.00
        by Spellbound
      
      
        DESCRIPTION_________________________________
      
        Beam is a lightning effect handler that uses a moving-point system to track where the beam
        goes. It also has a hit-box feature that allows you to do unit-to-unit lightning effects
        that strike coordinates offset from the core of the model, which gives the appearance that
        the beam is hitting the surface of the model.
      
        Similar to libraries like Missile and Buff System, every lightning effect generated by Beam
        has its own code that it can run, allowing to tell the system what specific beam does when
        it is cast, when and interval occures, when it begins to fade, or when it ends.
      
      
        INSTALLATION________________________________
      
        Beam requires TimerUtils. Get it from this map or at this link:
        http://www.wc3c.net/showthread.php?t=101322
      
        Then copy the system itself in your map and you're good to go.
      
      
        USAGE_______________________________________
      
        Beam requires a basic understanding or structs, but even if you don't have that knowledge,
        don't let that scare you! It's actually quite simple once you wrap your head around it. If
        you're already familiar with structs, you can move on to the API section. Otherwise, follow
        this link for a simple tutorial:
        [link]
      
    */
    //! novjass
      
        API_________________________________________
      
        Beam.create()
            //This will generate a beam instance. It doesn't do antyhing else. You must configure its
            //various parameters individually.
          
            Eg:
          
            local Beam b = Beam.create()
          
            set b.source = GetTriggerUnit()
            set b.beamFX = "CLPB"
          
            etc...
          
        [StructName].cast(your beam)
            //This is called after all necessary parameters have been set. It will create your beam.
      
    //! endnovjass
      
  
    globals
        public constant real TIMEOUT = .03125
        private constant location LOC = Location(0., 0.)
    endglobals
  
    public function GetLocZ takes real x, real y returns real
        call MoveLocation(LOC, x, y)
        return GetLocationZ(LOC)
    endfunction
  
    function GetLocalUnitZ takes unit u returns real
        call MoveLocation(LOC, GetUnitX(u), GetUnitY(u))
        return GetUnitFlyHeight(u) + GetLocationZ(LOC)
    endfunction
  
  
    // Beam getters
    function GetBeamAngle takes Beam b returns real
        return Atan2(b.targetpoint.y - b.sourcepoint.y, b.targetpoint.x - b.sourcepoint.x)
    endfunction
  
    function GetBeamLength takes Beam b returns real
        return SquareRoot( (b.targetpoint.x - b.sourcepoint.x)*(b.targetpoint.x - b.sourcepoint.x) + (b.targetpoint.y - b.sourcepoint.y)*(b.targetpoint.y - b.sourcepoint.y) )
    endfunction

    function GetBeamAngleOrigin takes Beam b returns real
        return Atan2(b.targetpointOrigin.y - b.sourcepointOrigin.y, b.targetpointOrigin.x - b.sourcepointOrigin.x)
    endfunction
  
    function GetBeamLengthOrigin takes Beam b returns real
        return SquareRoot( (b.targetpointOrigin.x - b.sourcepointOrigin.x)*(b.targetpointOrigin.x - b.sourcepointOrigin.x) + (b.targetpointOrigin.y - b.sourcepointOrigin.y)*(b.targetpointOrigin.y - b.sourcepointOrigin.y) )
    endfunction

    // Beam setters
    function SetBeamTime takes Beam b, real newTime returns nothing
        set b.beamTime = newTime
    endfunction
  
    function FadeBeam takes Beam b returns nothing
        set b.isFading = true
    endfunction
  
    native UnitAlive takes unit u returns boolean
  
    module BeamControl
      
        static method interval takes nothing returns nothing
          
            local Beam b = GetTimerData(GetExpiredTimer())
            local real x
            local real y
            local real z
            local real a
              
            // runs every TIMEOUT seconds
            static if thistype.onPeriod.exists then
                call thistype.onPeriod(b)
            endif
          
            if b.source != null then
                if b.sourceFacing then
                    set a = b.sourceAngle + GetUnitFacing(b.source) * bj_DEGTORAD
                else
                    set a = b.sourceAngle
                endif
                set x = GetUnitX(b.source) + Cos(a) * b.sourceDistance
                set y = GetUnitY(b.source) + Sin(a) * b.sourceDistance
                set z = GetLocalUnitZ(b.source) + b.sourceZOffset
                call Point.update(b.sourcepoint, x, y, z)
            endif
          
            if b.target != null then
                if b.targetFacing then
                    set a = b.targetAngle + (GetUnitFacing(b.target) + 180.) * bj_DEGTORAD
                else
                    set a = b.targetAngle
                endif
                set x = GetUnitX(b.target) + Cos(a) * b.targetDistance
                set y = GetUnitY(b.target) + Sin(a) * b.targetDistance
                set z = GetLocalUnitZ(b.target) + b.targetZOffset
                call Point.update(b.targetpoint, x, y, z)
            endif
          
            if b.sourceHitBox != 0. then
                // calculate hitbox distance
                set a = Atan2(b.targetpoint.y - b.sourcepoint.y, b.targetpoint.x - b.sourcepoint.x)
                set x = b.sourcepoint.x + Cos(a) * b.sourceHitBox
                set y = b.sourcepoint.y + Sin(a) * b.sourceHitBox
                call Point.update(b.launch, x, y, b.sourcepoint.z)
            else
                call Point.update(b.launch, b.sourcepoint.x, b.sourcepoint.y, b.sourcepoint.z)
            endif
          
            if b.targetHitBox != 0. then
                // calculate hitbox distance
                set a = Atan2(b.sourcepoint.y - b.targetpoint.y, b.sourcepoint.x - b.targetpoint.x)
                set x = b.targetpoint.x + Cos(a) * b.targetHitBox
                set y = b.targetpoint.y + Sin(a) * b.targetHitBox
                call Point.update(b.impact, x, y, b.targetpoint.z)
            else
                call Point.update(b.impact, b.targetpoint.x, b.targetpoint.y, b.targetpoint.z)
            endif
          
            // checks if the visual launch and impact points have moved (eg due to rotation) and if so,
            // update the beam's coordinates.
            if Point.hasMoved(b.launch) or Point.hasMoved(b.impact) then
                call MoveLightningEx(b.beam, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
            endif
          
            // if not in the fade phase
            if not b.isFading then
                if b.beamTime != 0 then // if zero, then beam lasts forever.
                    set b.beamTime = b.beamTime - TIMEOUT
                    if b.beamTime <= 0. then
                        set b.isFading = true
                        static if thistype.onFade.exists then
                            call thistype.onFade(b)
                        endif
                    endif
                endif
            // if in the fade phase
            else
                set b.alpha = b.alpha - b.alphaRate
                call SetLightningColor(b.beam, b.red, b.green, b.blue, b.alpha)
                if b.alpha <= 0. then
                    // runs when the beam has completely faded
                    static if thistype.onEnd.exists then
                        call thistype.onEnd(b)
                    endif
                    call b.destroy()
                endif
            endif
          
        endmethod
  
        static method cast takes Beam b returns nothing
          
            local real xSource
            local real ySource
            local real xTarget
            local real yTarget
            local real x
            local real y
            local real z
            local real a
            local real aBeam
          
            if not b.isCast then
          
                set b.isCast = true
              
                if b.source != null then
                    // if you have a source, calculate the angle and distance between the launch coordinates and the source.
                    // these values will then recalculate every interval to determine if the Point has moved or not.
                    set xSource = GetUnitX(b.source)
                    set ySource = GetUnitY(b.source)
                    set a = Atan2(b.yLaunch - ySource, b.xLaunch - xSource)
                    if  b.sourceFacing then
                        set b.sourceAngle = a - GetUnitFacing(b.source) * bj_DEGTORAD
                    else
                        set b.sourceAngle = a
                    endif
                    set b.sourceDistance = SquareRoot( (b.xLaunch - xSource)*(b.xLaunch - xSource) + (b.yLaunch - ySource)*(b.yLaunch - ySource) )
                    set b.sourceZOffset = b.zLaunch - GetLocalUnitZ(b.source)
                endif
                set b.sourcepoint = Point.create(b.xLaunch, b.yLaunch, b.zLaunch)
                set b.sourcepointOrigin = Point.create(b.xLaunch, b.yLaunch, b.zLaunch)
              
                if b.target != null then
                    // same as the above, but for targets
                    set xTarget = GetUnitX(b.target)
                    set yTarget = GetUnitY(b.target)
                    set a = Atan2(b.yImpact - yTarget, b.xImpact - xTarget)
                    if b.targetFacing then
                        set b.targetAngle = a - GetUnitFacing(b.target) * bj_DEGTORAD
                    else
                        set b.targetAngle = a
                    endif
                    set b.targetDistance = SquareRoot( (b.xImpact - xTarget)*(b.xImpact - xTarget) + (b.yImpact - yTarget)*(b.yImpact - yTarget) )
                    set b.targetZOffset = b.zImpact - GetLocalUnitZ(b.target)
                endif
                set b.targetpoint = Point.create(b.xImpact, b.yImpact, b.zImpact)
                set b.targetpointOrigin = Point.create(b.xImpact, b.yImpact, b.zImpact)
              
                // calculate visual launch from hitbox
                set aBeam = Atan2(b.yImpact - b.yLaunch, b.xImpact - b.xLaunch)
                set x = b.xLaunch + Cos(aBeam) * b.sourceHitBox
                set y = b.yLaunch + Sin(aBeam) * b.sourceHitBox
                set b.launch = Point.create(x, y, b.zLaunch)
                set aBeam = aBeam + 3.14159
                set x = b.xImpact + Cos(aBeam) * b.targetHitBox
                set y = b.yImpact + Sin(aBeam) * b.targetHitBox
                set b.impact = Point.create(x, y, b.zImpact)
              
                if b.alpha < 0. then // alpha cannot be lower than zero.
                    set b.alpha = 0.
                endif
                if b.fadeTime <= 0. then // fade time cannot be zero or lower.
                    set b.fadeTime = .25
                endif
                set b.alphaRate = (b.alpha/b.fadeTime) * TIMEOUT
                if b.alphaRate == 0. then // alphaRate cannot be zero or lower.
                    set b.alphaRate = .1
                endif
              
                set b.beam = AddLightningEx(b.beamFX, true, b.launch.x, b.launch.y, b.launch.z, b.impact.x, b.impact.y, b.impact.z)
                call SetLightningColor(b.beam, b.red, b.green, b.blue, b.alpha)
              
                if b.beamTime < 0. then
                    set b.isFading = true
                else
                    set b.isFading = false
                endif
              
                set b.t = NewTimerEx(b)
                call TimerStart(b.t, TIMEOUT, true, function thistype.interval)
            endif
          
        endmethod
      
    endmodule
  
    struct Point
      
        readonly real x
        readonly real y
        readonly real z
        private real xOld
        private real yOld
        private real zOld
      
        static method remove takes thistype point returns nothing
            set point.x = 0.
            set point.y = 0.
            set point.z = 0.
            set point.xOld = 0.
            set point.yOld = 0.
            set point.zOld = 0.
            call point.deallocate()
        endmethod
      
        static method hasMoved takes thistype point returns boolean
            if point.x != point.xOld or point.y != point.yOld or point.z != point.zOld then
                return true
            else
                return false
            endif
        endmethod
      
        static method update takes thistype point, real X, real Y, real Z returns nothing
            // checks if X, Y or Z are out-of-bounds and if so, set it to the playable limit.
            // credits to Retera for the code.
            if GetRectMinX(bj_mapInitialPlayableArea) > X then
                set X = GetRectMinX(bj_mapInitialPlayableArea)
            endif
            if GetRectMaxX(bj_mapInitialPlayableArea) < X then
                set X = GetRectMaxX(bj_mapInitialPlayableArea)
            endif
            if GetRectMinY(bj_mapInitialPlayableArea) > Y then
                set Y = GetRectMinY(bj_mapInitialPlayableArea)
            endif
            if GetRectMaxY(bj_mapInitialPlayableArea) < Y then
                set Y = GetRectMaxY(bj_mapInitialPlayableArea)
            endif
            set point.xOld = point.x
            set point.yOld = point.y
            set point.zOld = point.z
            set point.x = X
            set point.y = Y
            set point.z = Z
        endmethod
      
        static method create takes real X, real Y, real Z returns thistype
            local thistype point = allocate()
            set point.x = X
            set point.y = Y
            set point.z = Z
            set point.xOld = X
            set point.yOld = Y
            set point.zOld = Z
            return point
        endmethod
      
    endstruct
  
    struct Beam
      
        timer t
      
        unit source
        unit target
      
        // these reals will be used to recalculate the new coordiates of Points should the source or target move
        real sourceAngle
        real sourceDistance
        real sourceZOffset
        real targetAngle
        real targetDistance
        real targetZOffset
      
        // these booleans will move the point around the source/target with relation to their facing angle
        boolean sourceFacing
        boolean targetFacing
      
        // these values will either be the launch and impact coordinates, or offset the source and
        // target location by these values. Refer to the demo for a exmaple of how to use those as offsets.
        real xLaunch
        real yLaunch
        real zLaunch
        real xImpact
        real yImpact
        real zImpact
      
        // the hit box will act like a 'surface' and will offset the beam by the value it's set at.
        real sourceHitBox
        real targetHitBox
      
        real beamTime
        real fadeTime
      
        // these variables store the launch and impact Points of the beam instance.
        Point impact
        Point launch
        Point sourcepoint
        Point targetpoint
        Point sourcepointOrigin
        Point targetpointOrigin
      
        lightning beam
        string beamFX
        real red
        real blue
        real green
        real alpha
        real alphaRate
      
        boolean isFading
        boolean isCast
      
        method destroy takes nothing returns nothing
            if this.beam != null then
                call DestroyLightning(this.beam)
                set this.beam = null
            endif
            call ReleaseTimer(this.t)
            set this.t = null
            set this.source = null
            set this.target = null
            call Point.remove(this.impact)
            call Point.remove(this.launch)
            call Point.remove(this.sourcepoint)
            call Point.remove(this.targetpoint)
            call Point.remove(this.sourcepointOrigin)
            call Point.remove(this.targetpointOrigin)
            set this.isCast = false
            call this.deallocate()
        endmethod
      
        static method create takes nothing returns thistype
            local thistype b = allocate()
            //initialise all variables to their default value
            set b.t = null
            set b.source = null
            set b.target = null
            set b.sourceAngle = 0.
            set b.sourceDistance = 0.
            set b.sourceZOffset = 0.
            set b.targetAngle = 0.
            set b.targetDistance = 0.
            set b.targetZOffset = 0.
            set b.sourceFacing = false
            set b.targetFacing = false
            set b.xLaunch = 0.
            set b.yLaunch = 0.
            set b.zLaunch = 0.
            set b.xImpact = 0.
            set b.yImpact = 0.
            set b.zImpact = 0.
            set b.sourceHitBox = 0.
            set b.targetHitBox = 0.
            set b.beamTime = 1.
            set b.fadeTime = 0.25
            set b.impact = 0
            set b.launch = 0
            set b.sourcepoint = 0
            set b.targetpoint = 0
            set b.sourcepointOrigin = 0
            set b.targetpointOrigin = 0
            set b.beam = null
            set b.beamFX = null
            set b.red = 1.
            set b.blue = 1.
            set b.green = 1.
            set b.alpha = 1.
            set b.alphaRate = 0.
            set b.isFading = false
            set b.isCast = false
            return b
        endmethod
      
    endstruct

endlibrary

JASS:
scope BolaDemo initializer init
  
    globals
        private constant real CAST_DISTANCE = 64.
        private constant real BOLA_HEIGHT = 80.
        private constant real BOLA_WIDTH = 180.
        private constant real BOLA_EXPAND_SPEED = 3.
        private constant real BOLA_SPEED = 400. * .03125
        private constant real BOLA_SPIN_SPEED = (720. *  bj_DEGTORAD) * Beam_TIMEOUT // angle in radians per Beam_TIMEOUT
      
        private real array time
        private real array dist
        private real array aO
        private real array xO
        private real array yO
      
        private real array bolaAngle
        private real array bolaWidth
    endglobals
  
    struct Bola
      
        static method onPeriod takes Beam b returns nothing
            local real x1
            local real y1
            local real x2
            local real y2
            local real x
            local real y
            local real r = BOLA_WIDTH * .5
            set dist[b] = dist[b] + BOLA_SPEED
            set x = xO[b] + Cos(aO[b]) * dist[b]
            set y = yO[b] + Sin(aO[b]) * dist[b]
          
            // expand bola
            if bolaWidth[b] > r then
                set bolaWidth[b] = r
            elseif bolaWidth[b] < r then
                set bolaWidth[b] = bolaWidth[b] + BOLA_EXPAND_SPEED
            endif
          
            // rotate bola
            set bolaAngle[b] = bolaAngle[b] + BOLA_SPIN_SPEED
            set x1 = x + Cos(bolaAngle[b]) * bolaWidth[b]
            set y1 = y + Sin(bolaAngle[b]) * bolaWidth[b]
            set x2 = x + Cos(bolaAngle[b]) * bolaWidth[b] * -1
            set y2 = y + Sin(bolaAngle[b]) * bolaWidth[b] * -1
          
            call BJDebugMsg(R2S(GetRandomReal(1000.,2000.)))
            call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\BlackKeeperMissile\\BlackKeeperMissile.mdl", x, y))
          
            call Point.update(b.sourcepoint, x1, y1, BOLA_HEIGHT + Beam_GetLocZ(x1, y1))
            call Point.update(b.targetpoint, x2, y2, BOLA_HEIGHT + Beam_GetLocZ(x2, y2))
        endmethod
      
        implement BeamControl
      
    endstruct
  
    private function Actions takes nothing returns nothing
      
        local unit Source = GetTriggerUnit()
        local real xSource = GetUnitX(Source)
        local real ySource = GetUnitY(Source)
        local real xTarget = GetSpellTargetX()
        local real yTarget = GetSpellTargetY()
      
        // returns angle from the source to the target
        local real a = Atan2(yTarget - ySource, xTarget - xSource)
        local real dist = 80.//GetUnitCollision(Source)
      
        local Beam b = Beam.create()
      
        set b.beamFX = "FIBM"
        set b.xLaunch = xSource + Cos(a) * CAST_DISTANCE
        set b.yLaunch = ySource + Sin(a) * CAST_DISTANCE
        set b.zLaunch = GetUnitFlyHeight(Source) + BOLA_HEIGHT
        set b.xImpact = xSource + Cos(a) * CAST_DISTANCE
        set b.yImpact = ySource + Sin(a) * CAST_DISTANCE
        set b.zImpact = GetUnitFlyHeight(Source) + BOLA_HEIGHT
        set b.beamTime = 4.85
        set b.fadeTime = .15
        set b.sourceFacing = false
      
        set aO[b] = a
        set xO[b] = xSource
        set yO[b] = ySource
      
        set bolaAngle[b] = a + 90. * bj_DEGTORAD
        set bolaWidth[b] = 0.
      
        call Bola.cast(b)
      
        set Source = null
    endfunction
  
    private function init takes nothing returns nothing
        call RegisterSpellEffectEvent('A003', function Actions)
    endfunction

endscope
 

Attachments

  • Beam v1.00.w3x
    91.9 KB · Views: 64
Last edited:
Status
Not open for further replies.
Top