[vJASS] Lightning

Level 13
Joined
Nov 7, 2014
Messages
570
Lightning - tries to simplify the usage of the native type lightning

lightning.j:
JASS:
library Lightning

//! novjass

// References:
//    introduction to lightnings: http://www.hiveworkshop.com/threads/beginners-guide-to-lightning-effects.220370/
//    customizing lightnings: http://www.hiveworkshop.com/threads/how-to-customise-lightning-effects.203171/

// About:
//    The Lightning library tries to simplify using the native type lightning with other native types (units, items, rects, etc.).

// Introduction:
//    Each Lightning instance has two ends, a SRC (source, where the lightning originates from) end and
//    a DEST (destination, where the lightning "goes to") end. Both of those must be configured before
//    the Lightning can be used. The two ends can be linked/connected to different types of objects but
//    in order for the Lightning to be properly configured/constructed both ends must be linked/connected to something.

// API:

// creating/configuring
//

// in order to properly create a Lightning we have to open a configure "block"
static method begin_configure takes Lightning_Type lightning_type returns thistype

// where Lightning_Type can be one of the following:
// NOTE: these are the built-in lightning types that are unique, GUI has 4 more but they use the same texture as one of these:
//
Lightning_Type.LIGHTNING
Lightning_Type.DRAIN_LIFE
Lightning_Type.DRAIN_MANA
Lightning_Type.FINGER_OF_DEATH
Lightning_Type.FORKED_LIGHTNING
Lightning_Type.HEALING_WAVE
Lightning_Type.MAGIC_LEASH
Lightning_Type.MANA_BURN
Lightning_Type.MANA_FLARE
Lightning_Type.SPIRIT_LINK

// e.g:
set li = Lightning.begin_configure(Lightning_Type.FINGER_OF_DEATH)

// in the configure "block" we need to set/link both ends of the Lightning to something:

method set_end_xyz takes Lightning_End end, real x, real y, real z returns nothing
method set_end_location takes Lightning_End end, location l returns nothing
method set_end_unit takes Lightning_End end, unit u returns nothing
method set_end_item takes Lightning_End end, item it returns nothing
method set_end_destructable takes Lightning_End end, destructable d returns nothing
method set_end_rect takes Lightning_End end, rect r, Lightning_Rect_Point rp returns nothing
method set_end_camera takes Lightning_End end, camerasetup camera returns nothing // reference: http://www.hiveworkshop.com/threads/using-3d-points-within-world-editor.287597/
method set_end_lightning takes Lightning_End own_end, Lightning other, Lightning_End other_end returns nothing

// where Lightning_End can be one of the following:
Lightning_End.SRC
Lightning_End.DEST

// and Lightning_Rect_Point can be one of the following:
Lightning_Rect_Point.TOP_LEFT
Lightning_Rect_Point.TOP_RIGHT
Lightning_Rect_Point.BOTTOM_RIGHT
Lightning_Rect_Point.BOTTOM_LEFT
Lightning_Rect_Point.CENTER

// e.g:
call li.set_end_xyz(Lightning_End.SRC, 128.0, 256.0, 512.0)
call li.set_end_unit(Lightning_End.DEST, <some-unit>)

// after we've set both ends we have to "close/end the configure block"
method end_configure takes nothing returns nothing


// using the Lightning
//

// moves the Lightning to the positions determined by it's ends
method move takes nothing returns nothing

// for some types like destructable we can't get their height/z but we can make one up:
set li.src_z_offset = 80.0
set li.dest_z_offset = 96.0

// an end can be set to automatically "follow" the object it has been linked to
// NOTE: does not work for ends configured with set_end_xyz
method follow_through_time takes Lightning_End end, boolean flag returns nothing

// set's the color of the Lightning
// the format for the color is: AARRGGBB, e.g: blue = 0xFF0000FF
method set_color takes integer color returns nothing

// linearly transitions the color of the Lightning from start_color to end_color in dur number of seconds
method animate takes integer start_color, integer end_color, real dur returns nothing

// adds a duration to the Lightning, after dur number of seconds the Lightning will be automatically destroyed
method timed takes real dur returns nothing

// destroying
//

// destroyes the Lightning
// NOTE: the wrapped native lightning is not actually destroyed, it get's recycled (moved to "safe haven")
method destroy takes nothing returns nothing


// changing type
//

// changes the type of the Lightning to new_type
// NOTE: this method requires reassignment
method change_type takes Lightning_Type new_type returns thistype

// e.g:
set li = li.change_type(Lightning_Type.SPIRIT_LINK)

//! endnovjass

globals
    private constant real CDT = 1.0 / 20.0 // color update period (every 20th of a second)
    private constant real PDT = 1.0 / 32.0 // position update period

    // where lightnings are moved when they are recycled
    private constant real SAFE_HAVEN_X = -2147483648
    private constant real SAFE_HAVEN_Y = -2147483648
    private constant real SAFE_HAVEN_Z = -2147483648
endglobals

// for timed lightnings
private struct Timer extends array
    readonly static integer max_count = 0

    private static Timer head = 0 // a free list of timers
    private Timer next

    private timer t
    integer data
    real timeout

    static method start takes integer user_data, real timeout, code callback returns Timer
        local Timer this

        if head != 0 then
            set this = head
            set head = head.next

        else
            set max_count = max_count + 1
            if max_count > 8190 then
static if DEBUG_MODE then
                call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Lightning::Timer] error: could not allocate a Timer instance|r")
endif
                return 0
            endif

            set this = max_count
            set this.t = CreateTimer()
        endif

        set this.next = 0
        set this.data = user_data
        set this.timeout = timeout

        call TimerStart(this.t, this, false, null)
        call PauseTimer(this.t)
        call TimerStart(this.t, timeout, false, callback)

        return this
    endmethod

    method stop takes nothing returns nothing
        if this == 0 then
static if DEBUG_MODE then
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Lightning::Timer] error: cannot stop null Timer instance|r")
endif
            return
        endif

        if this.next != 0 then
static if DEBUG_MODE then
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Lightning::Timer] error: cannot stop Timer(" + I2S(this) + ") instance more than once|r")
endif
            return
        endif

        call TimerStart(this.t, 0.0, false, null)
        set this.next = head
        set head = this
    endmethod

    static method get_expired_data takes nothing returns integer
        local Timer t = Timer( R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) )
        local integer data = t.data
        call t.stop()
        return data
    endmethod
endstruct

/*enum*/ struct Lightning_Type extends array
    // vJass does not allow:
    //     static constant thistype FOO = 42 // error: constant structs are not supported
    // so we use readonly instead
    //
    readonly static thistype LIGHTNING = 1
    readonly static thistype DRAIN_LIFE = 2
    readonly static thistype DRAIN_MANA = 3
    readonly static thistype FINGER_OF_DEATH = 4
    readonly static thistype FORKED_LIGHTNING = 5
    readonly static thistype HEALING_WAVE = 6
    readonly static thistype MAGIC_LEASH = 7
    readonly static thistype MANA_BURN = 8
    readonly static thistype MANA_FLARE = 9
    readonly static thistype SPIRIT_LINK = 10

    static constant integer COUNT = 10

    private static string array ids
    private static string array names

    private static method onInit takes nothing returns nothing
        set ids[LIGHTNING] = "CHIM"
        set names[LIGHTNING] = "Lightning"

        set ids[DRAIN_LIFE] = "DRAL"
        set names[DRAIN_LIFE] = "Drain Life"

        set ids[DRAIN_MANA] = "DRAM"
        set names[DRAIN_MANA] = "Drain Mana"

        set ids[FINGER_OF_DEATH] = "AFOD"
        set names[FINGER_OF_DEATH] = "Figner of Death"

        set ids[FORKED_LIGHTNING] = "FORK"
        set names[FORKED_LIGHTNING] = "Forked Lightning"

        set ids[HEALING_WAVE] = "HWPB"
        set names[HEALING_WAVE] = "Healing Wave - Primary"

        set ids[MAGIC_LEASH] = "LEAS"
        set names[MAGIC_LEASH] = "Magic Leash"

        set ids[MANA_BURN] = "MBUR"
        set names[MANA_BURN] = "Mana Burn"

        set ids[MANA_FLARE] = "MFPB"
        set names[MANA_FLARE] = "Mana Flare"

        set ids[SPIRIT_LINK] = "SPLK"
        set names[SPIRIT_LINK] = "Spirit Link"
    endmethod

    method to_id takes nothing returns string
        return ids[this]
    endmethod

    method to_str takes nothing returns string
        return names[this]
    endmethod
endstruct

// /*enum*/ struct Lightning_Visibility extends array
//     static constant boolean VISIBLE_THROUGH_FOG = false
//     static constant boolean INVISIBLE_THROUGH_FOG = true
// endstruct

/*enum*/ struct Lightning_End extends array
    static constant integer SRC = 1
    static constant integer DEST = 2
endstruct

/*enum*/ struct Lightning_Link extends array
    static constant integer UNDEFINED = 0
    static constant integer XYZ = 1
    static constant integer LOCATION = 2
    static constant integer UNIT = 3
    static constant integer ITEM = 4
    static constant integer DESTRUCTABLE = 5
    static constant integer RECT = 6
    static constant integer CAMERA = 7
    static constant integer LIGHTNING = 8
endstruct

/*enum*/ struct Lightning_Rect_Point extends array
    static constant integer TOP_LEFT = 1
    static constant integer TOP_RIGHT = 2
    static constant integer BOTTOM_RIGHT = 3
    static constant integer BOTTOM_LEFT = 4
    static constant integer CENTER = 5
endstruct

struct Lightning extends array
    private static location loc = Location(0.0, 0.0)

    // there's a free list for each Lightning_Type
    private static integer I = Lightning_Type.COUNT + 2
    private static thistype array fl_heads // fl_heads[Lightning_Type.<type>] stores the head of that type
    private thistype fl_next

    // position update list
    private static thistype puls = Lightning_Type.COUNT + 1 // sentinel node
    private thistype pul_prev
    private thistype pul_next

    // color update list
    private static thistype culs = Lightning_Type.COUNT + 2 // sentinel node
    private thistype cul_prev
    private thistype cul_next

    private static timer ct = CreateTimer()
    private static timer pt = CreateTimer()

    private Lightning_Type type
    private lightning lightning

//! textmacro LIGHTNING_LINK takes p
    private Lightning_Link $p$type

    private real $p$x //
    private real $p$y // Lightning_Link.XYZ
    private real $p$z //
    private location $p$location // Lightning_Link.LOCATION

    private unit $p$unit // Lightning_Link.UNIT
    private item $p$item // Lightning_Link.ITEM
    private destructable $p$destructable // Lightning_Link.DESTRUCTABLE

    private rect $p$rect // Lightning_Link.RECT
    private Lightning_Rect_Point $p$rect_point // Lightning_Link.RECT

    private camerasetup $p$camera // Lightning_Link.CAMERA

    private Lightning $p$lightning // Lightning_Link.LIGHTNING
    private Lightning_End $p$lightning_end // Lightning_Link.LIGHTNING

    real $p$z_offset

    private boolean $p$ftt // follow through time
//! endtextmacro
//! runtextmacro LIGHTNING_LINK("src_")
//! runtextmacro LIGHTNING_LINK("dest_")

    // animate color
    private real a
    private real r
    private real g
    private real b
    private real sa
    private real sr
    private real sg
    private real sb
    private integer c_curr_step
    private integer c_max_steps

static if DEBUG_MODE then
    private boolean properly_configured
endif

    private static method onInit takes nothing returns nothing
        set puls.pul_prev = puls
        set puls.pul_next = puls

        set culs.cul_prev = culs
        set culs.cul_next = culs
    endmethod

    static method init_default takes Lightning_Type lt returns thistype
        local thistype this

        if fl_heads[lt] != 0 then
            set this = fl_heads[lt]
            set fl_heads[lt] = fl_heads[lt].fl_next

        else
            set I = I + 1
            if I > 8190 then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Lightning] error: could not allocate a Lightning instance|r")
                return 0
            endif
            set this = I
            set this.type = lt
            set this.lightning = AddLightningEx(lt.to_id(), false, src_x, src_y, src_z, dest_x, dest_y, dest_z)
        endif

        set this.fl_next = 0
        set this.pul_prev = 0
        set this.pul_next = 0
        set this.cul_prev = 0
        set this.cul_next = 0

//! textmacro LIGHTNING_LINK_DEFAULTS takes p
        set this.$p$type = Lightning_Link.UNDEFINED
        set this.$p$x = SAFE_HAVEN_X
        set this.$p$y = SAFE_HAVEN_Y
        set this.$p$z = SAFE_HAVEN_Z
        set this.$p$location = null
        set this.$p$unit = null
        set this.$p$item = null
        set this.$p$destructable = null
        set this.$p$rect = null
        set this.$p$camera = null
        set this.$p$lightning = 0
        set this.$p$z_offset = 0.0
        set this.$p$ftt = false
//! endtextmacro
//! runtextmacro LIGHTNING_LINK_DEFAULTS("src_")
//! runtextmacro LIGHTNING_LINK_DEFAULTS("dest_")

static if DEBUG_MODE then
        set this.properly_configured = false
endif

        return this
    endmethod

    private static method pul_is_empty takes nothing returns boolean
        return puls.pul_next == puls
    endmethod

    private static method pul_has_one_item takes nothing returns boolean
        return not pul_is_empty() and puls.pul_next.pul_next == puls
    endmethod

    private method add_to_pul takes nothing returns nothing
        set this.pul_prev = puls
        set this.pul_next = puls.pul_next
        set this.pul_prev.pul_next = this
        set this.pul_next.pul_prev = this
    endmethod

    private method remove_from_pul takes nothing returns nothing
        set this.pul_prev.pul_next = this.pul_next
        set this.pul_next.pul_prev = this.pul_prev
        set this.pul_prev = 0
        set this.pul_next = 0
    endmethod

    private method is_in_pul takes nothing returns boolean
        return this.pul_next != 0
    endmethod


    private static method cul_is_empty takes nothing returns boolean
        return culs.cul_next == culs
    endmethod

    private static method cul_has_one_item takes nothing returns boolean
        return not cul_is_empty() and culs.cul_next.cul_next == culs
    endmethod

    private method add_to_cul takes nothing returns nothing
        set this.cul_prev = culs
        set this.cul_next = culs.cul_next
        set this.cul_prev.cul_next = this
        set this.cul_next.cul_prev = this
    endmethod

    private method remove_from_cul takes nothing returns nothing
        set this.cul_prev.cul_next = this.cul_next
        set this.cul_next.cul_prev = this.cul_prev
        set this.cul_prev = 0
        set this.cul_next = 0
    endmethod

    private method is_in_cul takes nothing returns boolean
        return this.cul_next != 0
    endmethod

    method destroy takes nothing returns nothing
static if DEBUG_MODE then
        if this == 0 then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Lightning] error: cannot destroy null Lightning instance|r")
            return
        endif

        if this.fl_next != 0 then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Lightning] error: cannot destroy Lightning(" + I2S(this) + ") instance more than once|r")
            return
        endif
endif

        set this.src_x = SAFE_HAVEN_X
        set this.src_y = SAFE_HAVEN_Y
        set this.src_z = SAFE_HAVEN_Z
        set this.src_z_offset = 0.0
        set this.dest_x = SAFE_HAVEN_X
        set this.dest_y = SAFE_HAVEN_Y
        set this.dest_z = SAFE_HAVEN_Z
        set this.dest_z_offset = 0.0
        call this.move()

        if this.is_in_pul() then
            call this.remove_from_pul()

            if pul_is_empty() then
                call PauseTimer(pt)
            endif
        endif

        if this.is_in_cul() then
            call this.remove_from_cul()
            if cul_is_empty() then
                call PauseTimer(ct)
            endif
        endif

        set this.fl_next = fl_heads[this.type]
        set fl_heads[this.type] = this
    endmethod

    method move takes nothing returns nothing
static if DEBUG_MODE then
        if not this.properly_configured then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "[Lightning] error: cannot call method move() for Lightning(" + I2S(this) + ") before it's configured")
            return
        endif
endif
        call MoveLightningEx(this.lightning, false, this.src_x, this.src_y, this.src_z + this.src_z_offset, this.dest_x, this.dest_y, this.dest_z + this.dest_z_offset)
    endmethod

    private static method position_update takes nothing returns nothing
        local thistype li
        local Lightning_Link lt
        local Lightning_Rect_Point rp
        local Lightning_End other_end

        set li = puls.pul_next
        loop
            exitwhen li == puls

            if li.src_ftt then
                set lt = li.src_type
                if lt == Lightning_Link.LOCATION then
                    set li.src_x = GetLocationX(li.src_location)
                    set li.src_y = GetLocationY(li.src_location)
                    set li.src_z = GetLocationZ(li.src_location)

                elseif lt == Lightning_Link.UNIT then
                    set li.src_x = GetUnitX(li.src_unit)
                    set li.src_y = GetUnitY(li.src_unit)
                    call MoveLocation(loc, li.src_x, li.src_y)
                    set li.src_z = GetLocationZ(loc) + GetUnitFlyHeight(li.src_unit)

                elseif lt == Lightning_Link.ITEM then
                    set li.src_x = GetItemX(li.src_item)
                    set li.src_y = GetItemY(li.src_item)
                    call MoveLocation(loc, li.src_x, li.src_y)
                    set li.src_z = GetLocationZ(loc)

                elseif lt == Lightning_Link.DESTRUCTABLE then
                    set li.src_x = GetDestructableX(li.src_destructable)
                    set li.src_y = GetDestructableY(li.src_destructable)
                    call MoveLocation(loc, li.src_x, li.src_y)
                    set li.src_z = GetLocationZ(loc)

                elseif lt == Lightning_Link.RECT then
                    set rp = li.src_rect_point
                    if rp == Lightning_Rect_Point.TOP_LEFT then
                        set li.src_x = GetRectMinX(li.src_rect)
                        set li.src_y = GetRectMaxY(li.src_rect)
                    elseif rp == Lightning_Rect_Point.TOP_RIGHT then
                        set li.src_x = GetRectMaxX(li.src_rect)
                        set li.src_y = GetRectMaxY(li.src_rect)
                    elseif rp == Lightning_Rect_Point.BOTTOM_RIGHT then
                        set li.src_x = GetRectMaxX(li.src_rect)
                        set li.src_y = GetRectMinY(li.src_rect)
                    elseif rp == Lightning_Rect_Point.BOTTOM_LEFT then
                        set li.src_x = GetRectMinX(li.src_rect)
                        set li.src_y = GetRectMinY(li.src_rect)
                    else // if rp == Lightning_Rect_Point.CENTER then
                        set li.src_x = GetRectCenterX(li.src_rect)
                        set li.src_y = GetRectCenterY(li.src_rect)
                    endif
                    call MoveLocation(loc, li.src_x, li.src_y)
                    set li.src_z = GetLocationZ(loc)

                elseif lt == Lightning_Link.CAMERA then
                    set li.src_x = CameraSetupGetDestPositionX(li.src_camera)
                    set li.src_y = CameraSetupGetDestPositionY(li.src_camera)
                    call MoveLocation(loc, li.src_x, li.src_y)
                    set li.src_z = CameraSetupGetField(li.src_camera, CAMERA_FIELD_ZOFFSET) + GetLocationZ(loc)

                elseif lt == Lightning_Link.LIGHTNING then
                    if li.src_lightning_end == Lightning_End.SRC then
                        set li.src_x = li.src_lightning.src_x
                        set li.src_y = li.src_lightning.src_y
                        set li.src_z = li.src_lightning.src_z

                    else // if li.src_lighthing_end == Lightning_End.DEST then
                        set li.src_x = li.src_lightning.dest_x
                        set li.src_y = li.src_lightning.dest_y
                        set li.src_z = li.src_lightning.dest_z
                    endif
                endif
            endif

            if li.dest_ftt then
                set lt = li.dest_type
                if lt == Lightning_Link.LOCATION then
                    set li.dest_x = GetLocationX(li.dest_location)
                    set li.dest_y = GetLocationY(li.dest_location)
                    set li.dest_z = GetLocationZ(li.dest_location)

                elseif lt == Lightning_Link.UNIT then
                    set li.dest_x = GetUnitX(li.dest_unit)
                    set li.dest_y = GetUnitY(li.dest_unit)
                    call MoveLocation(loc, li.dest_x, li.dest_y)
                    set li.dest_z = GetLocationZ(loc) + GetUnitFlyHeight(li.dest_unit)

                elseif lt == Lightning_Link.ITEM then
                    set li.dest_x = GetItemX(li.dest_item)
                    set li.dest_y = GetItemY(li.dest_item)
                    call MoveLocation(loc, li.dest_x, li.dest_y)
                    set li.dest_z = GetLocationZ(loc)

                elseif lt == Lightning_Link.DESTRUCTABLE then
                    set li.dest_x = GetDestructableX(li.dest_destructable)
                    set li.dest_y = GetDestructableY(li.dest_destructable)
                    call MoveLocation(loc, li.dest_x, li.dest_y)
                    set li.dest_z = GetLocationZ(loc)

                elseif lt == Lightning_Link.RECT then
                    set rp = li.dest_rect_point
                    if rp == Lightning_Rect_Point.TOP_LEFT then
                        set li.dest_x = GetRectMinX(li.dest_rect)
                        set li.dest_y = GetRectMaxY(li.dest_rect)
                    elseif rp == Lightning_Rect_Point.TOP_RIGHT then
                        set li.dest_x = GetRectMaxX(li.dest_rect)
                        set li.dest_y = GetRectMaxY(li.dest_rect)
                    elseif rp == Lightning_Rect_Point.BOTTOM_RIGHT then
                        set li.dest_x = GetRectMaxX(li.dest_rect)
                        set li.dest_y = GetRectMinY(li.dest_rect)
                    elseif rp == Lightning_Rect_Point.BOTTOM_LEFT then
                        set li.dest_x = GetRectMinX(li.dest_rect)
                        set li.dest_y = GetRectMinY(li.dest_rect)
                    else // if rp == Lightning_Rect_Point.CENTER then
                        set li.dest_x = GetRectCenterX(li.dest_rect)
                        set li.dest_y = GetRectCenterY(li.dest_rect)
                    endif
                    call MoveLocation(loc, li.dest_x, li.dest_y)
                    set li.dest_z = GetLocationZ(loc)

                elseif lt == Lightning_Link.CAMERA then
                    set li.dest_x = CameraSetupGetDestPositionX(li.dest_camera)
                    set li.dest_y = CameraSetupGetDestPositionY(li.dest_camera)
                    call MoveLocation(loc, li.dest_x, li.dest_y)
                    set li.dest_z = CameraSetupGetField(li.src_camera, CAMERA_FIELD_ZOFFSET) + GetLocationZ(loc)

                elseif lt == Lightning_Link.LIGHTNING then
                    if li.dest_lightning_end == Lightning_End.SRC then
                        set li.dest_x = li.dest_lightning.src_x
                        set li.dest_y = li.dest_lightning.src_y
                        set li.dest_z = li.dest_lightning.src_z

                    else // if li.dest_lighthing_end == Lightning_End.DEST then
                        set li.dest_x = li.dest_lightning.dest_x
                        set li.dest_y = li.dest_lightning.dest_y
                        set li.dest_z = li.dest_lightning.dest_z
                    endif
                endif
            endif

            call MoveLightningEx(li.lightning, false, li.src_x, li.src_y, li.src_z + li.src_z_offset, li.dest_x, li.dest_y, li.dest_z + li.dest_z_offset)
            set li = li.pul_next
        endloop
    endmethod

    static method begin_configure takes Lightning_Type lightning_type returns thistype
        return init_default(lightning_type)
    endmethod

    method end_configure takes nothing returns nothing
static if DEBUG_MODE then
        set this.properly_configured = true

        if this.src_type == Lightning_Link.UNDEFINED then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "[Lightning] error: Lightning(" + I2S(this) + ")'s SRC end has not been configured")
            set this.properly_configured = false
        endif

        if this.dest_type == Lightning_Link.UNDEFINED then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "[Lightning] error: Lightning(" + I2S(this) + ")'s DEST end has not been configured")
            set this.properly_configured = false
        endif
endif
    endmethod

    method change_type takes Lightning_Type new_type returns thistype
        local thistype result

static if DEBUG_MODE then
        if not this.properly_configured then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "[Lightning] error: cannot call method change_type(...) for Lightning(" + I2S(this) + ") before it's configured")
            return 0
        endif
endif
        if this.type == new_type then
            return this
        endif

        set result = thistype.init_default(new_type)

//! textmacro LIGHTNING_LINK_COPY takes p
        set result.$p$type = this.$p$type
        set result.$p$x = this.$p$x
        set result.$p$y = this.$p$y
        set result.$p$z = this.$p$z
        set result.$p$location = this.$p$location
        set result.$p$unit = this.$p$unit
        set result.$p$item = this.$p$item
        set result.$p$destructable = this.$p$destructable
        set result.$p$rect = this.$p$rect
        set result.$p$rect_point = this.$p$rect_point
        set result.$p$camera = this.$p$camera
        set result.$p$lightning = this.$p$lightning
        set result.$p$lightning_end = this.$p$lightning_end
        set result.$p$z_offset = this.$p$z_offset
        set result.$p$ftt = this.$p$ftt
//! endtextmacro
//! runtextmacro LIGHTNING_LINK_COPY("src_")
//! runtextmacro LIGHTNING_LINK_COPY("dest_")

        set result.a = this.a
        set result.r = this.r
        set result.g = this.g
        set result.b = this.b
        set result.sa = this.sa
        set result.sr = this.sr
        set result.sg = this.sg
        set result.sb = this.sb
        set result.c_curr_step = this.c_curr_step
        set result.c_max_steps = this.c_max_steps

static if DEBUG_MODE then
        set result.properly_configured = this.properly_configured
endif

        if this.is_in_pul() then
            call result.add_to_pul()
        endif

        if this.is_in_cul() then
            call result.add_to_cul()
        endif

        call this.destroy()

        call result.move()
        return result
    endmethod

    // must be called after end_configure
    method follow_through_time takes Lightning_End end, boolean flag returns nothing
static if DEBUG_MODE then
        if not this.properly_configured then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "[Lightning] error: cannot call method follow_through_time(...) for Lightning(" + I2S(this) + ") before it's configured")
            return
        endif
endif

        if this.src_type == Lightning_Link.XYZ and this.dest_type == Lightning_Link.XYZ then
            return
        endif

        if end == Lightning_End.SRC then
            set this.src_ftt = flag
        else // if end == Lightning_End.DEST then
            set this.dest_ftt = flag
        endif

        if not this.is_in_pul() and flag then
            call this.add_to_pul()
            if pul_has_one_item() then
                call TimerStart(pt, PDT, true, function thistype.position_update)
            endif

        elseif this.is_in_pul() and not this.src_ftt and not this.dest_ftt then
            call this.remove_from_pul()
            if pul_is_empty() then
                call PauseTimer(pt)
            endif
        endif
    endmethod

    method set_end_xyz takes Lightning_End end, real x, real y, real z returns nothing
        if end == Lightning_End.SRC then
            set this.src_type = Lightning_Link.XYZ
            set this.src_x = x
            set this.src_y = y
            set this.src_z = z

        else // if end == Lightning_End.DEST then
            set this.dest_type = Lightning_Link.XYZ
            set this.dest_x = x
            set this.dest_y = y
            set this.dest_z = z
        endif
    endmethod

    method set_end_location takes Lightning_End end, location l returns nothing
        if end == Lightning_End.SRC then
            set this.src_type = Lightning_Link.LOCATION
            set this.src_location = l
            set this.src_x = GetLocationX(l)
            set this.src_y = GetLocationX(l)
            set this.src_z = GetLocationZ(l)

        else // if end == Lightning_End.DEST then
            set this.dest_type = Lightning_Link.LOCATION
            set this.dest_location = l
            set this.dest_x = GetLocationX(l)
            set this.dest_y = GetLocationX(l)
            set this.dest_z = GetLocationZ(l)
        endif
    endmethod

    method set_end_unit takes Lightning_End end, unit u returns nothing
        if end == Lightning_End.SRC then
            set this.src_type = Lightning_Link.UNIT
            set this.src_unit = u
            set this.src_x = GetUnitX(u)
            set this.src_y = GetUnitY(u)
            call MoveLocation(loc, this.src_x, this.src_y)
            set this.src_z = GetLocationZ(loc) + GetUnitFlyHeight(u)

        else // if end == Lightning_End.DEST then
            set this.dest_type = Lightning_Link.UNIT
            set this.dest_unit = u
            set this.dest_x = GetUnitX(u)
            set this.dest_y = GetUnitY(u)
            call MoveLocation(loc, this.dest_x, this.dest_y)
            set this.dest_z = GetLocationZ(loc) + GetUnitFlyHeight(u)
        endif
    endmethod

    method set_end_item takes Lightning_End end, item it returns nothing
        if end == Lightning_End.SRC then
            set this.src_type = Lightning_Link.ITEM
            set this.src_item = it
            set this.src_x = GetItemX(it)
            set this.src_y = GetItemY(it)
            call MoveLocation(loc, this.src_x, this.src_y)
            set this.src_z = GetLocationZ(loc)

        else // if end == Lightning_End.DEST then
            set this.dest_type = Lightning_Link.ITEM
            set this.dest_item = it
            set this.dest_x = GetItemX(it)
            set this.dest_y = GetItemY(it)
            call MoveLocation(loc, this.dest_x, this.dest_y)
            set this.dest_z = GetLocationZ(loc)
        endif
    endmethod

    method set_end_destructable takes Lightning_End end, destructable d returns nothing
        if end == Lightning_End.SRC then
            set this.src_type = Lightning_Link.DESTRUCTABLE
            set this.src_destructable = d
            set this.src_x = GetDestructableX(d)
            set this.src_y = GetDestructableY(d)
            call MoveLocation(loc, this.src_x, this.src_y)
            set this.src_z = GetLocationZ(loc)

        else // if end == Lightning_End.DEST then
            set this.dest_type = Lightning_Link.DESTRUCTABLE
            set this.dest_destructable = d
            set this.dest_x = GetDestructableX(d)
            set this.dest_y = GetDestructableY(d)
            call MoveLocation(loc, this.dest_x, this.dest_y)
            set this.dest_z = GetLocationZ(loc)
        endif
    endmethod

    method set_end_rect takes Lightning_End end, rect r, Lightning_Rect_Point rp returns nothing
        if end == Lightning_End.SRC then
            set this.src_type = Lightning_Link.RECT
            set this.src_rect = r
            set this.src_rect_point = rp

            if rp == Lightning_Rect_Point.TOP_LEFT then
                set this.src_x = GetRectMinX(r)
                set this.src_y = GetRectMaxY(r)
            elseif rp == Lightning_Rect_Point.TOP_RIGHT then
                set this.src_x = GetRectMaxX(r)
                set this.src_y = GetRectMaxY(r)
            elseif rp == Lightning_Rect_Point.BOTTOM_RIGHT then
                set this.src_x = GetRectMaxX(r)
                set this.src_y = GetRectMinY(r)
            elseif rp == Lightning_Rect_Point.BOTTOM_LEFT then
                set this.src_x = GetRectMinX(r)
                set this.src_y = GetRectMinY(r)
            else // if rp == Lightning_Rect_Point.CENTER then
                set this.src_x = GetRectCenterX(r)
                set this.src_y = GetRectCenterY(r)
            endif
            call MoveLocation(loc, this.src_x, this.src_y)
            set this.src_z = GetLocationZ(loc)

        else // if end == Lightning_End.DEST then
            set this.dest_type = Lightning_Link.RECT
            set this.dest_rect = r
            set this.dest_rect_point = rp

            if rp == Lightning_Rect_Point.TOP_LEFT then
                set this.dest_x = GetRectMinX(r)
                set this.dest_y = GetRectMaxY(r)
            elseif rp == Lightning_Rect_Point.TOP_RIGHT then
                set this.dest_x = GetRectMaxX(r)
                set this.dest_y = GetRectMaxY(r)
            elseif rp == Lightning_Rect_Point.BOTTOM_RIGHT then
                set this.dest_x = GetRectMaxX(r)
                set this.dest_y = GetRectMinY(r)
            elseif rp == Lightning_Rect_Point.BOTTOM_LEFT then
                set this.dest_x = GetRectMinX(r)
                set this.dest_y = GetRectMinY(r)
            else // if rp == Lightning_Rect_Point.CENTER then
                set this.dest_x = GetRectCenterX(r)
                set this.dest_y = GetRectCenterY(r)
            endif
            call MoveLocation(loc, this.dest_x, this.dest_y)
            set this.dest_z = GetLocationZ(loc)
        endif
    endmethod

    method set_end_camera takes Lightning_End end, camerasetup camera returns nothing
        if end == Lightning_End.SRC then
            set this.src_type = Lightning_Link.CAMERA
            set this.src_camera = camera
            set this.src_x = CameraSetupGetDestPositionX(camera)
            set this.src_y = CameraSetupGetDestPositionY(camera)
            call MoveLocation(loc, this.src_x, this.src_y)
            set this.src_z = CameraSetupGetField(camera, CAMERA_FIELD_ZOFFSET) + GetLocationZ(loc)

        else // if end == Lightning_End.DEST then
            set this.dest_type = Lightning_Link.CAMERA
            set this.dest_camera = camera
            set this.dest_x = CameraSetupGetDestPositionX(camera)
            set this.dest_y = CameraSetupGetDestPositionY(camera)
            call MoveLocation(loc, this.dest_x, this.dest_y)
            set this.dest_z = CameraSetupGetField(camera, CAMERA_FIELD_ZOFFSET) + GetLocationZ(loc)
        endif
    endmethod

    method set_end_lightning takes Lightning_End own_end, Lightning other, Lightning_End other_end returns nothing
        if own_end == Lightning_End.SRC then
            set this.src_type = Lightning_Link.LIGHTNING
            set this.src_lightning = other
            set this.src_lightning_end = other_end

            if other_end == Lightning_End.SRC then
                set this.src_x = other.src_x
                set this.src_y = other.src_y
                set this.src_z = other.src_z

            else // if other_end == Lightning_End.DEST then
                set this.src_x = other.dest_x
                set this.src_y = other.dest_y
                set this.src_z = other.dest_z
            endif

        else // if own_end == Lightning_End.DEST then
            set this.dest_type = Lightning_Link.LIGHTNING
            set this.dest_lightning = other
            set this.dest_lightning_end = other_end

            if other_end == Lightning_End.SRC then
                set this.dest_x = other.src_x
                set this.dest_y = other.src_y
                set this.dest_z = other.src_z

            else // if other_end == Lightning_End.DEST then
                set this.dest_x = other.dest_x
                set this.dest_y = other.dest_y
                set this.dest_z = other.dest_z
            endif
        endif
    endmethod

    private static integer alpha = 0
    private static integer red = 0
    private static integer green = 0
    private static integer blue = 0
    private static method get_argb takes integer color returns nothing
        local boolean is_neg = color < 0
        local integer t

        if is_neg then
            set color = color + 0x80000000
        endif

        set t = color / 0x100
        set blue = color - t * 0x100
        set color = t

        set t = color / 0x100
        set green = color - t * 0x100
        set color = t

        set t = color / 0x100
        set red = color - t * 0x100
        set color = t

        set t = color / 0x100
        set alpha = color - t * 0x100
        // set color = t
        if is_neg then
            set alpha = alpha + 0x80
        endif
    endmethod

    method set_color takes integer color returns nothing
static if DEBUG_MODE then
        if not this.properly_configured then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "[Lightning] error: cannot call method set_color(...) for Lightning(" + I2S(this) + ") before it's configured")
            return
        endif
endif
        call get_argb(color)
        call SetLightningColor(this.lightning, red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0)
    endmethod

    private static method animate_step takes nothing returns nothing
            local thistype li
            local thistype li_next

            set li = culs.cul_next
            loop
                exitwhen li == culs
                set li_next = li.cul_next

                call SetLightningColor(li.lightning, li.r, li.g, li.b, li.a)

                set li.c_curr_step = li.c_curr_step + 1
                if li.c_curr_step > li.c_max_steps then
                    call li.remove_from_cul()

                    if cul_is_empty() then
                        call PauseTimer(ct)
                    endif

                else
                    set li.a = li.a + li.sa
                    set li.r = li.r + li.sr
                    set li.g = li.g + li.sg
                    set li.b = li.b + li.sb
                endif

                set li = li_next
            endloop
    endmethod

    method animate takes integer start_color, integer end_color, real dur returns nothing
static if DEBUG_MODE then
        if not this.properly_configured then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "[Lightning] error: cannot call method animate(...) for Lightning(" + I2S(this) + ") before it's configured")
            return
        endif
endif

        call get_argb(start_color)
        set this.a = alpha / 255.0
        set this.r = red / 255.0
        set this.g = green / 255.0
        set this.b = blue / 255.0

        call get_argb(end_color)

        if dur < CDT then
            call SetLightningColor(this.lightning, red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0)
            return
        endif

        set this.c_max_steps = R2I(1.0 / CDT * dur)
        set this.c_curr_step = 0

        set this.sa = (alpha / 255.0 - this.a) / c_max_steps
        set this.sr = (red / 255.0 - this.r) / c_max_steps
        set this.sg = (green / 255.0 - this.g) / c_max_steps
        set this.sb = (blue / 255.0 - this.b) / c_max_steps

        if not this.is_in_cul() then
            call this.add_to_cul()
            if cul_has_one_item() then
                call TimerStart(ct, CDT, true, function thistype.animate_step)
            endif
        endif
    endmethod

    private static method timed_destroy takes nothing returns nothing
        call Lightning(Timer.get_expired_data()).destroy()
    endmethod

    method timed takes real dur returns nothing
static if DEBUG_MODE then
        if not this.properly_configured then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "[Lightning] error: cannot call method timed(...) for Lightning(" + I2S(this) + ") before it's configured")
            return
        endif
endif
        call Timer.start(this, dur, function thistype.timed_destroy)
    endmethod

static if DEBUG_MODE then
    method debug takes nothing returns nothing
        // call BJDebugMsg("Lightning { id: " + I2S(this) + ", type: " + this.type.to_str()) + ", src_x: " + R2S(this.src_x) + ", src_y: " + R2S(this.src_y) + ", src_z: " + R2S(this.src_z) + I2S(this) + ", dest_x: " + R2S(this.dest_x) + ", dest_y: " + R2S(this.dest_y) + ", dest_z: " + R2S(this.dest_z) + " }")
        // call MoveLocation(loc, this.dest_x, this.dest_y)
        // call BJDebugMsg("dest location z: " + R2S(GetLocationZ(loc)))
        // call BJDebugMsg("a: " + R2S(this.a))
        // call BJDebugMsg("r: " + R2S(this.r))
        // call BJDebugMsg("g: " + R2S(this.g))
        // call BJDebugMsg("b: " + R2S(this.b))
    endmethod
endif
endstruct

endlibrary
 

Attachments

  • lightning-demo.w3x
    40.7 KB · Views: 75
  • lightning-demo-pic.png
    lightning-demo-pic.png
    918.3 KB · Views: 162
ARGB - Wc3C.net for ARGB
and TimerUtils for timer struct.

Your API functions looks very great and handy, I would definitly use it, too.

naming could go more mainstream ;D I must laugh meanwhile each time we talk about it.^^

method set_end_xyz takes Lightning_End end, real x, real y, real z returns nothing

^imo something like

method setEndPointXYZ takes Lightning_EndPoint ep, real x, real y, real z returns nothing

is a bit easier understandable-- the "end" as actual "endpoint" seems a bit unnatural.

Same for most other API functions. Internal functions are not relevant as long as they're plausible.

The struct's name isn't clear from API docu.

Very good library, we need this.
 
Top