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

Torrent Array v2.0.0

Torrent Array v2.0.0


DESCRIPTION:

Torrent Array is a spell template that allows you to create many different spell variants, may it be
torrent-related spells or not. This allows you to do it without having to copy-paste the whole spell
code and then change the configuration. I can't explain it all here but all you need to know can be
found in the documentation. Another notable feature is the possibility to create different torrent
array shapes (See the images below). With the right configuration, you can create a circular or
even a spiral array of torrents.


HISTORY:

This resource started as a spell named "Torrents". Later on, due to constant updates and changes
in the spell's design, I found a way to allow users to have more than 1 set of configuration and
create many spell variants, while all are sharing a single spell mechanics.
And so, it is now a spell template. It now also has a better algorithm brought forth by the new
TorrentSystem included in the bundle. The resource also comes with four premade spell
configurations namely "Geysers", "Meteor Shower", "Eruption Helix", and "Earth, Water, and Fire"
- a combination of the three earlier spells, to give users an idea of how to make their own spell
variant.


DEPENDENCIES:

Required:
TorrentSystem
SpellEvent
SpellCloner
Table

Optional:
ResourcePreloader
WorldBounds




Torrent Array spell variants


Spell Description
full


Linear Geysers


Circular Geysers
203796-ee6e2a39f3e256dcfd7e356754066869.jpg


Spiral Geysers
203795-5dd3d16306f644757866aac445bc2111.jpg



Spell Description
full



220991-694da0b64b3ffcca9503d418d498d384.jpg

220988-38e7dc2bb4ad30f078da1226add39914.jpg





Spell Description
full







Spell Description
full


full






Torrent Array (New Resource Name)

v2.0.0
- Added another sample configuration - Earth, Water and Fire
- Updated existing dependencies
- Now uses SpellCloner for implementing local configurations
- Other changes

v1.1b
- Changed the configuration system
- Configurations are no longer attached to arrays (normal struct members), but to a boolexpr that is then evaluated by a trigger. As a result, you can now have dynamic configurations through the use of functions that can take parameters - Just like what you would do with a usual spell configuration.
- Updated Dependencies (specifically TorrentSystem)
- Other changes

v1.0
- Rewrote the entire code of the last version of Torrents
- Now integrated with TorrentSystem
- Now allows you to create as many configuration sets as you can, all with different activation spell
- Many other significant changes


Torrents (Old Resource Name)

v1.3c
- Not uploaded

v1.3b
- Not uploaded

v1.3
- Added ResourcePreloader as an optional requirement
- Added WorldBounds as an optional requirement
- Added AutoFly as an optional requirement
- Added a new boolean TARGET_IS_CENTER to the configuration
- Adding and removing crowform is now done at target units enumeration instead of periodically when being tossed
- Optimized code
- Some other fixes and changes

v1.2c
- Not uploaded

v1.2b
- Fixed the bug in the previous version where the torrent model dont appear when DummyRecycler is used
- Removed some redundant target unit check
- Caster is now the one to damage the targets instead of the dummy
- Other changes

v1.2
- Added Table to the spell requirement
- Added 3 optional requirements namely TimerUtils, DummyRecycler, and SpellEffectEVent
- Changed the variable naming to fit that of the JPAG's naming convention
- Other changes

v1.1c
- Added a new feature that enables you to choose whether enemy players can see the torrent borders or not
- Added to the configuration the option whether to enable or disable the toss unit feature

v1.1b
- Used a safe method in adding/removing crowform ability to the units
- Made a separate function for filtering target units
- Other changes

v1.1
- Added strom crow form to the dummy's default ability
- Added documentations throughout the script
- Inlined the arithmetic format to that of JPAG's
- Nullified the local vars inside the if nest
- Moved HASH and TEMPGROUP below the configuration section and initialized them within the global block

v1.0b
- Some Fixes
- Some code restructuring

v1.0
- First Upload
Previews
Contents

Torrent Array v2.0.0 (Map)

Reviews
MyPad
Nitpicks A lesser amount of spell requirements would be favorable. Spell samples should be separated from the system. Notes: None (Mostly addressed) Status: Approved

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
If this gets approved, I will release the update for the spell I made months ago, with this system integrated to it. The new update for that spell is pretty ready. I recoded the entire spell and there's some twist in the design of the configuration that is very nice in my opinion ;).
The mentioned spell is mow moved here.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
For some reason peasants get stuck in midair which seems to happen only when dead units/corpses are in the AOE of the spell as well.

In my opinion the damage should be done when the explosion happens and on the landing/crashing (falling from a great height) instead of the damage over time.

When DummyRecycler is disabled (because its optional) Torrent doesn't compile:
JASS:
static if LIBRARY_DummyRecycler then
    set dummy = GetRecycledDummyAnyAngle(.centerX, .centerY, 0)
    call DummyAddRecycleTimer(dummy, DUMMY_DURATION)
else
    set dummy = CreateUnit(DUMMY_OWNER, DUMMY_ID, .centerX, .centerY, 0) // <-- undeclared variable DUMMY_ID
    call UnitApplyTimedLife(dummy, 'BTLF', DUMMY_DURATION)
endif

I don't know why you have all of these:
JASS:
          */boolean selfFilter                      /*  Determines if the <caster> unit is allowed as target
          */boolean allyFilter                      /*  Determines if allies of the <caster> unit are allowed as targets
          */boolean structureFilter                 /*  Determines if structures are allowed as targets
          */boolean mechanicalFilter                /*  Determines if mechanical units are allowed as targets
          */boolean magicImmuneFilter               /*  Determines if magic immune units are allowed as targets
          */boolean etherealFilter                  /*  Determines if ethereal units are allowed as targets
          */boolean illusionFilter                  /*  Determines if illusions are allowed as targets
          */boolean deadFilter                      /*  Determines if dead units are allowed as targets

instead of a taking a single function that receives Torrent.instance (as a global) and sets Torrent.target_allowed = UnitAlive(Torrent.instance.u) or/and etc; for example. The function can be called with ForForce.

PS: all those optinal libraries make a big mess in terms of readability in my opinion
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
For some reason peasants get stuck in midair which seems to happen only when dead units/corpses are in the AOE of the spell as well.
I think I've found the cause of this, I'm not sure though.

In my opinion the damage should be done when the explosion happens and on the landing/crashing (falling from a great height) instead of the damage over time.
I think I'll leave the damage on landing part to the users (They can use the custom callback handlers).

When DummyRecycler is disabled (because its optional) Torrent doesn't compile:
Will fix this

instead of a taking a single function that receives Torrent.instance (as a global) and sets Torrent.target_allowed = UnitAlive(Torrent.instance.u)[/COLOR] or/and etc; for example. The function can be called with ForForce.
If I understood correctly, that would mean that I can only have 1 type of unit filters for all torrent instances. What I mean is that you can't make it so that Torrent(1) only targets allies while Torrent(2) only targets enemies.
 
Level 13
Joined
Nov 7, 2014
Messages
571
If I understood correctly, that would mean that I can only have 1 type of unit filters for all torrent instances. What I mean is that you can't make it so that Torrent(1) only targets allies while Torrent(2) only targets enemies.

I was thinking of something like this:
JASS:
library TargetAllowedDemo initializer init

struct TargetAllowed
    static force ta_force
    static thistype instance
    static unit target
    static boolean target_allowed
    boolexpr filter_func

    private static method onInit takes nothing returns nothing
        set ta_force = CreateForce()
        call ForceAddPlayer(ta_force, Player(15))
    endmethod

    static method create takes code filter_func returns thistype
        local thistype this = allocate()
        set this.filter_func = Filter(filter_func)
        return this
    endmethod

    method test takes unit u returns boolean
        if u == null then
            return false
        endif

        // call filter_func with TargetAllowed.instance and TargetAllowed.target as globals
        // the result is expected to be stored in TargetAllowed.target_allowed
        set instance = this
        set target = u
        call ForceEnumPlayers(ta_force, this.filter_func)
        return target_allowed
    endmethod
endstruct

globals
    TargetAllowed tauren_or_giant
    TargetAllowed undead_and_alive
    TargetAllowed ta_active
endglobals

function ta_tauren_or_giant takes nothing returns boolean
    local TargetAllowed ta = TargetAllowed.instance

    set ta.target_allowed = /*
        */ IsUnitType(ta.target, UNIT_TYPE_GIANT) /*
        */ or IsUnitType(ta.target, UNIT_TYPE_TAUREN)

    return false
endfunction

// Filter(function that_doesnt_return_boolean) seems to work although it might not =)
function ta_undead_and_alive takes nothing returns nothing // boolean
    local TargetAllowed ta = TargetAllowed.instance

    set ta.target_allowed = /*
        */ IsUnitType(ta.target, UNIT_TYPE_UNDEAD) /*
        */ and not IsUnitType(ta.target, UNIT_TYPE_DEAD)

    // return false
endfunction

function on_unit_selected takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local string un = GetUnitName(u)

    if ta_active == tauren_or_giant then
        if ta_active.test(u) then
            call BJDebugMsg(un + " is a tauren or a giant")
        else
            call BJDebugMsg(un + " is not a tauren nor a giant")
        endif

    elseif ta_active == undead_and_alive then
        if ta_active.test(u) then
            call BJDebugMsg(un + " is an alive undead unit")
        else
            call BJDebugMsg(un + " is either dead or not an undead unit")
        endif
    endif

    set u = null
endfunction

private function init takes nothing returns nothing
    local trigger t

    set tauren_or_giant = TargetAllowed.create(function ta_tauren_or_giant)
    set undead_and_alive = TargetAllowed.create(function ta_undead_and_alive)
    set ta_active = tauren_or_giant

    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SELECTED)
    call TriggerAddAction(t, function on_unit_selected)
endfunction

endlibrary
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I see, so that means the user will have to create different boolexprs functions for each torrent instances that he wish to have different unittype filters, right? That would be more hussle than just editing the boolean flags I guess.
If only the FilteringSystem library by The Wrecker supports this kind of filtering, it would make life easier.

EDIT:
I think the cause of the tossed units getting stuck midair is that at some cases, the timer is prematurely paused. But I still need to confirm this.

EDIT: Tested. Doesn't seem like it is the cause of the problem.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
EDIT:
I think the cause of the tossed units getting stuck midair is that at some cases, the timer is prematurely paused. But I still need to confirm this.

EDIT: Tested. Doesn't seem like it is the cause of the problem.


I think I've found why peasants get stuck in midair, if you comment out/prevent from executing this function (in the map's header):
JASS:
    private function A takes nothing returns nothing
        //local unit u = GetTriggerUnit()
        //call CreateUnit( GetOwningPlayer( u ), GetUnitTypeId( u ), GetRandomReal( I2R(WorldBounds.minX), I2R(WorldBounds.maxX) ), GetRandomReal( I2R(WorldBounds.minY), I2R(WorldBounds.maxY) ), GetRandomReal( 0, 360 ) )
        //call TriggerSleepAction( 10 )
        //call RemoveUnit( u )
        //set u = null
    endfunction
then they don't get stuck anymore, i.e the problem wasn't with the spell.

I think it has something to do with TSA and creating and killing units at the same time but I don't really know =).
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I tested what you said and indeed the bug is gone. Alternatively, I also tried replacing the group swapping method with something like this instead
JASS:
call GroupAddGroup(g, tempGroup)
loop
    set u = FirstOfGroup(tempGroup)
    exitwhen u == null
    call GroupRemoveUnit(tempGroup, u)
    //actions
endloop
and it also solves the bug. o_O

EDIT:
I now know exactly what's the problem =). It's because of the RemoveUnit(). Removed units aren't automatically removed from the group, they remain but they become null, thus, causing the loop to exit prematurely. Hence why Captain Griffen from wc3c made a GroupRefresh snippet.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
I now know exactly what's the problem =). It's because of the RemoveUnit(). Removed units aren't automatically removed from the group, they remain but they become null, thus, causing the loop to exit prematurely. Hence why Captain Griffen from wc3c made a GroupRefresh snippet.

Right... FirstOfGroup returns null on removed (explicitly via RemoveUnit or implicitly via unit decay) units.

Instead of using GroupRefresh (iterating the group and getting rid of removed units, before iterating the group with FirstOfGroup) you could also filter out dead units (corpses) so that they won't decay (get removed) while being tossed up and down.
Or if you want to support tossing dead bodies up and down you can call UnitSuspendDecay(u, true) while doing so and UnitSuspendDecay(u, false) when done. Of course this would only work for implicitly removed units, i.e calling RemoveUnit on a dead unit would still be problematic.
 
TorrentSystem and TorrentArray are both the main submission here? (It's not very clear to me because of the submission name and version numbers)
I will only look at system and array at first.

TorrentSystem:

Why not applying a filter func in config instead of providing all specific bools? (or it can be instance specific, if needed)
Though, the filters seem to cover most common cases by default already, but maybe the limitation is not needed.

JASS:
          */method onTorrentExplode takes boolexpr expr returns nothing/*
            - Adds a boolexpr of the code that will run upon the Torrent's explosion

          */method onTorrentVanish takes boolexpr expr returns nothing/*
            - Adds a boolexpr of the code that will run upon the Torrent's disappearance, after tossed units are landed
            - This will not run when <tossUnits> is false
^From docu I don't very understand this. Does it instantly fire the mentioned event(s), or does it only register code to it?

JASS:
            /* Check if this torrent tosses its targets
               If yes, make sure it has finished its business
               before destroying                                */
            if duration >= 2*TIMEOUT and .elapsed < duration then
What is the functional meaning? If the condtion doesn't met, then it won't be deallocated in the destroy call? - but the dummy will be recycled?

JASS:
                    loop
                        call DestroyEffect(LoadEffectHandle(table, this, count))
                        exitwhen count == 0
                        set count = count - 1
                    endloop
It seems usually better if a exit loop condition is on very top, or is there a reason why it is not?

set owner = GetOwningPlayer(caster)
^Seems it could be avoided when declaring owner as instance member.

Why is there a usage of locals for attackty/damagetype and other members, if we already have access to the data with this. inside loop?

Somehow I personaly don't very like such things like set table = TableArray[0x2000], but yeh.. your preference weights more I believe.

It's not really explained what a Torrent actually is. It starts right away with members, and functions to use.

Sorry, I'm pretty on a rush, and may continue a other day with more feedback, and also for the ArrayTorrent.

Edit:

Suggestion to rename speed to speed_x or something alike.

Torrent <->DummyUnit relation is 1:1, each torrent is associated by the unit. UnitIndex of the dummyunit could be used instead of allocation method.

TorrentArray:

Could you add a description of what the system exactly is?

The API needs to explain the variable types next to the names.

Maybe a list can be be required as external system, if you prefer.

Why are all members doubled that a TorrentSystem already provides? Like Damage/Attacktype, etc. A torrent array, should only take usage of it, and can additionaly have new members, but at it's best it should not double anything.

JASS:
/* Implement map bounds */
                static if LIBRARY_WorldBounds then
                    if centerX < WorldBounds.minX then
                        set centerX = WorldBounds.minX
                    elseif centerX > WorldBounds.maxX then
                        set centerX = WorldBounds.maxX
                    endif
                    if centerY < WorldBounds.minY then
                        set centerY = WorldBounds.minY
                    elseif centerY > WorldBounds.maxY then
                        set centerY = WorldBounds.maxY
                    endif
                else
                    if centerX < minX then
                        set centerX = minX
                    elseif centerX > maxX then
                        set centerX = maxX
                    endif
                    if centerY < minY then
                        set centerY = minY
                    elseif centerY > maxY then
                        set centerY = maxY
                    endif
                endif
^Such logics, only to get good x/y onCast. Maybe requring some libs is good for the code, or making an extra function to regulate the correct value. ( x = normalize/regulate(x))

Why not stunning a unit instead of pause?
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
TorrentSystem and TorrentArray are both the main submission here? (It's not very clear to me because of the submission name and version numbers)
Yes they are both the submissions. TorrentSystem is a system that allows you to easily create uhm well... Torrents (sorry I just borrowed the term from a spell in DotA but I guess not everyone is familiar with it so I need to add a better description soon) while TorrentArray is a spell that summons Torrents in a fancy way (atleast for me =)) using the TorrentSystem. KILLCIDE suggested to put them in two different bundles under 1 resource thread but I find it awkward to do so since the bundle for TorrentArray would also contain TorrentSystem since it is its requirement which would make a separate bundle for TorrentSystem redundant. So yeah, it might be confusing, should I revert both their version numbers to 1 or what can you suggest?

Btw, although I pretty much refer to TorrentArray as a spell, one can also argue or view it as a system because although it has a single core mechanics, it has a very 'open' style of configuration which allows you to configure many of its aspects (or even add to it using the callback functions, see the MeteorShower sample) compared to the usual configuration we often see, to the point where each configuration set that you make can already be considered as an individual spell itself (As KILLCIDE told me, it is a system that allows you to create many spell varieties) as you can see by testing the 2 sample configurations above.


Why not applying a filter func in config instead of providing all specific bools? (or it can be instance specific, if needed)
Though, the filters seem to cover most common cases by default already, but maybe the limitation is not needed.
Indeed, will include them in the update.


^From docu I don't very understand this. Does it instantly fire the mentioned event(s), or does it only register code to it?
They register callback functions, onTorrentExplode() registers a code that will run when a torrent explodes (i.e., when explode() method is called internally) while onTorrentVanish() registers a code that will run when tossed units land.


What is the functional meaning? If the condtion doesn't met, then it won't be deallocated in the destroy call? - but the dummy will be recycled?
Yes, calling destroy should make the torrent instantly vanish/disappear for the user however, it should not be the case for its effects upon the tossed units because doing so would break the mechanics, that's why the system waits for the all the units to land and leaves the deallocation part to the periodic method later instead.


t seems usually better if a exit loop condition is on very top, or is there a reason why it is not?
It is because the loops need to run <number of borders> + 1 times since I put the model of the Torrent itself in the same hashtable with 0 as its key. If I will move it to the top the condition must be == -1 instead of 0.


^Seems it could be avoided when declaring owner as instance member.
Well owner isn't declared as instance member, only caster or did you mean it's better to make owner an instance member instead?


Why is there a usage of locals for attackty/damagetype and other members, if we already have access to the data with
this.
inside loop?
Ah right, this one. It is done when I was still addicted of micro-optimizations =), I stored them in locals so that the loop will not have to read arrays (instance members) repeatedly since the loop will run > 1 times on average (since the loop is a unitgroup iteration, it could possibly run many times depending on the aoe). But right now, yeah I think it would be cleaner and shorter if I haven't done those plus I don't think it would really tax on performance that much so I could let those locals go =).


Suggestion to rename speed to speed_x or something alike.
Maybe zSpeed because it is along z (height) axis


Torrent <->DummyUnit relation is 1:1, each torrent is associated by the unit. UnitIndex of the dummyunit could be used instead of allocation method.
Nice catch, will do next update.


Could you add a description of what the system exactly is?

The API needs to explain the variable types next to the names.
Will do


Maybe a list can be be required as external system, if you prefer.
I'll try to use Nes's lists


Why are all members doubled that a TorrentSystem already provides? Like Damage/Attacktype, etc. A torrent array, should only take usage of it, and can additionaly have new members, but at it's best it should not double anything.
It seems doubled but it actually is not because 1 torrentarray contains 1 or more torrents but the user only have access to the torrentarray members and not the individual composing torrent's members, which means that the individual torrent's members should be based/copied from the torrentarray, not the torrentarray basing its data from the torrents.
It's like:
Code:
//config
torrentarray.data1 = <set by user>
torrentarray.data2 = <set by user>
//...

//internal (loop through all torrents)
torrent1.data1 = torrentarray.data1
torrent1.data2 = torrentarray.data2
//...

torrent2.data1 = torrentarray.data1
torrent2.data2 = torrentarray.data2
//...

torrentN.data1 = torrentarray.data1
torrentN.data2 = torrentarray.data2
//...


Such logics, only to get good x/y onCast. Maybe requring some libs is good for the code, or making an extra function to regulate the correct value.
Hmm, if I'd make it a separate function, It would still be of the same length because I only use them once in each lib.


Why not stunning a unit instead of pause?
Right, pause might not be so good as it would have some unwanted side-effects, but I prefer if there would not be a stun special effect attached overhead the units =). How about setting both propwindow and turnrate to 0.00? I'm not so sure how exactly it mimics stun, I imagine they can still use abilities. But if there are no other alternative, then I'll use the stun.
 
I personaly would maybe also tend to do TorrentSystem submission, and then TorrentArray as new submission, as TorrentSystem > TorrentArray. That fact alone that TorrentArray would anyways include TorrentSystem wouldn't bother me too much, as it's just like a normal requirement.

They register callback functions, onTorrentExplode() registers a code that will run when a torrent explode
Could we maybe rename it to registerOnTorrentExplode or so? I mean I have read the texts actually... but onTorrentExplode somehow sounds like some event to me as itself.
Maybe zSpeed because it is along z (height) axis
Ah ok, I didn't reaylize it.^^ Maybe yes.
Yes, calling destroy should make the torrent instantly vanish/disappear for the user however, it should not be the case for its effects upon the tossed units because doing so would break the mechanics, that's why the system waits for the all the units to land and leaves the deallocation part to the periodic method later instead.
But so there's no hard destroy? Like it instantly finished everything. Sorry maybe it's no too clever question, I dont have all scenarios in mind atm, if it makes sense for you not to allow a instant destroy then it maybe makes sense.

setting both propwindow and turnrate to 0.00?
Yeh it only tries to disable movement. Maybe a stun system is good, I'm currently not sure which is the 'best'.. though.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I personaly would maybe also tend to do TorrentSystem submission, and then TorrentArray as new submission
You mean separate submissions, or bundle?


Could we maybe rename it to registerOnTorrentExplode or so? I mean I have read the texts actually... but onTorrentExplode somehow sounds like some event to me as itself.
Right, I also find some of my old namings a bit off, will change it.


But so there's no hard destroy? Like it instantly finished everything.
Hmm, I'm actually thinking of one way to destroy the effects completely and not break the mechanics, that is, to drop all tossed units instantly.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Updated

- The configuration now mimics the common convention of spell configurations (constant globals for static configurations and constant functions that take arguments for dynamic configurations)

Torrent System is now made as a separate resource from this one.
 
Last edited:
Level 7
Joined
Feb 13, 2022
Messages
86
Super cool powers, I only wanted to use one of the four powers. It turned out to be a complete nightmare attempting to implement these powers and systems into my map. The author should've release each power individual so that way they would be simpler to implement.
 
Top