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

TNT Cactus v1.5

  • Like
Reactions: shadowvzs
Places a cactus in the ground. The cactus lives for some time and explodes with firey projectiles, dealing 114 damage to every enemy unit in range 350.

Cactus Duration: 3/4/5 seconds

Cooldown: 8 seconds

--------

How to import:

1. Create a hashtable variable named 'TNT_Cactus_H' and a group named 'TNT_Cactus_TempGroup'.
2. Copy all the Object Editor data (Dummy and the spells).
3. Copy the TNT Cactus folder and paste into your map.
4. Don't forget to credit me (bowser499) and bigapple90 (for Detonate3 icon) if you use this spell.
5. Enjoy!

--------


JASS:
// CONSTANTS

constant function TNT_Cactus_AbilityId takes nothing returns integer
    return 'A000'
endfunction

constant function TNT_Cactus_CactusId takes nothing returns integer
    return 'h000'
endfunction

constant function TNT_Cactus_FireProjectileId takes nothing returns integer
    return 'n001'
endfunction

constant function TNT_Cactus_ShowCountDown takes nothing returns boolean
    return true
endfunction

constant function TNT_Cactus_AoE takes nothing returns real
    return 350.
endfunction

function TNT_Cactus_Damage takes nothing returns real
    return 114.
endfunction

function TNT_Cactus_AbilityLevel takes unit spellCaster returns integer
    return GetUnitAbilityLevel(spellCaster,TNT_Cactus_AbilityId())
endfunction

function TNT_Cactus_CactusDuration takes unit spellCaster returns integer
    return 2 + TNT_Cactus_AbilityLevel(spellCaster)
endfunction

function TNT_Cactus_CactusEffect1 takes nothing returns string
    return "Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdl"
endfunction

function TNT_Cactus_CactusEffect2 takes nothing returns string
    return "Abilities\\Spells\\NightElf\\EntanglingRoots\\EntanglingRootsTarget.mdl"
endfunction

function TNT_Cactus_CactusDeathEffect takes nothing returns string
    return "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
endfunction

function TNT_Cactus_IsUnitMatching takes unit spellCaster, unit enumUnit returns boolean
    return IsUnitEnemy(spellCaster,GetOwningPlayer(enumUnit)) and not IsUnitType(enumUnit,UNIT_TYPE_DEAD) and not IsUnitType(enumUnit,UNIT_TYPE_STRUCTURE)
endfunction

function TNT_Cactus_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_NORMAL
endfunction

function TNT_Cactus_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_FIRE
endfunction

function TNT_Cactus_WeaponType takes nothing returns weapontype
    return WEAPON_TYPE_WHOKNOWS
endfunction

function TNT_Cactus_GetMaxEffects takes integer abilityLevel returns integer
    return 9 + abilityLevel * 3
endfunction

function TNT_Cactus_GetProjMaxHeight takes integer currentEffectCount returns real
    return GetRandomReal(-8.+22.*currentEffectCount,98.+22.*currentEffectCount)+250.
endfunction

function TNT_Cactus_GetProjFacingAngle takes nothing returns real
    return I2R(GetRandomInt(0,359))
endfunction

function TNT_Cactus_ExplosionText takes nothing returns string
    return "BOOM!"
endfunction

function TNT_Cactus_SecondaryExplosionEffect takes nothing returns string
    return "Abilities\\Spells\\Human\\Flare\\FlareCaster.mdl"
endfunction

function TNT_Cactus_FireDeathDuration takes nothing returns real
    return 1.
endfunction

function TNT_Cactus_FireDeath_DamageAoE takes nothing returns real
    return 240.
endfunction

function TNT_Cactus_FireDeath_Damage takes nothing returns real
    return 250.
endfunction

// ADDITIONAL FUNCTIONS

function Trig_TNT_Cactus_RemoveOnDeathTimer takes nothing returns nothing
    local timer deathTimer = GetExpiredTimer()
    local integer deathTimerId = GetHandleId(deathTimer)
    local unit whichUnit = LoadUnitHandle(udg_TNT_Cactus_H,deathTimerId,0)
    
    call RemoveUnit(whichUnit)
    call FlushChildHashtable(udg_TNT_Cactus_H,deathTimerId)
    call DestroyTimer(deathTimer)
    
    set deathTimer = null
    set whichUnit = null
endfunction 

function Trig_TNT_Cactus_RemoveOnDeath takes unit abilityCaster, unit whichUnit, real deathDuration returns nothing
    local timer deathTimer = CreateTimer()
    local integer deathTimerId = GetHandleId(deathTimer)
    local unit enumUnit
    
    call GroupEnumUnitsInRange(udg_TNT_Cactus_TempGroup,GetUnitX(whichUnit),GetUnitY(whichUnit),TNT_Cactus_FireDeath_DamageAoE(),null)
    
    loop
        set enumUnit = FirstOfGroup(udg_TNT_Cactus_TempGroup)
        exitwhen enumUnit == null
        if TNT_Cactus_IsUnitMatching(abilityCaster,enumUnit) then
            call UnitDamageTarget(abilityCaster,enumUnit,TNT_Cactus_FireDeath_Damage(),true,false,TNT_Cactus_AttackType(),TNT_Cactus_DamageType(),TNT_Cactus_WeaponType())
        endif
        call GroupRemoveUnit(udg_TNT_Cactus_TempGroup,enumUnit)
    endloop
    
    call SetUnitAnimation(whichUnit,"death")
    call SaveUnitHandle(udg_TNT_Cactus_H,deathTimerId,0,whichUnit)
    
    call TimerStart(deathTimer,deathDuration,false,function Trig_TNT_Cactus_RemoveOnDeathTimer)
    
    set deathTimer = null
endfunction 

function Trig_TNT_Cactus_FadingTextSingle takes player p, string msg, integer red, integer green, integer blue, real x, real y, real vel, real fade, real life returns nothing 
    local texttag t = CreateTextTag() 
    
    call SetTextTagText(t,msg,.024) 
    call SetTextTagPos(t,x,y,0.) 
    call SetTextTagColor(t,red,green,blue,255) 
    call SetTextTagVelocity(t,0,vel) 
    call SetTextTagVisibility(t,GetLocalPlayer()==p) 
    call SetTextTagFadepoint(t,fade) 
    call SetTextTagLifespan(t,life) 
    call SetTextTagPermanent(t,false) 
    
    set t = null 
endfunction

function Trig_TNT_Cactus_GetAngleBetweenCoords takes real xa, real xb, real ya, real yb returns real
    return 57.295827 * Atan2(yb - ya, xb - xa)
endfunction

function Trig_TNT_Cactus_MoveProjectile_Timer takes nothing returns nothing
    local timer moveTimer = GetExpiredTimer()
    local integer moveTimerId = GetHandleId(moveTimer)
    local unit whichProjectile = LoadUnitHandle(udg_TNT_Cactus_H,moveTimerId,0)
    local real projectileX = GetUnitX(whichProjectile)
    local real projectileY = GetUnitY(whichProjectile)
    local real finalX = LoadReal(udg_TNT_Cactus_H,moveTimerId,1)
    local real finalY = LoadReal(udg_TNT_Cactus_H,moveTimerId,2)
    local real moveSpeed = LoadReal(udg_TNT_Cactus_H,moveTimerId,3)/33.
    local real distancePassed = LoadReal(udg_TNT_Cactus_H,moveTimerId,4)
    local real finalDistance = LoadReal(udg_TNT_Cactus_H,moveTimerId,5)
    local real moveAngle = Trig_TNT_Cactus_GetAngleBetweenCoords(projectileX,finalX,projectileY,finalY)
    local real newX = projectileX + moveSpeed * Cos(moveAngle)
    local real newY = projectileY + moveSpeed * Sin(moveAngle)
    local boolean killProjectile = LoadBoolean(udg_TNT_Cactus_H,moveTimerId,6)
    local real deathTime = LoadReal(udg_TNT_Cactus_H,moveTimerId,7)
    
    call SaveReal(udg_TNT_Cactus_H,moveTimerId,4,distancePassed+moveSpeed)
    
    if distancePassed < finalDistance then
        call SetUnitX(whichProjectile,newX)   
        call SetUnitY(whichProjectile,newY)
    else
        if killProjectile then
            call Trig_TNT_Cactus_RemoveOnDeath(LoadUnitHandle(udg_TNT_Cactus_H,moveTimerId,8),whichProjectile,TNT_Cactus_FireDeathDuration())
        endif
        call FlushChildHashtable(udg_TNT_Cactus_H,moveTimerId)
        call PauseTimer(moveTimer)
        call DestroyTimer(moveTimer)
    endif
    
    set moveTimer = null
    set whichProjectile = null
endfunction

function Trig_TNT_Cactus_MoveProjectile takes unit abilityCaster, unit whichProjectile, real fromX, real fromY, real moveAngle, real moveDistance, real moveSpeed, boolean killProjectile returns nothing
    local timer moveTimer = CreateTimer()
    local integer moveTimerId = GetHandleId(moveTimer)
    
    call SaveUnitHandle(udg_TNT_Cactus_H,moveTimerId,0,whichProjectile)
    call SaveReal(udg_TNT_Cactus_H,moveTimerId,1,fromX+moveDistance*Cos(moveAngle*.0174533))
    call SaveReal(udg_TNT_Cactus_H,moveTimerId,2,fromY+moveDistance*Sin(moveAngle*.0174533))
    call SaveReal(udg_TNT_Cactus_H,moveTimerId,3,moveSpeed)
    call SaveReal(udg_TNT_Cactus_H,moveTimerId,4,0.)
    call SaveReal(udg_TNT_Cactus_H,moveTimerId,5,moveDistance)
    call SaveBoolean(udg_TNT_Cactus_H,moveTimerId,6,killProjectile)
    call SaveUnitHandle(udg_TNT_Cactus_H,moveTimerId,8,abilityCaster)
    call TimerStart(moveTimer,.03,true,function Trig_TNT_Cactus_MoveProjectile_Timer)

    set moveTimer = null
endfunction

function Trig_TNT_Cactus_ThrowUnitZ_Timer takes nothing returns nothing
    local timer flipTimer = GetExpiredTimer()
    local integer flipTimerId = GetHandleId(flipTimer)
    local real defaultZ = LoadReal(udg_TNT_Cactus_H,flipTimerId,0)
    local unit whichUnit = LoadUnitHandle(udg_TNT_Cactus_H,flipTimerId,1)
    local real maxZ = LoadReal(udg_TNT_Cactus_H,flipTimerId,2)
    local real currentZ = GetUnitFlyHeight(whichUnit)

    if currentZ > 1. then
        call SetUnitFlyHeight(whichUnit,currentZ+maxZ,0.)
        set maxZ = maxZ - 2.4525
        call SaveReal(udg_TNT_Cactus_H,flipTimerId,2,maxZ)
    else
        call SetUnitFlyHeight(whichUnit,defaultZ,0.)
        call FlushChildHashtable(udg_TNT_Cactus_H,flipTimerId)
        call PauseTimer(flipTimer)
        call DestroyTimer(flipTimer)
    endif

    set flipTimer = null
    set whichUnit = null
endfunction

function Trig_TNT_Cactus_ThrowUnitZ takes unit whichUnit, real maxZ returns nothing
    local timer flipTimer = CreateTimer()
    local integer flipTimerId = GetHandleId(flipTimer)
    local real currentHeight = GetUnitFlyHeight(whichUnit)

    call UnitAddAbility(whichUnit,'Amrf')
    call UnitRemoveAbility(whichUnit,'Amrf')

    call SetUnitFlyHeight(whichUnit,2.,0.)
    
    call SaveReal(udg_TNT_Cactus_H,flipTimerId,0,currentHeight)
    call SaveUnitHandle(udg_TNT_Cactus_H,flipTimerId,1,whichUnit)
    call SaveReal(udg_TNT_Cactus_H,flipTimerId,2,SquareRoot(maxZ*245.25)/10.)
    
    call TimerStart(flipTimer,.03,true,function Trig_TNT_Cactus_ThrowUnitZ_Timer)

    set flipTimer = null
endfunction

// SPELL FUNCTIONS

function Trig_TNT_Cactus_onDeath takes nothing returns nothing
    local timer deathTimer = GetExpiredTimer()
    local integer deathTimerId = GetHandleId(deathTimer)
    local unit abilityCaster = LoadUnitHandle(udg_TNT_Cactus_H,deathTimerId,0)
    local player casterOwner = GetOwningPlayer(abilityCaster)
    local unit explodingCactus = LoadUnitHandle(udg_TNT_Cactus_H,deathTimerId,1) 
    local integer abilityLevel = TNT_Cactus_AbilityLevel(abilityCaster) 
    local real targetX = GetUnitX(explodingCactus)
    local real targetY = GetUnitY(explodingCactus)
    local unit fireProjectile
    local real realHeight
    local real realAngle 
    local integer i = 1
    local unit enumUnit
    
    call ShowUnit(explodingCactus,false)
    call RemoveUnit(explodingCactus)
    call DestroyEffect(AddSpecialEffect(TNT_Cactus_CactusDeathEffect(),targetX,targetY))
    call DestroyEffect(AddSpecialEffect(TNT_Cactus_SecondaryExplosionEffect(),targetX,targetY))
     
    loop
        exitwhen i > TNT_Cactus_GetMaxEffects(abilityLevel) 
        set realHeight = TNT_Cactus_GetProjMaxHeight(i)
        set realAngle = TNT_Cactus_GetProjFacingAngle()
        set fireProjectile = CreateUnit(casterOwner,TNT_Cactus_FireProjectileId(),targetX,targetY,realAngle)
        
        call Trig_TNT_Cactus_ThrowUnitZ(fireProjectile,realHeight)
        call Trig_TNT_Cactus_MoveProjectile(abilityCaster,fireProjectile,targetX,targetY,realAngle,GetRandomReal(TNT_Cactus_AoE(),TNT_Cactus_AoE()+450.),500.,true)
        
        set i = i + 1
    endloop
    
    call GroupEnumUnitsInRange(udg_TNT_Cactus_TempGroup,targetX,targetY,TNT_Cactus_AoE(),null)
    
    loop
        set enumUnit = FirstOfGroup(udg_TNT_Cactus_TempGroup)
        exitwhen enumUnit == null
        if TNT_Cactus_IsUnitMatching(abilityCaster,enumUnit) then
            call UnitDamageTarget(abilityCaster,enumUnit,TNT_Cactus_Damage(),true,false,TNT_Cactus_AttackType(),TNT_Cactus_DamageType(),TNT_Cactus_WeaponType())
        endif
        call GroupRemoveUnit(udg_TNT_Cactus_TempGroup,enumUnit)
    endloop
    
    call FlushChildHashtable(udg_TNT_Cactus_H,deathTimerId)
    call DestroyTimer(deathTimer)
    
    set fireProjectile = null
    set deathTimer = null
    set explodingCactus = null 
    set casterOwner = null
    set abilityCaster = null
endfunction

function Trig_TNT_Cactus_CountDown takes nothing returns nothing
    local timer textTimer = GetExpiredTimer()
    local integer textTimerId = GetHandleId(textTimer)
    local real targetX = LoadReal(udg_TNT_Cactus_H,textTimerId,0)
    local real targetY = LoadReal(udg_TNT_Cactus_H,textTimerId,1)
    local real remainingDuration = LoadReal(udg_TNT_Cactus_H,textTimerId,2)-1
    local player casterOwner = LoadPlayerHandle(udg_TNT_Cactus_H,textTimerId,3)
    
    call SaveReal(udg_TNT_Cactus_H,textTimerId,2,remainingDuration)
    
    if remainingDuration > 0. then
        call Trig_TNT_Cactus_FadingTextSingle(casterOwner,I2S(R2I(remainingDuration)),255,0,0,targetX,targetY,.0355,1.,1.)   
    else
        call Trig_TNT_Cactus_FadingTextSingle(casterOwner,TNT_Cactus_ExplosionText(),255,0,0,targetX,targetY,.0355,1.,1.)
        call FlushChildHashtable(udg_TNT_Cactus_H,textTimerId)
        call PauseTimer(textTimer)
        call DestroyTimer(textTimer)
    endif
    
    set textTimer = null
    set casterOwner = null    
endfunction

function Trig_TNT_Cactus_Conditions takes nothing returns boolean
    local unit abilityCaster = GetTriggerUnit()
    local player casterOwner = GetTriggerPlayer()
    local real targetX = GetSpellTargetX()
    local real targetY = GetSpellTargetY()
    local unit explodingCactus
    local real cactusDuration = TNT_Cactus_CactusDuration(abilityCaster)
    local timer deathTimer
    local timer textTimer
    local integer deathTimerId
    local integer textTimerId
    
    if GetSpellAbilityId() == TNT_Cactus_AbilityId() then
        set explodingCactus = CreateUnit(casterOwner,TNT_Cactus_CactusId(),targetX,targetY,0.)
        set deathTimer = CreateTimer()
        set deathTimerId = GetHandleId(deathTimer)
    
        call DestroyEffect(AddSpecialEffect(TNT_Cactus_CactusEffect1(),targetX,targetY))
        call DestroyEffect(AddSpecialEffect(TNT_Cactus_CactusEffect2(),targetX,targetY))

        call UnitApplyTimedLife(explodingCactus,'BTLF',cactusDuration+.04)
    
        call SaveUnitHandle(udg_TNT_Cactus_H,deathTimerId,0,abilityCaster)
        call SaveUnitHandle(udg_TNT_Cactus_H,deathTimerId,1,explodingCactus)
    
        call TimerStart(deathTimer,cactusDuration,false,function Trig_TNT_Cactus_onDeath)
        
        if TNT_Cactus_ShowCountDown() then
            set textTimer = CreateTimer()
            set textTimerId = GetHandleId(textTimer)
            
            call SaveReal(udg_TNT_Cactus_H,textTimerId,0,targetX)
            call SaveReal(udg_TNT_Cactus_H,textTimerId,1,targetY)
            call SaveReal(udg_TNT_Cactus_H,textTimerId,2,cactusDuration)
            call SavePlayerHandle(udg_TNT_Cactus_H,textTimerId,3,casterOwner)
            
            call TimerStart(textTimer,1.,true,function Trig_TNT_Cactus_CountDown)
        endif
    endif
        
    set textTimer = null
    set deathTimer = null
    set explodingCactus = null 
    set casterOwner = null
    set abilityCaster = null
    return false
endfunction

//===========================================================================
function InitTrig_TNT_Cactus takes nothing returns nothing
    local trigger TNT_Cactus = CreateTrigger()
    set udg_TNT_Cactus_H = InitHashtable()
    call TriggerRegisterAnyUnitEventBJ(TNT_Cactus,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(TNT_Cactus,Condition(function Trig_TNT_Cactus_Conditions))
    set TNT_Cactus = null
endfunction
v1.5
  • Projectile leak removed completely.
  • Added AoE damage configurables.

v1.4
  • Projectile leak removed.

v1.3
  • Fixed the freezed loop caused by new configurable angle.
  • Added the supportive explosion effect.

v1.2
  • Added the text on the explosion.
  • The maxHeight and angle of the firey projectiles are now configurable.

v1.1
  • All the systems are now merged in one trigger and renamed following JESP standard.
  • Merged the actions and the conditions into 1 condition.
  • Special effects are now configurable.
  • Global trigger in the InitTrig function replaced with a local one.
  • The cactus is now removing upon death.
  • GetOwningPlayer(caster) replaced with GetTriggerPlayer().
  • Attack Type, Damage Type and Weapon Type are now configurable.
  • Added the optional spell code that allows to show or hide the countdown before explosion. Use TNT_Cactus_ShowCountDown function to toggle the countdown text.

Keywords:
cactus, fire, boom, blast, original, alchemist, gnoll
Contents

TNT Cactus v1.5 (Map)

Reviews
Approved. You could increase follow through time a bit, and make the ability not disable other abilities. 10:27, 18th Dec 2012 Magtheridon96: Everything looks fine now. Just one last thing: call KillUnit(whichProjectile) This isn't...

Moderator

M

Moderator

Reviewed by Maker, TNT Cactus v1.5, 5th Jan 2013

Approved.

You could increase follow through time a bit, and make the ability not disable other abilities.

10:27, 18th Dec 2012
Magtheridon96:

Everything looks fine now.
Just one last thing:

call KillUnit(whichProjectile)
This isn't enough to remove the dummy completely.
You should call RemoveUnit. Yes, this will ruin the effect, but the solution is to create and destroy a special effect using the model of the projectiles at the position of whichProjectile so it seems like the death animation played.
Then you would call RemoveUnit(whichProjectile).
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
Review:
1.I think removing the TNT_CactusAbilityLevel function is much better so it wont slow.
2.Mag can make this review too:Add an integer arguement on both real functions
3.Special effect should be configurable
4.Merge the condition ability being cast w/ the actions like:
[jass=]
function MyCond takes nothing returns boolean
if GetSpellAbilityId() == MyAbility then
//Actions here
endif
return false
endfunction[/code]
This way,it will be faster
5.Because you are just using the group in a one function,just make it a local.
6.Hashtable should have prefix(to prevent collision)
7.[jass=]exitwhen i > 9 + abilityLevel * 3[/code]
>
[jass=]
function GetMaxInteger takes integer level returns real
return 9 + level * 3
endfunction

//Usage
loop
exitwhen i > GetMaxInteger(abilityLevel)
[/code]
8.EnumUnit should be nulled.
That's all
 
Level 7
Joined
Jan 28, 2012
Messages
266
5.Because you are just using the group in a one function,just make it a local. why should he do that? the purpose of using a global group is so that you don't have to create and destroy which is slower then accessing a global group.
 
Top