HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Help on understanding bounce ball spell

03-19-2009, 10:15 AM#1
wraithseeker
I took this spell from Wc3C, this spell was made by lars and I claim no credit whatsoever. This spell simply is a bouncing rubber ball which bounces when hittings units, or terrain. I took the code out and the bouncing part seems like gibberish to me or rather rubbish. It uses some strange functions like
Collapse JASS:
ModuloReal
Collapse JASS:
RAbsBJ

The part of this
Collapse JASS:
local real angle2=bj_RADTODEG*Atan2(GetUnitY(ball)-ly,GetUnitX(ball)-lx)

Why does he uses bj_RADTODEG? Isn't radians better? Help me fix or teach me how to fix this or help me understand this bouncing thing easier or just simplify the code and I will apply this.

I don't get how did he make this, can this be simplified easier so I can understand and apply it to my spell? Thanks!

Collapse JASS:
function Trig_Rubber_Ball_Conditions takes nothing returns boolean
    return GetUnitTypeId(GetSummonedUnit()) == 'e000'
endfunction

function RubberBallLoop takes nothing returns nothing
    local timer tm=GetExpiredTimer()
    local string mis="Rubber Ball"+I2S(H2HI(tm))
    local unit caster=HI2Unit(GetStoredInteger(udg_CC,mis,"Caster"))
    local unit ball=HI2Unit(GetStoredInteger(udg_CC,mis,"Ball"))
    local real lx=GetStoredReal(udg_CC,mis,"LastX")
    local real ly=GetStoredReal(udg_CC,mis,"LastY")
    local real angle=GetStoredReal(udg_CC,mis,"Angle")
    local real dam=GetStoredReal(udg_CC,mis,"Damage")
    local integer index=GetStoredInteger(udg_CC,mis,"Index")
    local real angle2=bj_RADTODEG*Atan2(GetUnitY(ball)-ly,GetUnitX(ball)-lx)
    local location loc1
    local location loc2

    if index<=0 or ball==null or IsUnitDeadBJ(ball) then
        call DestroyTimer(tm)
        call FlushStoredMission(udg_CC,mis)
        set tm=null
        set caster=null
        set ball=null
        return
    endif

    if (GetUnitX(ball)-lx)*(GetUnitX(ball)-lx)+(GetUnitY(ball)-ly)*(GetUnitY(ball)-ly)<100 then
        set angle2=ModuloReal(GetRandomReal(90,270)+angle,360)
    else
        //kernel of rubber ball, quite simple? :)
        set angle2=ModuloReal(2*angle2-angle,360)
        //enf of kernel
    endif

    set loc1 = GetUnitLoc(ball)
    set loc2 = PolarProjectionBJ(loc1, 30.00, angle2)
    call SetUnitPositionLoc( ball, loc2)
    if RAbsBJ(ModuloReal(angle2-angle,360)) >30 and RAbsBJ(ModuloReal(angle2-angle,360))<330 then
      call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl",GetUnitX(ball),GetUnitY(ball)))
      call FadingText( null, I2S(R2I(dam))+"!", 255,0,0, GetUnitX(ball),GetUnitY(ball) )
      call UnitDamagePointLoc(caster,0,250,GetUnitLoc(ball),dam,ATTACK_TYPE_MELEE,DAMAGE_TYPE_NORMAL)
    endif
    
    call StoreInteger(udg_CC,mis,"Index",index-1)
    call StoreReal(udg_CC,mis,"Angle",angle2)
    call StoreReal(udg_CC,mis,"LastX",GetLocationX(loc1))
    call StoreReal(udg_CC,mis,"LastY",GetLocationY(loc1))
    call RemoveLocation(loc1)
    call RemoveLocation(loc2)
    set loc1=null
    set loc2=null
endfunction

function Trig_Rubber_Ball_Actions takes nothing returns nothing
    local timer tm=CreateTimer()
    local string mis="Rubber Ball"+I2S(H2HI(tm))
    local unit caster=GetSummoningUnit()
    local unit ball=GetSummonedUnit()
    local real angle=bj_RADTODEG*Atan2(GetUnitY(ball)-GetUnitY(caster),GetUnitX(ball)-GetUnitX(caster))

    call SetUnitTimeScale(ball,2)
    call UnitAddAbility(ball,'Aloc')
    call UnitRemoveAbility(ball,'Aloc')
    call SetUnitPathing(ball,true)
    call StoreInteger(udg_CC,mis,"Caster",H2HI(caster))
    call StoreInteger(udg_CC,mis,"Ball",H2HI(ball))
    call StoreReal(udg_CC,mis,"LastX",GetUnitX(caster))
    call StoreReal(udg_CC,mis,"LastY",GetUnitY(caster))
    call StoreInteger(udg_CC,mis,"Index",1000)
    call StoreReal(udg_CC,mis,"Angle",angle)
    call StoreReal(udg_CC,mis,"Damage",100)
    
    call TimerStart(tm,0.05,true,function RubberBallLoop)
    
    set tm=null
    set caster=null
    set ball=null
    set tm=null
    set caster=null
    set ball=null
endfunction

//===========================================================================
function InitTrig_Rubber_Ball takes nothing returns nothing
    set gg_trg_Rubber_Ball = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rubber_Ball, EVENT_PLAYER_UNIT_SUMMON )
    call TriggerAddCondition( gg_trg_Rubber_Ball, Condition( function Trig_Rubber_Ball_Conditions ) )
    call TriggerAddAction( gg_trg_Rubber_Ball, function Trig_Rubber_Ball_Actions )
endfunction
03-19-2009, 11:56 AM#2
akolyt0r
RAbsBJ returns the given value ..as a positive number ...(RAbsBJ(5)==RAbsBJ(-5)==5)
ModuloReal returns ...the modulus (=rest of division) (5 modulo 3.2 == 1.8)
03-19-2009, 01:51 PM#3
Vexorian
Quote:
Why does he uses bj_RADTODEG? Isn't radians better? Help me fix or teach me how to fix this or help me understand this bouncing thing easier or just simplify the code and I will apply this.
"Better" is not exactly the word here.

Well, radians are faster in theory, and are more standard in math, all blizzard trigonometry natives are radian based, and this is also the case for trigonometry functions in other languages.

However, some blizzard natives take degrees as arguments instead of radians, so we can't get rid of them. Degrees are also easier to understand for most people, in debug messages I often convert to degrees before displaying. And for configuration stuff I use degrees and then convert them to radians...

Everything in that spell can be simplified, specially because of the use of gamecache... want me to get started ?
03-19-2009, 02:00 PM#4
wraithseeker
Sure.
03-19-2009, 02:22 PM#5
Vexorian
Step 1. Don't attach so many stuff to gamecache, makes code look long , ugly, hard to read and etc. Just attach a single struct... Attaching handles to gamecache is very bad, if you need to use I2H your code is brokenly designed.

So we see things like
Collapse JASS:
    local unit caster=HI2Unit(GetStoredInteger(udg_CC,mis,"Caster"))
    local unit ball=HI2Unit(GetStoredInteger(udg_CC,mis,"Ball"))
    local real lx=GetStoredReal(udg_CC,mis,"LastX")
    local real ly=GetStoredReal(udg_CC,mis,"LastY")
    local real angle=GetStoredReal(udg_CC,mis,"Angle")
    local real dam=GetStoredReal(udg_CC,mis,"Damage")
    local integer index=GetStoredInteger(udg_CC,mis,"Index")
It is possible to guess the intended types...

Collapse JASS:
struct RubberBallData
    unit Caster
    unit Ball
    real LastX
    real LastY
    real Angle
    real Damage
    integer Index
endstruct
Have to change the spell so it uses the struct...


Collapse JASS:
function Trig_Rubber_Ball_Conditions takes nothing returns boolean
    return GetUnitTypeId(GetSummonedUnit()) == 'e000'
endfunction

struct RubberBallData
    unit Caster
    unit Ball
    real LastX
    real LastY
    real Angle
    real Damage
    integer Index
endstruct

function RubberBallLoop takes nothing returns nothing
    local timer tm=GetExpiredTimer()
    local string mis="Rubber Ball"+I2S(H2HI(tm))
    local RubberBallData rbd = rbd( GetStoredInteger( udg_CC, mis, "data") ) //just retrieve the struct from the game cache


    local unit caster=rbd.Caster
    local unit ball=rbd.Ball
    local real lx= rbd.LastX
    local real ly= rbd.LastY    /// Abstraction is your friend.
    local real angle= rbd.Angle
    local real dam= rbd.Damage
    local integer index= rbd.Index
    local real angle2=bj_RADTODEG*Atan2(GetUnitY(ball)-ly,GetUnitX(ball)-lx)
    local location loc1
    local location loc2

    if index<=0 or ball==null or IsUnitDeadBJ(ball) then
        call DestroyTimer(tm)
        call FlushStoredMission(udg_CC,mis)
        //also destroy the struct
        call rbd.destroy()
        
        set tm=null
        set caster=null
        set ball=null
        return
    endif

    if (GetUnitX(ball)-lx)*(GetUnitX(ball)-lx)+(GetUnitY(ball)-ly)*(GetUnitY(ball)-ly)<100 then
        set angle2=ModuloReal(GetRandomReal(90,270)+angle,360)
    else
        //kernel of rubber ball, quite simple? :)
        set angle2=ModuloReal(2*angle2-angle,360)
        //enf of kernel
    endif

    set loc1 = GetUnitLoc(ball)
    set loc2 = PolarProjectionBJ(loc1, 30.00, angle2)
    call SetUnitPositionLoc( ball, loc2)
    if RAbsBJ(ModuloReal(angle2-angle,360)) >30 and RAbsBJ(ModuloReal(angle2-angle,360))<330 then
      call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl",GetUnitX(ball),GetUnitY(ball)))
      call FadingText( null, I2S(R2I(dam))+"!", 255,0,0, GetUnitX(ball),GetUnitY(ball) )
      call UnitDamagePointLoc(caster,0,250,GetUnitLoc(ball),dam,ATTACK_TYPE_MELEE,DAMAGE_TYPE_NORMAL)
    endif
    
    set rbd.Index = Index -1
    set rbd.Angle = angle2
    set rbd.LastX = GetLocationX(loc1)
    set rbd.LastY = GetLocationY(loc1)
    call RemoveLocation(loc1)
    call RemoveLocation(loc2)
    set loc1=null
    set loc2=null
endfunction

function Trig_Rubber_Ball_Actions takes nothing returns nothing
    local timer tm=CreateTimer()
    local string mis="Rubber Ball"+I2S(H2HI(tm))
    local RubberBallData rbd = RubberBallData.create()
    local unit caster=GetSummoningUnit()
    local unit ball=GetSummonedUnit()
    local real angle=bj_RADTODEG*Atan2(GetUnitY(ball)-GetUnitY(caster),GetUnitX(ball)-GetUnitX(caster))

    call SetUnitTimeScale(ball,2)
    call UnitAddAbility(ball,'Aloc')
    call UnitRemoveAbility(ball,'Aloc')
    call SetUnitPathing(ball,true)
    // Yeah <3 abstraction
    call StoreInteger(udg_CC, mis , integer(rbd) )
    set rbd.Caster = caster
    set rbd.Ball = ball
    set rbd.LastX=GetUnitX(caster)
    set rbd.LastY=GetUnitY(caster)
    set rbd.Index=1000
    set rbd.Angle=angle
    set rbd.Damage=100
    

    call TimerStart(tm,0.05,true,function RubberBallLoop)
    
    set tm=null
    set caster=null
    set ball=null
    set tm=null
    set caster=null
    set ball=null
endfunction

//===========================================================================
function InitTrig_Rubber_Ball takes nothing returns nothing
    set gg_trg_Rubber_Ball = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rubber_Ball, EVENT_PLAYER_UNIT_SUMMON )
    call TriggerAddCondition( gg_trg_Rubber_Ball, Condition( function Trig_Rubber_Ball_Conditions ) )
    call TriggerAddAction( gg_trg_Rubber_Ball, function Trig_Rubber_Ball_Actions )
endfunction


That's step 1...
03-19-2009, 02:41 PM#6
Vexorian
Quote:
Collapse JASS:
    if (GetUnitX(ball)-lx)*(GetUnitX(ball)-lx)+(GetUnitY(ball)-ly)*(GetUnitY(ball)-ly)<100 then
        set angle2=ModuloReal(GetRandomReal(90,270)+angle,360)
    else
        //kernel of rubber ball, quite simple? :)
        set angle2=ModuloReal(2*angle2-angle,360)
        //enf of kernel
    endif
Quite some non-sense eh? Well, first of all verify what angle and angle2 hold before this thing, it seems angle holds the current angle the ball is facing. Angle2 instead holds the angle between the current position of the ball and the last one... ( bj_RADTODEG*Atan2(GetUnitY(ball)-ly,GetUnitX(ball)-lx))
What does angle2 do after this? It is used as a new direction for the ball.

That also helps us understand the if-then else, it seems it is checking whether the square of the distance between the ball and its previous position is < 100.0 , this means it will do the actions is the distance is less than 10.0 -

If the distance is less than 10, increase the angle by a random amount :
Collapse JASS:
        set angle2=ModuloReal(GetRandomReal(90,270)+angle,360)

ModuloReal is there just to ensure angle2 stays at range [0, 360.0) However, I don't think this is right per se... It seems that 270 is too big of a difference for an angle, in my opinion he wanted the angle to turn right or left sometimes, he should have used something like this:

Collapse JASS:
        set angle2=ModuloReal(GetRandomReal(-90,90)+angle,360)

What the hell does
Collapse JASS:
        //kernel of rubber ball, quite simple? :)
        set angle2=ModuloReal(2*angle2-angle,360)
        //enf of kernel
mean ?

Well, if angle2 is the new angle and angle1 was the last one., plus the distance has increased so absurdly, I think it means that the pathing caused a mistake in the ball's positioning, this most likely means that the ball hit something. ModuloReal(2*angle2-angle, 360) seems to be a trick to make the ball 'bounce', again ModuloReal is there just to ensure it stays in the correct range.
03-19-2009, 02:43 PM#7
wraithseeker
Collapse JASS:
Advise From Vile:

The calculations are nice but there are a few problems with them.
When casting on several angles, it just doesnt look very realistic. 
Sometimes the ball just bounces off the wall in the opposite direction, 
instead of changing it angle properly.


I set the angle to minus if the terrain isnt pathable and it worked well. Try it.

Forgot to include that, how do I apply it over there? I get the struct part but why retrieve it using gamecache? Could have used TimerUtils instead. can ModuleReal and RabsBJ be removed from the code? I don't see you using it. Never used them before and never had a need to.
03-19-2009, 02:59 PM#8
akolyt0r
you need their "functionality" ...but of course you can inline them ..
03-19-2009, 03:20 PM#9
Vexorian
Quote:
I set the angle to minus if the terrain isnt pathable and it worked well. Try it.
Not that great of an idea. I am sure negating the angle will work... but it won't look as bouncing, it will look like bouncing sometimes, but often it will just look as coming back (which is different to bouncing,...)

Quote:
Forgot to include that, how do I apply it over there? I get the struct part but why retrieve it using gamecache? Could have used TimerUtils instead.
That was just step 1, man... no, timer utils wouldn't be the best solution here, a single timer+array+loop will work better here.
03-19-2009, 03:32 PM#10
wraithseeker
Ok, continue on, I want to add something to my knockback system that when it reaches the end of the cliff using IsterrainWalkable by Anitarf, they bounce back realisticly.
Quote:
I am sure negating the angle will work... but it won't look as bouncing, it will look like bouncing sometimes, but often it will just look as coming back (which is different to bouncing,...)

Well that's fine.
03-19-2009, 03:38 PM#11
Anitarf
That's like an ancient spell, of course the code is ugly. We didn't know any better back then. The problem isn't in the spell, it's in you choosing to look at it's code.

Doing bouncing behaviour with angles just seems silly, vector math seems like a lot better choice for the task.
03-19-2009, 04:08 PM#12
wraithseeker
Well , I don't understand vectors at all..
03-19-2009, 04:21 PM#13
akolyt0r
Still you should take a look at anitarfs vector lib (vJass) testmap ..it got bouncing balls (press esc ingame)
03-20-2009, 02:59 AM#14
wraithseeker
I am in grade 8 so I have never heard of vectors, took a look at them and they were too puzzling for me.

@Vexorian

Negate which angle?