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

[v]Jass - Smart Mines v0.2

BTNInfernal.gif
Smart Mines
Description
The hero summons several intellegent fireballs which will seek and hunt down every enemy they can find. If an enemy escapes or dies while chased the mine will return to its spawn location. The amount of mines, their lifespan and their damage increases per level.


Level 1
Level 2Level 3

Lifespan: 12.5 Seconds
LifeSpan: 20 SecondsLifeSpan: 27.5 Seconds

Damage: 55
Damage: 85Damage: 115

Mines: 4
Mines: 6Mines: 8

Area of Effect: 250
Area of Effect: 400Area of Effect: 550

NOTE: The spell has a testmode, when enabled it will only affect footmen (even your own). Disable it when importing it to a map.

This spell requires Jass New Gen Pack. Download it from here

Spell Code:
JASS:
library SmartMines initializer Init requires TNTK

    globals
        private constant integer SID = 'smmi'
        //The rawcode of the ability
        
        private constant integer DID = 'smtm'
        //The rawcode of the dummy unit (Smart Mine)
        
        private constant real    CURVE = bj_E*1.55
        //The curvature for the jumps. Small values will cause a high jump
        
        private constant real    SPEED = 6.67
        //The movement speed of a mine
        
        private constant real    Z_OFFSET = 5.
        //The minimal height of a mine
        
        private constant string  DEATH_SFX = "Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
        //The special effect which is displayed when a mine explodes
        
        private constant string  MARK_SFX = "Abilities\\Spells\\Items\\VampiricPotion\\VampPotionCaster.mdl"//"Abilities\\Spells\\Orc\\CommandAura\\CommandAuraTarget.mdl"
        //Special effect which indicates the spawn and return location of a mine
        
        //Text Textag setup !
        
        private constant boolean WANT_TEXT_TAG = true
        //Set it to true if you want to have tags and
        //set it so false if you don´t
        
        //Color settings of the tags
        private constant integer TAG_RED   = 255
        private constant integer TAG_GREEN = 185
        private constant integer TAG_BLUE  = 0
        private constant integer TAG_ALPHA = 100
        
        private constant real    TAG_SIZE = 0.0175
        //The size of a Tag
        
        private constant boolean TESTMODE = true
        //If enabled the missles will only hunt footmen (even your own)
        //I´ve implemented this flag so that you can play a bit around 
        //with the mines and do experiments. Set it to false if you
        //import this spell to your map
        
        private constant boolean ALWAYS_CLOSEST_UNIT = true
        //Enable this if you want the missiles to seek alwayis the
        //closest enemy unit. If this is disabled the missiles will
        //randomly pick the next best enemy unit they can find.
        
        //Damage setup. Should be selfexplaining
        private constant attacktype ATTACK = null
        private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WEAPON = WEAPON_TYPE_WHOKNOWS
        
        //Globals i need to do several things... DON´T TOUCH !
        private integer TEMP_INT
        private real    TEMP_DIST
        private unit    TEMP_UNIT      = null
        private filterfunc ENUM_FILTER = null
        private group      ENUM_GRP    = null
        
    endglobals
    
    //The amount of mines spawned per level
    private constant function GetMineAmount takes integer level returns integer
        return level * 2 + 2
    endfunction
    
    //The Area of Effect in which the mines will be spawned
    private constant function GetSpreadRange takes real level returns real
        return level * 150 + 100
    endfunction
    
    //The range in which a mine detects enemies
    private constant function GetDetectionRange takes real level returns real
        return level * 0 + 330
    endfunction
    
    //If the distance between a chased enemy and a mine exceeds this value
    //the mine will return to it´s spawn location
    private constant function GetEscapeRange takes real level returns real
        return level * 0 + 350
    endfunction
    
    //The amount of time a mine exists
    private constant function GetDuration takes real level returns real
        return level * 7.5 + 5
    endfunction
    
    //The damage a mine deals when it hits an enemy
    private constant function GetDamage takes real level returns real
        return level * 30 + 25
    endfunction
        
    private struct Jump extends TNTKnock
    
        //Static location required to calculate the absolute Z
        static location LOC = Location(.0,.0)
        //Mininal distance between a mine and its target
        static constant real MIN_DIST = 10.0
        
        real maxdistance  
        boolean hasTarget = false //The result will be needed more than once
        texttag tt
        
        //Credits to D4RK_G4ND4LF for this function!
        static constant method Parabola takes real d, real m , real c returns real
            return (4*d*(m-d))/(c*m)
        endmethod
            
        //The condition which will destroy the knockback instance.
        //This behaves a bit different form the parent one.
        method onBreak takes nothing returns boolean
            return not .IsInMap() or ( .knocker != null and IsUnitType(.knocker,UNIT_TYPE_DEAD))
        endmethod
        
        //Actions which happen when the mine is moving
        method onKnock takes nothing returns nothing
            
            local real z = Parabola(.distance,.maxdistance,CURVE) + Z_OFFSET
            
            set .hasTarget = .target != null
            //Knockback instance will be disabled when the mine is searching enemies
            set .inuse = .hasTarget or .distance > thistype.MIN_DIST
            call SetUnitFacing(.knocker,.radiants*bj_RADTODEG)            
            call MoveLocation(thistype.LOC,.x,.y)
            set z = z - GetLocationZ(thistype.LOC)
            call SetUnitFlyHeight(.knocker,z,.0)
            call super.onKnock()
                        
            static if WANT_TEXT_TAG then
                call SetTextTagPosUnit(.tt,.knocker,.0)
            endif
                            
        endmethod
    
    endstruct
    
    //Holds relevant data for the mines
    private struct Mine extends Indexable
    
        Jump jmp //that way i can share fields
        player owner
        effect mark
        real runtime = 0.02
        real fixed_x
        real fixed_y
        //The spawn location
        real range
        real escape
        real damage
        real duration
        
        //Destructor method....
        method onDestroy takes nothing returns nothing
            
            call RemoveUnit(.jmp.knocker)
            call DestroyEffect(AddSpecialEffect(DEATH_SFX,.jmp.x,.jmp.y))
            call DestroyEffect(.mark)
                
            static if WANT_TEXT_TAG then
                call DestroyTextTag(.jmp.tt)
            endif
            
            call .jmp.Stop()
            
        endmethod
        
        //Will destroy a mine-instance when the duration is 0 or the mine is dead (exploded)
        method onBreak takes nothing returns boolean
            set .duration = .duration - .runtime
            return .duration <= 0.0 or .jmp.knocker == null or IsUnitType(.jmp.knocker,UNIT_TYPE_DEAD)
        endmethod
        
        //Peroid actions which be executed while the mine exists
        method onLoop takes nothing returns nothing
            
            //If the mine is IDLE
            if not (.jmp.hasTarget or .jmp.inuse) then
            
                set TEMP_DIST = .range*.range
                set TEMP_INT = this
                call GroupEnumUnitsInRange(ENUM_GRP,.fixed_x,.fixed_y,.range,ENUM_FILTER)
                
                //Did we find something ?
                if TEMP_UNIT != null then
                    call .jmp.StartHoming(.jmp.knocker,TEMP_UNIT,.fixed_x,.fixed_y,SPEED)
                    set .jmp.maxdistance = .escape
                endif
                
                //Needs to be nulled, elseway all mines would chase a found target
                set TEMP_UNIT = null
                
            endif
            
            //When we have a target
            if .jmp.hasTarget then
                
                //Ouch ! Somene got hit by a mine
                if .jmp.distance <= Jump.MIN_DIST then
                
                    call UnitDamageTarget(.jmp.knocker,.jmp.target,.damage,false,false,ATTACK,DAMAGE,WEAPON)
                    call .destroy()
                
                endif
                
                //Someone managed to escape the mine... or he/her got killed
                if .jmp.distance >= .escape or IsUnitType(.jmp.target,UNIT_TYPE_DEAD) then
                
                    set .jmp.inuse = false
                    call .jmp.StartLinearLoc(.jmp.knocker,GetUnitLoc(.jmp.knocker),Location(.fixed_x,.fixed_y),SPEED,true)
                    set .jmp.maxdistance = .jmp.distance
                
                endif
                
            endif
            
            static if WANT_TEXT_TAG then
                
                if .duration <= 1. then
                    call SetTextTagText(.jmp.tt,R2S(.duration),TAG_SIZE)
                else
                    call SetTextTagText(.jmp.tt,I2S(R2I(.duration)),TAG_SIZE)
                endif
                
            endif
        
        endmethod
        
        //creator method...
        static method create takes real sx, real sy, real tx, real ty, integer lv, player p returns thistype
        
            local thistype this = thistype.allocate()
            local texttag tt = null
            
            set .owner    = p 
            set .fixed_x  = tx
            set .fixed_y  = ty
            set .range    = GetDetectionRange(lv)
            set .escape   = GetEscapeRange(lv)
            set .damage   = GetDamage(lv)
            set .duration = GetDuration(lv)
            set .mark = AddSpecialEffect(MARK_SFX,tx,ty)
            
            //Initializing the jump-instance
            set .jmp = Jump.create()
            call .jmp.StartLinearLoc(CreateUnit(p,DID,sx,sy,0.0),Location(sx,sy),Location(tx,ty),SPEED,true)
            set .jmp.maxdistance = .jmp.distance
            
            //Configuring the mine
            call PauseUnit(.jmp.knocker,true)
            call SetUnitFacing(.jmp.knocker,.jmp.radiants*bj_RADTODEG)
            call UnitApplyTimedLife(.jmp.knocker,'BTLF',.duration)
            
            static if WANT_TEXT_TAG then
                set tt = CreateTextTag()
                call SetTextTagColor(tt,TAG_RED,TAG_GREEN,TAG_BLUE,255 - TAG_ALPHA)
                call SetTextTagPermanent(tt,false)
                call SetTextTagVisibility(tt,true)
                call SetTextTagLifespan(tt,.duration)
                call SetTextTagFadepoint(tt,.duration-1.)
                call SetTextTagPosUnit(tt,.jmp.knocker,0.0)
                set .jmp.tt = tt
                set tt = null
            endif
                        
            //In case someone decreased the escape range below the detection
            //range i swap the vaules to safe functionality
            if .escape <= .range then
                set sx = .escape
                set .escape = .range + Jump.MIN_DIST
                set .range = sx
            endif
            
            return this
            
        endmethod
        
    endstruct
        
    //Filter function which will return a target
    private function EnumFilterFunc takes nothing returns boolean
    
    //Things inside this function shouldn´t be too difficult to be not selfexplaining
    
        local unit u = GetFilterUnit()
        local real x
        local real y
        local real d
        local Mine this = TEMP_INT
        
        static if TESTMODE then
            local boolean b = GetUnitTypeId(u) == 'hfoo'
        else
            local boolean b = IsUnitEnemy(u,this.owner) and not ( IsUnitType(u,UNIT_TYPE_DEAD) or IsUnitType(u,UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MECHANICAL) or IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) or IsUnitType(u, UNIT_TYPE_ANCIENT) )
        endif
        
        if b then
        
            static if ALWAYS_CLOSEST_UNIT then
            
                set x = GetWidgetX(u) - this.fixed_x
                set y = GetWidgetY(u) - this.fixed_y
                set d = x*x+y*y
                                
                if d < TEMP_DIST then
                    set TEMP_DIST = d
                    set TEMP_UNIT = u
                endif
                
            else
                set TEMP_UNIT = u
            endif
            
        endif
        
        set u = null
        return false
    
    endfunction
        
    //When the spell is casted
    private function onCast takes nothing returns nothing
    
        local unit ca = GetTriggerUnit()
        local player p = GetOwningPlayer(ca)
        local integer lv = GetUnitAbilityLevel(ca,SID)
        local integer am = GetMineAmount(lv)
        local real range = GetSpreadRange(lv)
        local real cx = GetWidgetX(ca)
        local real cy = GetWidgetY(ca)
        local real x
        local real y
        //Getting some values
        
        loop //Creating all mines
            exitwhen am == 0
            set am = am -1
            set x = cx + GetRandomReal(-range,range)
            set y = cy + GetRandomReal(-range,range)
            // GetRandomReal(-range,range) is the same as range*Sin/Cos(GetRandomReal(0,2*bj_PI))
            call Mine.create(cx,cy,x,y,lv,p)
        endloop
        
        set ca = null
        set p = null
    
    endfunction
    
    //Checks if the correct spell was casted
    private function onCheck takes nothing returns boolean
        return GetSpellAbilityId() == SID
    endfunction
    
    //Initializer
    private function Init takes nothing returns nothing
    
        local trigger trig = CreateTrigger()  
        
        set ENUM_GRP = CreateGroup()
        set ENUM_FILTER = Filter( function EnumFilterFunc)
        call Preload(DEATH_SFX)
        call Preload(MARK_SFX)
        call PreloadStart()
        
        call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(trig,Filter( function onCheck))
        call TriggerAddAction(trig, function onCast)
                                
        set trig = null
                    
    endfunction

endlibrary

VersionChanges

v 0.1
-Initial Release

v 0.2
-Small visual tweaks
-Minor optimazations (thanks to D4RK_G4ND4LF)

Keywords:
smart, mine, smart mine, fire, red, bomb, chase, hunt, track
Contents

Noch eine WARCRAFT-III-Karte (Map)

Reviews
13:58, 16th Jun 2010 TriggerHappy: IIRC, GetUnitX/Y is faster than the widget alternative. The code was fairly good, though.

Moderator

M

Moderator

13:58, 16th Jun 2010
TriggerHappy:

IIRC, GetUnitX/Y is faster than the widget alternative.
The code was fairly good, though.
 
Level 9
Joined
Aug 2, 2008
Messages
219
Hehe thank you both for the comments. Regarding the red circles, the effect gets destroyed when the mine is dead, but the "death animation" lasts a bid longer so it disappears not right after the mine is dead. Every other effect model will get destroyed right after the mine is dead, just change the model path.
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
parabola function could be simplified
http://www.wolframalpha.com/input/?i=(-1*+((d+*+2)+/+m+-+1)+*+((d+*+2)+/+m+-+1)+++1)+*+(m+/+c)
for my GUI stuff I usually use 4 * height * currenctdist * (maxdist-currentdist)/(maxdist*maxdist)

most things with
square root of smthn > smthnelse
should be replaced with
smthn > smthnelse * smthnelse

and the knockback looks weird cause the mines have flying height but get stuck in terrain before the model would atually get stuck

5/5 +rep from me but I'd recommend to change these small things
 
Last edited:
Level 9
Joined
Aug 2, 2008
Messages
219
Thanx for the comments again.
Big thanks to Gandalf for the math suggestions.
I´ve changed the cosmetics and did those small optimizations already but i wait with the update until this got reviewed by a mod.

[updated]
Still not reviewed now, so i´ll use this update also as bump. However this version includes some small tweaks and the optimizations G4ND4LF mentioned.
 
Last edited:
Level 26
Joined
Jun 5, 2008
Messages
1,767
Got me a error.

When i tried to export your files and save it. I got a syntax error.
JASS:
static if SmartMines___WANT_TEXT_TAG then

I'd appreciate it if you fixed this as soon as possible.. or telling me how to fix it. The spell worked great when i tried yours. However, even the original spell file gets the same error if you click save.
Which means it's a fail of the spell and (probably) not my fault when it didn't work.

I've got the JNPG editor.. so it can't be that.
 
Last edited:
Level 26
Joined
Jun 5, 2008
Messages
1,767
Awesome.

I'll try it and see if works.

Edit:
Err.. this is all fine and all, but how do i actually install it? I mean, i don't want to mess up the actual version i got and the instructions they give says to take "the two files and replace two other files"
The problem with this is that there's like three folders each in that .7z archive and there's about 20 files in each one.

Edit x2:
Or is it way easier than i expect it to be? do i just have to extract the folders into the JNPG editor and compile after that?

Edit x3:
I gave up and downloaded an already patched version. It is now up to date. The same error appears though.

Edit x4:
Took a screeny, this is how it is supposed to look like, right? Just to make sure i'm not making a mistake here.
 

Attachments

  • Screenshots..RightOrWrong.jpg
    Screenshots..RightOrWrong.jpg
    188.6 KB · Views: 150
Last edited:
Level 9
Joined
Aug 2, 2008
Messages
219
The latest JassNewGenPack does not always have the latest JassHelper...
Well try this, download this file and extract it´s content. The go the executables folder from the extracted archive and copy the file jasshelper.exe and clijasshelper.exe. Then go to your jass new gen folder and open the jasshelper folder and replace the 2 old jasshelper files with those you copied before.
Run JassNew gen again and take a look a the JassHelper version number, the latest is 0.A.2.B

In case that won´t work too i´ll make an altered script, as promised.
 
Top