• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[vJASS] vJass Spell Issue

Status
Not open for further replies.
Level 8
Joined
Feb 17, 2007
Messages
368
I'm using the Whippy Rope spell provided by Hanky, but it seems to be glitching whenever I use it in a boss arena that has the terrain decreased by 1. When the rope lands, the unit goes toward the point at which it landed, however he also ascends in the air and remains slightly elevated above the arena. Why is this occurring and how do I fix it?

JASS:
//Whippy Rope by Hanky aka OrkOfMordor aka MDZ-OrkOfMordor

scope WhippyRope initializer init
  //===================================================================================================================
  //Constants
  globals
    private constant integer SpellId            ='A057'  //Ability Rawcode: Thunder Clash
    private constant integer DummyId            ='e00G'  //Unit Rawcode: Dummy
    private constant string LightningType       ="MBUR"  //Lightning Rawcode
    private constant real MissileStartZ         =45.     //The start z of the missile
    private constant real MissileRange          =90.     //The collision range of the missile
    private constant real CasterCollision       =128.    //The collision of the caster
    private constant real MissileMaxHeight      =210.    //The maximal fly height of the missile
    private constant real MissileSize           =2.      //Size of the MissileMdl
    private constant string MissileMdl          ="Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdl"//"Abilities\\Weapons\\WitchDoctorMissile\\WitchDoctorMissile.mdl"//The model of the missile
    private constant string MissileMdlAtt       ="origin"//The attach point of MissileMdl
    private constant string DamageMdl           ="Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl"//The model of the damage
    private constant string DamageMdlAtt        ="chest" //The attach point of DamageMdl
    private constant string MoveMdl             ="Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"//The model for the motion of the caster
    private constant string MoveMdlAtt          ="origin"//The attach point of MoveMdlAtt
    private constant string HitGroundMdl        ="Abilities\\Weapons\\WitchDoctorMissile\\WitchDoctorMissile.mdl"//The model which appear when the missile hit the ground
    private constant real periodic              =0.03    //The periodic motion time
      
    private rect MaxArea                        =null    //The maximal movearea of the missiles    
  endglobals
  
  private constant function RopeSpeed takes integer lvl returns real
    //The speed of the rope
    return 8.00+8.00*lvl
  endfunction
  
  private constant function CasterSpeed takes integer lvl returns real
    //The speed of the caster
    return 8.00+10.50*lvl
  endfunction
  
  private constant function Damage takes integer lvl returns real
    //The damage the enemy if it is hit by the rope
    return 100.00*lvl
  endfunction
  
  private function UnitFilter takes unit caster,unit enum returns boolean
    //The unit filter for all units who can be taken
    if GetUnitState(enum,UNIT_STATE_LIFE) > 0.00 then
    return GetUnitAbilityLevel(enum,'Avul')<=0
    endif
    
    return false
  endfunction
  //End Constants
  //===================================================================================================================
  
  //Conditions
  private function Whippy_Rope_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellId
  endfunction
  
  //Actions
  private struct GroundHitData
    unit caster
    unit dummy
    lightning rope
    effect gfx
    
    real distance
    real maxdistance
    real speed
    real array x[3]
    real array y[3]
    real array z[2]
  
    method motion takes nothing returns nothing
      local real z
    
      set .distance=.distance+.speed
      if .distance>.maxdistance or GetUnitState(.caster,UNIT_STATE_LIFE)<=0.00 then
        set .active=false
      else
        set .x[0]=.x[0]+.x[1]
        set .y[0]=.y[0]+.y[1]
        set z    =GetZ(.x[0],.y[0])
        
        call SetUnitX(.caster,.x[0])
        call SetUnitY(.caster,.y[0])
        call SetUnitFlyHeight(.caster,.z[0]-z,0.)
        call MoveLightningEx(.rope,true,.x[0],.y[0],.z[0]+MissileStartZ+z,.x[2],.y[2],.z[1])
        call DestroyEffect(AddSpecialEffectTarget(MoveMdl,.caster,MoveMdlAtt))
      endif
    endmethod
  
    method endmotion takes nothing returns nothing
      call SetUnitPathing(.caster,true)
      call DestroyLightning(.rope)
      call DestroyEffect(.gfx)
      call RemoveUnit(.dummy)
      
      set .caster=null
      set .dummy =null
      set .rope  =null
      set .gfx   =null
      
      call .destroy()
    endmethod
    
    //! runtextmacro CostumMotion("GroundHitData","motion","endmotion","periodic")
  endstruct
  
  private struct UnitHitData
    unit caster
    unit target
    unit dummy
    lightning rope
    effect gfx
    
    real speed
    real x
    real y
    real z
  
    method motion takes nothing returns nothing
      local real x       =GetUnitX(.target)
      local real y       =GetUnitY(.target)
      local real z       =GetUnitFlyHeight(.target)
      local real angle   =Atan2(y-.y,x-.x)
      local real distance=D2PXY(.x,.y,x,y)
    
      if distance>CasterCollision and GetUnitState(.target,UNIT_STATE_LIFE)>0.00 and GetUnitState(.caster,UNIT_STATE_LIFE)>0.00  then
        set .x=.x+.speed*Cos(angle)
        set .y=.y+.speed*Sin(angle)
        
        call SetUnitX(.caster,.x)
        call SetUnitY(.caster,.y)
        call SetUnitPosition(.dummy,x,y)
        call MoveLightningEx(.rope,true,.x,.y,.z+MissileStartZ+GetZ(.x,.y),x,y,z+GetZ(x,y))
        call DestroyEffect(AddSpecialEffectTarget(MoveMdl,.caster,MoveMdlAtt))
      else
        set .active=false
      endif
    endmethod
  
    method endmotion takes nothing returns nothing
      call SetUnitPathing(.caster,true)
      call DestroyLightning(.rope)
      call DestroyEffect(.gfx)
      call RemoveUnit(.dummy)
      
      set .caster=null
      set .dummy =null
      set .rope  =null
      set .gfx   =null
      
      call .destroy()
    endmethod
    
    //! runtextmacro CostumMotion("UnitHitData","motion","endmotion","periodic")
  endstruct
  
  private struct SearchData
    unit caster
    unit dummy
    lightning rope
    effect gfx
    
    real dmg
    real distance
    real maxdistance
    real array x[2]
    real array y[2]
    real array z[3]
    real speed
    
    integer lvl
  
    method motion takes nothing returns nothing
      local GroundHitData ghd
      local UnitHitData uhd
      local real cx   =GetUnitX(.caster)
      local real cy   =GetUnitY(.caster)
      local real cz   =GetZ(cx,cy)
      local real angle=Atan2(.y[1]-cy,.x[1]-cx)
      local real z
      local real curv
      local group g 
      local unit a
      local real az
      local real amr
      
      set .x[0]=.x[0]+.speed*Cos(angle)
      set .y[0]=.y[0]+.speed*Sin(angle)
      set z    =GetZ(.x[0],.y[0])
      set .distance=.distance+.speed
      set curv    =GetFlyParabula(.z[1],.z[0],.z[2],.distance/.maxdistance) + z
      
      call SetUnitPosition(.dummy,.x[0],.y[0])
      call SetUnitFlyHeight(.dummy,curv-z,0.)
      call MoveLightningEx(.rope,true,cx,cy,cz+MissileStartZ,.x[0],.y[0],curv)
      
      if curv<=z or not RectContainsCoords(MaxArea,.x[0],.y[0]) or .distance>.maxdistance then
        set ghd            =GroundHitData.create()
        set ghd.caster     =.caster
        set ghd.dummy      =.dummy
        set ghd.rope       =.rope
        set ghd.gfx        =.gfx
        set ghd.speed      =CasterSpeed(.lvl)
        set ghd.x[0]       =cx
        set ghd.y[0]       =cy
        set ghd.x[1]       =ghd.speed*Cos(angle)
        set ghd.y[1]       =ghd.speed*Sin(angle)
        set ghd.x[2]       =.x[0]
        set ghd.y[2]       =.y[0]
        set ghd.z[1]       =z
        set ghd.distance   =0.
        set ghd.maxdistance=D2PXY(cx,cy,.x[0],.y[0])
        set ghd.z[0]       =GetUnitDefaultFlyHeight(.caster)
        call DestroyEffect(AddSpecialEffect(HitGroundMdl,.x[0],.y[0]))
        call SetUnitFlyHeight(.dummy,0.,0.)
        call SetUnitPathing(.caster,false)
        call GroundHitData.addMotion(ghd)
        set .active=false
      else
        set g=GetUnitsInRange(MissileRange,.x[0],.y[0])
        call GroupRemoveUnit(g,.caster)
        call GroupRemoveUnit(g,.dummy)
        loop
          set a=FirstOfGroup(g)
          exitwhen a==null
          call GroupRemoveUnit(g,a)
        
          if UnitFilter(.caster,a) then
            set az=GetUnitDefaultFlyHeight(a)+z

            if az<curv and curv<MissileRange+az then
              if IsUnitEnemy(a,GetOwningPlayer(.caster)) then
                call DestroyEffect(AddSpecialEffectTarget(DamageMdl,a,DamageMdlAtt))
                call UnitDamageTarget(.caster,a,.dmg,true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
              endif
              if GetUnitState(a,UNIT_STATE_LIFE)>0.00 then
                set uhd=UnitHitData.create()
                set uhd.target     =a
                set uhd.caster     =.caster
                set uhd.rope       =.rope
                set uhd.dummy      =.dummy
                set uhd.gfx        =.gfx
                set uhd.speed      =CasterSpeed(.lvl)
                set uhd.x          =cx
                set uhd.y          =cy
                set uhd.z          =GetUnitDefaultFlyHeight(.caster)
          
                call SetUnitPathing(.caster,false)
                call UnitHitData.addMotion(uhd)
                call GroupClear(g)
                set .active=false
              endif
            endif
          endif
        endloop
        
        set g=null
      endif
    endmethod
  
    method endmotion takes nothing returns nothing
      set .caster=null
      set .dummy =null
      set .rope  =null
      set .gfx   =null
      
      call .destroy()
    endmethod
    
    //! runtextmacro CostumMotion("SearchData","motion","endmotion","periodic")
  endstruct

  private function Whippy_Rope_Actions takes nothing returns nothing
    local SearchData sd=SearchData.create()
    local unit u       =GetTriggerUnit()
    local real x       =GetSpellTargetX()
    local real y       =GetSpellTargetY()
    local real cx      =GetUnitX(u)
    local real cy      =GetUnitY(u)
    
    set sd.lvl        =GetUnitAbilityLevel(u,SpellId)
    
    set sd.distance   =0.
    set sd.x[0]       =cx
    set sd.y[0]       =cy
    set sd.x[1]       =x
    set sd.y[1]       =y
    set sd.z[0]       =GetZ(cx,cy)+MissileStartZ
    set sd.z[1]       =MissileMaxHeight
    set sd.z[2]       =GetZ(x,y)
    set sd.speed      =RopeSpeed(sd.lvl)
    
    if x==cx and y==cy then
      set sd.maxdistance=10.
    else
      set sd.maxdistance=D2PXY(cx,cy,x,y)
    endif
    
    set sd.caster     =u
    set sd.dummy      =CreateUnit(Player(14),DummyId,cx,cy,A2PXY(cx,cy,x,y))
    set sd.rope       =AddLightningEx(LightningType,true,cx,cy,sd.z[1]+MissileStartZ,cx,cy,sd.z[1]+MissileStartZ)
    set sd.gfx        =AddSpecialEffectTarget(MissileMdl,sd.dummy,MissileMdlAtt)
    set sd.dmg        =Damage(sd.lvl)
    
    call SetUnitScale(sd.dummy,MissileSize,MissileSize,MissileSize)
    call SetLightningColor(sd.rope,0.,1.,0.,0.5)
    call UnitAddFly(sd.caster)
    call UnitAddFly(sd.dummy)
    
    call SearchData.addMotion(sd)
    set u  =null
  endfunction

  //===========================================================================
  private function init takes nothing returns nothing
    local integer i =0

    set gg_trg_WhippyRope = CreateTrigger(  )
    set MaxArea            = bj_mapInitialPlayableArea
    loop
      call TriggerRegisterPlayerUnitEvent(gg_trg_WhippyRope, Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT, MainFunctions_filter)

      set i=i+1
      exitwhen i==bj_MAX_PLAYER_SLOTS
    endloop
    call TriggerAddCondition( gg_trg_WhippyRope, Condition( function Whippy_Rope_Conditions ))
    call TriggerAddAction( gg_trg_WhippyRope, function Whippy_Rope_Actions )
    
    //Preload Models
    call Preload(MissileMdl)
    call Preload(DamageMdl)
    call Preload(MoveMdl)
    call Preload(HitGroundMdl)
  endfunction
endscope
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
A demo map will help, mostly because this code is in a scope and it's obvious it uses some other stuff (function GetZ , boolexpr MainFunctions_filter, ...), and we don't know about it (probably a library MainFunctions, and maybe more).
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,223
I have found the error... In the Whippy Roap section in the GroundHitData struct in the motion method the line...
call SetUnitFlyHeight(.caster,.z[0]-z,0.)
z[0] is the default flying height of the unit
z is the height of the terrain

This makes no sense as he is setting the units flying height to the units default flying height - the terrains current height. This means it will go towards the ground for positive elevation terrain and will start to fly for negative elevation terrain.

Infact I have no idea why he has that line there. As far as I can see changing the caster's flying height is not part of the requirement specification of the spell so should not be there.

To fix, simply comment out (put // at the start of the line of code) the line causing the error mentioned above.

I advise reporting this to Hanky as it is clear that there are atleast 2 failures caused by the same fault which is probably the result of the error mentioned above.
 
he is setting the units flying height to the units default flying height - the terrains current height. This means it will go towards the ground for positive elevation terrain and will start to fly for negative elevation terrain.

No, you're wrong. If at ground height=0, flyheight=45, then groundheight=10 should have flyheight=35.

So it should be SetUnitFlyHeight==45-groundheight.

Edit: Then you should be killing the projectile if 45-groundheight<0 because it's hitting the ground.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,223
No, you're wrong. If at ground height=0, flyheight=45, then groundheight=10 should have flyheight=35.
You seem to not understand what unit fly height means. It is not the elevation above 0, it is the elevation above the ground level the unit is at. The problem is that he permantly changes the unit fly height above ground so that if cast while at negative altitude it will cause the unit to start to fly. Equally well if the unit is a flyer and casts it at positive altitudes it will start to go towards the ground in a nonsense way (and become closer to the ground the higher up the terrain is).
 
You seem to not understand what unit fly height means. It is not the elevation above 0, it is the elevation above the ground level the unit is at. The problem is that he permantly changes the unit fly height above ground so that if cast while at negative altitude it will cause the unit to start to fly. Equally well if the unit is a flyer and casts it at positive altitudes it will start to go towards the ground in a nonsense way (and become closer to the ground the higher up the terrain is).

Hi, I've been doing this for a long time. I don't have a fancy title under my name, but I have done vjass projectile physics in a struct stack before.

I know damn well how the Unit Fly Height works.

The reason for the math is to calculate what to set fly height to based on how the height of the ground changes.

If Unit Fly Height remains the same over a bumpy terrain, the projectile will move up and down with the terrain as it moves - that's where the math comes in to correct this.

If you want unit fly height to remain constant in relation to height 0, you have to do some math to subtract the height of the terrain at said position.

In your own words:

call SetUnitFlyHeight(.caster,.z[0]-z,0.)
z[0] is the default flying height of the unit
z is the height of the terrain

Therefore, as z approaches z[0], Unit Fly Height should approach 0.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,223
Therefore, as z approaches z[0], Unit Fly Height should approach 0.
Yes but if it is cast on high elevation terrain (such as a 1000 high mountain) or low elevation terrain (negative elevation) then the result is prety much nonsense. It works only for flat maps where all terrain is based around the elevation of 0 (which few are).

For what you are saying to be useful, z[0] would need to be the total unit height (ground + unit currently fly height) which it is not.

Additionally this still does not fix the fault of it permanently altering unit fly height when the spell ends (spell should end with unit at same height as it was when cast).
 
For what you are saying to be useful, z[0] would need to be the total unit height (ground + unit currently fly height) which it is not.

Sounds like a proper fix for OP to me.

I wasn't trying to say that line of code was correct - was just saying there was a reason for the math.
 
Status
Not open for further replies.
Top