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

Interfaces and Structs

Status
Not open for further replies.
Level 20
Joined
May 16, 2012
Messages
635
Hello Hive!

I was playing around creating my own missile system based on my favorite 2 missile systems, xemissile and Missile, by Vexorian and BPower respectively. I want to unite the best of both worlds and also use the new effect natives. I declared a interface that contains the event methods the can be implemented and while moving the effect(missile) I check if the method is implemented in the child struct using. example:

JASS:
if this.onHit.exists then

in my test map the missile movement works correctly, but the events are not called and I'm out of ideas why.

The interface:
JASS:
    private interface Events
        method onHit takes unit hit returns boolean defaults false
        method onDestructable takes destructable dest returns boolean defaults false
        method onTerrain takes nothing returns boolean defaults false
        method onPeriod takes nothing returns boolean defaults false
        method onFinish takes nothing returns boolean defaults false
        method onRemove takes nothing returns nothing defaults nothing
    endinterface

and the loop method where the missile is moved and the events are checked:
JASS:
private static method onLoop takes nothing returns nothing
            local integer  i = 0
            local thistype this
            local unit  u
            local real  a
            local real  newX
            local real  newY
            local real  newZ
            local real  velocity
            local real  point
            local real  pitch
            local real  terrainZ

            loop
                exitwhen i > didx
                    set this = missiles[i]

                    if not dead then
                        if launched then
                            // onPeriod event
                            if this.onPeriod.exists then
                                if allocated then
                                    if this.onPeriod() then
                                        call .terminate()
                                    endif
                                endif
                            endif

                            // onHit event
                            if this.onHit.exists then
                                if .collision > 0 and allocated then
                                    call GroupEnumUnitsInRange(hitGroup, coord.x, coord.y, .collision + COLLISION_SIZE, null)
                                    loop
                                        set u = FirstOfGroup(hitGroup)
                                        exitwhen u == null
                                            if not HaveSavedBoolean(table, this, GetHandleId(u)) then
                                                if IsUnitInRangeXY(u, coord.x, coord.y, .collision) then
                                                    call SaveBoolean(table, this, GetHandleId(u), true)
                                                    if this.onHit(u) then
                                                        call .terminate()
                                                        exitwhen true
                                                    endif
                                                endif
                                            endif
                                        call GroupRemoveUnit(hitGroup, u)
                                    endloop
                                endif
                            endif

                            // onDestructable event

                            set velocity = mspeed
                            set mspeed   = velocity + acceleration
                            set pitch    = coord.alpha

                            if distance + velocity >= coord.distance then
                                // onFinish event
                                if this.onFinish.exists then
                                    if allocated then
                                        if this.onFinish() then
                                            call .terminate()
                                        endif
                                    endif
                                endif

                                set dead = true
                                set point = coord.distance
                                set distance = distance + coord.distance - dist
                            else
                                set distance = distance + velocity
                                set point = dist + velocity
                            endif
                            set dist = point
                           
                            if target != null and GetUnitTypeId(target) != 0 then
                                call coord.moveImpact(GetUnitX(target), GetUnitY(target), GetUnitFlyHeight(target) + zoffset)
                            endif

                            set newX = coord.x + velocity*Cos(coord.angle)
                            set newY = coord.y + velocity*Sin(coord.angle)

                            // curved movement
                            if angle != 0. then
                                set velocity = 4*angle*point*(coord.distance - point)/coord.square
                                set a = angle + bj_PI/2
                                set newX = newX + velocity*Cos(a)
                                set newY = newY + velocity*Sin(a)
                                //set a = angle + Atan(-((4*angle)*(2*point - coord.distance))/coord.square)
                            endif

                            call MoveLocation(LOC, newX, newY)
                            set terrainZ = GetLocationZ(LOC)

                            // arced movement
                            if height == 0. and pitch == 0. then
                                set newZ = coord.z - terrainZ
                            else
                                set newZ = coord.z - terrainZ + coord.slope*point
                                if height != 0. then
                                    set newZ = newZ + (4*height*point*(coord.distance - point)/coord.square)
                                    //set pitch = pitch - Atan(((4*height)*(2*point - coord.distance))/coord.square)*bj_RADTODEG
                                endif
                            endif
   
                            call effect.orient(newX - coord.x, newY - coord.y, newZ - coord.z)
                            call effect.move(newX, newY, newZ)
                            set coord.x = newX
                            set coord.y = newY
                            set coord.z = newZ

                            // onTerrain event
                            if this.onTerrain.exists then
                                if coord.z < 0. and allocated then
                                    if this.onTerrain() then
                                        call .terminate()
                                    endif
                                endif
                            endif
                        endif
                    else
                        set i = remove(i)
                    endif
                set i = i + 1
            endloop
        endmethod

and this is the test code:
JASS:
scope MissilesTest
    private struct Beam extends Missiles
        method onPeriod takes nothing returns boolean
            call DisplayTextToPlayer(Player(0), 0, 0, "!")
            call DisplayTextToPlayer(Player(0), 0, 0, "X: " + R2S(.x))
            call DisplayTextToPlayer(Player(0), 0, 0, "Y: " + R2S(.y))
            call DisplayTextToPlayer(Player(0), 0, 0, "Z: " + R2S(.z))

            return false
        endmethod

        method onFinish takes nothing returns boolean
            call DestroyEffect(AddSpecialEffect("NetherBlast.mdx", .x, .y))

            return false
        endmethod
    endstruct

    private struct Test
        static method onCast takes nothing returns nothing
            local unit c     = GetTriggerUnit()
            local real fromX = GetUnitX(c)
            local real fromY = GetUnitY(c)
            local real fromZ = BlzGetUnitZ(c) + 60
            local real toX   = GetSpellTargetX()
            local real toY   = GetSpellTargetY()
            local real toZ   = fromZ
            local Beam beam  = Beam.new(fromX, fromY, fromZ, toX, toY, toZ)

            set beam.model = "Fel_Beam.mdx"
            set beam.speed = 1250
            set beam.scale = 0.5

            call beam.launch()
        endmethod

        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A00N', function thistype.onCast)
        endmethod
    endstruct
endscope

the full code so far:
JASS:
library Missiles requires optional WorldBounds
    globals
        public constant real PERIOD         = 1./32.
        public constant real COLLISION_SIZE = 128.
        private location     LOC            = Location(0., 0.)
    endglobals

    private interface Events
        method onHit takes unit hit returns boolean defaults false
        method onDestructable takes destructable dest returns boolean defaults false
        method onTerrain takes nothing returns boolean defaults false
        method onPeriod takes nothing returns boolean defaults false
        method onFinish takes nothing returns boolean defaults false
        method onRemove takes nothing returns nothing defaults nothing
    endinterface

    private struct Coordinates
        real x
        real y
        real z
        readonly real originX
        readonly real originY
        readonly real originZ
        readonly real impactX
        readonly real impactY
        readonly real impactZ
        readonly real angle
        readonly real distance
        readonly real square
        readonly real slope
        readonly real alpha

        /* -------------------------------- Operators ------------------------------- */
        // method operator z= takes real r returns nothing
        //     call MoveLocation(LOC, x, y)
        //     set z = r + GetLocationZ(LOC)
        // endmethod

        // method operator z takes nothing returns real
        //     call MoveLocation(LOC, x, y)
        //     return z - GetLocationZ(LOC)
        // endmethod

        private method calcParameters takes real dx, real dy returns nothing
            set .angle    = Atan2(dy, dx)
            set .square   = dx*dx + dy*dy
            set .distance = SquareRoot(.square)
            set .slope    = (.impactZ - originZ)/.distance
            set .alpha    = Atan(.slope)*bj_RADTODEG
        endmethod

        method moveImpact takes real newX, real newY, real newZ returns nothing
            set .impactX = newX
            set .impactY = newY
            call MoveLocation(LOC, .impactX, .impactX)
            set .impactZ = newZ + GetLocationZ(LOC)

            call calcParameters(newX - .originX, newY - .originY)
        endmethod
        /* ----------------------------------- end ---------------------------------- */

        /* -------------------------- Contructor/Destructor ------------------------- */
        method destroy takes nothing returns nothing
            call .deallocate()
        endmethod

        static method create takes real x, real y, real z, real tx, real ty, real tz returns thistype
            local thistype this = thistype.allocate()
            local real dx = tx - x
            local real dy = ty - y

            set .x        = x
            set .y        = y
            call MoveLocation(LOC, x, y)
            set .z        = z + GetLocationZ(LOC)
            set .originX  = x
            set .originY  = y
            set .originZ  = .z
            set .impactX  = tx
            set .impactY  = ty
            call MoveLocation(LOC, tx, ty)
            set .impactZ  = tz + GetLocationZ(LOC)
            call calcParameters(dx, dy)

            return this
        endmethod
        /* ----------------------------------- end ---------------------------------- */
    endstruct

    private struct Effect
        effect sfx
        string fxpath
        real   scale

        method setScale takes real scale returns nothing
            call BlzSetSpecialEffectScale(.sfx, scale)
        endmethod   

        method orient takes real x, real y, real z returns nothing
            local real norm
            local real yaw
            local real pitch
            local real roll
            local real N
            local real cp
       
            set norm = 1.00001*SquareRoot(x*x + y*y + z*z)
       
            if norm == 0 then
                return
            endif
       
            set x = x/norm
            set y = y/norm
            set z = z/norm
       
            set N = SquareRoot(x*x + y*y)
       
            if N == 0 then
                if z > 0 then
                    call BlzSetSpecialEffectOrientation(.sfx , 0 , -bj_PI/2 , 0)
                else
                    call BlzSetSpecialEffectOrientation(.sfx , 0 , bj_PI/2 , 0)
                endif
                return
            endif
       
            if y >= 0 then
                set pitch = -Asin(x*z/N)
                set cp = Cos(pitch)
                set roll = Asin(y*z/(N*cp))
                set yaw = Acos(x/cp)
            else
                set pitch = Asin(x*z/N) + bj_PI
                set cp = Cos(pitch)
                set roll = -Asin(y*z/(N*cp)) + bj_PI
                set yaw = Acos(x/cp)
            endif
       
            call BlzSetSpecialEffectOrientation(.sfx, yaw, pitch, roll)
        endmethod

        method move takes real x, real y, real z returns nothing
            static if not LIBRARY_WorldBounds then
                if not RectContainsCoords(bj_mapInitialPlayableArea, x, y) then
                    return
                endif
            elseif LIBRARY_WorldBounds then
                if not (x < WorldBounds.maxX and x > WorldBounds.minX and y < WorldBounds.maxY and y > WorldBounds.minY) then
                    return
                endif
            endif
            call BlzSetSpecialEffectPosition(.sfx, x, y, z)
        endmethod

        /* -------------------------- Contructor/Destructor ------------------------- */
        method destroy takes nothing returns nothing
            call DestroyEffect(.sfx)
            set .sfx    = null
            set .fxpath = null
            set .scale  = 1.
            call .deallocate()
        endmethod

        static method create takes real x, real y, real z returns thistype
            local thistype this = thistype.allocate()

            set .sfx = AddSpecialEffect("", x, y)
            call BlzSetSpecialEffectZ(.sfx, z)

            return this
        endmethod
        /* ----------------------------------- end ---------------------------------- */
    endstruct

    struct Missiles extends Events
        private static thistype array missiles
        private static integer        didx      = -1
        private static timer          t         = CreateTimer()
        private static hashtable      table     = InitHashtable()
        private static group          hitGroup  = CreateGroup()
        //----------------------------
        private real         dist
        //----------------------------
        unit                 source
        unit                 target
        player               owner
        integer              data
        real                 distance
        real                 mspeed
        real                 acceleration
        real                 collision
        real                 damage
        real                 zoffset
        readonly Coordinates coord
        readonly Effect      effect
        readonly boolean     dead
        readonly boolean     launched
        readonly boolean     allocated

        /* -------------------------------- Positions ------------------------------- */
        method operator x takes nothing returns real
            return coord.x
        endmethod

        method operator y takes nothing returns real
            return coord.y
        endmethod

        method operator z takes nothing returns real
            return coord.z 
        endmethod
        /* -------------------------- Model of the missile -------------------------- */
        method operator model= takes string fx returns nothing
            call DestroyEffect(effect.sfx)
            set effect.fxpath = fx
            set effect.sfx = AddSpecialEffect(fx, coord.x, coord.y)
            call BlzSetSpecialEffectZ(effect.sfx, coord.z)
        endmethod

        method operator model takes nothing returns string
            return effect.fxpath
        endmethod
        /* ----------------------------- Curved movement ---------------------------- */
        real angle
        method operator curve= takes real value returns nothing
            set angle = Tan(value)*coord.distance
        endmethod

        method operator curve takes nothing returns real
            return Atan(angle/coord.distance)
        endmethod
        /* ----------------------------- Arced Movement ----------------------------- */
        real height
        method operator arc= takes real value returns nothing
            set height = Tan(value)*coord.distance/4
        endmethod

        method operator arc takes nothing returns real
            return Atan(4*height/coord.distance)
        endmethod
        /* ------------------------------ Effect scale ------------------------------ */
        method operator scale= takes real value returns nothing
            set effect.scale = value
            call effect.setScale(value)
        endmethod

        method operator scale takes nothing returns real
            return effect.scale
        endmethod
        /* ------------------------------ Missile Speed ----------------------------- */
        method operator speed= takes real newspeed returns nothing
            set mspeed = newspeed*PERIOD
        endmethod

        method operator speed takes nothing returns real
            return mspeed
        endmethod
        /* ------------------------------- Flight Time ------------------------------ */
        method flightTime takes real duration returns nothing
            set mspeed = RMaxBJ(0.00000001, (coord.distance - dist)*PERIOD/RMaxBJ(0.00000001, duration))
        endmethod
        /* --------------------- Destroys the misisle instantly --------------------- */
        method terminate takes nothing returns nothing
            if allocated then
                // onRemove event
                static if this.onRemove.exists then
                    call this.onRemove()
                endif

                call FlushChildHashtable(table, this)
                set .dead      = true
                set .launched  = false
                set .allocated = false
            endif
        endmethod
        /* ------------------------------ Normal remove ----------------------------- */
        private method remove takes integer i returns integer
            call terminate()
            call effect.destroy()
            call coord.destroy()
            set missiles[i] = missiles[didx]
            set didx        = didx - 1

            if didx == -1 then
                call PauseTimer(t)
            endif

            call .deallocate()

            return i - 1
        endmethod

        private static method onLoop takes nothing returns nothing
            local integer  i = 0
            local thistype this
            local unit  u
            local real  a
            local real  newX
            local real  newY
            local real  newZ
            local real  velocity
            local real  point
            local real  pitch
            local real  terrainZ

            loop
                exitwhen i > didx
                    set this = missiles[i]

                    if not dead then
                        if launched then
                            // onPeriod event
                            if this.onPeriod.exists then
                                if allocated then
                                    if this.onPeriod() then
                                        call .terminate()
                                    endif
                                endif
                            endif

                            // onHit event
                            if this.onHit.exists then
                                if .collision > 0 and allocated then
                                    call GroupEnumUnitsInRange(hitGroup, coord.x, coord.y, .collision + COLLISION_SIZE, null)
                                    loop
                                        set u = FirstOfGroup(hitGroup)
                                        exitwhen u == null
                                            if not HaveSavedBoolean(table, this, GetHandleId(u)) then
                                                if IsUnitInRangeXY(u, coord.x, coord.y, .collision) then
                                                    call SaveBoolean(table, this, GetHandleId(u), true)
                                                    if this.onHit(u) then
                                                        call .terminate()
                                                        exitwhen true
                                                    endif
                                                endif
                                            endif
                                        call GroupRemoveUnit(hitGroup, u)
                                    endloop
                                endif
                            endif

                            // onDestructable event

                            set velocity = mspeed
                            set mspeed   = velocity + acceleration
                            set pitch    = coord.alpha

                            if distance + velocity >= coord.distance then
                                // onFinish event
                                if this.onFinish.exists then
                                    if allocated then
                                        if this.onFinish() then
                                            call .terminate()
                                        endif
                                    endif
                                endif

                                set dead = true
                                set point = coord.distance
                                set distance = distance + coord.distance - dist
                            else
                                set distance = distance + velocity
                                set point = dist + velocity
                            endif
                            set dist = point
                           
                            if target != null and GetUnitTypeId(target) != 0 then
                                call coord.moveImpact(GetUnitX(target), GetUnitY(target), GetUnitFlyHeight(target) + zoffset)
                            endif

                            set newX = coord.x + velocity*Cos(coord.angle)
                            set newY = coord.y + velocity*Sin(coord.angle)

                            // curved movement
                            if angle != 0. then
                                set velocity = 4*angle*point*(coord.distance - point)/coord.square
                                set a = angle + bj_PI/2
                                set newX = newX + velocity*Cos(a)
                                set newY = newY + velocity*Sin(a)
                                //set a = angle + Atan(-((4*angle)*(2*point - coord.distance))/coord.square)
                            endif

                            call MoveLocation(LOC, newX, newY)
                            set terrainZ = GetLocationZ(LOC)

                            // arced movement
                            if height == 0. and pitch == 0. then
                                set newZ = coord.z - terrainZ
                            else
                                set newZ = coord.z - terrainZ + coord.slope*point
                                if height != 0. then
                                    set newZ = newZ + (4*height*point*(coord.distance - point)/coord.square)
                                    //set pitch = pitch - Atan(((4*height)*(2*point - coord.distance))/coord.square)*bj_RADTODEG
                                endif
                            endif
   
                            call effect.orient(newX - coord.x, newY - coord.y, newZ - coord.z)
                            call effect.move(newX, newY, newZ)
                            set coord.x = newX
                            set coord.y = newY
                            set coord.z = newZ

                            // onTerrain event
                            if this.onTerrain.exists then
                                if coord.z < 0. and allocated then
                                    if this.onTerrain() then
                                        call .terminate()
                                    endif
                                endif
                            endif
                        endif
                    else
                        set i = remove(i)
                    endif
                set i = i + 1
            endloop
        endmethod

        private method reset takes nothing returns nothing
            set source       = null
            set target       = null
            set owner        = null
            set data         = 0
            set angle        = 0
            set height       = 0
            set distance     = 0
            set mspeed       = 0
            set acceleration = 0
            set collision    = 0
            set damage       = 0
            set zoffset      = 0
            set dead         = false
            set launched     = false
        endmethod

        method launch takes nothing returns nothing
            if not launched and not dead then
                set launched = true
            endif
        endmethod
        /* --------------------------- Main Creator method -------------------------- */
        static method create takes real x, real y, real z, real angle, real distance, real impactZ returns thistype
            local thistype this = thistype.allocate()
            local real impactX = x + distance*Cos(angle)
            local real impactY = y + distance*Sin(angle)
            //--------------------------------------------

            call reset()
            set coord          = Coordinates.create(x, y, z, impactX, impactY, impactZ)
            set effect         = Effect.create(x, y, z)
            set allocated      = true
            set didx           = didx + 1
            set missiles[didx] = this

            if didx == 0 then
                call TimerStart(t, PERIOD, true, function thistype.onLoop)
            endif

            return this
        endmethod
        /* -------------------------------- Wrappers -------------------------------- */
        static method new takes real fromX, real fromY, real fromZ, real toX, real toY, real toZ returns thistype
            local real dx = toX - fromX
            local real dy = toY - fromY
            local real dz = toZ - fromZ

            return create(fromX, fromY, fromZ, Atan2(dy, dx), SquareRoot(dx*dx + dy*dy), toZ)
        endmethod

        static method newEx takes real fromX, real fromY, real fromZ, real angle, real distance, real toZ returns thistype
            return create(fromX, fromY, fromZ, angle, distance, toZ)
        endmethod

        static method newHoming takes real fromX, real fromY, real fromZ, unit target, real zoffset returns thistype
            return new(fromX, fromY, fromZ, GetUnitX(target), GetUnitY(target), GetUnitFlyHeight(target) + zoffset)
        endmethod
    endstruct
endlibrary

If someone know why the "exists" conditional do not works plz tell me.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
interfaces are for polymorphism so it is meant to be extended by different structs that would have same set of actions (but different implementation of those actions), which in this case, should be the structs where you put your onHit(), onDestructable(), etc. The Missile struct does not need to extend the interface Event.
The .exists check always returns false in your Missile struct since it can't find those methods declared in the struct. Extending an interface does not create a declaration of those methods in the interface in your struct, even with the defaults keyword. Btw you can use static ifs for .exists .

What I think would be better to use here based on what you're trying to achieve is not interfaces but stub methods.
JASS:
struct Missile
    ...
    stub method onHit takes unit u returns boolean
        // If extending structs have no onHit() declaration themselves, this method itself will be called.
        return false
    endmethod

    private static method onLoop takes nothing returns nothing
        ...
        loop
            ...
            // onHit event
            if .collision > 0 and allocated then
                call GroupEnumUnitsInRange(hitGroup, coord.x, coord.y, .collision + COLLISION_SIZE, null)
                loop
                    set u = FirstOfGroup(hitGroup)
                    exitwhen u == null
                    if not HaveSavedBoolean(table, this, GetHandleId(u)) then
                        if IsUnitInRangeXY(u, coord.x, coord.y, .collision) then
                            call SaveBoolean(table, this, GetHandleId(u), true)
                            if this.onHit(u) then
                                call .terminate()
                                exitwhen true
                            endif
                        endif
                    endif
                    call GroupRemoveUnit(hitGroup, u)
                endloop
            endif
        endloop
    endmethod
    ...
endstruct

// User struct
struct Beam extends Missile
    method onHit takes unit u returns boolean
        if TargetFilter(u) then
            ...
            return true
        endif
        return false
    endmethod
endstruct

private struct Test
    static method onCast takes nothing returns nothing
        local unit c     = GetTriggerUnit()
        local real fromX = GetUnitX(c)
        local real fromY = GetUnitY(c)
        local real fromZ = BlzGetUnitZ(c) + 60
        local real toX   = GetSpellTargetX()
        local real toY   = GetSpellTargetY()
        local real toZ   = fromZ
        local Beam beam  = Beam.new(fromX, fromY, fromZ, toX, toY, toZ)

        set beam.model = "Fel_Beam.mdx"
        set beam.speed = 1250
        set beam.scale = 0.5

        call beam.launch()
    endmethod

    static method onInit takes nothing returns nothing
        call RegisterSpellEffectEvent('A00N', function thistype.onCast)
    endmethod
endstruct
 
Last edited:
Level 20
Joined
May 16, 2012
Messages
635
interfaces are for polymorphism so it is meant to be extended by different structs that would have same set of actions (but different implementation of those actions), which in this case, should be the structs where you put your onHit(), onDestructable(), etc. The Missile struct does not need to extend the interface Event.
The .exists check always returns false in your Missile struct since it can't find those methods declared in the struct. Extending an interface does not create a declaration of those methods in the interface in your struct, even with the defaults keyword. Btw you can use static ifs for .exists .

What I think would be better to use here based on what you're trying to achieve is not interfaces but stub methods.
JASS:
struct Missile
    ...
    stub method onHit takes unit u returns boolean
        // If extending structs have no onHit() declaration themselves, this method itself will be called.
        return false
    endmethod

    private static method onLoop takes nothing returns nothing
        ...
        loop
            ...
            // onHit event
            //if this.onHit.exists then -> no need
            if .collision > 0 and allocated then
                call GroupEnumUnitsInRange(hitGroup, coord.x, coord.y, .collision + COLLISION_SIZE, null)
                loop
                    set u = FirstOfGroup(hitGroup)
                    exitwhen u == null
                    if not HaveSavedBoolean(table, this, GetHandleId(u)) then
                        if IsUnitInRangeXY(u, coord.x, coord.y, .collision) then
                            call SaveBoolean(table, this, GetHandleId(u), true)
                            if this.onHit(u) then
                                call .terminate()
                                exitwhen true
                            endif
                        endif
                    endif
                    call GroupRemoveUnit(hitGroup, u)
                endloop
            endif
        endloop
    endmethod
    ...
endstruct

// User struct
struct Beam extends Missile
    method onHit takes unit u returns boolean
        if TargetFilter(u) then
            ...
            return true
        endif
        return false
    endmethod
endstruct

private struct Test
    static method onCast takes nothing returns nothing
        local unit c     = GetTriggerUnit()
        local real fromX = GetUnitX(c)
        local real fromY = GetUnitY(c)
        local real fromZ = BlzGetUnitZ(c) + 60
        local real toX   = GetSpellTargetX()
        local real toY   = GetSpellTargetY()
        local real toZ   = fromZ
        local Beam beam  = Beam.new(fromX, fromY, fromZ, toX, toY, toZ)

        set beam.model = "Fel_Beam.mdx"
        set beam.speed = 1250
        set beam.scale = 0.5

        call beam.launch()
    endmethod

    static method onInit takes nothing returns nothing
        call RegisterSpellEffectEvent('A00N', function thistype.onCast)
    endmethod
endstruct

I think I got it, but its weird, because xemissile by Vexorian use interfaces and the missile struct extends it without implementing the methods, also the structs created extends xemissile and not the event handler interface. I don't want to use stub methods because I want to keep BPower's idea of only running a piece of code when required(implemented) cuz it can save performance. But thx anyway!
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Xe's main missile struct does not implement the interface methods since it does not need a default action for collisions but the reason his works is because it does not check if those interface methods exists. Yours should also work if you remove the .exists check.
 
Last edited:
JASS:
interface MissileEvents
    method onHit takes unit hit returns boolean defaults false
    method onDestructable takes destructable dest returns boolean defaults false
    method onTerrain takes nothing returns boolean defaults false
    method onPeriod takes nothing returns boolean defaults false
    method onFinish takes nothing returns boolean defaults false
    method onRemove takes nothing returns nothing defaults nothing
endinterface

struct Missile extends MissileEvents

    private static hashtable table = InitHashtable()
    private static method onLoop takes nothing returns nothing
        local thistype this = LoadInteger(table, 0, 0)
     
        if ( this.onHit.exists ) then
            call this.onHit(null)
        else
            call BJDebugMsg("onHit does not exist")
        endif
        if ( this.onDestructable.exists ) then
            call this.onDestructable(null)
        else
            call BJDebugMsg("onDestructable does not exist")
        endif
    endmethod
 
    static method create takes nothing returns thistype
        local thistype this = allocate()
        local timer clock = CreateTimer()
        call SaveInteger(table, 0, 0, this)
        call TimerStart(clock, 1, true, function thistype.onLoop)
        return this
    endmethod
endstruct

struct DemoMissile extends Missile
    method onHit takes unit hit returns boolean
        call BJDebugMsg("struct DemoMissile onHit")
        return true
    endmethod

    private static method onInit takes nothing returns nothing
        call create()
    endmethod
endstruct
For me it prints:
  • struct DemoMissile onHit
  • onDestructable does not exist
 
I guess:
  • "new" "newEx" etc are statics from Missile base struct. They will call the "create()" from Missile struct.
  • when "beam.new()" is called, it will call the static from base struct Missile. => "create()" from Beam is actually never called.
Methods that are also implemented in derieved struct need to be called for object creation. stub methods of non-static methods new/newEx etc might do it, without much design change, but dunno maybe you would want something different.
 
Your child struct needs to be allocated as Beam, not as Missile for this to work. In your case, the 'new' method allocates a Missile type, not a Beam type.

Normally when you use extends and absolutely need the parents creator to take parameters, the best solution is to write a new create method for every child.
When you redeclared create taking parameters on the parent, you can pass them directly to the child allocator like this:

JASS:
struct A

integer test

static method create takes integer param returns thistype
    local thistype this = thistype.allocate()
    set this.test = param
    return this
endmethod

endstruct

struct B extends A

static method create takes integer param returns thistype
    //the child's allocate method will take over the parameters of the parent's create method:
    local thistype this = thistype.allocate(param)
    return this
endmethod

endstruct

See the difference?

The childs creator calls allocate on its own type, but the allocate method of child is actually just the parent's create in disguise, taking the same arguments.
 
Last edited:
Level 6
Joined
Mar 7, 2011
Messages
124
i think Icemanbo and Zwie are right. i think you're going to have to drop the alternate constructors from your base class entirely. i understand the appeal from a syntax perspective, but i don't think you can make them work with the JASS language. you need to maintain inheritance via create because youre trying to leverage the compiler's implicit capabilities around polymorphic interfaces, which includes the create chain. you could move away from this dependency on the compiler by adding your own Event abstraction, but then you'd lose a lot of the syntax and compiler-level performance that appeals with your implementation

the inheritance chain should look like:
instantiating child.create -> child.allocate -> parent.create -> parent.allocate -> etc
your current chain is:
instantiating child.create -> parent.new -> parent.create -> parent.allocate

your specific issue is that having child.create call child.allocate is not compatible with having child.create call parent.new. i like the suggestion Zwie gave, and you can see Vex had the same problem in his xemissile implementation
JASS:
struct xemissile extends missile
        public static method create takes real x,real y,real z, real tx,real ty,real tz returns xemissile
         local xemissile xm = xemissile.allocate(x,y,z, Atan2(ty-y,tx-x))
            call xm.setTargetPoint(tx,ty,tz)
         return xm
        endmethod
    endstruct

    struct xehomingmissile extends xemissile
        public static method create takes real x,real y,real z, unit target,real zoffset returns xehomingmissile
         local xehomingmissile xm = xehomingmissile.allocate(x,y,z, GetUnitX(target), GetUnitY(target), 0.0)
            set xm.zoffset=zoffset
            set xm.targetUnit=target
         return xm
        endmethod
    endstruct
 
Last edited:
Level 20
Joined
May 16, 2012
Messages
635
i think Icemanbo and Zwie are right. i think you're going to have to drop the alternate constructors from your base class entirely. i understand the appeal from a syntax perspective, but i don't think you can make them work with the JASS language. you need to maintain inheritance via create because youre trying to leverage the compiler's implicit capabilities around polymorphic interfaces, which includes the create chain. you could move away from this dependency on the compiler by adding your own Event abstraction, but then you'd lose a lot of the syntax and compiler-level performance that appeals with your implementation

the inheritance chain should look like:
instantiating child.create -> child.allocate -> parent.create -> parent.allocate -> etc
your current chain is:
instantiating child.create -> parent.new -> parent.create -> parent.allocate

your specific issue is that having child.create call child.allocate is not compatible with having child.create call parent.new. i like the suggestion Zwie gave, and you can see Vex had the same problem in his xemissile implementation
JASS:
struct xemissile extends missile
        public static method create takes real x,real y,real z, real tx,real ty,real tz returns xemissile
         local xemissile xm = xemissile.allocate(x,y,z, Atan2(ty-y,tx-x))
            call xm.setTargetPoint(tx,ty,tz)
         return xm
        endmethod
    endstruct

    struct xehomingmissile extends xemissile
        public static method create takes real x,real y,real z, unit target,real zoffset returns xehomingmissile
         local xehomingmissile xm = xehomingmissile.allocate(x,y,z, GetUnitX(target), GetUnitY(target), 0.0)
            set xm.zoffset=zoffset
            set xm.targetUnit=target
         return xm
        endmethod
    endstruct

yeah, I end up using just the create method, but thanks all for the help!
 
Status
Not open for further replies.
Top