Spell Quicksand

Viikuna

No Marlo no game.
Reaction score
265
And so we return and beging again.

I decided to make and submit a spell again, because its been so long since Ive done anything

Anyways, spell is called Quicksand and its pretty cool and eveything.

The spell is build of:
- 2 textures, made by me.
- One cool special effect model made by me.
- an awesome spell code made by me.
- 4 cool libraries from wc3c.net. ( I tried to use all
the standard systems, that people use in wc3c.net)

Required stuff: -vJass

Alright, I think there was all the needed information, so here comes the screenie:

QuicksandPic.jpg

That creenie is pretty bad, and you probably have no idea whats happening in it, so I try to tell what this spell is about.

When channeled, the spell turns soil under the enemy units in to deadly quicksand that swallows them under the ground. When channeling ends, units are violently pulled back to the surface through the hard rock. Deals damage and slows enemies.

-Well, that was maybe not so clear, but it basicly sucks units under the ground, which is kinda cool IMO. ( The one reason why I did this, was because I remember that Uberplayer once wondered how to move units below terrain level. )

Okay, here is the test map, go and play it: View attachment [Spell]Quicksand1.1.w3x

Note that I just finished making this spell few minutes ago, so its probably full of bugs. What I need is you to test it and tell me what to change.

I also post code here, so all those cool vJasser guyes from this site can read my code and tell me how to impro it:

JASS:
scope Quicksand initializer init/* requires Table, TimerUtils, 
 ... AutoIndex, SpellEvent */
 
 globals
       
      private constant integer ABILITY_ID          = 'Qsnd'
      
      private constant real ABILITY_TIMER_PERIOD  = .75
      
      // Dealer:
      private constant integer DUMMY_CASTER_ID     = 'deal'
      // SlowBuff stuff:
      private constant integer SLOW_ABILITY_ID     = 'Qslw'
      private constant string  SLOW_ORDER          = "slow"
      // ImpaleStuff:
      private constant integer IMPALE_ABILITY_ID   = 'AImp'
      private constant string  IMPALE_ORDER        = "impale"
      private constant integer IMPALE_BUFF_ID      ='BImp'
 
      // Effects:
      private constant string SAND_EFFECT                                          /*
*/    = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
      
      private constant string DAMAGE_EFFECT                                        /* 
*/    = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
      
      private constant string DAMAGE_EFFECT_ATTACH_POINT  = "chest"
      private constant real   PRELOAD_X                   = 0.0
      private constant real   PRELOAD_Y                   = 0.0
      // Image stuff:      
      private constant real IMAGE_TIMER_PERIOD = .025
      // the duration that it takes for sand texture to fade in
      private constant string SAND_FILE_PATH   = "Quicksand.blp"
      private constant real SAND_FADE_IN_TIME     =  .5
      // the duration that it takes for ground texture to dissapear
      private constant string GROUND_FILE_PATH = "SolidSurface.blp"
      private constant real GROUND_FADE_IN_TIME   =  1.
      private constant real GROUND_FADE_OUT_TIME   =  4.
 
 endglobals
 
 // Spells Area of Effect is calculated using this formula:
 private constant function RADIUS takes integer level returns real
     return 275. + ( level * 0. )
 endfunction
 
 // The amount of damage spell deals, is calculated using this formula:
 private constant function DAMAGE takes integer level returns real
     return 30. + ( level + 50. )
 endfunction
 
 private constant function IMAGE_SIZE takes real radius returns real
     return 600. + (radius * 0.0)
 endfunction
 
 // A wrapper function for your very own death check
 private function IsUnitAlive takes unit u returns boolean
     return GetWidgetLife(u) > .405
 endfunction
 
 // for your UnitDamageTargetEx function, or whatever thing you use
 private function SpellDamageTarget takes unit damager, unit damaged, real damage returns nothing
      call UnitDamageTarget(damager,damaged,damage,false,false,                 /*
*/    ATTACK_TYPE_NORMAL,DAMAGE_TYPE_MAGIC,WEAPON_TYPE_WHOKNOWS)
 endfunction
 
 
/*    
====================================================================================
====================================================================================
                  And so we return and begin again                          

        spell code v1.1                made by Viikuna 
        
====================================================================================        
====================================================================================       
*/
                                
 
private struct Image

    // this is a simple image struct, 
    // which currently has no other use than 
    // an ability to change images alpha value overtime
    
    // if you have some better image struct with loads of cool stuff, 
    // you can get rid of this and use it instead
    
                   image          image 
    private        real           a                     = 255.
    private        real           change                = 0
    private        integer        ticks                 = 0
    private        real           target                = 0
    private static timer          timer                 = CreateTimer()
    private static integer        Count                 = 0
    private static Image array    array

    static method create takes string path, real x, real y, real size, integer alpha, integer imagetype returns Image
        local Image this=Image.allocate()
        set .image=CreateImage(path,size,size,0,x-(size/2),y-(size/2),0,0,0,0,imagetype)
        call SetImageRenderAlways(.image,true)
        call ShowImage(.image,true)
        set .a=alpha
        call SetImageColor(.image,255,255,255,alpha)
        return this
    endmethod
    
    private static method periodic takes nothing returns nothing
        local integer i=0
        local Image this
        loop
            exitwhen i>=.Count
            set this=.array<i>
            if .ticks&gt;0 then
               set .a=.a+.change
               set .ticks=.ticks-1
            else
               set .a=.target
               set .Count=.Count-1
               if .Count&gt;0 then
                   set .array<i>=.array[.Count]
                   set i=i-1
               else
                  call PauseTimer(.timer)
               endif
            endif
            call SetImageColor(.image,255,255,255,R2I(.a))
            set i=i+1
        endloop
    endmethod
    
    method operator alpha takes nothing returns integer
        return R2I(.a)
    endmethod 
    
    method fade takes integer alpha, real duration returns nothing
        if duration&lt;=0.0 then
            set .a=alpha
            call SetImageColor(.image,255,255,255,alpha)
        else
            set .target=alpha
            set .ticks=R2I(duration/IMAGE_TIMER_PERIOD)
            set .change=(alpha-.a)/.ticks
            if .Count==0 then
                call TimerStart(.timer,IMAGE_TIMER_PERIOD,true,function Image.periodic)
            endif
            set .array[.Count]=this
            set .Count=.Count+1
        endif
    endmethod
    
    private method onDestroy takes nothing returns nothing
        call DestroyImage(.image)
    endmethod

endstruct


private struct DummyCaster

   // This little dummy caster struct can only cast
   // unit targetted and non channeling spells,
   // but thats everything we need for this ability anyways.
   
   // If you have some cool DummyCaster struct with lots of nice and smooth methods
   // feel free to get rid of this and use it instead.

   readonly integer  abilityId      =      0
   readonly integer  orderId        =      0
   private  integer  lvl            =      1
   private  player   player         =      Player(13)
   private  unit     unit
   
   method operator owner takes nothing returns player
       return .player
   endmethod
   
   method operator owner= takes player p returns nothing
       call SetUnitOwner(.unit,p,false)
       set .player=p
   endmethod
   
   method operator level takes nothing returns integer
       return .lvl
   endmethod
   
   method operator level= takes integer i returns nothing
       call SetUnitAbilityLevel(.unit,.abilityId,i)
       set .lvl=i
   endmethod
   
   static method create takes integer abilityId, integer orderId returns DummyCaster
       local DummyCaster this=DummyCaster.allocate()
       set .unit=CreateUnit(Player(13),DUMMY_CASTER_ID,0.0,0.0,0.0)
       call UnitAddAbility(.unit,abilityId)
       set .abilityId=abilityId
       set .orderId=orderId
       return this
   endmethod
   
   method castOnUnit takes unit target returns nothing
       call SetUnitX(.unit,GetUnitX(target))
       call SetUnitY(.unit,GetUnitY(target))
       call IssueTargetOrderById(.unit,.orderId,target)
   endmethod
   
endstruct

private struct ability

      // This struct contains the actual spell stuff

            unit    caster
            integer level
            real    x
            real    y
            real    radius
            real    size
            real    damage
    private timer   timer
    
    private Image   sand
    private Image   ground
    
    private        boolean       hasImpale
    private        DummyCaster   Impale
    private static DummyCaster   Slow
    
    private static group         EnumGroup=CreateGroup()
    private static filterfunc    DrownToSandFilter
    private static filterfunc    DamageAndSlowActions
    private static ability       Temp
    private static unit          TempUnit
    
            static ability array UnitData
            
    private static method drownToSandFilterFunction takes nothing returns boolean
        local ability this=.Temp
        set .TempUnit=GetFilterUnit()
        return                                                                      /*

*/               IsUnitAlive(.TempUnit)                                              /*
*/       and     GetUnitFlyHeight(.TempUnit) == 0.0                                  /*
*/       and     IsUnitEnemy(.TempUnit,GetOwningPlayer(.caster))                    /*
*/       and not IsUnitType(.TempUnit,UNIT_TYPE_STRUCTURE)                          /*
*/       and     GetUnitAbilityLevel(.TempUnit,IMPALE_BUFF_ID) == 0 
        

    endmethod
    
    private method drownToSandActions takes nothing returns nothing
        local unit u
        local integer i=0
        set .Temp=this
        call GroupEnumUnitsInRange(.EnumGroup,.x,.y,.radius,.DrownToSandFilter)
        set u=GroupPickRandomUnit(.EnumGroup)
        if u!=null then
            call .Impale.castOnUnit( u )
            call DestroyEffect(AddSpecialEffect(SAND_EFFECT,GetUnitX(u),GetUnitY(u)))
            call GroupRemoveUnit(.EnumGroup,u)
        endif
        set u=null
    endmethod
            
    private static method periodic takes nothing returns nothing
        local timer t=GetExpiredTimer()
        local ability this=GetTimerData(t)
        call .drownToSandActions()
    endmethod
    
    static method startEffect takes nothing returns nothing
        local timer t=GetExpiredTimer()
        local ability this=GetTimerData(t)
        call .drownToSandActions()
        call TimerStart(t,ABILITY_TIMER_PERIOD,true,function ability.periodic)  
    endmethod
    
    static method create takes unit caster, real x, real y returns ability
        local ability this=ability.allocate()
        set .caster=caster
        set .x=x
        set .y=y
        set .level=GetUnitAbilityLevel(.caster,ABILITY_ID)
        set .radius=RADIUS(.level)
        set .damage=DAMAGE(.level)
        set .size=IMAGE_SIZE(.radius)
        if not .hasImpale then
             set .hasImpale=true
            set .Impale=DummyCaster.create(IMPALE_ABILITY_ID,OrderId(IMPALE_ORDER))
        endif
        set .timer=NewTimer()
        set .sand=Image.create(SAND_FILE_PATH,.x,.y,.size,0,2)
        call .sand.fade(255,SAND_FADE_IN_TIME )
        call SetTimerData(.timer,this)
        call TimerStart(.timer,SAND_FADE_IN_TIME ,false,function ability.startEffect)
        return this
    endmethod
    
    static method damageAndSlow takes nothing returns boolean
        local unit u=GetFilterUnit()
        local ability this=ability.Temp
        if GetUnitAbilityLevel(u,IMPALE_BUFF_ID) &gt; 0 then
            call UnitRemoveAbility(u,IMPALE_BUFF_ID)
            call SpellDamageTarget(.caster,u,.damage)
            call .Slow.castOnUnit(u)
            call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT,u,DAMAGE_EFFECT_ATTACH_POINT))
        endif
        set u=null
        return false
    endmethod
    
    static method didAndGoon takes nothing returns nothing
         local timer t=GetExpiredTimer()
         local ability this=GetTimerData(t)
         call .ground.destroy()
         call .sand.destroy()
         call .destroy()
         call ReleaseTimer(t)
    endmethod
    
    static method fadeGround takes nothing returns nothing
         local timer t=GetExpiredTimer()
         local ability this=GetTimerData(t)
         call .ground.fade(0,GROUND_FADE_OUT_TIME)
         call TimerStart(.timer,GROUND_FADE_OUT_TIME,false,function ability.didAndGoon)
    endmethod
    
    static method endEffect takes nothing returns nothing
         local timer t=GetExpiredTimer()
         local ability this=GetTimerData(t)
         set .Temp=this
         set .Slow.level=.level
         call GroupEnumUnitsInRange(.EnumGroup,.x,.y,.radius,.DamageAndSlowActions)
         call TimerStart(.timer,1.,false,function ability.fadeGround)
    endmethod
    
    method endSpell takes nothing returns nothing
        call PauseTimer(.timer)
        set .ground=Image.create(GROUND_FILE_PATH,.x,.y,.size,0,2)
        call .ground.fade(255,GROUND_FADE_IN_TIME)
        call .sand.fade(0,GROUND_FADE_IN_TIME)
        call TimerStart(.timer,GROUND_FADE_IN_TIME,false,function ability.endEffect)
    endmethod
    
    static method onInit takes nothing returns nothing
        set .Slow                 = DummyCaster.create(SLOW_ABILITY_ID,OrderId(SLOW_ORDER))
        set .DrownToSandFilter    = Filter( function ability.drownToSandFilterFunction)
        set .DamageAndSlowActions = Filter( function ability.damageAndSlow)
    endmethod
    
endstruct 

// These function would go to ability struct, 
// but I couldnt get function interfaces to work with static methods,
// so here they are.

private function onEndCast takes nothing returns nothing
    local ability this = ability.UnitData[ GetUnitId(SpellEvent.CastingUnit) ]
    call this.endSpell()
endfunction

private function onEffect takes nothing returns nothing
       set ability.UnitData  [ GetUnitId(SpellEvent.CastingUnit) ]                /*
*/     = ability.create                                                           /*
*/     (SpellEvent.CastingUnit,SpellEvent.TargetX,SpellEvent.TargetY)
endfunction

private function init takes nothing returns nothing
    call RegisterSpellEffectResponse(ABILITY_ID,onEffect)
    call RegisterSpellEndCastResponse(ABILITY_ID,onEndCast)
    call DestroyEffect(AddSpecialEffect(SAND_EFFECT,PRELOAD_X,PRELOAD_Y))
    call DestroyEffect(AddSpecialEffect(DAMAGE_EFFECT,PRELOAD_X,PRELOAD_Y))
endfunction
 
endscope</i></i>


I wanna thank all those cool guyes who did NewGen and vJass and all other modding community people and their families and all the guyes who have somehow affected to making of this spell.

Special credits go to Anitarf, Grim001, and Vexorian, because these guys made the systems this spell uses. Also I wanna thank Kitabatake, who helped me with some mdl editing stuff.

All kind of comments and spamming posts are welcome.
 

BlackRose

Forum User
Reaction score
239
The image ruins the warcraft feel :|
I like how the units actually squirm when they fall down (or is it just me?, but the units like.... rock back and forth)
How did you make them go underground?
 

Viikuna

No Marlo no game.
Reaction score
265
Yea, My photoshop painting skills kinda suck. That sand texture is freehand, but I had to use one reference picture to get those dry ground textures riple thingies right.

edit. Actually that dry ground looks like a dry chocklade cake to me :/ Makes me hungry..

Squirming is kinda nice yea, it looks kinda good on those spiders at least. I had to download some lil tool to get it working, since I dont know how to convert x,y,z rotations to quaternions.

How did you make them go underground?

I cast impale on them. Its a custom impale model I made, which moves them down, instead of moving them up.
 

Jesus4Lyf

Good Idea™
Reaction score
397
The hotkey is wrong (it's B?). :p

Also... (as discussed on IRC)
>4 cool libraries from wc3c.net. ( I tried to use all the standard systems, that people use in wc3c.net)

I don't mind what you use in general, but SpellEvent is certainly not a TH thing. We have the original (GTrigger) that was released well before that system pinched the idea, and it is most likely more efficient (can't bench right now) and isn't a slap in the face to me. GTrigger was a unique concept I birthed, and that library appears to be more or less a cheap copy. (With less functionality.) :(

Also, other spells on TH use GTrigger, so there is an efficiency advantage and general smile ratings for using it as opposed to a WC3C ripoff.
 

Viikuna

No Marlo no game.
Reaction score
265
Hotkey is indeed wrong and learning tooltip says that spell name is Blizzard...

Anyways, dont kill me just because of those systems. It should be relatively easy to modify this to use GTrigger anyways, in case someone wants to use this in map that uses GTrigger.

And yea, spell is currently bugged, ( casting Impale is not instant, like casting Slow is. I was sure that it would be, but it seems that it aint. ) so I need to rewrite that dummy caster stuff. Also, there is some other small stuff I need to change, so Ill upload new version tomorrow.

Anyways, this should not stop you from testing the spell. I want moar comments.
 

Romek

Super Moderator
Reaction score
963
The sinking effect is impressive.

Struct members with the same name as the type is awfully annoying, though meh. I know you like doing that; and it's all private anyway.
 

quraji

zap
Reaction score
144
Cool spell, looks very good. The only gripe I have is that the texture sizes are pretty large. I think you could reduce the compression quality a lot with no noticeable difference.

Anyways, nice spell. Nice trick with Impale.
 

Viikuna

No Marlo no game.
Reaction score
265
Yea, texture size is kinda big currently.

Its not really that big as it says in import editor. They are both together 155 kb, but yea it is quite a lot. The problem is that Im not so good with all this compressing stuff, so if someone can advice me with that stuff, it would be cool.

Struct members with the same name as the type is awfully annoying, though meh. I know you like doing that; and it's all private anyway.

I picked that naming thingy from Cassiel, and I cant get rid of it. Sorry, but I just think it looks cool since it makes my code in TheHelpers Jass tags look all yellow & blue, like the flag of Sweden lols.
 

quraji

zap
Reaction score
144
Okay, I recompressed them and got the sizes down to 20KB (quicksand) and 25KB (solidsurface). Attached is the map with the new textures imported and the spell constants modified to use the new ones. The old ones are still there if size isn't an issue (you can barely notice the difference though).

Once again, bravo on the sinking, it's really cool :thup:
 

Attachments

  • [Spell]Quicksand (small textures).w3x
    268.1 KB · Views: 389

Viikuna

No Marlo no game.
Reaction score
265
Nice, I check those textures later.

I got the idea to use custom impale model from Cassiel and Tides of Blood team, since they use it for some even more cooler stuff than sinking units.

I kinda always wondered how impale works. You might have noticed, that that airtime value in object editor does nothing to the animation.

I also noticed that its possible to use cyclone model in with impale and it works perfectly. This just was so long time ago and I was so noob, that I knew nothing about mdl editing, so I couldnt check what role that special effect plays in moving units.
 

quraji

zap
Reaction score
144
I kinda always wondered how impale works. You might have noticed, that that airtime value in object editor does nothing to the animation.

I also noticed that its possible to use cyclone model in with impale and it works perfectly. This just was so long time ago and I was so noob, that I knew nothing about mdl editing, so I couldnt check what role that special effect plays in moving units.

I've barely ever stepped foot into the object editor, let alone messed with Impale.. how does it work?
Trigger editor is my home :p
 

Viikuna

No Marlo no game.
Reaction score
265
Here is the impale model, with all the useless stuff removed. ( Thanks for Kitabatake for doing that for me )

I added some comments there.


Code:
// Impale model thingy by Viikuna

Version {
	FormatVersion 800,
}
Model "ImpaleHitTarget" {
	NumHelpers 1,
	NumAttachments 1,
	BlendTime 150,
	MinimumExtent { -19.3327, -24.0321, -827.758 },
	MaximumExtent { 18.6553, 19.9398, 358.038 },
	BoundsRadius 593.304,
}
Sequences 1 {
	Anim "Birth" {
		Interval { 2233, 3600 },
		NonLooping,
	}
}
GlobalSequences 1 {
	Duration 0,

}

// This helper is from where game reads unit movement stuff:


Helper "Bone dummy move" {
	ObjectId 0,

         // Translation obviously moves unit up and then down.
         // Isnt that awesome?

	Translation 6 {
		Bezier,
		2233: { 0, 0, 0 },
			InTan { 0, 0, -0.623377 },
			OutTan { 0, 0, 8.72728 },
		2700: { 0, 0, 392.728 },
			InTan { 0, 0, 327.918 },
			OutTan { 0, 0, 420.503 },
		2900: { 0, 0, 439.632 },
			InTan { 0, 0, 447.529 },
			OutTan { 0, 0, 421.206 },
		3367: { 0, 0, 0 },
			InTan { 0, 0, 9.7696 },
			OutTan { 0, 0, -0.697829 },
		3400: { 0, 0, 0 },
			InTan { 0, 0, 0 },
			OutTan { 0, 0, 0 },
		3600: { 0, 0, 0 },
			InTan { 0, 0, -4.33847 },
			OutTan { 0, 0, 23.1385 },
	}

      // Rotation values are in quaternions. You probably want some tool, so you can
      // convert Eulerian rotations to quaternions unless you are some weird guy 
      // who can picture this quaterion rotations in your mind...
                      

	Rotation 8 {
		Linear,
		2233: { 0, 0, 0, 1 },
		2400: { -0.041404, -0.071714, 0, -0.996566 },
		2567: { 0.131919, -0.225829, -0.0305204, 0.964711 },
		2900: { -0.086916, -0.0738265, -0.00645926, 0.993455 },
		3167: { 0.12924, 0.0856951, 0.00970795, 0.987856 },
		3367: { 0, 0, 0, 1 },
		3400: { 0, 0, 0, 1 },
		3600: { 0, 0, 0, 1 },
	}
}

// Effect must be attached to this Sprite First attachment point in object editor.
// Cyclone model also uses this same attachment point, so it is possible to use cyclone model
// in impale spell

Attachment "Sprite First Ref" {
	ObjectId 1,
	Parent 0,
	AttachmentID 0,
}
PivotPoints 2 {
	{ 0.00652508, 0.00118157, 0.000312805 },
	{ 0.00702869, 0.00117978, -0.185619 },
}

You just use custom impale model instead of normal one. You can change the airtime in object editor to whatever you want. It does nothing but determines, how long unit is invulnerable and paused before getting damaged and stunned.
 

Viikuna

No Marlo no game.
Reaction score
265
Fixxed Impale casting stuff and did some other neat stuff too.

Uploaded new code and testmap.
 

Viikuna

No Marlo no game.
Reaction score
265
Bump

edit. Someone ruined my cool quadro bumb :´(
 

Hatebreeder

So many apples
Reaction score
381
This Spell is cool :O

Although, I wish that the code is more readable without all this /*

Anyway, I've skimmed through all the code and didn't find anything to be optimized...

Guess it's ready for approval?
 

Dirac

22710180
Reaction score
147
i made an spell just like this one long time ago. This is pretty interesting, might help me to improve mine

The way i sucked units into the ground was creating a flat circle over the ground with texture and make a terrain level modification under it, looked weird but still worked. I cant read the code... which method u used?
 

Renendaru

(Evol)ution is nothing without love.
Reaction score
309
i made an spell just like this one long time ago. This is pretty interesting, might help me to improve mine

The way i sucked units into the ground was creating a flat circle over the ground with texture and make a terrain level modification under it, looked weird but still worked. I cant read the code... which method u used?

He used a custom impale model, it's in one of the posts here.
 

Romek

Super Moderator
Reaction score
963
Approved. ;)
 

bOb666777

Stand against the ugly world domination face!
Reaction score
117
Is it just me or could he have only posted the model and have as many good comments? I think that's pretty much what interests people in this spell.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top