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

Thievery v1.4

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
This is my first spell written in vJass

Tooltip:
The hero summons up a treasure box, releasing waves and explosion periodically dealing minor damage. After some time, it would explode dealing massive damage to units within range.

This spell is based on Zidane Tribal's skill, Thievery, in Final Fantasy IX

Have fun and Enjoy!

Spell is fully MUI and leakless (i think??)

Requires:
GroupUtils by Rising_Dusk

Code as someone may request it to be posted O,o :

JASS:
//*
//* Thievery
//*  - by jim7777
//*
//* This ability is based on the ability of Zidane Tribal in Final Fantasy IX
//* This spell is fully MUI
//*
//* Installation:
//* -Copy this trigger, the GroupUtils libraries (if you don't have it on your map)
//* -Copy the 2 dummy units and the ability into your map
//* -Edit the configurables to fit your needs
//* -DO NOT FORGET to edit the calculate function to suit your needs!!!
//* -Test your map
//* -Lastly, enjoy it!
//*
//*****************************************************************************

library Thievery initializer Init requires GroupUtils
//CONFIGURABLES
    globals
        private constant integer ABILITY_ID = 'A000' // RawCode of the ability (Thievery)
        private constant integer DUMMY_ID = 'h000' // RawCode of the dummy unit (Thievery Dummy)
        private constant integer ATTACKER_ID = 'n000' //RawCode of the attacker unit (Dummy Thievery Attacker)
        private constant real INTERVAL = 0.3 //interval seconds between each minor damage.
        private constant real MINOR_DMG = 30. //damage dealt every after interval
        private constant real RANGE = 400. //maximum range of units to be affected
        private constant real FINAL_DMG = 100. // set the base of the final damage here to be multiplied to the ability level
        private constant string SFX = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl" //SFX for final blast
        private constant string SFX2 = "Abilities\\Spells\\Undead\\ReplenishMana\\ReplenishManaCaster.mdl" //SFX for final blast
        private constant string SFX3 = "Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl" //SFX for final blast
        private constant string PERSFX = "Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO //Attack type when dealing damage 
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL //Damage type when dealing damage
        private constant boolean ENABLE_RECYCLING = false
        //set this to false if you want more special effects, set this to true if you want to work the spell faster but no special effects
        // ENABLE_RECYLING is very important as it will almost change the spell if you want it more effects but use more memory (because of periodically creating units) or vice-versa
    endglobals
//Set some calculations here on how it would calculate the final damage  
    private function Calculate takes unit u returns real
        local real r
        local integer level = GetUnitAbilityLevel(u,ABILITY_ID)
        set r = FINAL_DMG*level // Deals FINAL_DMG*level of ability final damage (simple math will do this and you could do this in the way you like. This is just a sample
        // FINAL_DMG should be set in your globals or use a custom one if you want
        return r
    endfunction
//End of setting damage
//*                                                    *//
//*--------------- END OF CONFIGURATION ---------------*//
//*       
    globals
        private hashtable thiefht = InitHashtable()
    endglobals
    
    private function ThiefSetUnitId takes unit u, unit caster, integer value returns nothing
        local integer i = LoadInteger(thiefht,StringHash("thiefcastables"),GetHandleId(caster)) + 1
        call SaveUnitHandle(thiefht,value,GetHandleId(caster),u)
        call SaveInteger(thiefht,StringHash("thiefcastables"),GetHandleId(caster),i)
    endfunction
    
    private function ThiefEndRecycle takes unit caster returns nothing
        call SaveInteger(thiefht,StringHash("thiefcastables"),GetHandleId(caster),0)
    endfunction

    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == ABILITY_ID
    endfunction
    
    private function ShowSFX takes unit u returns nothing
        local real x
        local real y
        local real ux = GetUnitX(u)
        local real uy = GetUnitY(u)
        local integer i = 1
        loop
            exitwhen(i > 4)
            set x = ux + RANGE * Cos(1.570796*i)
            set y = uy + RANGE * Sin(1.570796*i)
            call DestroyEffect(AddSpecialEffect(SFX2,x,y))
            call DestroyEffect(AddSpecialEffect(SFX3,x,y))
            set i = i + 1
        endloop
    endfunction
    
    private struct Thief
        unit caster
        unit dummy
        location target
        integer count = 0
        real dmg
        boolean GroupDmg
        boolean MinorDmg
        static integer array Index
        static integer Total = 0
        static timer time = null
        
        static method GroupDamage takes nothing returns nothing
            local Thief dat
            local unit f = GetEnumUnit()
            local integer i = 0
            loop
                exitwhen i >= thistype.Total
                set dat = thistype.Index[i]
                if IsUnitEnemy(f,GetOwningPlayer(dat.caster)) and dat.GroupDmg then
                    call UnitDamageTarget(dat.caster,f,dat.dmg,true,true,ATTACK_TYPE,DAMAGE_TYPE,null)
                endif
                set i = i + 1
            endloop
            set f = null
        endmethod
        
        static method MinorDamage takes nothing returns nothing
            local Thief dat
            local unit f = GetEnumUnit()
            local integer i = 0
            loop
                exitwhen i >= thistype.Total
                set dat = thistype.Index[i]
                if IsUnitEnemy(f,GetOwningPlayer(dat.caster)) and dat.MinorDmg then
                    call UnitDamageTarget(dat.caster,f,MINOR_DMG,true,true,ATTACK_TYPE,DAMAGE_TYPE,null)
                endif
                set i = i + 1
            endloop
            set f = null
        endmethod 
        
        static method onLoop takes nothing returns nothing
            local Thief dat
            local real rnd = GetRandomReal(0,75)
            local real ang 
            local real dist = GetRandomReal(100,500)
            local real x
            local real y
            local group g = NewGroup()
            local unit f
            local unit d
            local integer i = 0
            loop
                exitwhen i >= thistype.Total
                set dat = thistype.Index[i]
                set ang = GetUnitFacing(dat.caster) + GetRandomReal(0,360)
                set x = GetUnitX(dat.dummy)
                set y = GetUnitY(dat.dummy)
                if ENABLE_RECYCLING == true then
                    call DestroyEffect(AddSpecialEffect(PERSFX,x,y))
                    if LoadInteger(thiefht,StringHash("thiefcastables"),GetHandleId(dat.caster)) == 0 then
                        set d = CreateUnit(GetOwningPlayer(dat.caster),ATTACKER_ID,x,y,ang) 
                        call ThiefSetUnitId(d,dat.caster,dat)
                    else
                        set d = LoadUnitHandle(thiefht,dat,GetHandleId(dat.caster))
                    endif
                else
                    set d = CreateUnit(GetOwningPlayer(dat.caster),ATTACKER_ID,x,y,ang) 
                endif
                set dat.count = dat.count + 1
                if dat.count > 8 then
                    call DestroyEffect(AddSpecialEffect(SFX,GetUnitX(dat.dummy),GetUnitY(dat.dummy)))
                    call GroupUnitsInArea(g, GetUnitX(dat.dummy), GetUnitY(dat.dummy), RANGE)
                    set dat.GroupDmg = true
                    call ForGroup(g,function Thief.GroupDamage)
                    set dat.GroupDmg = false
                    call ShowSFX(dat.dummy)
                    call PauseUnit(dat.caster,false)
                    call RemoveLocation(dat.target)
                    call RemoveUnit(dat.dummy)
                    call ThiefEndRecycle(dat.caster)
                    if ENABLE_RECYCLING == true then
                        call UnitApplyTimedLife(d,'BTLF',0.8)
                        set d = null
                    endif
                    call dat.destroy()
                    
                    set thistype.Total = thistype.Total - 1
                    set thistype.Index[i] = thistype.Index[thistype.Total]

                    set i = i - 1
                else
                    set x = GetUnitX(dat.dummy) + dist * Cos(ang * 0.017453292)
                    set y = GetUnitY(dat.dummy) + dist * Sin(ang * 0.017453292)
                    if dat.count == 1 then
                        call SetUnitAnimation(dat.dummy,"stand")
                        call IssuePointOrderLoc(d,"attackground",Location(x,y))
                    else
                    call IssuePointOrderLoc(d, "attackground",Location(x,y))
                    call GroupUnitsInArea(g, GetUnitX(dat.dummy), GetUnitY(dat.dummy), RANGE)
                    set dat.MinorDmg = true
                    call ForGroup(g,function Thief.MinorDamage)
                    set dat.MinorDmg = false
                    endif
                endif
                call ReleaseGroup(g)
                if ENABLE_RECYCLING == false then
                    call UnitApplyTimedLife(d,'BTLF',0.8)
                    set d = null
                endif
                set i = i + 1
            endloop
            
            if thistype.Total == 0 then
                call PauseTimer(thistype.time)
            endif
            set f = null
            set g = null
        endmethod
        
        static method start takes unit u, location t returns Thief
            local Thief dat = Thief.allocate()
            local real facing = GetUnitFacing(u) - 45
            local real x = GetLocationX(t)
            local real y = GetLocationY(t)
            call PauseUnit(u,true)
            set dat.GroupDmg = false
            set dat.MinorDmg = false
            set dat.caster = u
            set dat.target = t
            set dat.dmg = Calculate(u)
            set dat.dummy = CreateUnit(GetOwningPlayer(u),DUMMY_ID,x,y,facing)
            call SetUnitAnimation(dat.dummy,"birth")
            if thistype.Total == 0 then
                call TimerStart(thistype.time, INTERVAL, true, function thistype.onLoop)
            endif
            
            set thistype.Index[thistype.Total] = dat
            set thistype.Total = thistype.Total + 1
            return dat
        endmethod
    endstruct
    
    private function Actions takes nothing returns nothing
        call Thief.start(GetTriggerUnit(),GetSpellTargetLoc())
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddAction(t,function Actions)
        call TriggerAddCondition(t,Condition(function Conditions))
        set Thief.time = CreateTimer()
        call Preload(SFX)
        call Preload(SFX2)
        call Preload(SFX3)
        call Preload(PERSFX)
        set t = null
    endfunction

endlibrary



v1.0
- Initial Release
v1.1
- Fix up code and avoided some functions (Thanks to Mortar and Adiktuz)
v1.2
- Fix up some code
- Added up a boolean if the user wants recycling (It may reduce the spell's SFX if true and works as normal if false. Default: false)
- Now uses Hashtables because of the Recycling addition.
v1.3
- Remove TimerUtils requirement and usage
- Added up a few functions for indexing
- Fix some bugs
- Rebuilt Calculate function
v1.4
- Changed from Scope to Library
- Updated some parts of the code.
- Fix some bugs
- Optimized the hashtable parts.


This spell is kinda simple i think and my coding is maybe messy. Please help me in cleaning it a bit :D hope it would be nice because its my second try in using structs

Keywords:
thievery, final, fantasy, thief, zidane, tribal, jim7777, explosion, treasure, box, vjass, jass
Contents

Thievery v1.4 (Map)

Reviews
12th Dec 2015 IcemanBo: For too long time as NeedsFix. Rejected. Bribe - The spell effect art doesn't really fit. A treasure box from a Paladin mixed with really evil orb sounds followed by a very orcish warstomp. Fixing up the art would help...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: For too long time as NeedsFix. Rejected.

Bribe -

The spell effect art doesn't really fit. A treasure box from a Paladin mixed with really evil orb sounds followed by a very orcish warstomp. Fixing up the art would help to show off the mechanics a lot better.

You don't need GetSpellTargetLoc. Use GetSpellTargetX/Y instead of locations.

You could simplify this:

JASS:
    private function Calculate takes unit u returns real
        local real r
        local integer level = GetUnitAbilityLevel(u,ABILITY_ID)
        set r = FINAL_DMG*level // Deals FINAL_DMG*level of ability final damage (simple math will do this and you could do this in the way you like. This is just a sample
        // FINAL_DMG should be set in your globals or use a custom one if you want
        return r
    endfunction

Into this:

JASS:
    private function Calculate takes unit u returns real
        return FINAL_DMG*GetUnitAbilityLevel(u,ABILITY_ID) // Deals FINAL_DMG*level of ability final damage (simple math will do this and you could do this in the way you like. This is just a sample
        // FINAL_DMG should be set in your globals or use a custom one if you want
    endfunction

This line: if ENABLE_RECYCLING == true then should be static if ENABLE_RECYCLING then
 
Level 10
Joined
May 27, 2009
Messages
494
about the last 2 .. how could I fix those i don't know how and I know it could be but how could I make dat.caster damage the group?? when calling it in a function instead of FirstofGroup and fixing polarprojection

and the damage is based on the user so I just made it that way because they can change it whatever they want but its from my map and the damage calculation is different so i change it that way..

EDIT:
ok currently fixing polarprojection using the straight one @@

looking for fixing firstofgroup in dealing damage >.>
 
Last edited:
about the last 2 .. how could I fix those i don't know how and I know it could be but how could I make dat.caster damage the group?? when calling it in a function instead of FirstofGroup and fixing polarprojection

and the damage is based on the user so I just made it that way because they can change it whatever they want but its from my map and the damage calculation is different so i change it that way..

EDIT:
ok currently fixing polarprojection using the straight one @@

looking for fixing firstofgroup in dealing damage >.>

polar projection can be fixed by using the functions that it calls...

for FOG you can use a group loop... you can save the struct instance into a global variable and then create a local instance of the struct and make it equal to the global variable to use it inside the group loop...
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Code Review:

  • Don't use locations, use GetSpellTargetX/Y instead
  • Using DestroyGroup makes GroupUtils pointless. Use ReleaseGroup.
  • You can use ENUM_GROUP instead of a local group.
  • I'm pretty sure you should just use GroupEnumUnitsInArea instead of GroupUnitsInArea
  • I don't know why busterkomo said you don't need the local struct but I think you do.
    Alternatively, instead of having a global integer data, you could have a static member of your struct type in your struct and use that instead.
    Example:
    JASS:
    static thistype data
  • You could use a global array for the spell's damage and use Calculate just as a configuration function.
  • You could just use radians directly to avoid needless conversions.
  • You don't really need r in ShowSFX.
    JASS:
            loop
                exitwhen(i > 4)
                set x = GetUnitX(u) + RANGE * Cos(1.570796*i) // 1.570796 is 90 degrees. 
                set y = GetUnitY(u) + RANGE * Sin(1.570796*i) 
                call DestroyEffect(AddSpecialEffect(SFX2,x,y))
                call DestroyEffect(AddSpecialEffect(SFX3,x,y))
                set i = i + 1
            endloop
    It may be a good idea to use local variables for storing the unit's coordinates.
  • The == true is not really needed in your damage functions.
 
Last edited:
Level 10
Joined
May 27, 2009
Messages
494
ok ok sorry i really codes to messy in structs :(

fixing my errors yeah and I will try watermelons' static thistype data because i still need to add something (the toss coin effect, but not still this time because i still need to get this code right before adding that coz its somewhat over..)

and about the releasegroup.. sorry because in my Map, i don't use the GroupUtils because I have a function for it :D I just copy pasted that part from my map .. gotta get going
 
Top