• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Windcut 1.3

A spell i made for a real life friend to use in his campaign.

The spell is maybe a bit simple, but at least it isn't a complete omnislash ripoff.

The library contains 2 libraries i made, if there are 2 other that are a standard and i am not aware of notify me.

The 2 libraries i coded are:
FadeUnitTimed
StopAnimationTimed

I hope someone finds this spell usefull, i sure did spend some time to make it match my friends request.

The spell's idea and concept aren't mine since it is a request.

Please notify me of any possible improvements and bugs/leaks that i may have overlooked.

Used libraries:



JASS:
library Windcut initializer InitWindCut requires TimerUtils,GroupUtils,MathBasic,TimedFadeUnit,TimedAnimationStop,Group2Group

//SETTINGS
globals
    // ID's
    private constant integer Spell_ID = 'A000' // spell ID must match this value
    private constant integer Dummy_ID = 'e000' // dummy unit Id must match this value
    // end of ID setup
    
    
    private constant boolean InvulnerableCaster = false // if true caster is invulnerable
    
    
    // damage
    private constant real BaseDamage = 60.00 // base damage per slash
    private constant real DamageInc = 25 // damage added on base damage with every levelup of the ability
    // end of damage options
    
    // eyecandy
    private constant string SlashEff = "Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl" // effect created on unit strike
    
    private constant string WeaponEff = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl" // effect created on illusions blade
    
    private constant string TrailEff = "Abilities\\Weapons\\GlaiveMissile\\GlaiveMissileTarget.mdl" // trail effect which is created on the ground as the hero moves
    
    private constant integer ESR = 3 // ESR = Effect Spam Ration, increase in this = less effects, decrease = more effects (used for trail effects)
    
    // end of eyecandy options
    
    // fade options
    
    private constant real CasterFade = 0.55 // takes a value from 0.00 to 1.00; 0.00 is transparent, 1.00 is normal value of every unit.
                                           // 0.7  = 70% visibility, 30% transparency
                                           
    private constant real ImageFade = 0.45 // same as CasterFade but for illusions
    
    private constant real FadeTime = 0.3 // fade time for illusions, 0.3 = 0.3 seconds of delay before the trail illusions behind the heroes are removed
    
    private constant real AnimStopValue = 0.85 // varies from model to model, takes a value when the animation is stoped
                                               // blademaster's animation of attack ("attack") is 1.167 seconds so this is
                                               // a user defined value, there is no equation for it
                                               
    private constant real FadeTimeEx = 3.00 // this value is used for the illusions that play the attack animation
                                            // this value should be larget than FadeTime to show the full eyecandy
                                            // and the effect of TimedAnimationStop
                                            
    // end of fade options
    
    // speed related things
    private constant real FPS = 30.00 // optimal values from 30 to 60
    
    private constant real ChargeSpeed = 1025 *  (1/FPS) // speed of the hero,uses WC3 movement speed values, don't touch anything after the sign *
    
    private constant real AoE = 366.66 // AoE which is actualy used for a strange AoE calculated by distance from caster to cast point
    // end of speed options
    
endglobals
//END OF SETTINGS

// Don't Touch the stuff bellow this point

globals

    private unit TU = null
    private player TP = null
    private real TR = 0.00
    private group FG = CreateGroup()
    
    private constant real MinOffset = 50.00

endglobals

private function GroupFilter takes nothing returns boolean
    return GetWidgetLife(GetFilterUnit()) > .405 and IsUnitInGroup(GetFilterUnit(),FG)==false and GetFilterUnit() != TU and IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE)==false and IsUnitType(GetFilterUnit(),UNIT_TYPE_FLYING)==false and IsUnitEnemy(GetFilterUnit(),TP) and IsUnitType(GetFilterUnit(),UNIT_TYPE_MECHANICAL)==false
endfunction

private function groupfunc takes nothing returns nothing
    call GroupAddUnit(FG,GetEnumUnit())
endfunction

private constant function GetDamage takes integer lvl returns real
    return BaseDamage + (lvl-1)*DamageInc
endfunction

private function ExtractInteger takes real r returns integer
    local integer i = R2I(r)
    local real s = r - I2R(i)
    if s >= 0.50 then
        set i = i+1
    endif
    return i
endfunction

private function condition2run takes nothing returns boolean
    local windcut this
    local real d
    local integer groupnum
    local integer i
    local real x
    local real y
    if GetSpellAbilityId() == Spell_ID then
        set this = windcut.Create()
        set this.g = NewGroup()
        set this.d = NewGroup()
        set this.u = GetTriggerUnit()
        set this.t = NewTimer()
        set this.x = GetUnitX(this.u)
        set this.y = GetUnitY(this.u)
        set this.tpx = GetSpellTargetX()
        set this.tpy = GetSpellTargetY()
        set this.rad = RBL(this.x,this.y,this.tpx,this.tpy)
        set this.dmg = GetDamage(GetUnitAbilityLevel(this.u,Spell_ID))
        call SetUnitVertexColor(this.u,255,255,255, 255 - R2I(CasterFade*255))
        if InvulnerableCaster then
            call SetUnitInvulnerable(this.u,true)
        endif
        call PauseUnit(this.u,true)
        
            set d = DBL(this.x,this.y,this.tpx,this.tpy)
            set groupnum = ExtractInteger(d / AoE)
            set i = 0
            set x = this.x
            set y = this.y
        
            if groupnum < 1 then
                set groupnum = 1
            endif
            
            loop
                exitwhen i > groupnum
                set TU = this.u
                set TP = GetOwningPlayer(TU)
                call GroupEnumUnitsInRange(this.d,x,y,AoE,Filter(function GroupFilter))
                call ForGroup(this.d,function groupfunc)
                call Group2Group(this.d,this.g)
                set x = x + (AoE) * Cos(this.rad)
                set y = y + (AoE) * Sin(this.rad)
                call GroupClear(this.d)
                set i = i+1
            endloop

            call GroupClear(FG)
            
        call SetTimerData(this.t,integer(this))
        call TimerStart(this.t,1/FPS,true,function windcut.motion)
        set this.tar = FirstOfGroup(this.g)
        set this.tx = GetUnitX(this.tar)
        set this.ty = GetUnitY(this.tar)
        set this.rad = RBL(this.x,this.y,this.tx,this.ty)
    endif
    return false
endfunction

struct windcut 

    unit u
    unit tar
    group g
    group d
    timer t
    real x
    real y
    real tx
    real ty
    real rad
    real tpx
    real tpy
    real dmg
    
    method onDestroy takes nothing returns nothing
        call SetUnitVertexColor(this.u,255,255,255,255)
        if InvulnerableCaster then
            call SetUnitInvulnerable(this.u,false)
        endif
        call PauseUnit(this.u,false)
        set this.u = null
        call GroupClear(this.g)
        call GroupClear(this.d)
        call ReleaseGroup(this.g)
        call ReleaseGroup(this.d)
        set this.g = null
        set this.d = null
    endmethod
    
    static method Create takes nothing returns windcut
        local windcut this = windcut.allocate()
        return(this)
    endmethod
    
    static method motion takes nothing returns nothing
        local windcut this = GetTimerData(GetExpiredTimer())
        local unit tu = CreateUnit(GetOwningPlayer(this.u),Dummy_ID,this.x,this.y,this.rad*bj_RADTODEG)
        call SetUnitX(tu,this.x)
        call SetUnitY(tu,this.y)
        call SetUnitFacing(this.u,this.rad*bj_RADTODEG)
        if this.tar != null then
            set this.tx = GetUnitX(this.tar)
            set this.ty = GetUnitY(this.tar)
            set this.rad = RBL(this.x,this.y,this.tx,this.ty)
            set this.x = this.x + ChargeSpeed * Cos(this.rad)
            set this.y = this.y + ChargeSpeed * Sin(this.rad)
            call SetUnitX(this.u,this.x)
            call SetUnitY(this.u,this.y)
            if TrailEff != "" and GetRandomInt(0,ESR) == ESR then
                call DestroyEffectTimed(AddSpecialEffect(TrailEff,this.x,this.y),1)
            endif
            if DBL(this.x,this.y,this.tx,this.ty) <= 128 and this.tar != null then
                call UnitDamageTarget(this.u,this.tar,this.dmg,true,false,null,null,null)
                if WeaponEff != "" then
                    call DestroyEffectTimed(AddSpecialEffectTarget(WeaponEff,tu,"weapon"),3)
                endif
                if SlashEff != "" then
                    call DestroyEffectTimed(AddSpecialEffectTarget(SlashEff,this.tar,"chest"),2)
                endif
                call SetUnitAnimation(tu,"attack")
                call FadeUnitTimed(tu,FadeTimeEx,ImageFade)
                call StopAnimationTimed(tu,0.80)
                call GroupRemoveUnit(this.g,this.tar)
                set this.tar = FirstOfGroup(this.g)
            else
                call FadeUnitTimed(tu,FadeTime,ImageFade)
            endif
        else
            if DBL(this.x,this.y,this.tpx,this.tpy) <= MinOffset then
                call RemoveUnit(tu)
                call PauseTimer(GetExpiredTimer())
                call ReleaseTimer(GetExpiredTimer())
                set this.t = null
                call this.destroy()
            else
                if TrailEff != "" and GetRandomInt(0,ESR) == ESR then
                    call DestroyEffectTimed(AddSpecialEffect(TrailEff,this.x,this.y),1)
                endif
                set this.rad = RBL(this.x,this.y,this.tpx,this.tpy)
                set this.x = this.x + ChargeSpeed * Cos(this.rad)
                set this.y = this.y + ChargeSpeed * Sin(this.rad)
                call SetUnitX(this.u,this.x)
                call SetUnitY(this.u,this.y)
                call FadeUnitTimed(tu,FadeTime,ImageFade)
            endif
        endif
        if this.x > MaxX or this.x < MinX or this.y > MaxY or this.y < MinY then
            set this.tar = null
            set this.x = this.x - ChargeSpeed * Cos(this.rad)
            set this.y = this.y - ChargeSpeed * Sin(this.rad)
            set this.tpx = this.x
            set this.tpy = this.y
        endif
    endmethod
    
    
endstruct

private function InitWindCut takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function condition2run))
    call GroupClear(FG)
endfunction

endlibrary



v1.0 rellease
v1.1 added code in the description, inlined GroupAddGroup() function
v1.2 made the struct private, made the effects disapear timer to take an automatically calculated time from FadeTimeEx value.



Keywords:
slash,blade,blood,gore,blademaster,wind,cut,windcut,sword,impact,strike,furious,fury,omnislash,dota
Contents

Kingz - Windcut (Map)

Reviews
15:03, 5th Feb 2010 TriggerHappy: Took my updates.
Level 9
Joined
Aug 2, 2008
Messages
219
Nice spell you made there, not the most original one but quite good. Only a few things about coding:

Suggestions
  • Structs should be private
  • You might preload the special effects
  • You should use native UnitAlive takes unit id returns boolean instead of GetWidgetLife() <= 0.405
  • (optional)It would be nice if you could link the threads from the libraries you used to the description
Some minor flaws i found out but i´m rather sleepy right now so i might have overlooked something.

~TNT
 
Level 25
Joined
Jun 5, 2008
Messages
2,572
In some strange cases native UnitAlive takes unit id returns boolean can bug and The_Reborn_Devil uses GetWidgetLife() so i don't think it is an issue.

I will link the thread for the libraries with the next update.

EDIT:

If i made the struct private i couldn't call upon the Create method:
JASS:
    static method Create takes nothing returns windcut
        local windcut this = windcut.allocate()
        return(this)
    endmethod

And i use that struct/method outside the struct:
JASS:
local windcut this
...
set this = windcut.Create()
 
Level 19
Joined
Feb 25, 2009
Messages
2,004
Pros
+Different from other "walk" or "cut" spells here
+Animations are nice
+Lagless SFXs which is realy nice
+I realy like the redirections when you hit a unit
+MUIness FTW.

Cons
-Animations are not realy good, especially when theres no targets in your way
-Well hes cutting them, but I didn't saw any "blood" SFXs
-SFX on movement is not realy nice, I mean its a death effect from an arrow, not realy fitting the "wind" part of the spell
-Collision are a big problem in this kind of spells, I think you need a system to detect when the caster is closer to a unit and turn off/on its collision, because its :mad: atm

Rate: 4/5
Vote: Approval
 
Level 9
Joined
Aug 2, 2008
Messages
219
Ahh ok never mind about the IsUnitAlive thingy. I meant making the struct private and not the creator method. When structs are private you can still access them (and everything inside them). Just move the windcut struct below the 'dont touch this' globalsblock and make it private.
 
Level 8
Joined
Nov 20, 2008
Messages
445
Why do you have a Create method like this ? This is practicly the same as create() so I dont see a reason for it. And I suggest you move all the code from your condition function into the Create(which you should rename to just create as theres no need for a new method when you have the other one already there for you) method, it looks better and more organised. Also when working withing the structs methods you don't need to type "this". Using just a dot works with no problems.

Example

JASS:
static method create takes real x, real y returns thistype
     local thistype this = thistype.allocate()

     set .x = x
     set .y = y

     return this
endmethod

This will work. And it saves writing time.
 
Level 25
Joined
Jun 5, 2008
Messages
2,572
Well for unit model you need to modify the dummy unit.

For the Slash effect you need to modify the SlashEff variable, for the trail effect you need to modify TrailEff variable for the weapon effect you need to modify WeaponEff variable.

NOTE: When replacing the paths to special effects your new path must have \\ instead of \ .

Example:

Wrong path:
JASS:
"Abilities\Spells\Human\DispelMagic\DispelMagicTarget.mdl"

Proper path:
JASS:
"Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
 
Level 7
Joined
Mar 24, 2008
Messages
184
Nice spell :) i was looking for something to add a trail behind units and i found this which looks very nice and i think i'm going to use your libraries (with proprer credits of course).
I don't know if maybe i'm missing something, but i've noticed that the variable

JASS:
private constant real AnimStopValue = 0.85

on line 39 Is never used. I suppose it goes on line 157 in

JASS:
call StopAnimationTimed(tu,0.80)

in place of the 0.80
 
Top