HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Question about Spell Triggering with xecollider

10-05-2010, 04:26 PM#1
Quillraven
Hi,

i have some questions about xecollider usage. i created a spell that summons missiles around a target location which are homing towards the center. enemy units hit by a missile take damage once and are added to a hittarget group. each unit within this group is pulled towards the center.

my questions now:
- what is the best/a good way to deal with this hittarget unitgroup that is shared through all missileinstances?
i created a "groupsemaphor" to count the references on the shared group and release it, when no more reference is acquiring the resource.

- is there a better way for the unit pulling?
i loop thourgh all units each xeanimationperiod, calculate the angle of the unit between the center and its position and move it then towards the center.

- is there anything else that i can do better/easier than i m doing it now?

Hidden information:

Collapse JASS:
scope WaterImplosion initializer init

globals
    private constant integer SPELL_ID       = 'A006'
    private constant string MISSILE_PATH    = "Abilities\\Weapons\\WaterElementalMissile\\WaterElementalMissile.mdl"
    private constant string EFFECT_AOE      = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
    private constant string EFFECT_TARGET   = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
    private constant real MISSILE_SCALE     = 1.5
    private constant real MISSILE_SPEED     = 550.0
    private constant integer COLL_SIZE      = 50
    private constant integer MAX_MISSILES   = 12
    
    private integer array DAMAGE
    private real array DAM_PER_INT
    private integer array RADIUS
    private xedamage XEDAM                  = 0
endglobals

private function SetupValues takes nothing returns nothing
    set XEDAM = xedamage.create()
    
    set XEDAM.damageNeutral = false
    set XEDAM.dtype = DAMAGE_TYPE_COLD
    set XEDAM.exception = UNIT_TYPE_MAGIC_IMMUNE
    call XEDAM.factor( UNIT_TYPE_STRUCTURE, 0 )
    call XEDAM.useSpecialEffect( EFFECT_TARGET, "origin" )
    
    set DAMAGE[0] = 30
    set DAMAGE[1] = 40
    set DAMAGE[2] = 50
    set DAMAGE[3] = 60
    set DAMAGE[4] = 70
    
    set DAM_PER_INT[0] = 0.75
    set DAM_PER_INT[1] = 0.75
    set DAM_PER_INT[2] = 0.80
    set DAM_PER_INT[3] = 0.80
    set DAM_PER_INT[4] = 0.80
    
    set RADIUS[0] = 350
    set RADIUS[1] = 350
    set RADIUS[2] = 375
    set RADIUS[3] = 375
    set RADIUS[4] = 400
endfunction

// end of user configuration





// all missiles use the same group
// to avoid multiple times damage on one target
// each missile instance calls the acquire method to increase the reference counter by 1
// each missile instance calls the release method on destruction to decrease the reference counter by 1
// if the counter reaches 0 the group will be released
private struct GroupSemaphor
    group g
    integer references
    
    method acquire takes nothing returns nothing
        set .references = .references + 1
    endmethod
    
    method release takes nothing returns nothing
        set .references = .references - 1
        if( .references <= 0 )then
            call .destroy()
        endif
    endmethod
    
    static method create takes nothing returns GroupSemaphor
        local GroupSemaphor this = GroupSemaphor.allocate()
        
        set .g = NewGroup()
        set .references = 0
        
        return this
    endmethod
    
    method onDestroy takes nothing returns nothing
        call ReleaseGroup( .g )
        set .references = 0
    endmethod
endstruct

// each missile deals damage to enemy targets
// the primary missile with the missileid zero creates the special effect at the end of the spell
// and pulls all targets towards the center of the spell
private struct Missile extends xecollider
    unit caster
    integer lvl
    integer mid // missile id
    GroupSemaphor pgroup
    real range
    real targetX
    real targetY
    static Missile tempthis
    
    // in the group callback method we pull all targets that are still
    // in range of the spell towards the center of the spell
    static method group_callback takes nothing returns nothing
        local unit u = GetEnumUnit()
        local real angle = 0
        local real x = 0
        local real y = 0
        local real offset = 0
        
        if( IsUnitInRangeXY( u, .tempthis.targetX, .tempthis.targetY, .tempthis.range ) )then
            set x = GetUnitX( u )
            set y = GetUnitY( u )
            set offset = .tempthis.speed * XE_ANIMATION_PERIOD
            set angle = Atan2( .tempthis.targetY - y, .tempthis.targetX - x )
            call SetUnitX( u, GetUnitX( u ) + offset * Cos( angle ) )
            call SetUnitY( u, GetUnitY( u ) + offset * Sin( angle ) )
        endif
        
        set u = null
    endmethod
    
    // when an enemy unit is hit, we deal some damage to it
    // and at it to the hittargets group
    method onUnitHit takes unit hitTarget returns nothing
        if( .caster != null and XEDAM.allowedTarget( .caster, hitTarget ) )then
            if( not IsUnitInGroup( hitTarget, .pgroup.g ) )then
                call XEDAM.damageTarget( .caster, hitTarget, DAMAGE[.lvl-1] + DAM_PER_INT[.lvl-1]*GetHeroInt( .caster, true ) )
                call GroupAddUnit( .pgroup.g, hitTarget )
            endif
        endif
    endmethod
    
    // we periodically update the remaining range of each missile
    // to destroy them, when they reach the target location
    method loopControl takes nothing returns nothing
        set .range = .range - .speed*XE_ANIMATION_PERIOD
        if( .range <= 0 )then
            call .terminate()
        elseif( .mid == 0 )then
            set .tempthis = this
            call ForGroup( .pgroup.g, function Missile.group_callback )
        endif
    endmethod
    
    static method create takes unit c, real x, real y, real tx, real ty, integer level, integer missileID, GroupSemaphor g returns Missile
        local real angle = Atan2( ty-y, tx-x )
        local Missile this = Missile.allocate( x, y, angle )
        
        set .caster = c
        set .lvl = level
        set .mid = missileID
        set .range = RADIUS[.lvl-1]
        set .targetX = tx
        set .targetY = ty
        set .pgroup = g
        call .pgroup.acquire()
        
        set .collisionSize = COLL_SIZE
        set .scale = MISSILE_SCALE
        set .fxpath = MISSILE_PATH
        set .speed = MISSILE_SPEED
        set .angleSpeed = 2*bj_PI
        set .owner = GetOwningPlayer( c )
        call .setTargetPoint( tx, ty )
        
        return this
    endmethod
    
    method onDestroy takes nothing returns nothing
        if( .mid == 0 )then
            call DestroyEffect( AddSpecialEffect( EFFECT_AOE, .x, .y ) )
        endif
        call .pgroup.release()
    endmethod
endstruct

private function Conditions takes nothing returns boolean
    local integer i = 0
    local real angle = 0
    local unit caster = null
    local real tx = 0
    local real ty = 0
    local integer lvl = 0
    local GroupSemaphor g = 0
        
    if( GetSpellAbilityId() == SPELL_ID ) then
        set caster = GetTriggerUnit()
        set tx = GetSpellTargetX()
        set ty = GetSpellTargetY()
        set lvl = GetUnitAbilityLevel( caster, SPELL_ID )
        set angle = 2*bj_PI/MAX_MISSILES
        set g = GroupSemaphor.create()

        // create the missiles in a circle around the target location
        loop
            exitwhen i >= MAX_MISSILES
            call Missile.create( caster, tx + (RADIUS[lvl-1]-COLL_SIZE) * Cos( angle*i ), ty + (RADIUS[lvl-1]-COLL_SIZE) * Sin( angle*i ), tx, ty, lvl, i, g )
            set i = i + 1
        endloop
        
        set caster = null
    endif
    return false
endfunction

private function init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    
    call SetupValues()
    call DestroyEffect( AddSpecialEffect( MISSILE_PATH, 0, 0 ) )
    call DestroyEffect( AddSpecialEffect( EFFECT_AOE, 0, 0 ) )
    call DestroyEffect( AddSpecialEffect( EFFECT_TARGET, 0, 0 ) )
    call XE_PreloadAbility( SPELL_ID )
    
    call TriggerAddCondition( trig, function Conditions )
    call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
endfunction

endscope

10-05-2010, 04:37 PM#2
DioD
it can be done better only with native jass, all vJass extension compile into mess.
10-06-2010, 12:33 PM#3
Anitarf
It seems to me that it would make more sense in terms of code organization if your spell trigger created the "semaphor" object which would then itself create the missiles as well as move the units that the missiles hit towards the centre. The code wouldn't be significantly different from what you have now, just differently organised.
10-07-2010, 07:10 AM#4
Quillraven
Quote:
it can be done better only with native jass, all vJass extension compile into mess.
is this a serious comment? i don't get it at all


Quote:
It seems to me that it would make more sense in terms of code organization if your spell trigger created the "semaphor" object which would then itself create the missiles as well as move the units that the missiles hit towards the centre. The code wouldn't be significantly different from what you have now, just differently organised.

i coded it that way with some of my older spells. you then don't need the groupsemaphor since the spellstruct can contain the one group and you can save f.e. the spellinstance within the missilestruct to get accessto the group.

however this needs f.e. private keyword and makes the code (to me) more unreadable since there is no clean "code splitting" like you'd have it in java or c++.


another thing is, that xecollider misses some methods imo. first of all, when you take the code from the first post and set the missile speed to 600 and make a check like
Code:
 IsUnitInRangeXY( xefxdummy, targetx, targety, COLLSIZE )
the check will never come true. seems like a problem of the xecollider code when the missile has an anglespeed.
second, since you have no access to the xefxdummy, i tried the following:
Code:
if( x >= targetx-collsize and x <= targetx+collsize and
   y >= targety-collsize and y <= targety+collsize )
same problem again.

the actuale working code looks like this. i used a lifetime now to destroy the missiles but imo thats not very accurate and nice:
Hidden information:

Collapse JASS:
scope WaterImplosion initializer init

globals
    private constant integer SPELL_ID       = 'A006'
    private constant string MISSILE_PATH    = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveMissile.mdl"
    private constant string EFFECT_AOE      = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
    private constant string EFFECT_TARGET   = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
    private constant real MISSILE_SCALE     = 0.35
    private constant real MISSILE_TIME      = 0.80  // life time of each missile
    private constant real MISSILE_SPEED     = 600.0
    private constant integer COLL_SIZE      = 50
    private constant integer MAX_MISSILES   = 8
    
    private integer array DAMAGE
    private real array DAM_PER_INT
    private integer array RADIUS
    private xedamage XEDAM                  = 0
endglobals

private function SetupValues takes nothing returns nothing
    set XEDAM = xedamage.create()
    
    set XEDAM.damageNeutral = false
    set XEDAM.dtype = DAMAGE_TYPE_COLD
    set XEDAM.exception = UNIT_TYPE_MAGIC_IMMUNE
    call XEDAM.factor( UNIT_TYPE_STRUCTURE, 0 )
    call XEDAM.useSpecialEffect( EFFECT_TARGET, "origin" )
    
    set DAMAGE[0] = 30
    set DAMAGE[1] = 40
    set DAMAGE[2] = 50
    set DAMAGE[3] = 60
    set DAMAGE[4] = 70
    
    set DAM_PER_INT[0] = 0.75
    set DAM_PER_INT[1] = 0.75
    set DAM_PER_INT[2] = 0.80
    set DAM_PER_INT[3] = 0.80
    set DAM_PER_INT[4] = 0.80
    
    set RADIUS[0] = 350
    set RADIUS[1] = 350
    set RADIUS[2] = 375
    set RADIUS[3] = 375
    set RADIUS[4] = 400
endfunction

// end of user configuration





// each missile deals damage to enemy targets
// the primary missile with the missileid zero creates the special effect at the end of the spell
// and pulls all targets towards the center of the spell
private struct Missile extends xecollider
    unit caster
    integer lvl
    integer mid // missile id
    GroupSemaphor pgroup
    real range
    real targetX
    real targetY
    static Missile tempthis
    
    // in the group callback method we pull all targets that are still
    // in range of the spell towards the center of the spell
    static method group_callback takes nothing returns nothing
        local unit u = GetEnumUnit()
        local real angle = 0
        local real x = 0
        local real y = 0
        local real offset = 0
        
        if( IsUnitInRangeXY( u, .tempthis.targetX, .tempthis.targetY, .tempthis.range ) )then
            set x = GetUnitX( u )
            set y = GetUnitY( u )
            set offset = .tempthis.speed * XE_ANIMATION_PERIOD
            set angle = Atan2( .tempthis.targetY - y, .tempthis.targetX - x )
            call SetUnitX( u, GetUnitX( u ) + offset * Cos( angle ) )
            call SetUnitY( u, GetUnitY( u ) + offset * Sin( angle ) )
        endif
        
        set u = null
    endmethod
    
    // when an enemy unit is hit, we deal some damage to it
    // and add it to the hittargets group
    method onUnitHit takes unit hitTarget returns nothing
        if( .caster != null and XEDAM.allowedTarget( .caster, hitTarget ) and not IsUnitInGroup( hitTarget, .pgroup.g ) )then
            call XEDAM.damageTarget( .caster, hitTarget, DAMAGE[.lvl-1] + DAM_PER_INT[.lvl-1]*GetHeroInt( .caster, true ) )
            call GroupAddUnit( .pgroup.g, hitTarget )
        endif
    endmethod
    
    // we periodically update the remaining range of each missile
    // to pull enemy units correctly
    method loopControl takes nothing returns nothing
        local real dx = .x - .targetX
        local real dy = .y - .targetY
        
        set .range = SquareRoot( dx*dx + dy*dy )
        if( .mid == 0 )then
            set .tempthis = this
            call ForGroup( .pgroup.g, function Missile.group_callback )
        endif
    endmethod
    
    static method create takes unit c, real x, real y, real tx, real ty, integer level, integer missileID, GroupSemaphor g returns Missile
        local real angle = Atan2( ty-y, tx-x )
        local Missile this = Missile.allocate( x, y, angle + bj_PI/2 )
        
        set .caster = c
        set .lvl = level
        set .mid = missileID
        set .range = RADIUS[.lvl-1]
        set .targetX = tx
        set .targetY = ty
        set .pgroup = g
        call .pgroup.acquire()
        
        set .collisionSize = COLL_SIZE
        set .scale = MISSILE_SCALE
        set .fxpath = MISSILE_PATH
        set .speed = MISSILE_SPEED
        set .expirationTime = MISSILE_TIME
        set .angleSpeed = bj_PI
        set .owner = GetOwningPlayer( c )
        call .setTargetPoint( tx, ty )
        
        return this
    endmethod
    
    method onDestroy takes nothing returns nothing
        if( .mid == 0 )then
            call DestroyEffect( AddSpecialEffect( EFFECT_AOE, .targetX, .targetY ) )
        endif
        call .pgroup.release()
    endmethod
endstruct

private function Conditions takes nothing returns boolean
    local integer i = 0
    local real angle = 0
    local unit caster = null
    local real tx = 0
    local real ty = 0
    local integer lvl = 0
    local GroupSemaphor g = 0
        
    if( GetSpellAbilityId() == SPELL_ID ) then
        set caster = GetTriggerUnit()
        set tx = GetSpellTargetX()
        set ty = GetSpellTargetY()
        set lvl = GetUnitAbilityLevel( caster, SPELL_ID )
        set angle = 2*bj_PI/MAX_MISSILES
        set g = GroupSemaphor.create()

        // create the missiles in a circle around the target location
        loop
            exitwhen i >= MAX_MISSILES
            call Missile.create( caster, tx + (RADIUS[lvl-1]-COLL_SIZE) * Cos( angle*i ), ty + (RADIUS[lvl-1]-COLL_SIZE) * Sin( angle*i ), tx, ty, lvl, i, g )
            set i = i + 1
        endloop
        
        set caster = null
    endif
    return false
endfunction

private function init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    
    call SetupValues()
    call DestroyEffect( AddSpecialEffect( EFFECT_AOE, 0, 0 ) )
    call DestroyEffect( AddSpecialEffect( EFFECT_TARGET, 0, 0 ) )
    call XE_PreloadAbility( SPELL_ID )
    
    call TriggerAddCondition( trig, function Conditions )
    call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
endfunction

endscope



another question:
is it possible to create a rotating missile with xecollider that rotates around unit u with an offset range of X and rotates around the unit 4 times a second?

i saw a rotating method within the xecollider library but there are no comments and when i checked out the periodicmovementmethod i found this
Code:
if (this.angleMode == ANGLE_ROTATING)  then
    //nothing (ns is already ns)
elseif( this.angleMode != ANGLE_NO_MOVEMENT) then 
so there is no angle_rotating possible yet?
10-07-2010, 08:38 AM#5
Tot
Quote:
Originally Posted by Quillraven
is this a serious comment? i don't get it at all

ever looked into the output of jh? 25-50% of it are useless...


Quote:
Originally Posted by Quillraven
Code:
if (this.angleMode == ANGLE_ROTATING)  then
    //nothing (ns is already ns)
elseif( this.angleMode != ANGLE_NO_MOVEMENT) then 
so there is no angle_rotating possible yet?
extend xecollidier yourself and it's possible...