• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System] Light Unit Indexer

Level 10
Joined
Sep 19, 2011
Messages
527
Notice: Stable version.

JASS:
library LightUnitIndexer /* v0.2.1
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
*   Light Unit Indexer by Ruke
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       Assigns unique ids to units.
*
*   Why choose it?
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       If you need a light system, who does not requires other systems, work with 
*       hashtable instead of UnitUserData (custom value in GUI) and does not use
*       interfaces, structs and other stuffs like that, choose it.
*       
*       Also, if you need a GUI friendly unit indexer, choose it too :3.
*
*   How to import?
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       First, create a new trigger called LightUnitIndexer and then paste this code.
*       You also will need to create a new ability based on Defend (Human -> Units),
*       no need to modify nothing on it.
*
*   Functions
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       function TriggerRegisterIndexEvent takes trigger t returns event
*       function TriggerRegisterDeIndexEvent takes trigger t returns event
*
*       function GetIndexedUnit takes nothing returns unit
*           -   Returns the unit who has been indexed
*       function GetDeIndexedUnit takes nothing returns unit
*           -   Returns the unit who has been deindexed
*
*       function GetUnitById takes integer i returns unit
*       function GetUnitId takes unit u returns integer
*
*       function IsIndexLocked takes integer i returns boolean
*       function LockIndex takes integer i, boolean b returns nothing
*           -   Lock (or unlock) the index. If you lock it,
*           -   the index will not be recycled when the
*           -   unit is deindexed
*
*       function IsUnitIndexed takes unit u returns boolean
*
*   Thanks to
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       Jesus4Lyf
*       Nestharus
*       muZk
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    globals
        // 1.00 = Index event
        // 2.00 = DeIndex event
        // ------------------------------------------
        // Uncomment this line if you will not create
        // the variable LUIS_EVENT with the variable
        // editor (Ctrl + B)
        // ------------------------------------------
        //real udg_LUIS_EVENT = 0.00
        
        // Change it to the rawcode of the ability based on
        // Defend (Human -> Units)
        private constant integer LEAVER_ABILITY = 'LUIS'
        
        private integer array lock
        private integer array recycle
        private integer recycleCount = 0
        
        private group indexCache = CreateGroup()
        private group deIndexCache = CreateGroup()
        private boolean inCache = true
        
        private unit array units
        private integer count = 0
        
        private unit eventUnit = null
        
        private hashtable ht = InitHashtable()
    endglobals
    
    function TriggerRegisterIndexEvent takes trigger t returns event
        return TriggerRegisterVariableEvent(t, "udg_LUIS_EVENT", EQUAL, 1.00)
    endfunction
    
    function TriggerRegisterDeIndexEvent takes trigger t returns event
        return TriggerRegisterVariableEvent(t, "udg_LUIS_EVENT", EQUAL, 2.00)
    endfunction
    
    function GetIndexedUnit takes nothing returns unit
        return eventUnit
    endfunction
    
    function GetDeIndexedUnit takes nothing returns unit
        return eventUnit
    endfunction
    
    function GetUnitById takes integer i returns unit
        return units[i]
    endfunction
    
    function GetUnitId takes unit u returns integer
        return LoadInteger(ht, GetHandleId(u), 0)
    endfunction
    
    function IsIndexLocked takes integer i returns boolean
        return lock[i] > 0
    endfunction
    
    function LockIndex takes integer i, boolean b returns nothing
        if (b) then
            set lock[i] = lock[i] + 1
        else
            set lock[i] = lock[i] - 1
        endif
    endfunction
    
    function IsUnitIndexed takes unit u returns boolean
        return HaveSavedInteger(ht, GetHandleId(u), 0)
    endfunction
    
    private function FireEvent takes unit u, real r returns nothing
        local unit prev = eventUnit
        set eventUnit = u
        
        set udg_LUIS_EVENT = r
        set udg_LUIS_EVENT = 0.00
        
        set eventUnit = prev
        set prev = null
    endfunction
    
    private function IndexUnit takes unit u returns nothing
        local integer i
        
        if (IsUnitIndexed(u) == false) then
            if (recycleCount == 0) then
                set count = count + 1
                set i = count
            else
                set i = recycle[recycleCount]
                set recycleCount = recycleCount - 1
            endif
            
            call SaveInteger(ht, GetHandleId(u), 0, i)
            
            call UnitAddAbility(u, LEAVER_ABILITY)
            call UnitMakeAbilityPermanent(u, true, LEAVER_ABILITY)
            
            if (inCache) then
                call GroupAddUnit(indexCache, u)
            else
                call FireEvent(u, 1.00)
            endif
        endif
    endfunction
    
    private function RemoveIndex takes unit u returns nothing
        local integer handleId = GetHandleId(u)
        local integer id = GetUnitId(u)
        
        if (IsIndexLocked(id) == false) then
            set recycleCount = recycleCount + 1
            set recycle[recycleCount] = id
        endif
        
        set units[id] = null
        
        call SaveInteger(ht, handleId, 0, 0)
        call RemoveSavedInteger(ht, handleId, 0)
    endfunction
    
    private function DeIndexUnit takes unit u returns nothing
        if (GetUnitAbilityLevel(u, LEAVER_ABILITY) == 0 and IsUnitIndexed(u)) then
            if (inCache) then
                call GroupAddUnit(deIndexCache, u)
            else
                call FireEvent(u, 2.00)
                call RemoveIndex(u)
            endif
        endif
    endfunction
    
    private function OnEnter takes nothing returns boolean
        call IndexUnit(GetFilterUnit())
        return false
    endfunction
    
    private function OnLeave takes nothing returns boolean
        call DeIndexUnit(GetFilterUnit())
        return false
    endfunction
    
    private function Run takes nothing returns nothing
        local unit u
        
        set inCache = false
        
        loop
            set u = FirstOfGroup(indexCache)
            exitwhen u == null
            call GroupRemoveUnit(indexCache, u)
            
            call FireEvent(u, 1.00)
        endloop
        
        loop
            set u = FirstOfGroup(deIndexCache)
            exitwhen u == null
            call GroupRemoveUnit(deIndexCache, u)
            
            call FireEvent(u, 2.00)
            call RemoveIndex(u)
        endloop
        
        call DestroyGroup(indexCache)
        call DestroyGroup(deIndexCache)
        
        call DestroyTimer(GetExpiredTimer())
        
        set indexCache = null
        set deIndexCache = null
    endfunction
    
    private module Init
        private static method onInit takes nothing returns nothing
            local integer maxPlayers = bj_MAX_PLAYERS
            local trigger t = CreateTrigger()
            local boolexpr onLeave = Condition(function OnLeave)
            local region r = CreateRegion()
            local player p
            local unit u
            
            loop
                exitwhen maxPlayers < 0
                
                set p = Player(maxPlayers)
                
                call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, onLeave)
                call SetPlayerAbilityAvailable(p, LEAVER_ABILITY, false)
                
                call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, p, null)
                
                loop
                    set u = FirstOfGroup(bj_lastCreatedGroup)
                    exitwhen u == null
                    call GroupRemoveUnit(bj_lastCreatedGroup, u)
                    
                    call IndexUnit(u)
                endloop
                
                set maxPlayers = maxPlayers - 1
            endloop
        
            call RegionAddRect(r, GetWorldBounds())
            call TriggerRegisterEnterRegion(CreateTrigger(), r, Condition(function OnEnter))
            
            set t = null
            set onLeave = null
            set r = null
            set p = null
            
            call TimerStart(CreateTimer(), 0, false, function Run)
        endmethod
    endmodule
    
    private struct LUI extends array
        implement Init
    endstruct
endlibrary

Changelog:
  • v0.2.1
    • Added LUI struct
    • Added Init module
  • v0.2.0
    • Added RemoveIndex function
    • Changed documentation
    • Initialization feature
    • Added FireEvent function
  • v0.1.0
    • Added index lock/unlock feature (thanks Nestharus)
    • Fixed buggy UNIT_EVENT (thanks Nestharus)
  • v0.0.1
    • Changed name (RIP LUIS :()

Enjoy it, comments and suggestions are welcome.
 

Attachments

  • Light Unit Indexer.w3x
    20.9 KB · Views: 132
Last edited:
UnitUserData (which some abilities reset it to 0)

Please elaborate

Also, your locks should use a counter, not a boolean.

edit
I should also point out that this doesn't work with modules or any sort of initializer

edit
Also, acronyms for system names aren't really acceptable at THW. Only well known acronyms are really acceptable. If you look through all of the JASS resources on THW, you will not find one with an acronym unless it is a well known one, like MD5 or AES.

Only on wc3c and TH were acronyms really acceptable, and even then, very few on wc3c were acronyms.

Timer Tools lib is named Tt as the optimizer didn't work when it was made, so it had all of the shortened stuff applied to it to increase its speed. However, in an update, you can be sure that Tt is going to be renamed to TimerTools.

CTL was given an acronym again for speed. It is too late to rename it as too many resources now use it : (.

edit
oh yea, and your EVENT_UNIT w/e is buggy, haha...

JASS:
//on index instance 1
if (create < 2) then
set create = create + 1
call CreateUnit(...)
//event unit == null
endif

//on index instance 2
if (create < 2) then
set create = create + 1
call CreateUnit(...)
//event unit == created unit
endif

nice first attempt, but lots of common mistakes ^)^
 
Level 10
Joined
Sep 19, 2011
Messages
527
Please elaborate

Putting info...

Also, your locks should use a counter, not a boolean.

Why? ._.

Also, acronyms for system names aren't really acceptable at THW. Only well known acronyms are really acceptable. If you look through all of the JASS resources on THW, you will not find one with an acronym unless it is a well known one, like MD5 or AES.

Only on wc3c and TH were acronyms really acceptable, and even then, very few on wc3c were acronyms.

Timer Tools lib is named Tt as the optimizer didn't work when it was made, so it had all of the shortened stuff applied to it to increase its speed. However, in an update, you can be sure that Tt is going to be renamed to TimerTools.

CTL was given an acronym again for speed. It is too late to rename it as too many resources now use it : (.

Well yeah, but naming a function/global variable LightUnitIndexerSystem___Name its kinda ugly :c, but can be changed if needed.

oh yea, and your EVENT_UNIT w/e is buggy, haha...

Sacrilege!
I will look it out, thanks for reporting :3.

Fixed.

nice first attempt, but lots of common mistakes ^)^

Next wave please ;D
 
Last edited:
Level 10
Joined
Sep 19, 2011
Messages
527
You haven't elaborated on what skills reset unit user data
I want to find the ability, give me a moment.

don't ask why on the locks, there is a very valid reason, figure it out ;p. There is a reason both AIDS and Unit Indexer use integers, not booleans. Until you do integers, this will still have bugged behavior.

:ogre_icwydt:
Haha, well lets check then >.<
 
timer1
lock index

timer2
lock index

timer1 expires
unlock index

timer2 expires
doh, index doesn't exist!
if a unit was created and another timer locked it, this'll end up locking that unit incorrectly
this will also end up cleaning stuff up that doesn't exist for that unit and may even cause permanent leaks

thus the need for a counter

edit
unit event variable wasn't fixed.. you are still nulling it and not using some sort of stack. Do not use an array to fix it, that's not the best answer ;o.
 
Level 10
Joined
Sep 19, 2011
Messages
527
timer1
lock index

timer2
lock index

timer1 expires
unlock index

timer2 expires
doh, index doesn't exist!
if a unit was created and another timer locked it, this'll end up locking that unit incorrectly
this will also end up cleaning stuff up that doesn't exist for that unit and may even cause permanent leaks

thus the need for a counter

I realize that I was having a wrong concept of what the lock feature does.

edit
unit event variable wasn't fixed.. you are still nulling it and not using some sort of stack. Do not use an array to fix it, that's not the best answer ;o.

I didn't want to add an stack here, you can easily save the unit on a variable... but meh I will have to add it.
You're right.
 
Last edited:
at initialization (all before game starts, considered to be 1 instant, considered to be at the same time)

module1
register

module2
create unit (runs module1)

module3
register (uh oh, won't fire for unit created by module2)

you'll have to maintain a list of index/deindex stuff for before the game starts and run all of those events for all modules registering before the game starts

UnitIndexer is the only lib that correctly does this, so it is the only unit indexer out there that does initialization correctly

for this system to even be considered, you need to provide all correct behavior like unit indexer does, otherwise your system will be considered buggy and vastly inferior to unit indexer
 
Level 10
Joined
Sep 19, 2011
Messages
527
at initialization (all before game starts, considered to be 1 instant, considered to be at the same time)

module1
register

module2
create unit (runs module1)

module3
register (uh oh, won't fire for unit created by module2)

you'll have to maintain a list of index/deindex stuff for before the game starts and run all of those events for all modules registering before the game starts

UnitIndexer is the only lib that correctly does this, so it is the only unit indexer out there that does initialization correctly

for this system to even be considered, you need to provide all correct behavior like unit indexer does, otherwise your system will be considered buggy and vastly inferior to unit indexer

Again, you're right, I'm working on it.

edit
Running a timer on init:

JASS:
private function Run takes nothing returns nothing
    local integer i = bj_MAX_PLAYERS
    local trigger t = CreateTrigger()
    local boolexpr oL = Condition(function OnLeave)
    local region r = CreateRegion()
    local player p
    local unit u
    
    loop
        exitwhen i < 0
        
        set p = Player(i)
        
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, oL)
        call SetPlayerAbilityAvailable(p, LEAVER_ABILITY, false)
        
        call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, p, null)
        
        loop
            set u = FirstOfGroup(bj_lastCreatedGroup)
            exitwhen u == null
            call GroupRemoveUnit(bj_lastCreatedGroup, u)
            
            call IndexUnit(u)
        endloop
        
        set i = i - 1
    endloop
    
    call RegionAddRect(r, GetWorldBounds())
    call TriggerRegisterEnterRegion(CreateTrigger(), r, Condition(function OnEnter))
    
    set t = null
    set oL = null
    set r = null
    set p = null
    
    call DestroyTimer(GetExpiredTimer())
endfunction

private function Init takes nothing returns nothing
    // to avoid problems (firing events) on init u.u
    call TimerStart(CreateTimer(), 0, false, function Run)
endfunction

Fix the problem, but, this is valid?.

Back on the "UnitUserData is reset to 0 by some abilities", I was testing all the morph/hexx abilities and nothing seems happen :\, I will wait for feedback for one of my map makers friend.

Glasses on, here comes a quotation of Troll-Brain u.u:
Troll-Brain said:
Never believe some warcraft "fact" without a proof, even from an "experienced" user, that's how myths & legends born.
 
Last edited:
Level 10
Joined
Sep 19, 2011
Messages
527
Given that unit user data doesn't apparently reset, you might as well use unit user data over a hashtable.

Apparently, but some users want that space free... what to do <.<.

It is, after all, a solid improvement ; P. Lowers memory cost by freeing a hashtable, lowers code size, and improves performance.

Thanks to you :ogre_hurrhurr:. So, version 1? :3

edit
So, at the end, the timer trick is valid?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
If you want to make a "light" one, you would not use any event at all.

And the only way where it would worth it, it's when you have the full control of unit creation/destruction (so it requires a custom decay system, unit training, and so one).

This is how i see the thing :

- a custom CreateUnit function which creates the unit and then eventually index it (if the unit is created because of valid function arguments)
- a custom RemoveUnit function which removes the unit and eventually deindex it (if the unit is being removed).

This way, if for some reason you don't want to index an unit, then you just have to use the native CreateUnit function instead of your custom one.

Also, for a not indexed unit, use RemoveUnit is fine, but you should always use the custom RemoveUnit function for every unit.

In fact i started to make a such unit indexer, but the requirements are too much imho, even if yeah full control of units means more possibilities.

EDIT :

Ok we have not the same meaning for "light", my idea is the best one in term of efficiency but it's the worst one in term of requirements, because it requires a bunch of custom systems if you want to do even simple things such as unit decay/training, which are already available in a native way ...
Now, in some custom maps it's still a valid option.

Also obvioulsy that would not be gui friendly at all.

The only real con i have about UnitIndexer is about this silly WorldBounds requirement, an UnitIndexer is something really used, even for some quick test.
And for this kind of test, the library requirements are just annoying.
That's why i have "repacked" it to have 0 requirements for my personal usage.
 
Last edited:
Level 10
Joined
Sep 19, 2011
Messages
527
If you want to make a "light" one, you would not use any event at all.

That is one option, in fact it's a good one :3. But I will keep the events to make it more GUI friendly.

Ok we have not the same meaning for "light"

xD, my "light" idea is low requirements (0 until now :3) and low numbers of code lines (of course having in mind efficiency). Besides, I try in all the cases that I can, to follow the KISS concept.

I will update this in a few days ;), I have some work to do :/.

Greetings.
 
xD, my "light" idea is low requirements (0 until now :3) and low numbers of code lines (of course having in mind efficiency). Besides, I try in all the cases that I can, to follow the KISS concept.

I think most, if not all of the JASSer have the same idea of light as Troll does. I do as well. Same with mag.

Troll pretty much summed up what everyone was thinking but wouldn't say ;p.

I'm also sure that everyone thinks that the WorldBounds req is pretty annoying. I think that as well ;p. It's a really silly req, lol... but I required it back when I was the "uber let's go modular" crazy person >.<.
 
^If you have unit indexes, you can easily attach data to units via simple arrays rather than making a ton of hashtables. There is a hashtable limit after all.
Table solves this, but arrays are way faster and cleaner :V

A hashtable gets slower when it has more data in it.
This is because common implementations of hashtables don't use incredibly good hashing algorithms because they don't need to, and if they did, they would be EXTREMELY slow.
Imagine having to do one SHA-2 just to store or retrieve one piece of data from a data structure. That would be terrible.

They use simple hash functions to get something like an 8-bit hash.
If it were an 8-bit hash, they would then have an array of size 256.

No, this array is not for your values, it's for the linked lists of values :p

If I were to pass 2 pairs of indexes to the hashtable, and it the hash function h(x,y) outputs the same 8-bit key for both, then it would put both values in the same linked list.

This is common knowledge.

There is no definite speed difference between an array and a hashtable. If you knew Blizzard's hash function, you would be able to figure out the exact speed by using multiple hashtables with a set of indexes that don't cause collisions in said hash function.
 
Level 15
Joined
Feb 15, 2006
Messages
851
^If you have unit indexes, you can easily attach data to units via simple arrays rather than making a ton of hashtables. There is a hashtable limit after all.
Table solves this, but arrays are way faster and cleaner :V

A hashtable gets slower when it has more data in it.
This is because common implementations of hashtables don't use incredibly good hashing algorithms because they don't need to, and if they did, they would be EXTREMELY slow.
Imagine having to do one SHA-2 just to store or retrieve one piece of data from a data structure. That would be terrible.

They use simple hash functions to get something like an 8-bit hash.
If it were an 8-bit hash, they would then have an array of size 256.

No, this array is not for your values, it's for the linked lists of values :p

If I were to pass 2 pairs of indexes to the hashtable, and it the hash function h(x,y) outputs the same 8-bit key for both, then it would put both values in the same linked list.

This is common knowledge.

There is no definite speed difference between an array and a hashtable. If you knew Blizzard's hash function, you would be able to figure out the exact speed by using multiple hashtables with a set of indexes that don't cause collisions in said hash function.
Ton of hastables?? just one with custom keys...

EDIT: I've haven't read the whole topic, but is really really big the difference in load respect an array?? or it's just a mtter of milliseconds??
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
It's just easier to use.
This way you're sure that the index will be unique for each unit and can be used for an array (or struct instance ofc)
And if you use unit index instead of unit themselves, then you don't have to worry about some leak.
 
initialization still bugged

module A
create unit

module B
register (won't throw index event until after 0 seconds)

module C
remoev unit

module B
fires, cleans a unit that is deindexed but was never created for module B

-error-

also changed your thread name as it was pretty funny to read, lol

edit
after all of these updates, this is going to be virtually identical to UnitIndexer except that it will have a little less feature support, a bit heavier code, hashtable use (meaning slower), and no reqs (meaning heavier, more code)

so.. I wonder.. what exactly is the point of this? Why would anyone want to use this?

It'd be smarter if UnitIndexer was just repacked with no reqs and to use 'Adef' for quick testing
 
Level 10
Joined
Sep 19, 2011
Messages
527
also changed your thread name as it was pretty funny to read, lol

What kind of sorcery is this ._.?, I didn't change the name of the thread.

after all of these updates, this is going to be virtually identical to UnitIndexer except that it will have a little less feature support, a bit heavier code, hashtable use (meaning slower), and no reqs (meaning heavier, more code)

Well, this does not use structs (only the goddamn initialization that apparently does not fix the module problem <.<), I like no requirements and this is GUI friendly (not the big deal, but you know :3).

so.. I wonder.. what exactly is the point of this? Why would anyone want to use this?

Anyone who only want an unit indexer who adds indexs to units, provides two events more and nothing more with no need to add other libraries?.

So, I will check to see if I can fix the module problem :).
 
Okay, Ruke, listen up.

I'm going to be honest and straight-to-the-point here.

This resource is a bad idea.

Believe me. You will fix it, improve it and perfect it. Then you'll have a UnitIndexer clone with no events, hashtable usage and no requirements.
This is the problem that most resources face these days :/
The only way they could be better is if they had the same code as something that already exists, which is why allowing the user to waste his time with them is pretty evil.

I'm doing you a huge favor D:
 
Level 10
Joined
Sep 19, 2011
Messages
527
Okay, Ruke, listen up.

I'm going to be honest and straight-to-the-point here.

This resource is a bad idea.

Believe me. You will fix it, improve it and perfect it. Then you'll have a UnitIndexer clone with no events, hashtable usage and no requirements.
This is the problem that most resources face these days :/
The only way they could be better is if they had the same code as something that already exists, which is why allowing the user to waste his time with them is pretty evil.

I'm doing you a huge favor D:

It's hard to say bye to LUIS, but meh, graveyard this. You guys are right, at the end this will be a clone <.<.
 
Top