Spellpack Random Spells

Exide

I am amazingly focused right now!
Reaction score
448
Hi.

I'm trying to learn vJASS, so I decided to play around with it, creating some spells. I'm pretty satisfied with the result so far, so I decided to put them into a pack and post them here.
Suggestions on how to improve the code is appriciated.

These spells don't have anything in common, hence the lousy thread name.

Requirements: NewGen.

Leakless: I believe so.
Lagless: Yup.
MUI: I doubt it. :(
Pros: Highly Modifiable.




Pushback
Not so original.
A simple spell that targets a point and pushes all nearby enemy backwards, damaging them in the process. Also kills trees that pushed units come in contact with.

Code:
JASS:

scope Pushback initializer Run     //-!Do Not Touch!-

globals
    private constant integer ABILITYID = 'A001'    //Ability Rawcode
    private constant string FIRSTSPECIALEFFECT = "Abilities\\Spells\\Human\\ThunderClap\\ThunderClapCaster.mdl"    //This Effect Must Have A Birth Animation!
    private constant string SECONDSPECIALEFFECT = "Units\\NightElf\\Wisp\\WispExplode.mdl"    //This Effect Must Have A Birth Animation!
    private constant real MIN_DAMAGE = 5    //Formula = MIN_DAMAGE + MAX_DAMAGE * Level of ABILITYID of CASTER
    private constant real MAX_DAMAGE = 5    //5 + 5 * X  by default
    
    private group PUSHBACKGROUP = CreateGroup()   //-!Do Not Touch!-
    private timer TIMER = CreateTimer()     //-!Do Not Touch!-
    private rect AREA = null     //-!Do Not Touch!-
    private unit CASTER     //-!Do Not Touch!-
    private real DAMAGE = 0     //-!Do Not Touch!-
    private real SPELLTARGETX     //-!Do Not Touch!-
    private real SPELLTARGETY     //-!Do Not Touch!-
    private real DESTRUCTABLEX     //-!Do Not Touch!-
    private real DESTRUCTABLEY     //-!Do Not Touch!-
endglobals


//===========================================================================
//=============//-!Do Not Touch Anything Below This Point!-==================
//===========================================================================

private function B takes nothing returns boolean
    return (GetSpellAbilityId() == ABILITYID)
endfunction

function J takes nothing returns boolean
    return (GetLearnedSkill() == ABILITYID)
endfunction

function I takes nothing returns nothing
    set CASTER = GetTriggerUnit()
    set DAMAGE = MIN_DAMAGE + MAX_DAMAGE * GetUnitAbilityLevel(CASTER, ABILITYID)
endfunction

function H takes nothing returns boolean
    local real xf = GetDestructableX(GetFilterDestructable())
    local real yf = GetDestructableY(GetFilterDestructable())
    local real dx = DESTRUCTABLEX - xf
    local real dy = DESTRUCTABLEY - yf
    local real dist = SquareRoot(dx * dx + dy * dy)
    local boolean result
    
    set result = dist <= bj_enumDestructableRadius
    return result
endfunction

private function G takes nothing returns nothing
    call KillDestructable(GetEnumDestructable())
endfunction

private function F takes nothing returns nothing
    call GroupRemoveUnit(PUSHBACKGROUP, GetTriggerUnit())
endfunction

private function E takes nothing returns nothing
    local unit u = GetEnumUnit()
    local real x = GetUnitX(u)
    local real y = GetUnitY(u)
    local real dx = x - SPELLTARGETX
    local real dy = y - SPELLTARGETY
    local real distance = SquareRoot(dx * dx + dy * dy)
    local real angle = bj_RADTODEG * Atan2(y - SPELLTARGETY, x - SPELLTARGETX)
    
    if (distance < 400) then
        call PauseUnit(u, true)
        set DESTRUCTABLEX = x + 8 * Cos(angle * bj_DEGTORAD)
        set DESTRUCTABLEY = y + 8 * Sin(angle * bj_DEGTORAD)
        call SetUnitPosition(u, DESTRUCTABLEX, DESTRUCTABLEY)
        call UnitDamageTarget(CASTER, u, DAMAGE, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_DIVINE, WEAPON_TYPE_WHOKNOWS)
        
        set bj_enumDestructableRadius = 150
        if (AREA == null) then
            set AREA = Rect(DESTRUCTABLEX - 150, DESTRUCTABLEY - 150, DESTRUCTABLEX + 150, DESTRUCTABLEY + 150)
        else
            call SetRect(AREA, DESTRUCTABLEX - 150, DESTRUCTABLEY - 150, DESTRUCTABLEX + 150, DESTRUCTABLEY + 150)
        endif
        
        call EnumDestructablesInRect(AREA, Filter(function H), function G)
    else
        call PauseUnit(u, false)
        call GroupRemoveUnit(PUSHBACKGROUP, u)
    endif
endfunction

private function D takes nothing returns nothing
    call ForGroup(PUSHBACKGROUP, function E)
endfunction

private function C takes nothing returns boolean
    return (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false) and (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405) and (IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(CASTER)) == true)
endfunction

private function A takes nothing returns nothing
    local location loc = GetSpellTargetLoc()
    set SPELLTARGETX = GetLocationX(loc)
    set SPELLTARGETY = GetLocationY(loc)

    call DestroyEffect(AddSpecialEffect(FIRSTSPECIALEFFECT, SPELLTARGETX, SPELLTARGETY))
    call DestroyEffect(AddSpecialEffect(SECONDSPECIALEFFECT, SPELLTARGETX, SPELLTARGETY))
    call GroupEnumUnitsInRange(PUSHBACKGROUP, SPELLTARGETX, SPELLTARGETY, 400, Filter(function C))
    
    call RemoveLocation(loc)
    set loc = null
endfunction

//===========================================================================
private function Run takes nothing returns nothing
    local trigger t = CreateTrigger()
    local trigger tt = CreateTrigger()
    local trigger ttt = CreateTrigger()
    local integer i = 0
    
    loop
    exitwhen i == 16
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(tt, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
        call TriggerRegisterPlayerUnitEvent(ttt, Player(i), EVENT_PLAYER_HERO_SKILL, null)
        set i = i + 1
    endloop
    
    call TriggerAddCondition(t, Condition( function B))
    call TriggerAddCondition(ttt, Condition( function J))
    call TriggerAddAction(t, function A)
    call TriggerAddAction(tt, function F)
    call TriggerAddAction(ttt, function I)
    
    call TimerStart(TIMER, .02, true, function D)
endfunction

endscope


Life Drain:
An aura that passively steals hp away from nearby enemies and give them to the Hero.

Code:
JASS:

scope LifeDrain initializer Run     //-!Do Not Touch!-

globals
    private constant integer ABILITYID = 'A000'    //Ability Rawcode
    private constant integer AREAOFEFFECT = 600    //The Area of Effect
    private  string array RANDOMEFFECT     //These Effects Are Declared In initializer Run. These Effects Must Have A Birth Animation!    
    private real INTERVAL = 2    //The Amount Of Time Between The Life Drains.    
    private real MIN_AMOUNT = 2    //Formula = MIN_AMOUNT + MAX_AMOUNT * Level of ABILITYID of DRAINER
    private real MAX_AMOUNT = 2    //2 + 2 * X  by default
    private integer MIN_TARGETS = 3    //Formula = MIN_TARGETS * Level of ABILITYID of DRAINER  3 * X  by default
    
    private integer NUMBEROFTARGETS = 0   //-!Do Not Touch!-
    private group LIFEDRAINGROUP = CreateGroup()   //-!Do Not Touch!-
    private timer TIMER = CreateTimer()   //-!Do Not Touch!-
    private real AMOUNT = 0   //-!Do Not Touch!-
    private unit DRAINER   //-!Do Not Touch!-
endglobals


//===========================================================================
//=============//-!Do Not Touch Anything Below This Point!-==================
//===========================================================================

function B takes nothing returns boolean
    return (GetLearnedSkill() == ABILITYID)
endfunction

private function E takes nothing returns nothing
    call GroupRemoveUnit(LIFEDRAINGROUP, GetTriggerUnit())
endfunction

private function D takes nothing returns boolean
    return (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false) and (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405) and (IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(DRAINER)) == true)
endfunction

function C takes nothing returns nothing
    local real x = GetUnitX(DRAINER)
    local real y = GetUnitY(DRAINER)
    local integer i = 0
    local unit u 
    
    call GroupEnumUnitsInRange(LIFEDRAINGROUP, x, y, AREAOFEFFECT, Filter(function D))
    loop
        set u = FirstOfGroup(LIFEDRAINGROUP)
        exitwhen u == null
        call DestroyEffect(AddSpecialEffectTarget(RANDOMEFFECT[GetRandomInt(0,6)], u, "chest"))
        call UnitDamageTarget(DRAINER, u, AMOUNT, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_DIVINE, WEAPON_TYPE_WHOKNOWS)
        call SetWidgetLife(DRAINER, GetWidgetLife(DRAINER) + AMOUNT)
        call GroupRemoveUnit(LIFEDRAINGROUP, u)
        
        set i = i + 1
        if (i == NUMBEROFTARGETS) then
            call GroupClear(LIFEDRAINGROUP)
            set u = null
            return
        endif
    endloop
    
    set u = null
endfunction

function A takes nothing returns nothing
    set DRAINER = GetTriggerUnit()
    set NUMBEROFTARGETS = MIN_TARGETS * GetUnitAbilityLevel(DRAINER, ABILITYID)
    set AMOUNT = MIN_AMOUNT + MAX_AMOUNT * GetUnitAbilityLevel(DRAINER, ABILITYID)
endfunction

//===========================================================================
function Run takes nothing returns nothing
    local trigger t = CreateTrigger()
    local trigger tt = CreateTrigger()
    local integer i = 0
    
    loop
    exitwhen i == 16
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_HERO_SKILL, null)
        call TriggerRegisterPlayerUnitEvent(tt, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
        set i = i + 1
    endloop
    
    call TriggerAddCondition(t, Condition( function B))
    call TriggerAddAction(t, function A)
    call TriggerAddAction(tt, function E)
    call TimerStart(TIMER, INTERVAL, true, function C)
    
    set RANDOMEFFECT[0] = "Objects\\Spawnmodels\\Orc\\Orcblood\\HeroShadowHunterBlood.mdl"     //-Feel Free To Touch-
    set RANDOMEFFECT[1] = "Objects\\Spawnmodels\\Orc\\Orcblood\\BattrollBlood.mdl"
    set RANDOMEFFECT[2] = "Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodRifleman.mdl"
    set RANDOMEFFECT[3] = "Objects\\Spawnmodels\\NightElf\\NightElfBlood\\NightElfBloodDruidRaven.mdl"
    set RANDOMEFFECT[4] = "Objects\\Spawnmodels\\Orc\\Orcblood\\OrcBloodHeroFarSeer.mdl"
    set RANDOMEFFECT[5] = "Objects\\Spawnmodels\\Undead\\UndeadBlood\\UndeadBloodGargoyle.mdl"
    set RANDOMEFFECT[6] = "Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodKnight.mdl"     //-Feel Free To Touch-
endfunction

endscope


Some Orb:
A custom orb effect.
This passive ability has a chance of hitting the enemy with a variety of spells, when attacking.
Clarification:
Passive ability. X% chance of occuring. Upon activation; creates a dummy, casts one out of Y (3, by default) spells on the target. Activates when a unit is attacked and takes damage by the Hero. (Not simply order attack.)

Code:
JASS:

scope SomeOrb initializer Run     //-!Do Not Touch!-

globals
    private constant integer ABILITYID = 'A002'    //Ability Rawcode
    private constant integer BUFFID = 'B003'    //Buff Rawcode
    private integer array ORBABILITY    //Ability Rawcode of the Orb Effect.  These Integers Are Declared In initializer Run.
    private string array ORBORDER    //Order String of the Orb Effect.  These Strings Are Declared In initializer Run.
    private integer DUMMYUNITID = 'h001'    //Dummy Unit ID
    private integer MAXNUMBEROFORBS = 3    //The max number of different Orb Effects.
    private integer MAX_CHANCE = 8    //Formula = X = MAX_CHANCE - Level of ABILITYID of BUFFPLACER = 1 chance in X
                                      //For Example: X = 8 - 1/2/3/4 = 7/6/5/4 = 1 chance in 7/6/5/4 ~ 14%/16%/20%/25%

    private group ORBGROUP = CreateGroup()   //-!Do Not Touch!-
    private timer TIMER = CreateTimer()   //-!Do Not Touch!-
    private integer CHANCETOOCCUR   //-!Do Not Touch!-
    private integer ABILITYLEVEL   //-!Do Not Touch!-
    private unit BUFFPLACER   //-!Do Not Touch!-
    private integer OWNERID   //-!Do Not Touch!-
endglobals


//===========================================================================
//=============//-!Do Not Touch Anything Below This Point!-==================
//===========================================================================

private function B takes nothing returns boolean
    return (GetLearnedSkill() == ABILITYID)
endfunction

private function D takes nothing returns boolean
    return (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false) and (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405) and (IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(BUFFPLACER)) == true)
endfunction

private function C takes nothing returns nothing
    local real x = GetUnitX(BUFFPLACER)
    local real y = GetUnitY(BUFFPLACER)
    local integer r
    local integer i
    local unit u
    local unit d
    
    call GroupEnumUnitsInRange(ORBGROUP, x, y, 200, Filter(function D))
    loop
        set u = FirstOfGroup(ORBGROUP)
        exitwhen u == null
        set i = GetUnitAbilityLevel(u, BUFFID)
        if (i != 0) then
            set i = GetRandomInt(1, CHANCETOOCCUR)
            if (i == 1) then
                set r = GetRandomInt(1, MAXNUMBEROFORBS)
                set d = CreateUnit(Player(OWNERID), DUMMYUNITID, x, y, 270)
                call UnitApplyTimedLife(d, 'BTLF', 3)
                call UnitAddAbility(d, ORBABILITY[r])
                call SetUnitAbilityLevel(d, ORBABILITY[r], ABILITYLEVEL)
                call IssueTargetOrder(d, ORBORDER[r], u)
            endif
        endif
        call GroupRemoveUnit(ORBGROUP, u)
    endloop
    
    set u = null
endfunction

private function A takes nothing returns nothing
    set BUFFPLACER = GetTriggerUnit()
    set ABILITYLEVEL = GetUnitAbilityLevel(BUFFPLACER, ABILITYID)
    set OWNERID = GetPlayerId(GetOwningPlayer(BUFFPLACER))
    set CHANCETOOCCUR = MAX_CHANCE - GetUnitAbilityLevel(BUFFPLACER, ABILITYID)
    if (CHANCETOOCCUR < 1) then
        set CHANCETOOCCUR = 1
    endif
endfunction

//===========================================================================
private function Run takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    
    loop
    exitwhen i == 16
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_HERO_SKILL, null)
        set i = i + 1
    endloop
    
    call TriggerAddCondition(t, Condition( function B))
    call TriggerAddAction(t, function A)
    
    call TimerStart(TIMER, .01, true, function C)
    
    set ORBABILITY[1] = 'A003'     //-Feel Free To Touch-
    set ORBABILITY[2] = 'A005'
    set ORBABILITY[3] = 'A006'
    set ORBORDER[1] = "frostnova"
    set ORBORDER[2] = "deathcoil"
    set ORBORDER[3] = "chainlightning"     //-Feel Free To Touch-
endfunction

endscope


Flash Step:
Bleach rip-off.
Grants X% chance of evading incoming attacks.
Blinks the Hero to position of target move/smart-order, if the distance between the Hero and the point is greater than 250 and less than Y. Costs 25 (by default) mana. 2 seconds cd (also by default.)
Code:
JASS:

scope FlashStep initializer Run     //-!Do Not Touch!-

globals
    private constant integer ABILITYID = 'A004'    //Ability Rawcode
    private real MINDISTANCE = 200    //Formula = MINDISTANCE + MINDISTANCE * Level of ABILITYID of FLASHUNIT
    private constant string FIRSTSPECIALEFFECT = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"    //This Effect Must Have A Birth Animation!
    private constant string SECONDSPECIALEFFECT = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"    //This Effect Must Have A Birth Animation!
    private integer MANACOST = 25     //Mana cost of Flash Step
    private real COOLDOWN = 2     //Cooldown time of Flash Step
    
    private trigger FLASHTRIGGER = CreateTrigger()     //-!Do Not Touch!-
    private real MAXDISTANCE     //-!Do Not Touch!-
    private unit FLASHUNIT     //-!Do Not Touch!-
endglobals


//===========================================================================
//=============//-!Do Not Touch Anything Below This Point!-==================
//===========================================================================

private function B takes nothing returns boolean
    return (GetLearnedSkill() == ABILITYID)
endfunction

private function C takes nothing returns boolean
    return (GetIssuedOrderId() == OrderId("smart")) or (GetIssuedOrderId() == OrderId("move"))
endfunction

private function E takes nothing returns nothing
    call TriggerSleepAction(COOLDOWN)
    call EnableTrigger(FLASHTRIGGER)
endfunction

private function D takes nothing returns nothing
    local real currentmana = GetUnitState(FLASHUNIT, UNIT_STATE_MANA)
    local real x = GetOrderPointX()
    local real y = GetOrderPointY()
    local real xx = GetUnitX(FLASHUNIT)
    local real yy = GetUnitY(FLASHUNIT)
    local real dx = x - xx
    local real dy = y - yy
    local real distance = SquareRoot(dx * dx + dy * dy)
    
    if (currentmana > MANACOST) then
        if (distance > 250) and (distance < MAXDISTANCE) then
            call DestroyEffect(AddSpecialEffect(FIRSTSPECIALEFFECT, xx, yy))
            call SetUnitPosition(FLASHUNIT, x, y)
            call SetUnitState(FLASHUNIT, UNIT_STATE_MANA, currentmana - MANACOST)
            call DestroyEffect(AddSpecialEffect(SECONDSPECIALEFFECT, x, y))
            call DisableTrigger(FLASHTRIGGER)
            call E.execute()
        endif
    endif
endfunction

private function A takes nothing returns nothing
    set FLASHUNIT = GetTriggerUnit()
    set MAXDISTANCE = MINDISTANCE + MINDISTANCE * GetUnitAbilityLevel(FLASHUNIT, ABILITYID)
    
    call TriggerRegisterUnitEvent(FLASHTRIGGER, FLASHUNIT, EVENT_UNIT_ISSUED_POINT_ORDER)
    call TriggerAddCondition(FLASHTRIGGER, Condition( function C))
    call TriggerAddAction(FLASHTRIGGER, function D)
endfunction

//===========================================================================
private function Run takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    
    loop
    exitwhen i == 16
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_HERO_SKILL, null)
        set i = i + 1
    endloop
    
    call TriggerAddCondition(t, Condition( function B))
    call TriggerAddAction(t, function A)
endfunction

endscope

Demomap attached.
Feedback appriciated. :)
 

Attachments

  • [Spellpack] Random Spells.w3x
    37.3 KB · Views: 259

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
I dont call theese "vJASS" spells more like "JASS" spells using scopes. To make them "real" vJASS spells use structs and stuff like that.

And you should not use triggers that way......
 

Exide

I am amazingly focused right now!
Reaction score
448
I dont call theese "vJASS" spells more like "JASS" spells using scopes. To make them "real" vJASS spells use structs and stuff like that.

And you should not use triggers that way......

Care to explain more?
 

Viikuna

No Marlo no game.
Reaction score
265
Well, you can do vJass without structs just fine, but yea we do see that you are not familiar with all the tricks yet.

Its a good start I guess, althought there is so much to improve that I dont feel like listing everything right now.

Ill list more later, but for now preload all effects and abilities and use only one static dummy caster instead of creating them on spell cast.

...

Also, you use timers in very weird GUI way, so you might wanna learn how timers are used in Jass. ( Check out some spell that uses TimerUtils, its pretty standard so you probably find easily some spell using it )

Also one the main points of vJass is to take advantage of all cool systems out there. I understand that you want to do stuff like pushbacks by yourself, since you are learning how to do that stuff, and thats exactly how you should do if you want to learn, but when submitting stuff fot other people to use, you should use for example Rising_Dusks Knockback system instead.

Suggestion: Go shopping to wc3c.net script section, its free !
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
You should not use such systems for spells that you submit since the amount of systems the user is forced to import should be kept as low as possible aside from the normal stuff like TU.
 

Viikuna

No Marlo no game.
Reaction score
265
Gwypaas is wrong. Importing systems is no problem, because vJass libraries and other NewGen stuff makes it easy.

Not using knokcback system is, because it is unnecessary hard to make all you 20 knockbacking abilities to work with some other spell that gives 50% knockback resistance, unless you use some generalized knokcback system.

Also, users might wanna use some different kind of knockback, so it is easier to modify your spell, when they have to change only that one line where knockback is called to use their knockback funtion instead.

Modularity is good.


That Jesus4Lyfs tutorial is pretty good I guess, but it contains some leaking methods and mainly teaches you to use his systems, so if you are not planning to use those systems, it is not the first tutorial you should read.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
You forgot one big problem, almost no systems kan use knockback resistance and stuff like that and almost all users uses different systems. The only "standard" systems we have is TU, the Unit Indexing Systems on Wc3c that uses the same syntax and systems that does the same job as TT. The other systems will almost always force the user to import them and is never even close to needed. And, if you want knockback resistance and such things then you know how to code the spell to use your system. And you forgot one thing, almost all users that will do that will use their own system because that fits their needs better.
 

Flare

Stops copies me!
Reaction score
662
Small criticism of the code (from a quick glance) - your function names could be more descriptive. I'm not saying you need something like
JASS:
function ThisIsTheForGroupFunction takes nothing returns nothing

Just have something short that effectively describes what the function is doing e.g
JASS:
function GroupCallback takes nothing returns nothing
//or, a tad shorter even
function GroupFunc takes nothing returns nothing

Non-descriptive naming of functions are far more likely to confuse you

Gwypaas is wrong. Importing systems is no problem, because vJass libraries and other NewGen stuff makes it easy.

Not using knokcback system is, because it is unnecessary hard to make all you 20 knockbacking abilities to work with some other spell that gives 50% knockback resistance, unless you use some generalized knokcback system.

Also, users might wanna use some different kind of knockback, so it is easier to modify your spell, when they have to change only that one line where knockback is called to use their knockback funtion instead.

Modularity is good.


That Jesus4Lyfs tutorial is pretty good I guess, but it contains some leaking methods and mainly teaches you to use his systems, so if you are not planning to use those systems, it is not the first tutorial you should read.
In the case of a spell as simple (in effect, since it's just doing a simple knockback) as Exide's Pushback, a knockback system is excessive, because Pushback would become less of a spell, and more of a demonstration of how to use the knockback system

While a system would be useful, you must first consider the spell itself before you go near systems. Once you know what the spell will do, then you should look towards what systems will be relevant and useful

A generalized knockback system is of little use if you are going beyond a straight-forward knockback (e.g. applying a damage-over-time effect for the duration of the knockback).


Also, Gwypaas isn't really wrong. Systems can be very useful, but if you have a system for handling knockback, damage dealing, unit attributes, and all these other things, the map becomes less of your own work, and more of a collaboration of everyone else's work, with a few additions on your part
 

Viikuna

No Marlo no game.
Reaction score
265
Well, it is really a simple spell so you got a point, but generally using systems is what you should be doing.
You forgot one big problem, almost no systems kan use knockback resistance and stuff like that and almost all users uses different systems.

You can modify whatever knockback system you are using to support stuff like that when you need it. There is nothing wrong with modifying other peoples systems for your own use. It is probably faster than modify all knockbacking spells in your map. ( Depends how many there is ofc )

If I see some cool spell I wanna use in my map, I ofc need to edit it a bit, so it uses my knockback system, which is ofc better than those common ones.

So, if this spell already uses some knockback system, I dont really have to do so much editing: just to change that line where knockback function is called + maybe some constants.

If there is a knockback coded inside spell scope, it just means some more extra work. ( Even the Optimizer removes all the non used code, I dont feel like leaving any un used knockback functions inside some spell, since it looks ugly as hell )


So, even if people use different systems, it is less job to get spell that uses at least some systems to work in their map and with their systems.

You might think that it is easier to import dozens of knockbacking spells with their own knockback code than import spells using different systems and modifying them to work in your map with your systems, and it probably is, but you will end up banging your head in the wall ( like this: :banghead: ) when you want to implement some new stuff that somehow affects to all knockbacking spells in your map.

So, thats why supporting systems is good and why you should take advantage of systems when submitting stuff and making maps.

Also, Gwypaas isn't really wrong. Systems can be very useful, but if you have a system for handling knockback, damage dealing, unit attributes, and all these other things, the map becomes less of your own work, and more of a collaboration of everyone else's work, with a few additions on your part


Then you should probably stop using custom models by other as well as all thsoe models there is in the game by Blizzard, since it is someone elses work? Jesus Flare, havent you ever made a map? Systems dont make a map. What systems you are using doesnt even affect the gameplay anyway lols. Systems are just a way to do things, not the thing itself.

A generalized knockback system is of little use if you are going beyond a straight-forward knockback (e.g. applying a damage-over-time effect for the duration of the knockback).

We have vJass, so no, not true at all.

While a system would be useful, you must first consider the spell itself before you go near systems. Once you know what the spell will do, then you should look towards what systems will be relevant and useful

This is true.
 

Exide

I am amazingly focused right now!
Reaction score
448
I don't know how to use TimerUtils (or any other system) or how to apply them to my triggers.
I believe I managed to find TimerUtils (TimerUtils (Blue flavor for 1.23b or later)), but simply looking at it does nothing for me.

I'm under the impression that I should import TimerUtils into my map, and then (somehow) replace my Timers in my triggers with functions from this system.
-Can anyone show me how on, for example, my Pushback trigger?
 

Flare

Stops copies me!
Reaction score
662
I don't know how to use TimerUtils (or any other system) or how to apply them to my triggers.
I believe I managed to find TimerUtils (TimerUtils (Blue flavor for 1.23b or later)), but simply looking at it does nothing for me.

I'm under the impression that I should import TimerUtils into my map, and then (somehow) replace my Timers in my triggers with functions from this system.
-Can anyone show me how on, for example, my Pushback trigger?
Something reasonably (IMO) simple would be...
JASS:
globals
  group G = CreateGroup ()
  unit GlobUnit
endglobals

struct X //contains your data for units under the effect of the knockback
  timer t
  unit caster //optional - if you wanted things like damage during knockback, and such, you'd want this
  unit target
  real distance
  real angle
endstruct

function TimerCallback takes nothing returns nothing
  local X data = GetTimerData (GetExpiredTimer ())
  local real x = GetUnitX (data.target)
  local real y = GetUnitY (data.target)
  set x = x + Cos (data.angle) * (DistancePerSec * TimerInterval)
  set y = y + Sin (data.angle) * (DistancePerSec * TimerInterval)
  call SetUnitPosition (data.target, x, y) //you can use SetUnitX and SetUnitY instead of this, if you want
  set data.distance = data.distance - (DistancePerSec * TimerInterval)
  if data.distance <= 0 then //i.e. full distance has been travelled
    call ReleaseTimer (data.t) //TimerUtils function for freeing up a timer
    call data.destroy () //destroys the struct instance, so we can always ensure instances are available for future casting of this spell
  endif
endfunction

function GroupCallback takes nothing returns nothing
  local X data = X.create () //create a struct instance for ourselves :3
  set data.t = NewTimer () //TimerUtils function for grabbing a timer
  set data.distance = (your max distance)
  set data.caster = GlobUnit //if you need this
  set data.target = GetEnumUnit ()
  set data.angle = (calculate knockback angle, convert to radians where necessary)
  call TimerStart (data.t, TimerInterval, true, function TimerCallback)
endfunction

function GroupEnum takes nothing returns boolean
  return (conditions)
endfunction

function TriggerActions takes nothing returns nothing
  local location l = GetSpellTargetLoc () //assuming you are using point-target
  //local real x
  //local real y
  set GlobUnit = GetTriggerUnit ()
  //set x = GetUnitX (GlobUnit)
  //set y = GetUnitY (GlobUnit)
  call GroupEnumUnitsInRange (G, GetLocationX (l), GetLocationY (l), radius, Condition (function GroupEnum)) //GetLocationX/Y can be replaced with the x and y variables, depending on the spell's targeting
  call ForGroup (function GroupCallback)
  call RemoveLocation (l)
  set l = null
endfunction

Hopefully that's fairly straight-forward :p

Systems dont make a map
They don't make a map, but they can contribute alot to the gameplay (e.g. the knockback system in Age of Myths is quite effective in providing good functionality, which allows for alot of interaction between spells and items, making gameplay that bit more fun) and some maps wouldn't be anywhere near as fun (e.g. Elimination Tournament or MaD Balls Arena wouldn't be very fun if they dropped the projectile physics systems they use in favour of something which WC3 could provide by default i.e. f*ck-all :p)

We have vJass, so no, not true at all.
A generalized knockback system being fully capable of provided very specific functionality? Find me a knockback system (that currently exists) that'll spin me unit around in circles while he is being knocked back, I personally don't know of any knockback systems that'd do that :p

But ye, we're getting a bit off-topic now, so any more discussion on systems should probably continue via PM :p
 

Exide

I am amazingly focused right now!
Reaction score
448
Something reasonably (IMO) simple would be...
JASS:
globals
  group G = CreateGroup ()
  unit GlobUnit
endglobals

struct X //contains your data for units under the effect of the knockback
  timer t
  unit caster //optional - if you wanted things like damage during knockback, and such, you'd want this
  unit target
  real distance
  real angle
endstruct

function TimerCallback takes nothing returns nothing
  local X data = GetTimerData (GetExpiredTimer ())
  local real x = GetUnitX (data.target)
  local real y = GetUnitY (data.target)
  set x = x + Cos (data.angle) * (DistancePerSec * TimerInterval)
  set y = y + Sin (data.angle) * (DistancePerSec * TimerInterval)
  call SetUnitPosition (data.target, x, y) //you can use SetUnitX and SetUnitY instead of this, if you want
  set data.distance = data.distance - (DistancePerSec * TimerInterval)
  if data.distance <= 0 then //i.e. full distance has been travelled
    call ReleaseTimer (data.t) //TimerUtils function for freeing up a timer
    call data.destroy () //destroys the struct instance, so we can always ensure instances are available for future casting of this spell
  endif
endfunction

function GroupCallback takes nothing returns nothing
  local X data = X.create () //create a struct instance for ourselves :3
  set data.t = NewTimer () //TimerUtils function for grabbing a timer
  set data.distance = (your max distance)
  set data.caster = GlobUnit //if you need this
  set data.target = GetEnumUnit ()
  set data.angle = (calculate knockback angle, convert to radians where necessary)
  call TimerStart (data.t, TimerInterval, true, function TimerCallback)
endfunction

function GroupEnum takes nothing returns boolean
  return (conditions)
endfunction

function TriggerActions takes nothing returns nothing
  local location l = GetSpellTargetLoc () //assuming you are using point-target
  //local real x
  //local real y
  set GlobUnit = GetTriggerUnit ()
  //set x = GetUnitX (GlobUnit)
  //set y = GetUnitY (GlobUnit)
  call GroupEnumUnitsInRange (G, GetLocationX (l), GetLocationY (l), radius, Condition (function GroupEnum)) //GetLocationX/Y can be replaced with the x and y variables, depending on the spell's targeting
  call ForGroup (function GroupCallback)
  call RemoveLocation (l)
  set l = null
endfunction

Hopefully that's fairly straight-forward :p


Thanks, but this is way above my head. :p

Why aren't the functions private?
Is this MUI? -Why?
What are DistancePerSec and TimerInterval? (The names are rather obvious, but how does it work? They aren't variables, right? Am I supposed to replace them with something?)
How is this trigger better than mine?
 

Flare

Stops copies me!
Reaction score
662
Why aren't the functions private?
For the sake of simplicity - you are new to both structs and TimerUtils, so I don't see the point in adding additional syntax that may add to any confusion

That, and it's easier for me to leave out private for the sake of less typing :p Your version of the spell should be private (and within a scope, for the obvious reason that you need enclosure within a scope/library to use private members)

Is this MUI? -Why?
This is MUI because I am associating each unit being knocked back with it's own unique timer (unit A gets timer A, unit B gets timer B). When TimerUtils acquires the ID value for a timer (through SetTimerData), the timer shouldn't have the same ID as any other timer (although, there is a bug which can cause an issue where multiple handles will have the same ID, but you needn't worry about that if you're using structs). Since each timer has a unique ID, we can safely save and retrieve data for each unit, by looking to the timer (via GetExpiredTimer) and then play around with that unit, and other variables, in the timer callback. Two units will never be handled by the same timer using the method in my example, so there will be no mixing up of variable data unless you deliberately do it :p

What are DistancePerSec and TimerInterval?
These would be configurable constants used to determine how far the unit will be moved each timer interval e.g. if DistancePerSec is 500, and TimerInterval is 0.02 (although, for a good balance between fluidity and avoiding lag, I would suggest 0.03125 as a timer interval, for 32 executions/sec), the unit would be moved 10 Warcraft units of distance everytime the timer expires (since 500*0.02 is 10). You can replace them with just any ol' number, but for the sake of any people who wish to use the spell, I suggest that they be constants, set aside from any other globals, at the top of your script e.g.
JASS:
scope Pushback
//Configurable properties
globals
  private constant real TimerInterval = 0.03125
  private constant real DistancePerSec = 500
//any other configurable properties e.g. the spell's area of effect
endglobals

//Optionally, you can put a message here to indicate that the configurable property section ends here

//the spell code goes down here, i.e.
globals
  group G = CreateGroup ()
  unit GlobUnit
endglobals

struct X //contains your data for units under the effect of the knockback
  timer t
  unit caster //optional - if you wanted things like damage during knockback, and such, you'd want this
  unit target
  real distance
  real angle
endstruct

function TimerCallback takes nothing returns nothing
  local X data = GetTimerData (GetExpiredTimer ())
  local real x = GetUnitX (data.target)
  local real y = GetUnitY (data.target)
  set x = x + Cos (data.angle) * (DistancePerSec * TimerInterval)
  set y = y + Sin (data.angle) * (DistancePerSec * TimerInterval)
  call SetUnitPosition (data.target, x, y) //you can use SetUnitX and SetUnitY instead of this, if you want
  set data.distance = data.distance - (DistancePerSec * TimerInterval)
  if data.distance <= 0 then //i.e. full distance has been travelled
    call ReleaseTimer (data.t) //TimerUtils function for freeing up a timer
    call data.destroy () //destroys the struct instance, so we can always ensure instances are available for future casting of this spell
  endif
endfunction

function GroupCallback takes nothing returns nothing
  local X data = X.create () //create a struct instance for ourselves :3
  set data.t = NewTimer () //TimerUtils function for grabbing a timer
  set data.distance = (your max distance)
  set data.caster = GlobUnit //if you need this
  set data.target = GetEnumUnit ()
  set data.angle = (calculate knockback angle, convert to radians where necessary)
  call TimerStart (data.t, TimerInterval, true, function TimerCallback)
endfunction

function GroupEnum takes nothing returns boolean
  return (conditions)
endfunction

function TriggerActions takes nothing returns nothing
  local location l = GetSpellTargetLoc () //assuming you are using point-target
  //local real x
  //local real y
  set GlobUnit = GetTriggerUnit ()
  //set x = GetUnitX (GlobUnit)
  //set y = GetUnitY (GlobUnit)
  call GroupEnumUnitsInRange (G, GetLocationX (l), GetLocationY (l), radius, Condition (function GroupEnum)) //GetLocationX/Y can be replaced with the x and y variables, depending on the spell's targeting
  call ForGroup (function GroupCallback)
  call RemoveLocation (l)
  set l = null
endfunction


How is this trigger better than mine?
One thing would be that it's MUI (I don't know if yours is or not, I haven't checked). The main purpose of the example was to give you a basic understanding of how you would use structs and TimerUtils for this kind of spell (although, the same principle will apply to a variety of other spells)

Although, if you're unsure of anything, feel free to send me a PM or log onto the chat (I'm usually on the chat from ~6:30 Tuesday to Saturday, and most of the day Sunday and Monday, even though I do go AFK a good bit :D)
 

Exide

I am amazingly focused right now!
Reaction score
448
I tried modifying it a bit on my own, obviously failing.

Why doesn't your scope have any initializer? (Or is it for the sake of typing?)

I tried this, but it didn't work:

JASS:

scope Pushback initializer Run

globals
    private group G = CreateGroup()
    private unit GlobUnit
    private real SPELLTARGETX
    private real SPELLTARGETY
    private real TimerInterval = .02
endglobals

struct X //contains your data for units under the effect of the knockback
    timer t
    unit caster
    unit target
    real distance     //Should these be private?
    real angle
endstruct

private function TriggerConditions takes nothing returns boolean
    return (GetSpellAbilityId() == 'A001')
endfunction

private function TimerCallback takes nothing returns nothing
    local X data = GetTimerData(GetExpiredTimer())
    local real x = GetUnitX (data.target)
    local real y = GetUnitY (data.target)
    set x = x + Cos(data.angle) * (8 * TimerInterval)
    set y = y + Sin(data.angle) * (8 * TimerInterval)
    
    call SetUnitPosition(data.target, x, y) //you can use SetUnitX and SetUnitY instead of this, if you want
    set data.distance = data.distance - (8 * TimerInterval)
    if data.distance <= 0 then //i.e. full distance has been travelled
        call ReleaseTimer(data.t) //TimerUtils function for freeing up a timer
        call data.destroy() //destroys the struct instance, so we can always ensure instances are available for future casting of this spell
    endif
endfunction

private function GroupCallback takes nothing returns nothing
    local real x
    local real y
    
    local X data = X.create() //create a struct instance for ourselves
    set data.t = NewTimer() //TimerUtils function for grabbing a timer
    set data.distance = 400
    set data.caster = GlobUnit
    set data.target = GetEnumUnit()
    set x = GetUnitX(data.target)
    set y = GetUnitY(data.target)
    set data.angle = bj_RADTODEG * Atan2(y - SPELLTARGETY, x - SPELLTARGETX)
    
    call TimerStart(data.t, TimerInterval, true, function TimerCallback)
endfunction

private function GroupEnum takes nothing returns boolean
    return (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false) and (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405) and (IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(GlobUnit)) == true)
endfunction

private function TriggerActions takes nothing returns nothing
    local location l = GetSpellTargetLoc()
    
    set SPELLTARGETX = GetLocationX(l)
    set SPELLTARGETY = GetLocationY(l)
    set GlobUnit = GetTriggerUnit()
    
    call GroupEnumUnitsInRange(G, SPELLTARGETX, SPELLTARGETY, 400, Condition(function GroupEnum))
    call ForGroup(G, function GroupCallback)
    
    call RemoveLocation(l)
    set l = null
endfunction

//===========================================================================
private function Run takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    
    loop
    exitwhen i == 16
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set i = i + 1
    endloop
    
    call TriggerAddCondition(t, Condition( function TriggerConditions))
    call TriggerAddAction(t, function TriggerActions)
endfunction

endscope


Nothing happens when I cast the spell.
EDIT: Are we using TimerUtils in this trigger? :p
 

Flare

Stops copies me!
Reaction score
662
Are you sure it's not working at all? I can't see anything* particularly wrong, other than
JASS:
8 * TimerInterval

8 * 0.02 is... about 0.16? You're moving each target 0.16 distance every 0.02 seconds, which is a TINY amount. Try changing those 8's to a number between 300 and 500 (that'd be a reasonable speed). The number you are multiplying by TimerInterval is the speed travelled PER SECOND. 8 distance/sec is a very small amount, so it'd take a while to notice any significant effect :p

Also,
JASS:
set data.angle = bj_RADTODEG * Atan2(y - SPELLTARGETY, x - SPELLTARGETX)

Remove the bj_RADTODEG conversion. The native Cos and Sin functions require the angle in radians, so converting to degrees will only mess things up :p

Why doesn't your scope have any initializer? (Or is it for the sake of typing?)
Again, for the sake of less typing for me :D You were already using a scope initializer, so I didn't think it'd be necessary to add in the stuff that you already had done :)

Are we using TimerUtils in this trigger?
Yes - NewTimer, ReleaseTimer, GetTimerData and SetTimerData are all functions from the TimerUtils library

*I've only looked at the spell briefly, so I may have missed something
 

Exide

I am amazingly focused right now!
Reaction score
448
Are you sure it's not working at all? I can't see anything* particularly wrong, other than
JASS:
8 * TimerInterval

8 * 0.02 is... about 0.16? You're moving each target 0.16 distance every 0.02 seconds, which is a TINY amount. Try changing those 8's to a number between 300 and 500 (that'd be a reasonable speed). The number you are multiplying by TimerInterval is the speed travelled PER SECOND. 8 distance/sec is a very small amount, so it'd take a while to notice any significant effect :p

Ah, I thought it would act like in my first spell - moving it 8 distance per 0.02 seconds.
I replaced it with 500.


Also,
JASS:
set data.angle = bj_RADTODEG * Atan2(y - SPELLTARGETY, x - SPELLTARGETX)

Remove the bj_RADTODEG conversion. The native Cos and Sin functions require the angle in radians, so converting to degrees will only mess things up :p

I don't quite follow you here.
I know I should be, but I'm gonna go with the old 'blame my lousy math-skills'.

You mean like this:
JASS:

set data.angle = Atan2(y - SPELLTARGETY, x - SPELLTARGETX)

?


Still not working, though.
 

Flare

Stops copies me!
Reaction score
662
I don't quite follow you here.
Atan2(dy, dx) returns an angle in radians
You are converting said angle to degrees
Cos/Sin require an angle in radians to get the correct result

Comprendé? (always wanted to say that :D)

set data.angle = bj_RADTODEG * Atan2(y - SPELLTARGETY, x - SPELLTARGETX)
You haven't set a value for SPELLTARGETX or SPELLTARGETY... hopefully that'll fix it :D
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    The bots will show up as users online in the forum software but they do not show up in my stats tracking. I am sure there are bots in the stats but the way alot of the bots treat the site do not show up on the stats
  • Varine Varine:
    I want to build a filtration system for my 3d printer, and that shit is so much more complicated than I thought it would be
  • Varine Varine:
    Apparently ABS emits styrene particulates which can be like .2 micrometers, which idk if the VOC detectors I have can even catch that
  • Varine Varine:
    Anyway I need to get some of those sensors and two air pressure sensors installed before an after the filters, which I need to figure out how to calculate the necessary pressure for and I have yet to find anything that tells me how to actually do that, just the cfm ratings
  • Varine Varine:
    And then I have to set up an arduino board to read those sensors, which I also don't know very much about but I have a whole bunch of crash course things for that
  • Varine Varine:
    These sensors are also a lot more than I thought they would be. Like 5 to 10 each, idk why but I assumed they would be like 2 dollars
  • Varine Varine:
    Another issue I'm learning is that a lot of the air quality sensors don't work at very high ambient temperatures. I'm planning on heating this enclosure to like 60C or so, and that's the upper limit of their functionality
  • Varine Varine:
    Although I don't know if I need to actually actively heat it or just let the plate and hotend bring the ambient temp to whatever it will, but even then I need to figure out an exfiltration for hot air. I think I kind of know what to do but it's still fucking confusing
  • The Helper The Helper:
    Maybe you could find some of that information from AC tech - like how they detect freon and such
  • Varine Varine:
    That's mostly what I've been looking at
  • Varine Varine:
    I don't think I'm dealing with quite the same pressures though, at the very least its a significantly smaller system. For the time being I'm just going to put together a quick scrubby box though and hope it works good enough to not make my house toxic
  • Varine Varine:
    I mean I don't use this enough to pose any significant danger I don't think, but I would still rather not be throwing styrene all over the air
  • The Helper The Helper:
    New dessert added to recipes Southern Pecan Praline Cake https://www.thehelper.net/threads/recipe-southern-pecan-praline-cake.193555/
  • The Helper The Helper:
    Another bot invasion 493 members online most of them bots that do not show up on stats
  • Varine Varine:
    I'm looking at a solid 378 guests, but 3 members. Of which two are me and VSNES. The third is unlisted, which makes me think its a ghost.
    +1
  • The Helper The Helper:
    Some members choose invisibility mode
    +1
  • The Helper The Helper:
    I bitch about Xenforo sometimes but it really is full featured you just have to really know what you are doing to get the most out of it.
  • The Helper The Helper:
    It is just not easy to fix styles and customize but it definitely can be done
  • The Helper The Helper:
    I do know this - xenforo dropped the ball by not keeping the vbulletin reputation comments as a feature. The loss of the Reputation comments data when we switched to Xenforo really was the death knell for the site when it came to all the users that left. I know I missed it so much and I got way less interested in the site when that feature was gone and I run the site.
  • Blackveiled Blackveiled:
    People love rep, lol
    +1
  • The Helper The Helper:
    The recipe today is Sloppy Joe Casserole - one of my faves LOL https://www.thehelper.net/threads/sloppy-joe-casserole-with-manwich.193585/
  • The Helper The Helper:
    Decided to put up a healthier type recipe to mix it up - Honey Garlic Shrimp Stir-Fry https://www.thehelper.net/threads/recipe-honey-garlic-shrimp-stir-fry.193595/
  • The Helper The Helper:
    Here is another comfort food favorite - Million Dollar Casserole - https://www.thehelper.net/threads/recipe-million-dollar-casserole.193614/

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top