• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[vJASS] Spells + Dummies + FlyHeight + Terrain Deformations = Weird unit z's

Status
Not open for further replies.
Level 4
Joined
Sep 25, 2005
Messages
71
FIXED: Set dummy unit movement type to hover, they now work appropriately

Alright. I'm getting there, but something new has come up. Whenever I have a spell which involves moving a dummy unit, and there's a change in Z, I had it set to change the unit's Z relative to the ground. This works for spells I want traveling along a line well enough, and especially so for ones with special fx (though theoretically if I need a z of fx I could deploy dummy units, attach the special fx with it as the target, etc.)... But now I've got sort of another issue.

One of my spells had uses of lightning effects on top of it. It would create a cone of damaging lightning. It works and for the most part looks fine (there's an issue wherein the Z for a terrain drop/raise seems wayyyyyy too low/high to the point it coasts into the ground instead of staying the desired amount of units above in Z), but as I show in the video, whenever there's some form of raised/lowered ground that isn't a terrain height change, the z between the units and the lightning effects changes in a way that is different an definitely not as I intended.

[youtube]
http://www.youtube.com/watch?v=vj0A5fajVyU
[/youtube]

The method I had for setting a "constant" fly height for the projectiles relative to the ground was using a GetLocationZ that used a location created from the x/y values of the dummy unit:

JASS:
    static method LightningConeExecution takes nothing returns nothing
        local integer i = 0
        local integer ballcount = 0
        local unit enemy
        local effect collisionFX
        local location zLoc
        local real array z
        local LightningConeData localLightningCone
        loop
            exitwhen i >= LightningConeInstances
            set localLightningCone = LightningConeArray[i]
            if localLightningCone.distance < localLightningCone.maxDist then
            if localLightningCone.transparency > 0. and (localLightningCone.distance / localLightningCone.maxDist) > .85 then
                set localLightningCone.transparency = localLightningCone.transparency - .20
            endif
            loop
                exitwhen ballcount == 6    
                
                    call MoveUnit(localLightningCone.balls[ballcount], LightningConeSpeed, GetUnitFacing(localLightningCone.balls[ballcount]))
// Stuff that matters is here
                    set zLoc = Location(GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]))
                    set z[ballcount] = GetLocationZ(zLoc) + 36.
                    call SetUnitFlyHeight(localLightningCone.balls[ballcount], z[ballcount], 0.)
                    if (localLightningCone.distance / localLightningCone.maxDist) > .60 then
                        call SetUnitVertexColor(localLightningCone.balls[ballcount], 255, 255, 255, R2I(localLightningCone.transparency*255))
                    endif    
                    if ballcount != 6 then
                        call MoveLightningEx(localLightningCone.beams[ballcount], true, GetUnitX(localLightningCone.balls[ballcount-1]), GetUnitY(localLightningCone.balls[ballcount-1]), z[ballcount-1], GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]), z[ballcount])
                    endif
                    if (localLightningCone.distance / localLightningCone.maxDist) > .60 then
                        call SetLightningColor(localLightningCone.beams[ballcount], 100., 100., 100.,localLightningCone.transparency)
                    endif
                    call GroupEnumUnitsInRange(LightningConeEnumGroup, GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]), 128., null)
                    loop
                        set enemy = FirstOfGroup(LightningConeEnumGroup)
                        exitwhen enemy == null
                        if ValidAliveTargetGroupedGround(enemy, localLightningCone.caster, localLightningCone.LightningConeTargets) == true then
                            call GroupAddUnit(localLightningCone.LightningConeTargets, enemy)
                            call UnitDamageTarget(localLightningCone.caster, enemy, 50., true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, null)
                            set collisionFX = AddSpecialEffectTarget("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", enemy, "chest")
                            call DestroyEffect(collisionFX)
                        endif
                        call GroupRemoveUnit(LightningConeEnumGroup, enemy)
                    endloop
                                        
                set ballcount = ballcount + 1
            endloop
                set ballcount = 0
                set localLightningCone.distance = localLightningCone.distance + LightningConeSpeed
            endif
            
            if localLightningCone.distance >= localLightningCone.maxDist then
                set LightningConeInstances = LightningConeInstances - 1
                set LightningConeArray[i] = LightningConeArray[LightningConeInstances]
                call DestroyGroup(localLightningCone.LightningConeTargets)
                call localLightningCone.destroy()
            endif		
            set collisionFX = null
            set zLoc = null
            set i = i + 1
        endloop

        if LightningConeInstances == 0 then
            call PauseTimer(LightningConeTimer)
        endif
    endmethod

If there's a better way to keep a projectile above the ground at a certain height above the ground I'd appreciate some insight. I was even considering just dismissing the idea of it changing in height in favor of it affecting units above/below but simply coasting on at the appropriate height of the casting terrain.

In regards to the other spells, such an implementation would be easy (I think I mentioned it above:) they simply create special fx based on the x/y of the dummy projectile unit, and I could create a unit with proper attachment points (origin,) give it the moving properties of the projectile dummy, and attach it there before killing it, but here I have to manage lightning effects.

Anyways, if anyone's got some advice, or there's something critical about the spell in its current form (mostly I don't want leaks,) let me know.
 

Attachments

  • MvMAbilityDump.w3x
    50.5 KB · Views: 52
Last edited:
Level 4
Joined
Sep 25, 2005
Messages
71
use a projectile system on the hive.

Coding my own spells, not much to learn from copy/pasting others'.

Either way, I posted the map, I posted the part that matters, but for the sake of things, here's the whole bit:

JASS:
globals
	timer LightningConeTimer = CreateTimer()
	real LightningConeInterval = .0125
    real LightningConeSpeed = 20
	integer LightningConeInstances = 0
	group LightningConeEnumGroup = CreateGroup()
	LightningConeData array LightningConeArray
endglobals

struct LightningConeData

    unit caster
    unit array balls[6]
    player casterOwner
    lightning array beams[6]
    real distance
    real maxDist
    real transparency
    group LightningConeTargets
    
    static method LightningConeExecution takes nothing returns nothing
        local integer i = 0
        local integer ballcount = 0
        local unit enemy
        local effect collisionFX
        local location zLoc
        local real array z
        local LightningConeData localLightningCone
        loop
            exitwhen i >= LightningConeInstances
            set localLightningCone = LightningConeArray[i]
            if localLightningCone.distance < localLightningCone.maxDist then
            if localLightningCone.transparency > 0. and (localLightningCone.distance / localLightningCone.maxDist) > .85 then
                set localLightningCone.transparency = localLightningCone.transparency - .20
            endif
            loop
                exitwhen ballcount == 6    
                
                    call MoveUnit(localLightningCone.balls[ballcount], LightningConeSpeed, GetUnitFacing(localLightningCone.balls[ballcount]))
                    set zLoc = Location(GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]))
                    set z[ballcount] = GetLocationZ(zLoc) + 36.
                    call SetUnitFlyHeight(localLightningCone.balls[ballcount], z[ballcount], 0.)
                    if (localLightningCone.distance / localLightningCone.maxDist) > .60 then
                        call SetUnitVertexColor(localLightningCone.balls[ballcount], 255, 255, 255, R2I(localLightningCone.transparency*255))
                    endif    
                    if ballcount != 6 then
                        call MoveLightningEx(localLightningCone.beams[ballcount], true, GetUnitX(localLightningCone.balls[ballcount-1]), GetUnitY(localLightningCone.balls[ballcount-1]), z[ballcount-1], GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]), z[ballcount])
                    endif
                    if (localLightningCone.distance / localLightningCone.maxDist) > .60 then
                        call SetLightningColor(localLightningCone.beams[ballcount], 100., 100., 100.,localLightningCone.transparency)
                    endif
                    call GroupEnumUnitsInRange(LightningConeEnumGroup, GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]), 128., null)
                    loop
                        set enemy = FirstOfGroup(LightningConeEnumGroup)
                        exitwhen enemy == null
                        if ValidAliveTargetGroupedGround(enemy, localLightningCone.caster, localLightningCone.LightningConeTargets) == true then
                            call GroupAddUnit(localLightningCone.LightningConeTargets, enemy)
                            call UnitDamageTarget(localLightningCone.caster, enemy, 50., true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, null)
                            set collisionFX = AddSpecialEffectTarget("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", enemy, "chest")
                            call DestroyEffect(collisionFX)
                        endif
                        call GroupRemoveUnit(LightningConeEnumGroup, enemy)
                    endloop
                                        
                set ballcount = ballcount + 1
            endloop
                set ballcount = 0
                set localLightningCone.distance = localLightningCone.distance + LightningConeSpeed
            endif
            
            if localLightningCone.distance >= localLightningCone.maxDist then
                set LightningConeInstances = LightningConeInstances - 1
                set LightningConeArray[i] = LightningConeArray[LightningConeInstances]
                call DestroyGroup(localLightningCone.LightningConeTargets)
                call localLightningCone.destroy()
            endif		
            set collisionFX = null
            set zLoc = null
            set i = i + 1
        endloop

        if LightningConeInstances == 0 then
            call PauseTimer(LightningConeTimer)
        endif
    endmethod
    
    
    static method create takes unit whichUnit, real range returns LightningConeData
        local integer i = 0
        local LightningConeData data = LightningConeData.allocate()
        local effect casterFX
        local location targetLoc = GetSpellTargetLoc()
        local real targetLocX = GetLocationX(targetLoc)
        local real targetLocY = GetLocationY(targetLoc)
        local real ballFacing = (bj_RADTODEG*Atan2(targetLocY-GetUnitY(whichUnit),targetLocX-GetUnitX(whichUnit))) - 35.
        set data.LightningConeTargets = CreateGroup()
        set data.caster =  whichUnit
        set data.casterOwner = GetOwningPlayer(data.caster)
        set data.distance = 0.
        set data.maxDist = range
        set data.transparency = 1.
        
        set casterFX = AddSpecialEffect("Abilities\\Spells\\Orc\\LightningBolt\\LightningBoltMissile.mdl", GetUnitX(whichUnit), GetUnitY(whichUnit))
        call DestroyEffect(casterFX)
        set casterFX = null     
        loop
            exitwhen i == 6
            
            set data.balls[i] = CreateUnit(data.casterOwner, 'o002', GetUnitX(data.caster), GetUnitY(data.caster), (ballFacing))
            call SetUnitVertexColor(data.balls[i], 255, 255, 255, 255)
            call SetUnitPathing(data.balls[i], false)
            call SetUnitX(data.balls[i], GetUnitX(data.caster))
            call SetUnitY(data.balls[i], GetUnitY(data.caster))
            set ballFacing = ballFacing+10.
            call SetUnitFacing(data.balls[i], ballFacing)
            if i != 0 then
                set data.beams[i] = AddLightningEx("FORK", true, GetUnitX(data.balls[i-1]), GetUnitY(data.balls[i-1]), 36., GetUnitX(data.balls[i]), GetUnitY(data.balls[i]), 36.)
            endif
            
            set i = i + 1
        endloop
                
        set LightningConeArray[LightningConeInstances] = data        
        
    	if LightningConeInstances <= 0 then
            call TimerStart(LightningConeTimer, LightningConeInterval, true, function LightningConeData.LightningConeExecution)
        endif
        
        set LightningConeInstances = LightningConeInstances + 1
        set targetLoc = null
        
        return data
    endmethod
    
    method onDestroy takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen i == 6
        call RemoveUnit(.balls[i])
        set i = i + 1
    endloop
    set i = 0
    loop
        exitwhen i == 6
        call DestroyLightning(.beams[i])
        set i = i + 1
    endloop
    endmethod
    
endstruct

//====================================/ Condition /====================================================

function checkLightningCone takes nothing returns boolean
    local LightningConeData localLightningCone
	if GetSpellAbilityId() == 'A006' then
        set localLightningCone = LightningConeData.create(GetTriggerUnit(), 900.)
	endif
	return false
endfunction

//======================================/ Init /========================================================

function InitTrig_Lightning_Cone takes nothing returns nothing
	local trigger localTrigVar = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(localTrigVar, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition( localTrigVar, Condition(function checkLightningCone))
	set localTrigVar = null
endfunction

I am now having issues with the fact that whenever it creates the dummy units, because they are not "flying" units and by default have pathing on, they spawn next to the casting unit. For the most part the spell works as intended, but I tried to mask the fact it created units beside. Flying units have issues with terrain changes, hovers do not, but I need to have the units not just spawn next to the unit! That's what this is all about:

JASS:
            call SetUnitPathing(data.balls[i], false)
            call SetUnitX(data.balls[i], GetUnitX(data.caster))
            call SetUnitY(data.balls[i], GetUnitY(data.caster))
            set ballFacing = (bj_RADTODEG*Atan2(targetLocY-GetUnitY(data.caster),targetLocX-GetUnitX(data.caster)))+25.
            call SetUnitFacing(data.balls[i], ballFacing - (10 * i))

But unfortunately, I still see the units when they spawn, and they don't get moved to the casting unit's location. I tried coding them to spawn -in front of- the casting unit, but that just meant that if another unit were in front of the casting unit, we'd have an issue where it would spawn adjacent to -that-.
 
Last edited:
That's because you are not taking account of the terrain height :/
Unit's fly height depends on the location's height up to the unit's height.
To do it properly, subtract the location's height to a given value.\

Also, you are missing some AutoFlier, that's why your dummy won't fly.
Dummies must have "Foot" as their movement, or "None"
Then use an AutoFlier everytime you create one:
JASS:
if UnitAddAbility(myDummy, 'Amrf') and UnitRemoveAbility(myDummy, 'Amrf') then
endif
 
Level 4
Joined
Sep 25, 2005
Messages
71
That's because you are not taking account of the terrain height :/
Unit's fly height depends on the location's height up to the unit's height.
To do it properly, subtract the location's height to a given value.\

Also, you are missing some AutoFlier, that's why your dummy won't fly.
Dummies must have "Foot" as their movement, or "None"
Then use an AutoFlier everytime you create one:
JASS:
if UnitAddAbility(myDummy, 'Amrf') and UnitRemoveAbility(myDummy, 'Amrf') then
endif

Ideally we have a unit start with none or fly, get to proper z height, then not fly and continue as scripted.

The flying movement type causes a number of undesirable effects that I described, basically it has a minimum "hover" height set that makes the dummy not hug the ground (to a height) correctly. If there's anyway to set it's height, fly it, then remove the "flying" movetype, so that we have all the dummies spawn on the exact center of the casting unit, yet do not float unnecessarily over terrain, that is ideal.
 
use movement type of none... then just enable fly height via adding and removing the crow form ability... then you can freely set fly height

that's basically how you create dummies that could change heights...

then if you want it to have constant missile height, you just need to take into account the terrain Z when calculating current fly height...

it would be

new fly height = fly height (the constant one) - terrain Z

Coding my own spells, not much to learn from copy/pasting others'.

you can always look at other systems to see how it's done... That way you're still doing things on your own...
 
Level 4
Joined
Sep 25, 2005
Messages
71
EDIT: Mostly fixed, but dummies still spawn outside of the unit's position, almost always to the northeast of of the unit. I try setting the dummy unit's x/y to the position of the caster after spawning but they continue to just be outside, even with NONE movement type and collision off/collision size 0...

use movement type of none... then just enable fly height via adding and removing the crow form ability... then you can freely set fly height

that's basically how you create dummies that could change heights...

then if you want it to have constant missile height, you just need to take into account the terrain Z when calculating current fly height...

it would be

new fly height = fly height (the constant one) - terrain Z



you can always look at other systems to see how it's done... That way you're still doing things on your own...

This equation doesn't work.

I'm not looking for projectiles that float always at a set real, I'm looking for them to be set above it.

JASS:
                    set zLoc = Location(GetUnitX(localTest.Missiles[Missilecount]), GetUnitY(localTest.Missiles[Missilecount]))
                    set z[Missilecount] = FX_Z + GetLocationZ(zLoc)
                    call SetUnitFlyHeight(localTest.Missiles[Missilecount], z[Missilecount], 0.)

This is giving me the same crap as flying does. It's too low when it changes z level down, it's too high when it changes z level up. The "storm crow" ability trick isn't working with this. It had the right height when the move type was set to hover but I want the units to spawn in the correct orientation and position. Please recommend real solutions...

[youtube]
http://www.youtube.com/watch?v=s_amMhSPruw
[/youtube]

This demonstrates the problem completely. With more complex/precise abilities dummies out of place is bad. However, having fireballs way too high or low depending on terrain changes is entirely unacceptable.

Fixed it by switching the z + terrain z to just a fixed z value, however the dummies are still occuring from the top right corner of the casting unit rather than center! Argh.
 
Last edited:
Status
Not open for further replies.
Top