• 🏆 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!

Haunt v1.21

  • Like
Reactions: FrIkY
Credits to Dynasti for helping me much :)

A little and not very complex spell i made for my map, decided to upload it since i havnt uploaded something for a while.

It will shoot a "projectile" towards the targeted unit, when it reaches him it will deal damage and send a "projectile" back to the caster and when it reach him it will heal him for a certain amount.

i like how this spell works, but i feel like it miss something.

Comments, Suggestions and Critic are welcome :)

ps. sry for bad pic :p

JASS:
scope Haunt initializer init
//===================================================================================
//= Haunt by Ciebron v1.21
//= 
//= Requires: -The Custom Model in the Import list
//=    
//= 
//= Description: Shoots a projectile towards target unit it will deal Damage to him
//=              and then return to the caster and heal him for % of the damage
//= 
//= Extra: This spell needs the imported model 'dummy.mdx'. else you wont be able to
//=        attach a SFX to the dummy
//===================================================================================
globals
    //The RawCode of the spell
    private constant integer Abil_id = 'A000'
    //The RawCode of the dummy
    private constant integer Dummy_id = 'h000'
    //NOTE: only change this if u have changed the crow from ability in the editor
    private constant integer Fly_id = 'Amrf'
    //The SFX that will apear when the projectile hits the Target
    private constant string HitSFX = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl"
    //The SFX that will apear when the projectile hits the Caster
    private constant string HealSFX = "Abilities\\Spells\\Items\\AIil\\AIilTarget.mdl"
    //The SFX that will attach too the projectile when it travels to the target
    private constant string Projectile1 = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilMissile.mdl"
    //The SFX that will attach too the projectile when it travels back to the caster
    private constant string Projectile2 = "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl"
    //Where the SFX will get attached to the projectile
    private constant string AttachPoint = "origin"
    //Where the SFX will attach when the projectile hits the target/caster
    private constant string AttachTargetPoint = "origin"
    // Defines how fast the projectile will move
    private constant real Move_Speed = 20.
    //How much the caster will be healed of the procentage. eg. 1. is 100% of the dmg, 0.75 is 75% of the damage.
    private constant real Heal_Amount = .50
    //The Dummy's Fly Height (set it to what ever that fit's your SFX)
    private constant real Dummy_Height = 75.
    //The Attacktype of the spell
    //Usefull information about damage types. http://www.wc3campaigns.net/showpost.php?p=1030046&postcount=19
    private constant attacktype AtkType = ATTACK_TYPE_MAGIC
    //The Damagetype of the spell
    private constant damagetype DmgType = DAMAGE_TYPE_MAGIC
    //The Weapontype of the spell (sound)
    private constant weapontype WepType = null
    //Timer Intervals
    private constant real Interval = 0.03
endglobals

//This function is for setting the spells damage
private constant function Damage takes integer Level returns real
    return 100.+(Level*50)
endfunction

//===========================================================================================

globals
    private constant real Projectile_Size = Move_Speed * 2 + 1
endglobals

private struct Data
unit Caster
unit Target
unit Dummy

real Damage
real z

player Play

effect SFX

static integer array Ar
static integer Total = 0
static timer Time = CreateTimer()

static real x
static real y
static real tx
static real ty
static real x2
static real y2
static real angle
static real nH

    method NewHeight takes real h returns real // A method used for setting the missile's height
        set .nH = (h-.z) / ( SquareRoot((.tx-.x)*(.tx-.x)+(.ty - .y)*(.ty-.y)) / Move_Speed * Interval ) //DSG - Works out rate of change needed by working out the change in height and the time to reach target.
        if .nH < 0 then //DSG - If rate of change of height is - it will not work so this inverts it to be positive.
            set .nH = -.nH
        endif
        call SetUnitFlyHeight(.Dummy, h+Dummy_Height, .nH ) // Setting the missiles new height at a calculated rate
        return h
    endmethod
    
    static method create takes unit u,unit t returns Data
        local Data Dat = Data.allocate()
        local real Height = GetUnitFlyHeight(t)
        
        set Dat.Caster = u
        set Dat.Target = t
        set Dat.x = GetUnitX(Dat.Caster)
        set Dat.y = GetUnitY(Dat.Caster)
        set Dat.tx = GetUnitX(Dat.Target)
        set Dat.ty = GetUnitY(Dat.Target)
        set Dat.angle = Atan2(Dat.ty-Dat.y,Dat.tx-Dat.x)
        set Dat.Play = GetOwningPlayer(Dat.Caster)
        set Dat.Dummy = CreateUnit(Dat.Play,Dummy_id,Dat.x+50*Cos(Dat.angle),Dat.y+50*Sin(Dat.angle),(Dat.angle*57.29582))
        set Dat.Damage = Damage(GetUnitAbilityLevel(Dat.Caster,Abil_id))      
        set Dat.SFX = AddSpecialEffectTarget(Projectile1,Dat.Dummy,AttachPoint)
        set Dat.z = Dummy_Height
        call UnitAddAbility(Dat.Dummy, Fly_id)
        call SetUnitFlyHeight(Dat.Dummy,Dummy_Height,0.)
        call UnitRemoveAbility(Dat.Dummy, Fly_id)
        if Height > 0. then
            set Dat.z = Dat.NewHeight(Height)
        endif
        
        if Dat.Total == 0 then
            call TimerStart(Dat.Time, Interval, true, function Data.Loop)
        endif

        set Dat.Ar[Dat.Total] = Dat
        set Dat.Total = Dat.Total + 1
        
        set u = null
        set t = null
        
        return Dat
    endmethod
    
    static method Loop takes nothing returns nothing
        local Data Dat
        local integer i = 0
        
        loop
            exitwhen i >= Dat.Total
            set Dat = Dat.Ar[i]
            
            set Dat.x = GetUnitX(Dat.Dummy)
            set Dat.y = GetUnitY(Dat.Dummy)
            set Dat.tx = GetUnitX(Dat.Target)
            set Dat.ty = GetUnitY(Dat.Target)
            set Dat.x2 = Dat.tx-Dat.x
            set Dat.y2 = Dat.ty-Dat.y
            
            if SquareRoot(Dat.x2 * Dat.x2 + Dat.y2 * Dat.y2) <= Projectile_Size then
                if Dat.Target != Dat.Caster then
                    call DestroyEffect(AddSpecialEffectTarget(HitSFX,Dat.Target,AttachTargetPoint))
                    call DestroyEffect(Dat.SFX)
                    call KillUnit(Dat.Dummy)
                    if not IsUnitType(Dat.Target,UNIT_TYPE_MAGIC_IMMUNE) and GetWidgetLife(Dat.Target) > .305 then
                        call UnitDamageTarget(Dat.Caster,Dat.Target,Dat.Damage,false,false,AtkType,DmgType,WepType)
                        set Dat.Target = Dat.Caster
                        set Dat.x = GetUnitX(Dat.Target)
                        set Dat.y = GetUnitY(Dat.Target)
                        set Dat.angle = Atan2(Dat.y-Dat.ty,Dat.x-Dat.tx)
                        //! Yes i could reuse the old dummy but cause it will look wierd i create a new one
                        set Dat.Dummy = CreateUnit(Dat.Play,Dummy_id,Dat.tx+50*Cos(Dat.angle),Dat.ty+50*Sin(Dat.angle),Dat.angle*57.29582)
                        //! New dummy new "fly hack"
                        call UnitAddAbility(Dat.Dummy, Fly_id)
                        call SetUnitFlyHeight(Dat.Dummy, Dat.z,0. )
                        call UnitRemoveAbility(Dat.Dummy, Fly_id)
                        //! Make so it will fly down to the caster again smoothly
                        call Dat.NewHeight(GetUnitFlyHeight(Dat.Target))
                        set Dat.SFX = AddSpecialEffectTarget(Projectile2,Dat.Dummy,AttachPoint)
                    else
                        set Dat.Total = Dat.Total - 1
                        set Dat.Ar[i] = Dat.Ar[Dat.Total]
                        set i = i - 1
                        call Dat.destroy()
                    endif
                else
                    call SetWidgetLife(Dat.Caster,GetWidgetLife(Dat.Caster)+(Dat.Damage*Heal_Amount))
                    call DestroyEffect(AddSpecialEffectTarget(HealSFX,Dat.Caster,AttachTargetPoint))
                    call KillUnit(Dat.Dummy)
                    call DestroyEffect(Dat.SFX)
                    set Dat.Total = Dat.Total - 1
                    set Dat.Ar[i] = Dat.Ar[Dat.Total]
                    set i = i - 1
                    call Dat.destroy()
                endif
            else
                set Dat.angle = Atan2(Dat.ty-Dat.y,Dat.tx-Dat.x)
                call SetUnitX(Dat.Dummy,Dat.x+Move_Speed*Cos(Dat.angle))
                call SetUnitY(Dat.Dummy,Dat.y+Move_Speed*Sin(Dat.angle))
                call SetUnitFacing(Dat.Dummy,(Dat.angle*57.29582))
            endif
            
            set i = i + 1
        endloop
        
        if Dat.Total == 0 then
            call PauseTimer(Dat.Time)
        endif
        
    endmethod
    
    method onDestroy takes nothing returns nothing
        set .Caster = null
        set .Target = null
        set .Play = null
        set .Dummy = null
        set .SFX = null
    endmethod
    
endstruct

private function OnCast takes nothing returns boolean
    local Data Dat
    local unit u
    local unit t
    
    if GetSpellAbilityId()==Abil_id then
        set u = GetTriggerUnit()
        set t = GetSpellTargetUnit()
        set Dat = Data.create(u,t)
        set u = null
        set t = null
    endif
    
    return false
endfunction

private function init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    local integer index = 0
    
    loop
        call TriggerRegisterPlayerUnitEvent(trig,Player(index),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
        
    call TriggerAddCondition(trig,Filter(function OnCast))
    
    call Preload(HitSFX)
    call Preload(HealSFX)
    call Preload(Projectile1)
    call Preload(Projectile2)
    
    set trig = null
endfunction

endscope



v1.0 First Release

v1.1 Minor Performace Improvements and changed to dummy's classification :)

v1.2 Replaced locals with static's, made the Missile to adjust it's height too it's target and changed some effects.



Enjoy

Keywords:
Haunt, Ciebron, Life Steal, Shadow, Darkness, Evil.
Contents

Haunt v1.2 (Map)

Reviews
21:31, 22nd Sep 2009 The_Reborn_Devil: The triggering is nice and I couldn't find any leaks. The effects are good as well. It's a nice spell which I think many people would find useful. Spell Approved. I Recommend this spell btw.

Moderator

M

Moderator

21:31, 22nd Sep 2009
The_Reborn_Devil:

The triggering is nice and I couldn't find any leaks.
The effects are good as well.
It's a nice spell which I think many people would find useful.
Spell Approved.

I Recommend this spell btw.
 
Level 11
Joined
Apr 6, 2008
Messages
760
I like the spell but a suggestion..

Don't use a peasant for a dummy because it puts that annoying builder thing at the bottom left hand part of the screen..

Other than that i like it.. nice job..

ah forgot too change that, was on my to-do list.

Ciebron! Nice to see you working on the spells again :D

Yes, and u will see alot more from me :p started my own project :eek:

btw nice too see u too again :p


Edit:

hmm where is the "edit" button for resourse's? :eek:
 
Level 18
Joined
Oct 18, 2007
Messages
930
:p reminds me of my spell "Life Bolt" but what the heck ^^

Ok you could need to work on some efficiency on your scripts. I could show you a trick that would get rid of your timer utils. It is not that efficient to start alot of timers for movement :p

Contact me if you want anyhelp on improving your script ;)

Edit: You should use an Indexer for your movement.

Here is a fast example
JASS:
scope SomeMissileSpell

    globals
        private constant real Interval = 0.035
        [...]
        private constant real MissileSpeed = 25
        [...]
//==============
        private constant real SafeCollide = MissileSpeed * 2 + 1
    endglobals

    [...]

    private struct Movement
        unit u // Caster
        player p // Owner of caster
        integer l // Level of ability

        unit m // Missile
        unit t // Target

        static integer array Index
        static integer Total = 0
        static timer Tim = CreateTimer()

        static real nX = 0
        static real nY = 0
        static real nX2 = 0
        static real nY2 = 0
        static real nA = 0

        static method Loop takes nothing returns nothing
            local Movement dat
            local integer i = 0

            loop
                exitwhen i >= dat.Total
                set dat = dat.Index[i]

                set dat.nX = GetUnitX(dat.m)
                sat dat.nY = GetUnitY(dat.m)
                set dat.nX2 = GetUnitX(dat.t)
                set dat.nY2 = GetUnitY(dat.t)

                set dat.nA = Atan2(dat.nY2 - dat.nY, dat.nX2 - dat.nX)

                if SquareRoot((dat.nX2 - dat.nX) * (dat.nX2 - dat.nX) + (dat.nY2 - dat.nY) * (dat.nY2 - dat.nY)) > SafeCollide then
                    [Your actions for on collision]
                    [...]
                    // Destroy setup you NEED to have
                    [...Misc... like effects]
                    call KillUnit(dat.m)
                    set dat.m = null
                    set dat.t = null
                    set dat.u = null
                    set dat.p = null

                    call dat.destroy()

                    set dat.Total = dat.Total - 1
                    set dat.Index[i] = dat.Index[dat.Total]

                    set i = i - 1 // Important
                else
                    call SetUnitX(dat.m, dat.nX + MissileSpeed * Cos(dat.nA))
                    call SetUnitY(dat.m, dat.nY + MissileSpeed * Sin(dat.nA))
                    call SetUnitFacingTimed(dat.m, dat.nA * bj_RADTODEG, 0)
                endif

                set i = i + 1
            endloop

            if dat.Total == 0 then
                call PauseTimer(dat.Tim)
            endif
        endmethod
endscope

Edit Again:
For the creation func you need to input this somewhere near the end of the method
JASS:
[...]
if dat.Total == 0 then
    call StartTimer(dat.Tim, Interval, true, function Movement.Loop)
endif

set dat.Index[dat.Total] = dat
set dat.Total = dat.Total + 1
[...]
 
Level 18
Joined
Oct 18, 2007
Messages
930
Ok looks like you copied Silvenon's bugged indexer

You did:
JASS:
                    set Ar[i] = Ar[Total - 1]
                    set Total = Total - 1
It is stupid to do it that way, do
JASS:
set Total = Total - 1
set Ar[i] = Ar[Total]

And the same for creation. You did this
JASS:
set Total = Total + 1
set Ar[Total - 1] = dat
Do this instead
JASS:
set Ar[Total] = dat
set Total = Total + 1

And in your loop you forgot to
JASS:
set i = i - 1
when destroying the struct. Without it the loop will skip structs when a struct is destroyed.

And you need to clean up your code and dont use alot of not needed funcs.

I dont like this
JASS:
private function PreLoad takes nothing returns nothing
    call Preload(HitSFX)
    call Preload(HealSFX)
    call Preload(Projectile1)
    call Preload(Projectile2)
endfunction
just preload all that stuff in the Initializer func

A thing that could kill your spell is this:
JASS:
SquareRoot((tx-x)*(tx-x) + (ty-y)*(ty-y)) <= Move_Speed

Add this constant global into your global block and use it instead.
JASS:
private constant real Safe_Collide = Move_Speed * 2 + 1

Then your distance safe would be
JASS:
SquareRoot((tx-x)*(tx-x) + (ty-y)*(ty-y)) <= Safe_Collide

Without it the missile could attach itself to the target without getting destroyed.


Dont null the things like you did here:
JASS:
unit Caster = null //The Caster
unit Target = null //The Target
unit Dummy = null //The Dummy

real Damage = 0. //How much dmg it will cause

player Play = null //The Owner of the Caster

effect SFX = null //The Effect on the dummy

Just null them when struct is destroyed. It saves memory to make the variables null when destroyed, else the varibles can contain data troughout the entire game.

And you don't need this
JASS:
real Damage = 0. //How much dmg it will cause
Use the damage variable and damage inc per level.

You dont need this
JASS:
    loop
        call TriggerRegisterPlayerUnitEvent(trig,Player(index),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop

You could just use this instead
JASS:
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
I could continue to point out stuff in your spell that needs improvement but i dont want to spend rest of my day explaining it to you.
 
Level 7
Joined
Jul 12, 2008
Messages
295
WoW thats a rly nice spell. Btw i don't understand JASS but the spell is good. Continue with the good work
 
Level 11
Joined
Apr 6, 2008
Messages
760
Ok looks like you copied Silvenon's bugged indexer

You did:
JASS:
                    set Ar[i] = Ar[Total - 1]
                    set Total = Total - 1
It is stupid to do it that way, do
JASS:
set Total = Total - 1
set Ar[i] = Ar[Total]

And the same for creation. You did this
JASS:
set Total = Total + 1
set Ar[Total - 1] = dat
Do this instead
JASS:
set Ar[Total] = dat
set Total = Total + 1

ye allway wonder why he did that.

And in your loop you forgot to
JASS:
set i = i - 1
when destroying the struct. Without it the loop will skip structs when a struct is destroyed.

ops, forgot that...

A thing that could kill your spell is this:
JASS:
SquareRoot((tx-x)*(tx-x) + (ty-y)*(ty-y)) <= Move_Speed

Add this constant global into your global block and use it instead.
JASS:
private constant real Safe_Collide = Move_Speed * 2 + 1

Then your distance safe would be
JASS:
SquareRoot((tx-x)*(tx-x) + (ty-y)*(ty-y)) <= Safe_Collide

Without it the missile could attach itself to the target without getting destroyed.

will do :)


Dont null the things like you did here:
JASS:
unit Caster = null //The Caster
unit Target = null //The Target
unit Dummy = null //The Dummy

real Damage = 0. //How much dmg it will cause

player Play = null //The Owner of the Caster

effect SFX = null //The Effect on the dummy

Just null them when struct is destroyed. It saves memory to make the variables null when destroyed, else the varibles can contain data troughout the entire game.

And you don't need this
JASS:
real Damage = 0. //How much dmg it will cause
Use the damage variable and damage inc per level.

have this because if they upgrade in while its flying it will do more damage then its supose to


well i will make some changes, and thanks
 
Level 18
Joined
Oct 18, 2007
Messages
930
And you use locals on loop :p
JASS:
        local real x
        local real y
        local real tx
        local real ty
        local real angle

and better to use static members for an indexer, this could be useful if you have more than 1 indexer in your spell

You could just easly use 8 statics in struct, it increases efficiency :p

JASS:
struct YourStruct
    [your members]

    static integer array Index
    static integer Total = 0
    static timer Tim = CreateTimer()

    static real nX = 0 // Missile X
    static real nY = 0 // Missile Y
    static real nX2 = 0 // Target X
    static real nY2 = 0 // Target Y

    static real nA = 0 // Angle from missile to target
 
Level 18
Joined
Oct 18, 2007
Messages
930
You dont need to add crow form everytime, 1 time on the start works flawless

And i noticed this:
JASS:
    private real Projectile_Size = Move_Speed * 2 + 1
Make it a constant

and
JASS:
    private real tR
[...]
        set tR = GetUnitFlyHeight(Dat.Target)
        if tR > 0. then
            set Dat.z = Dat.NewHeight(tR)
        endif
You dont need it

do this instead and remove tR
JASS:
        if GetUnitFlyHeight(Dat.Target) > 0. then
            set Dat.z = Dat.NewHeight(tR)
        endif

You dont need to the member 'z'. I used it because i make my missile spell bounce.

And again,
JASS:
// you forgot it again >.<
set i = i - 1

This could kill your code if you have this in the onDestroy Method
JASS:
        if .Total == 0 then
            call PauseTimer(.Time)
        endif

Put it in the loop >.<


Please clean up your code :p

 
Level 1
Joined
Sep 22, 2009
Messages
1
Hmm i noticed that when i use the spell on me it leaves the special effect(that skull xD) nothing special but something.. :D
Other then that its GREAT!
 
Level 2
Joined
Mar 30, 2010
Messages
10
I cannot Imort this spell into my map and i cannot import the ability on a unit of my map so can you show me the triggers im new at this kind of things and i realy like this ability i want it in my map its so coooool...
 
Top