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

Techtree Contest #12 - Evolution

Status
Not open for further replies.

Kyrbi0

Arena Moderator
Level 45
Joined
Jul 29, 2008
Messages
9,501
@Spellbound DON'T CAVE! ; )

@xYours Trulyx Anything you need for testing purposes can be brought about via triggers. We have done much the same for Hero Contests for years & years; simply have a code the players can enter to spawn stuff for testing. Or an item attached to the hero, or a special building with custom triggered abilities.

No need to change the map.
 
Level 7
Joined
Feb 10, 2016
Messages
59
I use the regions to create crabs on the river. There is a techtree upgrade in my race that grants you vision of those crabs. Nothing special. If you actually want to import the race then you have to set the crab spawns somewhere else :p

You don't necessarily need to use regions for that, though. You can use triggers to search for shallow water on the map. I've written a very poorly designed bit of JASS that demonstrates this.

JASS:
//===========================================================================
// Function to figure out if a point is in shallow water
//===========================================================================
function SpawnCrabsValidLoc takes location crabPoint returns boolean
    if (IsTerrainPathableBJ(crabPoint, PATHING_TYPE_FLOATABILITY) == false) then
        return false
    endif
    if (IsTerrainPathableBJ(crabPoint, PATHING_TYPE_WALKABILITY) == false) then
        return false
    endif
    return true
endfunction

//===========================================================================
// This trigger picks random points on the map and checks to see if they
// are shallow water. If so, it makes a crab at the point. This loops
// over and over until it makes 10 crabs. That right there would crash the
// game if there was no shallow water. Watch out!
//===========================================================================
function Trig_SpawnCrabs_Actions takes nothing returns nothing
    local integer crabCounter = 0
    local real crabX = 0.0
    local real crabY = 0.0
    local location crabPoint = Location(0,0)
    loop
        exitwhen crabCounter >= 10
        set crabX = GetRandomReal(GetRectMinX(GetPlayableMapRect()),GetRectMaxX(GetPlayableMapRect()))
        set crabY = GetRandomReal(GetRectMinY(GetPlayableMapRect()),GetRectMaxY(GetPlayableMapRect()))
        call MoveLocation(crabPoint,crabX,crabY)
        if ( SpawnCrabsValidLoc(crabPoint)) then
            call CreateNUnitsAtLoc( 1, 'ncrb', Player(PLAYER_NEUTRAL_PASSIVE), crabPoint, bj_UNIT_FACING )
            set crabCounter = crabCounter + 1  
        else
        endif
    endloop
    call RemoveLocation(crabPoint)
endfunction

//===========================================================================
// This InitTrig runs at the start and puts together the SpawnCrabs
// trigger. It might be a bit redundand, I don't know, I'm not a JASS
// expert or anything.
//===========================================================================
function InitTrig_SpawnCrabs takes nothing returns nothing
    set gg_trg_SpawnCrabs = CreateTrigger()
    call TriggerAddAction( gg_trg_SpawnCrabs, function Trig_SpawnCrabs_Actions )
endfunction

I would suggest editing it further if you actually want to use it, since what I've written is just something quick that I thought of and could potentially have game-crashing bugs. Then again, I am competing against you in this contest. Maybe I'd have a better chance if you did use this code as-is! :D
 
Level 13
Joined
Sep 26, 2017
Messages
159
Oh come on, who doesn't love crabs?
ikr
i love em so im making an entire race for that
xd

Wanted to post an update that I have done, but I decided to get all the assets (like icons) ready before that.
Here's current status:
- most of the units are ready, the rest just has their looks and no abilities whatsoever;
- using PROBABLY not an imbalanced method, I have managed to implement two flying units in the entire crab mess;
- three out of four heroes done, but very few icons for their abilities are implemented;
- planning on a large upgrade tree, not much ready yet;
- wait, woah, is this huge crab their only way to harvest lumber!?
 
The Wellings Logo.png


Lore:
A secret cult that originates in a remote area near a lake somewhere in the Southern Kalimdor, rumored to be worshiping a sacred being under the deep blue ocean, was later revealed as a cult that worshiped the "Riverqueen". The locals dislikes false god-worshiping and attempted to destroy the cult. All of the cultists were killed and their bodies were left at their lodge. The day after that, all of the bodies were gone. The Riverqueen has claimed her loyal worshipers' souls and sinks their bodies under the deep sea. The cultists were given a chance for revenge and was reborn as the Wellings, creatures made of water with the deranged souls of the worshipers. Those that successfully killed their killers will ascend and become the Riverqueen's foot soldiers.


Wellings Units.png




Wellings Building.png




Wellings Heroes.png




The Wellings excels at early game skirmishes, especially when Humidils gets their Puddle Lashes power spikes. Mid game to late game, you need to start replacing Tier 1 units with more powerful units.

Humidils

Use Humidils' Puddle Lashes to slow enemies' attack speed. If you take large numbers of Humidils, try to attack different targets to make them most out of the attack speed debuff.
Rivulets

Although Reef Shot may not seem to be a great passive ability, any damage boost is always better for your units. About 4-6 Rivulets is enough to take down a mage or flying unit in seconds.
Seatapper

This guy is your main tanker. Put them in the frontlines so they can soak up the damage for your squishier targets so they can safely damage your enemies. The regeneration upgrade will heal your Seatappers faster, making them quite durable in long fights.
Ascended

Ascended can deal tons of damage to the back line, and Relentless Charge makes it easier for them to reach their targets. Conquerer is a great passive if you're planning to take a lot of Ascendeds, although it is not recommended to take more than 6 of them.
Volley Basin

Your basic and only siege unit. As with all siege units, they are very vulnerable to melee and flying units, so be sure to put them in a safe distance. With the Crushing Tide upgrade, time your attacks properly to knock enemies up multiple times if you have many Volley Basins.
Runic Watcher

Flying ranged unit with bouncing attacks. Sounds intriguing, but don't overdo it. If you're planning to take large amounts of Runic Watchers, get the Ethereal Body upgrade for the increased durability.
Abyssal Guardian

The Wellings' Ultimate unit. Position your attacks properly to make the most out of Staggering Blows. Use Safeguard on important targets such as heroes and mages.

Madlord

It's best to know which spell you want to cast first. Casting Deranged Fling and then Tidal Prison will crowd control your enemy even longer. However, casting Tidal Prison and then Deranged Fling is a good way to chase priority targets. If you still want the bonus damage, you can wait out the snare duration and cast fling at the near-end of the duration to get the bonus damage while still crowd controlling them for a long time. While using Insanity's bonus movement speed to reach important enemies at the backline may sound like a good idea, the Madlord is quite squishy without Safeguard.
Aquachanter

Land a group stun with Shimmering Bubble and use Call of Ascended on them to burst down squishy targets. Blob Attack synergies well with Essence Rift as more units means more mana restored.
Tide Priest

Predict where your enemies will move to land a perfect Seastriker Orb as it has a 1.2 second delay. Salvation's cast range is quite short, but the channel range is unlimited. After you cast it, the target can move wherever it wants and still get the healing. Abyssal Voyage is situational. If you use it in battle, it puts the spell on a long cooldown to save just one allied unit, so proper usage of the spell is required.
Ocean Vanguard

Use Riptide to disable a clumped group of enemies. It's best to use Titan's Endurance after casting Waterway Reversion so the hero can sustain all the damage he receives from taunted units.




M O D E L S
-skrab
-General Frank
-Champara Bros
-icewolf055
-Tarrasque
-JetFangInferno
-Retera
-RightField
-PrMosquito
-Power
-Zerox
-JesusHipster
-UgoUgo
-Kino
-Dionesiist
-exfyre
-Daelin
-s4nji
-nhocklanhox6
-Pvt.Toma
-Ujimasa Hojo
-Frotty

I C O N S
-Marcos DAB
-Banzay89
-PeeKay
-Heinvers
-Hellx-Magnus
-Anachron
-CRAZYRUSSIAN
-SkriK
-HappyCockroach
-dansaDisco
-Praytic
-BLazeKraze
-AL0NE
-Darkfang
-Legal_Ease
-Raging Ent
-Murlocologist
-Deathclaw24
--Stygian-
-JollyD
-4eNNightmare
-mortal
-NFWar
-Freezer
-Nudl9
-Chucky
-Dionesiist
-Blood Raven

S K I N S
-Hawkwing
-FrIkY

S P E L L S and S Y S T E M S
-Chaosy
-f0rsAk3n
-Kingz
-Spellbound
-Bribe



This will be my entry. IF there is any bugs or missing stuff please notify me so i can fix it :)
 

Attachments

  • THW.TC12.xYours Trulyx.Wellings v7.w3x
    4.3 MB · Views: 124
Last edited:

Kyrbi0

Arena Moderator
Level 45
Joined
Jul 29, 2008
Messages
9,501
Verah Noice! (I really oughta start on mine...) Looks really cohesive overall (aesthetically). Two questions:
- Is the Abyssal Guardian a tinted version of the alternate version of Direfury's little "Gnome Wizard" model?
- Where did you get the Ascended model from?
 
Verah Noice! (I really oughta start on mine...) Looks really cohesive overall (aesthetically). Two questions:
- Is the Abyssal Guardian a tinted version of the alternate version of Direfury's little "Gnome Wizard" model?
- Where did you get the Ascended model from?

- Yes, I edited that myself and cut out the gnome animations.
- That is also and edit of a Halberdier model with Knight of Order skin. I put some water particles on it (Same goes to Elite Ascended)

As I said, Hive lacks water models so I have to make/edit them on my own (Rivulets, Ascended, Volley Basin and Elite Ascended.
 
I have a question:

Can I use models or textures without teamcolor? I was planning to use some of Mr.Goblin's Horror Textures but they don't have TC.

I use that all the time. No rules said its not allowed to use non-tc models.

Anyways, I updated the race with a few bug fixes regarding Abyssal Guardian evolution and added Backpack to the race (I forgot about this upgrade). Also fixed Riptide pausing flying units for eternity.
 
Last edited:
I have a question:

Can I use models or textures without teamcolor? I was planning to use some of Mr.Goblin's Horror Textures but they don't have TC.
You can, but keep in mind that if there isn't some other way to identify the unit's player colour, you might lose points.
 
Level 10
Joined
Oct 5, 2008
Messages
355
Just a rule question: if one if the buildings creates units ehich are more or less unmoveable and extensions of itself, do these count as a rule violation, since these didn't spawned from tge base unit itself?
Im asking because i'm tinkering with ideas and i want the main building to be an organic being which harvests gold and lumber. But, because of balance reasons (so you can still worker-harrass), i want these to be units, these needs to be buildable.
 
Producing units would contradict the rules, unless said unit is your basic unit. You could work around this by having the 'building addon units' come pre-packaged with the building (they appear after the building is done building) and those can then be upgraded into the other buildings addons. Eacg addon would probably count toward your building-type limit.
 
Level 10
Joined
Oct 5, 2008
Messages
355
Thank you for the clarification.

Sadly they cannot be the basic units since they need to be more or less immobile, building bound harvester. And they cannot come prebuild else it would give 100% gold harvest rate from the get go. So what i habe in mind would require some ugly workarounds by sacrificing the main unit to the starting building. This is still a possibility though, but would require some thinking to make it intuitive on the ui and the gameplay.

So i would like to join the contest. Concept and first wip will come this weekend.
 
Level 7
Joined
Feb 10, 2016
Messages
59
Sadly they cannot be the basic units since they need to be more or less immobile, building bound harvester. And they cannot come prebuild else it would give 100% gold harvest rate from the get go.

You might be able to work around this by having the main building automatically spawn the addons, and then have the addons pay a cost to transform into a different kind of addon that does the harvesting. That technically wouldn't be "training" the harvester, it would be "upgrading" into it. Also, keep in mind that Humans, Orcs, and Night Elves can all have 100% gold harvest rate from the get go if they put all of their workers into gold, so that's not necessarily a bad thing.

Depending on what concept you have in mind, you might also give those addons some other transform options, such as defensive structures, or maybe make them self-sacrifice into certain units.
 
Level 10
Joined
Oct 5, 2008
Messages
355
Thats a good idea, although i fear that this would make them kinda play like the zerg. Although this hobestly isn't that bad. I throw some concepts together and look what sticks. Currebtly i only really began to collect some gui systems to get a modular coding base going.

Edit: for the crab spawning trigger, i would highly suggest instead of picking random points to iterate through every 68x68 area (brush size) of the map once and save the valid locstioms. Would only require a simple 2d array (for the x and y of the valid squares) and one iteration through the map. Then you can just always pick a random number out of the valid locations wothout much iterations. Because depending on the map, chances are you pick through masses of points.
 
Last edited:
Edit: for the crab spawning trigger, i would highly suggest instead of picking random points to iterate through every 68x68 area (brush size) of the map once and save the valid locstioms. Would only require a simple 2d array (for the x and y of the valid squares) and one iteration through the map. Then you can just always pick a random number out of the valid locations wothout much iterations. Because depending on the map, chances are you pick through masses of points.

Regions are fine with me. Going simple because I'm no vJass/Jass/Wurst user. Just a plain GUI guy.

Anyway, sorry for my long absence. Internet broke down 5 days. I was implementing a medium-sized rework to my race. Updated my map and here's a list of changes.

v2.0
Missing Stuffs
- Stonework upgrades now increases gold/lumber cost for each tier and has more research timer.

Reworks
Ocean Vanguard went through a lot of change:
- Whirlpool Strike has been removed and swapped with Riptide.
- New ability: Titan's Endurance. Increases armor and health regeneration for 10 seconds.
- Luminescence Mace now heals allies that attacks marked units. (Vanguard doesn't count)
- Waterway Reversion no longer grants armor and marks target with Luminescence. Grants damage reduction instead.

Aquachanter got some change. Just one ability though:
- Blob Attack no longer grants vision but instead slows enemies.

Tide Priest got a new ability too:
- Mind Flare removed.
- New ability: Seastriker Orb. Summons an orb that damages nearby enemies when it dies.

Balance Changes
- Ascendeds' Relentless Charge reduced cooldown. (12.00 -> 11.00)
- Tide Priest's Salvation has reduced interval for quicker healing. (Technically draining mana)
- Tide Priest's Celerity Aura now also increases attack speed.
- Ocean Vanguard's Riptide increased damage to compensate for Whirlpool Strike.
- Madlord's Insanity duration increased (20.00 -> 25.00)


@Spellbound you can make this my final entry as I doubt there's any more bugs and missing things.
 
Level 10
Joined
Oct 5, 2008
Messages
355
Now heres a little sneakpeek of what i'm working so far for the contest:

WIP1 ForgottenCult.jpg

The race will be called "Forgotten cult". Design-wise it will go into the direction of fanatical faceless scarificing themself to ascend to higher forms or summoning forth eldrith creatures. The main building is the forgotten scion (->forgotten one ->wound of worlds), which uses polyps (the tentacles), created out of sacrificed cultists, to harvest lumber/gold.

The forgotten scion creates the forgotten mine out of the goldmine (does that count as upgrading, since it came from a goldmine or should i change that? Would require soem work but that wouldn't be a problem).

The basic unit is the cultist, which can be summoned by the forgotten scion (will add some nice effect to that).

Currently i'm working on the summoning system. The pylops will rip the trees out in one go, giving you the full amount of lumber without returning to the forgotten one. Problem is that you have to wait until they accumulated that amount equal to the trees hp. I don't know if i want to add an option to give you less lumber but more directly. The pylops actively seek around the last tree felled.

Pylops can be used for early defense. Unassigned pylops works as tower extensions of the forgotten one, featuring a ranged attack somewhat similar in dps than the militias but only a bit better (since they cannot move)

Cultists sacrifice themself willingly when you order them on the mine or trees, making controling your economy confortable even with the upgrading mechanic of the race.

Just a word of warning though. Should the forgotten one die, so will all his connected pylops. This is the tradeoff for the almost instant mobility they possess.
 
Level 7
Joined
Feb 10, 2016
Messages
59
The polyps will rip the trees out in one go, giving you the full amount of lumber without returning to the forgotten one. Problem is that you have to wait until they accumulated that amount equal to the trees hp. I don't know if i want to add an option to give you less lumber but more directly.

Hmm. An interesting mechanic, although I wonder if it would make more sense to have the polyp slowly digest the tree after ripping it out, giving you that lumber bit-by-bit in the process. It's up to you, of course, but that might help even out the lumber flow to more closely match the other races.
 
Level 10
Joined
Oct 5, 2008
Messages
355
That is a concern i have, too . Currently it takes around 46 seconds (a bit higher lumber rate than normal worker with 1.1 per second to compensate) until a polyp returns lumber (50), which makes them -very- vulnerable to early attacks and harassing. I believe splitting it up would be better for the melle gameplay.
 
Level 10
Joined
Oct 5, 2008
Messages
355
@xYours Trulyx So i played your race and you got some very great concepts behind it, just some things that came to mind:

-It would really improve gameplay when you give out a message when an extractor runs out of trees/gold. Sometimes i found myself deprived of lumber because i didn't noticed that the extractors got the best of the trees.
-Take a lookm on your flying spellcaster. For some reason it didn't let me build it, despite meeting the requirements. I would take a look at the UPG Done Trigger. You remove the build unit, add the upgraded resevour but still try to add the builded unit into the unit group, which doesn't exist anymore.
-The T1 summoning item has 1 charge, despite stating the tooltip saying it has 3.

Overall the race is very solid and has some interesting gameplay. I look forward how it will turn out. (I really liked the idea with the crabs :3 )
 
Last edited:
@Lord_Earthfire Thanks for your feedback and opinion. My race uses a CnP'ed trigger of Harvest Lumber from a previous contest made by Kingz if I recall correctly.

-It was made it Jass/vJass/whatever the fck so I don't really know how to edit it to warn you about dried trees. I had to start from scratch and made a new trigger in sweet GUI format. This time, it warns you about no more trees nearby.
Gold warning should have been implemented but I guess it's yet another bug. Fixed the system that caused it to not warn players about gold running out.

-That slipped right through me. I guess I was in such a hurry to post the update. Fixed.

-It was actually intentional as there are 3 stocks in the shop. Little bit of a typo there, but I changed it. Now have 1 stock and 3 charges instead and slightly modified the tentacle's duration.

I also used this time to change Abyssal Guardian. Damage modified and Staggering Blows now only inflicts 50% damage to nearby enemies (previously 60%).
 
Well, using Docking System, I wrote up a construct list that allows me to build any kind of building, train any kind of unit and upgrade with impunity any other kind of building. The drawback is that the base worker and building I'd variables are not static, that is, they are constant, and I'm not yet done with the training and upgrade. (I feel like expanding that one later on).

In my WIP, the models that you see there may or may not be final.

@Spellbound

I should have pointed out that I was using your system when writing up and perfecting mine. :p

In other news, would rebuilding a unit via training (taking a certain amount of base units docked in the base building, and using them up (hiding them), ordering the base building to train the desired unit) be okay? The desired unit is unavailable for training mechanically (by conventional means of clicking at it and it displaying gold and lumber info), but must be invoked via a spell (within a Spellbook).

If it is okay, the reason why I'm going at this route is that I want the progress bar of the race to be affected by conventional means (warpten). If not okay, I'll change the mechanics of training.

For more clarification on the matter, I will put up my WIP map later.
 
Last edited:
Level 7
Joined
Feb 10, 2016
Messages
59
so we are allowed to replace a race, right? We're not expected to have our race playable with the 4 other races simultaneously?

Keep in mind that the original contest post says this:

The current four melee races may not be edited; copy and paste the default units you are about to edit.

I suppose you could "replace" a race by having that race spawn your custom race's starting units instead, but that's really clunky. You can also do the old trick where choosing a race and a certain upkeep value will spawn your custom race, but that's still somewhat clunky. The smoothest way to implement extra races that I know is to give user players a dialog button at the start that they can click to choose the custom race.
 
This is quite the pain to develop, specially for this contest, but here is the system I'm developing:

JASS:
library CustomBuildSystem /*

    */ requires /*
      
        Legend:
            #? -> optional resource
            #  -> required resource
          
        *||------------------------||*
        */  DockingSystem           /*
            #? SmartTrack
            #? Table
          
            -> Spellbound
          
            link: https://www.hiveworkshop.com/threads/dockingsystem-v1-05-2.299959/#resource-78986
          
        *||------------------------||*
      
        *||------------------------||*
        */  Table                   /*
            -> Bribe
          
            link: https://www.hiveworkshop.com/threads/snippet-new-table.188084/
        *||------------------------||*
      
        *||------------------------||*
        */  ListT                   /*
            # Table
            # Alloc
          
            -> Bannar
          
            link: https://www.hiveworkshop.com/threads/containers-list-t.249011/
        *||------------------------||*
  
        *||------------------------||*
        */  Alloc                   /*
            -> Sevion
          
            link: https://hiveworkshop.com/threads/snippet-alloc.192348/
        *||------------------------||*
      
        *||------------------------||*
        */  UnitDex                 /*
            -> TriggerHappy
          
            link: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
        *||------------------------||*
      
        *||------------------------||*
        */  RegisterPlayerUnitEvent /*
            # RegisterNativeEvent
          
            -> Bannar
          
            link: https://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
        *||------------------------||*
      
        *||------------------------||*
        */  UnitEventsEx            /*
            # UnitDex
            # RegisterPlayerUnitEvent
            #? ListT
            #? WorldBounds
          
            -> Spellbound
          
            link: https://www.hiveworkshop.com/threads/vjass-version-of-gui-unit-event.306289/#post-3270559
          
        *||------------------------||*
        */  SimError                /*
            -> Vexorian
          
            link: http://www.wc3c.net/showthread.php?t=101260
        *||------------------------||*
      
      
        *||------------------------||*
        */  BuilderTrace            /*
            # UnitDex
            # Table
            # OrderIndex
          
            -> MyPad
          
            link: Not yet defined...
    */
  
native GetUnitGoldCost takes integer unitid returns integer
native GetUnitWoodCost takes integer unitid returns integer

globals
    private group copyGroup_tempGroup = null
endglobals

private function OnCopyGroup takes nothing returns nothing
    call GroupAddUnit(copyGroup_tempGroup, GetEnumUnit())
endfunction

private function CopyGroup takes group whichGroup, group tempGroup returns nothing
    set copyGroup_tempGroup = whichGroup

    call GroupClear(whichGroup)
    call ForGroup(tempGroup, function OnCopyGroup)
endfunction

static if DEBUG_MODE then

private function printError takes string msg returns nothing
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "|cffff0000Error:|r |cffffcc00" + msg + "|r")
endfunction

endif

private module Initializer
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod  
endmodule

private struct BuildSystemEx extends array
    readonly static Table constructType                  = 0
    readonly static Table stationHolder                  = 0
    readonly static TableArray unitTypeLimit             = 0
  
    readonly static TableArray dataBase         = 0
  
    static method operator construct takes nothing returns integer
        return 1
    endmethod
  
    static method operator upgrade takes nothing returns integer
        return 2
    endmethod
  
    static method operator train takes nothing returns integer
        return 3
    endmethod
  
    static method getConstructType takes integer abilId returns integer
        return thistype.constructType.integer[abilId]
    endmethod
  
    static method increment takes player p, integer unitId returns nothing
        local integer pIndex = GetPlayerId(p) + 1
      
        set thistype.unitTypeLimit[pIndex].integer[unitId] = thistype.unitTypeLimit[pIndex].integer[unitId] + 1
        call SetPlayerTechMaxAllowed(p, unitId, thistype.unitTypeLimit[pIndex].integer[unitId])
    endmethod

    static method decrement takes player p, integer unitId returns nothing
        local integer pIndex = GetPlayerId(p) + 1
      
        set thistype.unitTypeLimit[pIndex].integer[unitId] = thistype.unitTypeLimit[pIndex].integer[unitId] - 1
        call SetPlayerTechMaxAllowed(p, unitId, thistype.unitTypeLimit[pIndex].integer[unitId])
    endmethod
  
    private static method init takes nothing returns nothing
        set thistype.stationHolder  = Table.create()
        set thistype.constructType  = Table.create()

        set thistype.dataBase       = TableArray[12]
        set thistype.unitTypeLimit  = TableArray[bj_MAX_PLAYER_SLOTS + 1]
    endmethod
  
    implement Initializer
endstruct

private struct SubBuildList extends array
    implement Alloc
  
    //  Ability is unique, so...
    private static Table abilityTable = 0
  
    readonly integer unitId
    readonly integer abilId
    readonly integer count
  
    readonly boolean consumesWorker
  
    static method getInstance takes integer abilId returns thistype
        return thistype.abilityTable.integer[abilId]
    endmethod
  
    static method create takes integer unitId, integer abilId, boolean consumesWorker, integer count returns thistype
        local thistype result = thistype.abilityTable.integer[abilId]
      
        if result == 0 then
            set result        = thistype.allocate()
          
            set result.unitId           = unitId
            set result.abilId           = abilId
            set result.count            = count
            set result.consumesWorker   =   consumesWorker
          
            set thistype.abilityTable.integer[abilId] = result
        endif
        return result
    endmethod
  
    private static method initVar takes nothing returns nothing
        set thistype.abilityTable = Table.create()
    endmethod
  
    private static method init takes nothing returns nothing
        call thistype.initVar()
    endmethod
  
    implement Initializer
endstruct

private struct BuildList extends array
    implement Alloc
  
    private static constant player NEUTRAL = Player(PLAYER_NEUTRAL_PASSIVE)
  
    private static Table holderTable            = 0
    private static Table subRefTable            = 0
  
    private static SubBuildList currentTemp     = 0
  
    private integer abilHolder
    private IntegerList subList
  
    static method create takes integer unitId, integer abilId, integer abilHolder, boolean consumesWorker, integer count returns thistype
        local thistype      result  = thistype.holderTable.integer[abilHolder]
        local SubBuildList  temp    = 0
      
        if result == 0 then
            set result              = thistype.allocate()
            set result.subList      = IntegerList.create()
            set result.abilHolder   = abilHolder
          
            set thistype.holderTable.integer[abilHolder] = result
        endif
      
        set temp = SubBuildList.getInstance(abilId)
        if temp == 0 then
            set temp = SubBuildList.create(unitId, abilId, consumesWorker, count)

            call result.subList.push(temp)
          
            set thistype.subRefTable.integer[temp] = result
        endif
      
        return result
    endmethod
  
    private static method onSpellCastHide takes nothing returns nothing
        local unit enum = GetEnumUnit()
      
        call SetUnitInvulnerable(enum, true)
      
        if thistype.currentTemp.consumesWorker then
            call SetUnitUseFood(enum, false)
        endif
      
        call PauseUnit(enum, true)
        call ShowUnit(enum, false)
      
        set enum = null
    endmethod
  
    private static method onSpellCastUndock takes nothing returns nothing
        call DockingSystem.undock(GetEnumUnit())
        call thistype.onSpellCastHide()
    endmethod
  
    private static method onSpellCast takes nothing returns nothing
        local unit station              = GetTriggerUnit()
        local unit plug
      
        local BuildSystem whichBuild  
      
        local SubBuildList temp         = SubBuildList.getInstance(GetSpellAbilityId())
        local thistype  handler         = thistype.subRefTable.integer[temp]
      
        local integer constructType     = BuildSystemEx.getConstructType(handler.abilHolder)
      
        local integer stationHandle     = GetHandleId(station)
        local integer stationId         = GetUnitTypeId(station)
      
        local integer plugHandle      
        local integer plugCount         = 0
      
        local IntegerListItem iter    
      
        local player stationOwner       = GetOwningPlayer(station)
      
        local trigger trig
        local trigger detector
      
        set whichBuild = BuildSystem(BuildSystemEx.stationHolder.integer[stationId])
      
        if constructType == BuildSystemEx.construct then
            set BuildSystemEx.dataBase[1].player[stationHandle] = stationOwner
            set BuildSystemEx.dataBase[2].real[stationHandle]   = GetUnitX(station)
            set BuildSystemEx.dataBase[3].real[stationHandle]   = GetUnitY(station)
            set BuildSystemEx.dataBase[4].group[stationHandle]  = CreateGroup()
          
            //  Get the number of available plugs...
            loop
                exitwhen GetNumberOfOccupiedSockets(station) == 0 or (plugCount >= temp.count)
              
                set plug = GetSocketPlug(GetRandomSocketWithState(station, true))
                call GroupAddUnit(BuildSystemEx.dataBase[4].group[stationHandle], plug)
              
                set plugCount = plugCount + 1
            endloop
          
            if plugCount < temp.count then
                call SimError(stationOwner, "Construction of " + GetObjectName(temp.unitId) + " requires "/*
                                       */ + "at least " + I2S(temp.count) + " " + GetObjectName(whichBuild.workerId))
              
                call DestroyGroup(BuildSystemEx.dataBase[4].group[stationHandle])
              
                call BuildSystemEx.dataBase[1].player.remove(stationHandle)
                call BuildSystemEx.dataBase[2].real.remove(stationHandle)
                call BuildSystemEx.dataBase[3].real.remove(stationHandle)
                call BuildSystemEx.dataBase[4].group.remove(stationHandle)
              
                set station = null
                set plug = null
              
                return
            endif
          
            call ShowUnit(station, false)
            call SetUnitX(station, WorldBounds.minX)
            call SetUnitY(station, WorldBounds.minY)
            call SetUnitInvulnerable(station, true)
          
            /*
            set detector = CreateTrigger()
            call TriggerRegisterPlayerStateEvent(detector, stationOwner, PLAYER_STATE_RESOURCE_GOLD, NOT_EQUAL, GetPlayerState(stationOwner, PLAYER_STATE_RESOURCE_GOLD))
            call TriggerRegisterPlayerStateEvent(detector, stationOwner, PLAYER_STATE_RESOURCE_LUMBER, NOT_EQUAL, GetPlayerState(stationOwner, PLAYER_STATE_RESOURCE_LUMBER))
            */
          
            set plug = FirstOfGroup(BuildSystemEx.dataBase[4].group[stationHandle])
            call DockingSystem.undock(plug)
          
            call BuildSystemEx.increment(stationOwner, temp.unitId)
            if IssueBuildOrderById(plug, temp.unitId, BuildSystemEx.dataBase[2].real[stationHandle], BuildSystemEx.dataBase[3].real[stationHandle]) then              
                set thistype.currentTemp = temp
                call ForGroup(BuildSystemEx.dataBase[4].group[stationHandle], function thistype.onSpellCastUndock)
          
                call ShowUnit(plug, true)
                call PauseUnit(plug, false)
              
                //  Reissue the order...
                // call IssueBuildOrderById(plug, temp.unitId, BuildSystemEx.dataBase[2].real[stationHandle], BuildSystemEx.dataBase[3].real[stationHandle])
              
                set plugHandle = GetHandleId(plug)
              
                //  Let the builder point at the station ...
                set BuildSystemEx.dataBase[5].unit[plugHandle]      = station
                set BuildSystemEx.dataBase[6].integer[plugHandle]   = temp
              
                call SetUnitX(plug, BuildSystemEx.dataBase[2].real[stationHandle])
                call SetUnitY(plug, BuildSystemEx.dataBase[3].real[stationHandle])
            else
                call BuildSystemEx.decrement(stationOwner, temp.unitId)
                call DockingSystem.dock(plug, GetClosestSocketWithState(station, plug, false))
              
                loop
                    exitwhen plug == null
                  
                    call GroupRemoveUnit(BuildSystemEx.dataBase[4].group[stationHandle], plug)
                    set plug = FirstOfGroup(BuildSystemEx.dataBase[4].group[stationHandle])
                endloop
              
                call SetUnitInvulnerable(station, false)
                call SetUnitX(station, BuildSystemEx.dataBase[2].real[stationHandle])
                call SetUnitY(station, BuildSystemEx.dataBase[3].real[stationHandle])
                call ShowUnit(station, true)

                call DestroyGroup(BuildSystemEx.dataBase[4].group[stationHandle])
              
                call BuildSystemEx.dataBase[1].player.remove(stationHandle)
                call BuildSystemEx.dataBase[2].real.remove(stationHandle)
                call BuildSystemEx.dataBase[3].real.remove(stationHandle)
                call BuildSystemEx.dataBase[4].group.remove(stationHandle)
              
                set station = null
                set plug = null
              
                return
            endif
          
        elseif constructType == BuildSystemEx.upgrade then
            set BuildSystemEx.dataBase[7].player[stationHandle] = stationOwner
            set BuildSystemEx.dataBase[8].group[stationHandle]  = CreateGroup()
          
            //  Get the number of available plugs...
            loop
                exitwhen GetNumberOfOccupiedSockets(station) == 0 or (plugCount >= temp.count)
              
                set plug = GetSocketPlug(GetRandomSocketWithState(station, true))
                call GroupAddUnit(BuildSystemEx.dataBase[8].group[stationHandle], plug)
              
                set plugCount = plugCount + 1
            endloop
          
            if plugCount < temp.count then
                call SimError(stationOwner, "Upgrading to " + GetObjectName(temp.unitId) + " requires "/*
                                       */ + "at least " + I2S(temp.count) + " " + GetObjectName(whichBuild.workerId))
              
                call DestroyGroup(BuildSystemEx.dataBase[2].group[stationHandle])
              
                call BuildSystemEx.dataBase[7].player.remove(stationHandle)
                call BuildSystemEx.dataBase[8].group.remove(stationHandle)
              
                set station = null
                set plug = null
              
                return
            endif
          
            call UnitRemoveAbility(station, handler.abilHolder)
            call UnitAddAbility(station, handler.abilHolder)
          
            call PauseUnit(station, true)
            call IssueImmediateOrderById(station, 851972)
            call PauseUnit(station, false)

            call BuildSystemEx.increment(stationOwner, temp.unitId)

            if IssueImmediateOrderById(station, temp.unitId) then
                set thistype.currentTemp = temp
                call ForGroup(BuildSystemEx.dataBase[8].group[stationHandle], function thistype.onSpellCastUndock)
              
                set BuildSystemEx.dataBase[9].integer[stationHandle] = temp
                set BuildSystemEx.dataBase[10].integer[stationHandle] = stationId
            else
                call BuildSystemEx.decrement(stationOwner, temp.unitId)
              
                loop
                    set plug = FirstOfGroup(BuildSystemEx.dataBase[8].group[stationHandle])
                    exitwhen plug == null
                                      
                    call GroupRemoveUnit(BuildSystemEx.dataBase[8].group[stationHandle], plug)
                endloop
              
                call DestroyGroup(BuildSystemEx.dataBase[8].group[stationHandle])
              
                call BuildSystemEx.dataBase[7].player.remove(stationHandle)
                call BuildSystemEx.dataBase[8].group.remove(stationHandle)
              
                set station = null
                set plug = null
              
                return
            endif
          
        elseif constructType == BuildSystemEx.train then
          
            /*
            set BuildSystemEx.dataBase[1].player[stationHandle] = stationOwner
            set BuildSystemEx.dataBase[2].group[stationHandle]  = CreateGroup()
          
            //  Get the number of available plugs...
            loop
                exitwhen GetNumberOfOccupiedSockets(station) == 0 or (plugCount >= temp.count)
              
                set plug = GetSocketPlug(GetRandomSocketWithState(station, true))
                call GroupAddUnit(BuildSystemEx.dataBase[2].group[stationHandle], plug)
              
                set plugCount = plugCount + 1
            endloop
          
            if plugCount < temp.count then
                call SimError(stationOwner, "Upgrading to " + GetObjectName(temp.unitId) + " requires "/*
                                       */ + "at least " + I2S(temp.count) + " " + GetObjectName(whichBuild.workerId))
              
                call DestroyGroup(BuildSystemEx.dataBase[2].group[stationHandle])
              
                call BuildSystemEx.dataBase[1].player.remove(stationHandle)
                call BuildSystemEx.dataBase[2].group.remove(stationHandle)
              
                set station = null
                set plug = null
              
                return
            endif
          
            call UnitRemoveAbility(station, handler.abilHolder)
            call UnitAddAbility(station, handler.abilHolder)
          
            call PauseUnit(station, true)
            call IssueImmediateOrderById(station, 851972)
            call PauseUnit(station, false)

            call BuildSystemEx.increment(stationOwner, temp.unitId)

            if IssueImmediateOrderById(station, temp.unitId) then
                set thistype.currentTemp = temp
                call ForGroup(BuildSystemEx.dataBase[2].group[stationHandle], function thistype.onSpellCastUndock)
              
                set BuildSystemEx.dataBase[3].integer[stationHandle] = temp
                set BuildSystemEx.dataBase[4].integer[stationHandle] = stationId
            else
                call BuildSystemEx.decrement(stationOwner, temp.unitId)
              
                loop
                    set plug = FirstOfGroup(BuildSystemEx.dataBase[2].group[stationHandle])
                    exitwhen plug == null
                                      
                    call GroupRemoveUnit(BuildSystemEx.dataBase[2].group[stationHandle], plug)
                endloop
              
                call DestroyGroup(BuildSystemEx.dataBase[2].group[stationHandle])
              
                call BuildSystemEx.dataBase[1].player.remove(stationHandle)
                call BuildSystemEx.dataBase[2].group.remove(stationHandle)
              
                set station = null
                set plug = null
              
                return
            endif
            */
          
        endif
      
        set station = null
    endmethod
  
    private static method initVar takes nothing returns nothing
        set thistype.holderTable = Table.create()
        set thistype.subRefTable = Table.create()
    endmethod
  
    private static method initListener takes nothing returns nothing
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onSpellCast)
    endmethod
  
    private static method init takes nothing returns nothing
        call thistype.initVar()
        call thistype.initListener()
    endmethod
  
    implement Initializer
endstruct

struct BuildSystem extends array
    implement Alloc
  
    private static constant integer DEFAULT_WORKER_COUNT    = 4
  
    private static constant real    DEFAULT_WORKER_DIST     = 45.
    private static constant real    DEFAULT_WORKER_HEIGHT   = 30.
    private static constant real    DEFAULT_WORKER_RAD      = 0.
    private static constant real    DEFAULT_DETECT_DIST     = 250.
  
    private static constant boolean DEFAULT_CONSUMES_WORKER = true
  
    //  These store the unit types which have been passed into the BuildSystem
    private static Table unitTypeList       = 0
    //  This stores the owner of the unit whose unit type has been registered
    private static Table unitOwner          = 0
    //  This stores the flag of the unit whether it triggered a unit death
    //  event or not
    private static Table unitDeathFlag      = 0
    //  This stores the flag for all stations...
    private static Table stationFlag        = 0
  
    //  Main members. Can be read, but not written.
    readonly integer workerId
    readonly integer buildingId
  
    private trigger onPredock
    private trigger onDock
    private trigger onUndock
  
    private IntegerList constructList
    private IntegerList upgradeList
    private IntegerList trainList
  
    integer workerCount
  
    real    workerHeight
    real    workerDist
    real    workerStartRad
    real    workerDetectDist
  
    boolean workerConsumed
  
    static method create takes integer workerId, integer buildingId returns thistype
        local thistype result = BuildSystemEx.stationHolder.integer[buildingId]
      
        if result == 0 then
            set result                  = thistype.allocate()
          
            set result.workerId         = workerId
            set result.buildingId       = buildingId
          
            set result.constructList    = IntegerList.create()
            set result.upgradeList      = IntegerList.create()
            set result.trainList        = IntegerList.create()
          
            set result.workerCount      = DEFAULT_WORKER_COUNT
            set result.workerHeight     = DEFAULT_WORKER_HEIGHT
            set result.workerDist       = DEFAULT_WORKER_DIST
            set result.workerStartRad   = DEFAULT_WORKER_RAD
            set result.workerDetectDist = DEFAULT_DETECT_DIST
            set result.workerConsumed   = DEFAULT_CONSUMES_WORKER
          
            set result.onPredock        = CreateTrigger()
            set result.onDock           = CreateTrigger()
            set result.onUndock         = CreateTrigger()
          
            set BuildSystemEx.stationHolder.integer[buildingId] = result
        endif
      
        return result
    endmethod
  
    private static method precheckAbil takes integer abilHolder returns boolean
        return BuildSystemEx.constructType.integer.has(abilHolder)
    endmethod
  
    private static method addUnitType takes integer unitId returns nothing
        if unitId == 0 then
            debug call printError("thistype.addUnitType: Invalid unit type!")
            return
        endif
      
        if not thistype.unitTypeList.integer.has(unitId) then
            set thistype.unitTypeList.integer[unitId] = IntegerList(thistype.unitTypeList.integer[0]).push(unitId).last
        endif
    endmethod
  
    private static method isUnitPhysical takes unit u returns boolean
        return (not IsUnitType(u, UNIT_TYPE_SUMMONED)) and (not IsUnitIllusion(u))
    endmethod
  
    method addConstructAbil takes integer abilHolder returns nothing
        if thistype.precheckAbil(abilHolder) then
            debug call printError("thistype.addConstructAbil: Ability type was already defined.")
            return
        endif
      
        call this.constructList.push(abilHolder)
        set BuildSystemEx.constructType.integer[abilHolder] = BuildSystemEx.construct
    endmethod

    method addUpgradeAbil takes integer abilHolder returns nothing
        if thistype.precheckAbil(abilHolder) then
            debug call printError("thistype.addUpgradeAbil: Ability type was already defined.")
            return
        endif
      
        call this.upgradeList.push(abilHolder)
        set BuildSystemEx.constructType.integer[abilHolder] = BuildSystemEx.upgrade
    endmethod
  
    method addTrainAbil takes integer abilHolder returns nothing
        if thistype.precheckAbil(abilHolder) then
            debug call printError("thistype.addTrainAbil: Ability type was already defined.")
            return
        endif
      
        call this.trainList.push(abilHolder)
        set BuildSystemEx.constructType.integer[abilHolder] = BuildSystemEx.train
    endmethod
  
    method addConstructUnit takes integer unitId, integer abilId, integer abilHolder, integer count returns nothing
        if BuildSystemEx.constructType.integer[abilHolder] != BuildSystemEx.construct then
            debug call printError("thistype.addConstructUnit: Ability id is not of type construct.")
            return
        endif
        call BuildList.create(unitId, abilId, abilHolder, this.workerConsumed, count)
        call thistype.addUnitType(unitId)
    endmethod

    method addUpgradeUnit takes integer unitId, integer abilId, integer abilHolder, integer count returns nothing
        if BuildSystemEx.constructType.integer[abilHolder] != BuildSystemEx.upgrade then
            debug call printError("thistype.addUpgradeUnit: Ability id is not of type upgrade.")
            return
        endif
        call BuildList.create(unitId, abilId, abilHolder, this.workerConsumed, count)
        call thistype.addUnitType(unitId)
    endmethod
  
    method addTrainUnit takes integer unitId, integer abilId, integer abilHolder, integer count returns nothing
        if BuildSystemEx.constructType.integer[abilHolder] != BuildSystemEx.train then
            debug call printError("thistype.addTrainUnit: Ability id is not of type train.")
            return
        endif
        call BuildList.create(unitId, abilId, abilHolder, this.workerConsumed, count)
        call thistype.addUnitType(unitId)
    endmethod
  
    method setOnDockEvent takes integer dockEvent, code whichHandler returns nothing
        if dockEvent == EVENT_ON_PRE_DOCK then
            call DestroyTrigger(this.onPredock)
          
            set this.onPredock = CreateTrigger()
            call TriggerAddCondition(this.onPredock, Condition(whichHandler))
          
        elseif dockEvent == EVENT_ON_DOCK then
            call DestroyTrigger(this.onDock)
          
            set this.onDock = CreateTrigger()
            call TriggerAddCondition(this.onDock, Condition(whichHandler))
          
        elseif dockEvent == EVENT_ON_UNDOCK then
            call DestroyTrigger(this.onUndock)
          
            set this.onUndock = CreateTrigger()
            call TriggerAddCondition(this.onUndock, Condition(whichHandler))          
        debug else
            debug call printError("thistype.setOnDockEvent: Unrecognized dock event!")
        endif
    endmethod
  
    private method initStation takes unit station returns nothing
        local integer i             = 0
        local integer stationHandle = GetHandleId(station)
      
        local real pSlice           = 2*bj_PI/(this.workerCount)
        local real stationX         = GetUnitX(station)
        local real stationY         = GetUnitY(station)
      
        if not thistype.stationFlag.boolean.has(stationHandle) then
            set thistype.stationFlag.boolean[stationHandle] = false
          
            call DockingSystem.createStation(station, this.workerDetectDist)
            loop
                exitwhen i >= this.workerCount
              
                call DockingSystem.addSocket(station, /*
                                            */stationX + this.workerDist*Cos(I2R(i)*pSlice + this.workerStartRad),/*
                                            */stationY + this.workerDist*Sin(I2R(i)*pSlice + this.workerStartRad),/*
                                            */this.workerHeight, (I2R(i)*pSlice + this.workerStartRad + (bj_PI/2))/*
                                            */ * bj_RADTODEG)
                set i = i + 1
            endloop
        endif
    endmethod
  
    private method removeStation takes unit station returns nothing
        local integer stationHandle = GetHandleId(station)
      
        if thistype.stationFlag.boolean.has(stationHandle) then
            call thistype.stationFlag.boolean.remove(stationHandle)
          
            call DockingSystem.terminateStation(station)
        endif
    endmethod
  
    /*
         -----------------------------------
        |   Handler methods                 |
         -----------------------------------
    */
  
    private static method onConstructStart takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit builder              = GetBuildingUnit()
        local unit station
      
        local player buildingOwner      = GetOwningPlayer(building)
      
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
        local integer builderHandle     = GetHandleId(builder)
      
        local SubBuildList  buildList   = BuildSystemEx.dataBase[6].integer[buildingId]
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
      
        //  If building is a station...
        if temp != 0 then
            call temp.removeStation(building)
        endif
      
        if BuildSystemEx.dataBase[5].unit.has(builderHandle) then
            //  Move data reference to the building currently being built...
            set station = BuildSystemEx.dataBase[5].unit[builderHandle]
            set BuildSystemEx.dataBase[5].unit[buildingHandle]      = station
            set BuildSystemEx.dataBase[6].integer[buildingHandle]   = BuildSystemEx.dataBase[6].integer[builderHandle]
          
            call BuildSystemEx.dataBase[5].unit.remove(builderHandle)
            call BuildSystemEx.dataBase[6].integer.remove(builderHandle)
          
            //  Correct the incremental value...
            call BuildSystemEx.decrement(buildingOwner, buildingId)
          
            if buildList.consumesWorker then
                call UnitAddAbility(builder, 'Aloc')
            endif
        endif
      
        set builder  = null
        set building = null
    endmethod
  
    private static method onConstructCancel takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit builder              = GetBuildingUnit()
        local unit station
        local unit plug
      
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
      
        local integer builderHandle     = GetHandleId(builder)
      
        local integer stationHandle
      
        local real    stationX
        local real    stationY
      
        local player  stationOwner
        local group   stationGrp
      
        local trigger trig
      
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
        local SubBuildList  buildList   = 0
      
        //  Triggered by removal
        if GetTriggerEventId() == null then
            set building = GetIndexedUnit()
            set builder  = GetBuilder(building)
          
            set buildingId      = GetUnitTypeId(building)
            set buildingHandle  = GetHandleId(building)
          
            set builderHandle   = GetHandleId(builder)
        endif
      
        if BuildSystemEx.dataBase[5].unit.has(buildingHandle) then
            set station         = BuildSystemEx.dataBase[5].unit[buildingHandle]
          
            set stationHandle   = GetHandleId(station)
          
            set stationOwner    = BuildSystemEx.dataBase[1].player[stationHandle]
            set stationX        = BuildSystemEx.dataBase[2].real[stationHandle]
            set stationY        = BuildSystemEx.dataBase[3].real[stationHandle]
            set stationGrp      = BuildSystemEx.dataBase[4].group[stationHandle]
          
            set buildList       = BuildSystemEx.dataBase[6].integer[buildingHandle]
          
            call BuildSystemEx.dataBase[5].unit.remove(buildingHandle)
            call BuildSystemEx.dataBase[6].integer.remove(buildingHandle)
          
            if buildList.consumesWorker then
                call UnitRemoveAbility(builder, 'Aloc')
            endif
          
            call SetUnitInvulnerable(station, false)
            call SetUnitX(station, stationX)
            call SetUnitY(station, stationY)
            call ShowUnit(station, true)
          
            set plug = FirstOfGroup(stationGrp)
            loop
                exitwhen plug == null
              
                call ShowUnit(plug, true)
                call SetUnitInvulnerable(plug, false)
                call SetUnitPosition(plug, stationX, stationY)
                call PauseUnit(plug, false)
              
                if GetLocalPlayer() == stationOwner then
                    call SelectUnit(plug, true)
                endif
              
                if buildList.consumesWorker then
                    call SetUnitUseFood(plug, true)
                endif
              
                call GroupRemoveUnit(stationGrp, plug)
                set plug = FirstOfGroup(stationGrp)
            endloop
          
            call BuildSystemEx.dataBase[1].player.remove(stationHandle)
            call BuildSystemEx.dataBase[2].real.remove(stationHandle)
            call BuildSystemEx.dataBase[3].real.remove(stationHandle)
            call BuildSystemEx.dataBase[4].group.remove(stationHandle)
          
            call DestroyGroup(stationGrp)
          
            set station    = null
            set stationGrp = null
        endif
      
        set builder  = null
        set building = null
    endmethod
  
    private static method onConstructFinish takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit builder              = GetBuildingUnit()
        local unit station              = null
        local unit plug
      
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
      
        local integer builderHandle     = GetHandleId(builder)
      
        local integer stationHandle
      
        local real    stationX
        local real    stationY
      
        local player  stationOwner
        local group   stationTempGrp    = null
        local group   stationGrp
              
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
      
        local SubBuildList buildList    = 0
      
        if BuildSystemEx.dataBase[5].unit.has(buildingHandle) then
            set station         = BuildSystemEx.dataBase[5].unit[buildingHandle]
          
            set stationHandle   = GetHandleId(station)
          
            set stationOwner    = BuildSystemEx.dataBase[1].player[stationHandle]
            set stationX        = BuildSystemEx.dataBase[2].real[stationHandle]
            set stationY        = BuildSystemEx.dataBase[3].real[stationHandle]
            set stationGrp      = BuildSystemEx.dataBase[4].group[stationHandle]
          
            set buildList       = BuildSystemEx.dataBase[6].integer[stationHandle]
          
            set stationTempGrp  = CreateGroup()
            call CopyGroup(stationTempGrp, stationGrp)
        endif
      
        call thistype.onConstructCancel()
      
        if station != null then
            set plug = FirstOfGroup(stationTempGrp)
            loop
                exitwhen plug == null
              
                call GroupRemoveUnit(stationTempGrp, plug)
              
                if buildList.consumesWorker then
                    call RemoveUnit(plug)
                endif
              
                set plug = FirstOfGroup(stationTempGrp)
            endloop
          
            call RemoveUnit(station)
        endif
      
        if stationTempGrp != null then
            call DestroyGroup(stationTempGrp)
        endif
      
        if temp != 0 then
            call temp.initStation(building)
        endif
      
        set stationTempGrp  = null
        set station         = null
        set stationGrp      = null
      
        set builder         = null
        set building        = null
    endmethod
  
    /*
          ------------------
         |                  |
         |  Upgrades        |
         |                  |
          ------------------
    */
    private static method onUpgradeStart takes nothing returns nothing
        local unit building             = GetTriggerUnit()
      
        local player buildingOwner      = GetOwningPlayer(building)
      
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
      
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
          
        if temp != 0 then
            call DockingSystem.undockAll(building)
        endif
      
        if BuildSystemEx.dataBase[9].integer.has(buildingHandle) then
            //  Raise a flag telling the system that the unit is upgrading.
            set BuildSystemEx.dataBase[11].boolean[buildingHandle] = true
        endif
      
        set building = null
    endmethod
  
    private static method onUpgradeCancel takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit plug
      
        local real buildingX            = GetUnitX(building)
        local real buildingY            = GetUnitY(building)
      
        local group plugGroup
      
        local player buildingOwner      = GetOwningPlayer(building)
      
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
        local integer upgradeId        
      
        local SubBuildList buildList  
      
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
      
        if BuildSystemEx.dataBase[9].integer.has(buildingHandle) then
            set plugGroup   = BuildSystemEx.dataBase[8].group[buildingHandle]
            set buildList   = SubBuildList(BuildSystemEx.dataBase[9].integer[buildingHandle])
            set upgradeId   = buildList.unitId
          
            call BuildSystemEx.decrement(buildingOwner, upgradeId)
          
            //  Show the units...
            set plug = FirstOfGroup(plugGroup)
            loop
                exitwhen plug == null
              
                call ShowUnit(plug, true)
                call SetUnitInvulnerable(plug, false)
                call SetUnitPosition(plug, buildingX, buildingY)
                call PauseUnit(plug, false)
              
                if buildList.consumesWorker then
                    call SetUnitUseFood(plug, true)
                endif
              
                call GroupRemoveUnit(plugGroup, plug)
                set plug = FirstOfGroup(plugGroup)
            endloop
          
            call DestroyGroup(plugGroup)
          
            set plugGroup = null
          
            call BuildSystemEx.dataBase[7].player.remove(buildingHandle)
            call BuildSystemEx.dataBase[8].group.remove(buildingHandle)
            call BuildSystemEx.dataBase[9].integer.remove(buildingHandle)
            call BuildSystemEx.dataBase[10].integer.remove(buildingHandle)
            call BuildSystemEx.dataBase[11].boolean.remove(buildingHandle)
        endif
      
        set building = null
    endmethod
  
    private static method onUpgradeFinish takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit plug
      
        local real buildingX            = GetUnitX(building)
        local real buildingY            = GetUnitY(building)
      
        local group plugGroup
        local group plugGroupCopy       = null
      
        local player buildingOwner      = GetOwningPlayer(building)
      
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
        local integer upgradeId        
        local integer lastBuildingId  
      
        local SubBuildList buildList  
      
        local thistype temp            
      
        if BuildSystemEx.dataBase[3].integer.has(buildingHandle) then
            set plugGroup       = BuildSystemEx.dataBase[2].group[buildingHandle]
            set buildList       = SubBuildList(BuildSystemEx.dataBase[3].integer[buildingHandle])
            set upgradeId       = buildList.unitId
            set lastBuildingId  = BuildSystemEx.dataBase[4].integer[buildingHandle]
          
            set temp = BuildSystemEx.stationHolder.integer[lastBuildingId]
          
            set plugGroupCopy   = CreateGroup()
            call CopyGroup(plugGroupCopy, plugGroup)
          
            if temp != 0 then
                call temp.removeStation(building)
              
                //  Outsource it to transformation.
                /*
                if thistype.unitTypeList.has(lastBuildingId) then
                    call BuildSystemEx.decrement(buildingOwner, lastBuildingId)
                endif
                */
            endif
          
            //  Counter the effects of decrement when calling onUpgradeCancel...
            call BuildSystemEx.increment(buildingOwner, buildingId)
        endif
      
        call thistype.onUpgradeCancel()
      
        set plug = FirstOfGroup(plugGroupCopy)
        loop
            exitwhen plug == null
          
            call GroupRemoveUnit(plugGroupCopy, plug)
          
            if buildList.consumesWorker then
                call RemoveUnit(plug)
            endif
          
            set plug = FirstOfGroup(plugGroupCopy)
        endloop
      
        if plugGroupCopy != null then
            call DestroyGroup(plugGroupCopy)
        endif
      
        set plugGroupCopy = null
        set building = null
    endmethod
  
    private static method onUnitEnter takes nothing returns nothing
        local unit u             = GetIndexedUnit()
        local integer unitId     = GetUnitTypeId(u)
        local integer uHandle    = GetHandleId(u)
        local player uOwner      = GetOwningPlayer(u)
      
        local thistype inst      = BuildSystemEx.stationHolder.integer[unitId]
      
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
      
        //  Unit is a station.
        if inst != 0 then
            call inst.initStation(u)
        endif
      
        if thistype.unitTypeList.integer.has(unitId) then
            call BuildSystemEx.increment(uOwner, unitId)
              
            set thistype.unitOwner.player[uHandle]      = uOwner
            set thistype.unitDeathFlag.boolean[uHandle] = false
        endif
      
        set u = null
    endmethod
  
    private static method onUnitExit takes nothing returns nothing
        local unit u            = GetIndexedUnit()
        local integer unitId    = GetUnitTypeId(u)
        local integer uHandle   = GetHandleId(u)
        local player uOwner     = GetOwningPlayer(u)
      
        local thistype inst     = BuildSystemEx.stationHolder.integer[unitId]
      
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
      
        if inst != 0 then
            call inst.removeStation(u)
        endif

        //  Unit must have undergone construction when it was removed...
        if BuildSystemEx.dataBase[5].unit.has(uHandle) then
            debug call BJDebugMsg("Unit was undergoing construction!")
            call thistype.onConstructCancel()
        endif
      
        if thistype.unitTypeList.integer.has(unitId) then
            if not thistype.unitDeathFlag.boolean[uHandle] then
                call BuildSystemEx.decrement(uOwner, unitId)
            endif
          
            call thistype.unitOwner.player.remove(uHandle)
            call thistype.unitDeathFlag.boolean.remove(uHandle)
        endif
      
        set u = null
    endmethod
  
    private static method onUnitDeath takes nothing returns nothing
        local unit u            = GetTriggerUnit()
        local integer unitId    = GetUnitTypeId(u)
        local integer uHandle   = GetHandleId(u)
        local player uOwner     = GetOwningPlayer(u)
            
        local thistype inst      = BuildSystemEx.stationHolder.integer[unitId]

        if GetTriggerEventId() == null then
            //  Called on reincarnation
            set u = GetEventUnit()
        endif
      
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
      
        if inst != 0 then
            if GetNumberOfOccupiedSockets(u) != 0 then
                call DockingSystem.undockAll(u)
            endif
        endif
      
        //  Unit must have undergone construction when it was destroyed...
        if BuildSystemEx.dataBase[5].unit.has(uHandle) then
            call thistype.onConstructCancel()
        endif
      
        if thistype.unitTypeList.integer.has(unitId) then
            if not thistype.unitDeathFlag.boolean[uHandle] then
                call BuildSystemEx.decrement(uOwner, unitId)
              
                set thistype.unitDeathFlag.boolean[uHandle] = true              
            endif          
        endif
      
        set u = null
    endmethod
  
    private static method onUnitChangeOwner takes nothing returns nothing
        local unit u            = GetTriggerUnit()
        local integer unitId    = GetUnitTypeId(u)
        local integer uHandle   = GetHandleId(u)
        local player uOwner     = GetOwningPlayer(u)
      
        local thistype inst     = BuildSystemEx.stationHolder.integer[unitId]
      
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
      
        if inst != 0 then
            call DockingSystem.undockAll(u)
        endif
      
        if thistype.unitTypeList.integer.has(unitId) then
            call BuildSystemEx.decrement(thistype.unitOwner.player[uHandle], unitId)
              
            set thistype.unitOwner.player[uHandle] = uOwner
          
            call BuildSystemEx.increment(thistype.unitOwner.player[uHandle], unitId)
        endif
      
        set u = null
    endmethod
  
    private static method onUnitRevive takes nothing returns nothing
        local unit u            = GetEventUnit()
        local integer unitId    = GetUnitTypeId(u)
        local integer uHandle   = GetHandleId(u)
        local player uOwner     = GetOwningPlayer(u)
      
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
      
        if thistype.unitTypeList.integer.has(unitId) then
            if thistype.unitDeathFlag.boolean[uHandle] then
                call BuildSystemEx.increment(uOwner, unitId)
              
                set thistype.unitDeathFlag.boolean[uHandle] = false
            endif
        endif
      
        set u = null
    endmethod
  
    private static method onUnitTransform takes nothing returns nothing
        local unit u                = GetEventUnit()
      
        local integer prevUnitId    = GetEventTransformType()
        local integer unitId        = GetUnitTypeId(u)
        local integer uHandle       = GetHandleId(u)
        local player uOwner         = GetOwningPlayer(u)
      
        local thistype inst         = BuildSystemEx.stationHolder.integer[prevUnitId]
      
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
      
        if inst != 0 then
            call inst.removeStation(u)
        endif
      
        if thistype.unitTypeList.integer.has(GetEventTransformType()) then
            call BuildSystemEx.decrement(uOwner, GetEventTransformType())
        endif
          
        if thistype.unitTypeList.integer.has(unitId) then
            call BuildSystemEx.increment(uOwner, unitId)
        endif
      
        set u = null
    endmethod
  
    private static method onPreDockHandler takes nothing returns nothing
        local unit station          = GetStation()
        local unit plug             = GetPlug()
      
        local integer stationId     = GetUnitTypeId(station)
        local integer stationHandle = GetHandleId(station)
        local integer plugId        = GetUnitTypeId(plug)
        local integer plugHandle    = GetHandleId(plug)
      
        local player plugOwner      = GetOwningPlayer(plug)
      
        local thistype temp         = BuildSystemEx.stationHolder.integer[stationId]
      
        //  Nothing to do here...
        if (temp == 0) then
            // Don't interfere with other stations...
            set plug    = null
            set station = null
          
            return
        endif
      
        if (temp.workerId != plugId) then
            call InterruptDocking()
          
            set plug    = null
            set station = null
          
            return
        endif
              
        if (GetOwningPlayer(plug) != GetOwningPlayer(station)) then
            call InterruptDocking()
            call SimError(plugOwner, "Cannot dock into a station owned by another player!")
          
            set plug    = null
            set station = null
        endif
      
        //  Unit is being upgraded. Don't allow docking!
        if BuildSystemEx.dataBase[7].boolean.has(stationHandle) then
            call InterruptDocking()
            call SimError(plugOwner, "Upgrade is in progress... you cannot dock at this moment.")
          
            set plug    = null
            set station = null
          
            return
        endif

        call ConditionalTriggerExecute(temp.onPredock)
      
        set plug    = null
        set station = null
    endmethod
  
    private static method onDockHandler takes nothing returns nothing
        local thistype temp = BuildSystemEx.stationHolder.integer[GetUnitTypeId(GetStation())]
      
        if temp != 0 then
            call ConditionalTriggerExecute(temp.onDock)
        endif
    endmethod
  
    private static method onUndockHandler takes nothing returns nothing
        local thistype temp = BuildSystemEx.stationHolder.integer[GetUnitTypeId(GetStation())]
      
        if temp != 0 then
            call ConditionalTriggerExecute(temp.onUndock)
        endif
    endmethod
  
    private static method initVar takes nothing returns nothing
        set thistype.unitTypeList   = Table.create()
        set thistype.unitOwner      = Table.create()
        set thistype.unitDeathFlag  = Table.create()
        set thistype.stationFlag    = Table.create()
      
        set thistype.unitTypeList.integer[0] = IntegerList.create()
    endmethod
  
    private static method initListener takes nothing returns nothing
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_START, function thistype.onConstructStart)
        //call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, function thistype.onConstructCancel)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function thistype.onConstructFinish)
      
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_UPGRADE_START, function thistype.onUpgradeStart)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_UPGRADE_CANCEL, function thistype.onUpgradeCancel)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_UPGRADE_FINISH, function thistype.onUpgradeFinish)
      
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onUnitDeath)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CHANGE_OWNER, function thistype.onUnitChangeOwner)
      
        call RegisterUnitIndexEvent(Condition(function thistype.onUnitEnter), EVENT_UNIT_INDEX)
        call RegisterUnitIndexEvent(Condition(function thistype.onUnitExit), EVENT_UNIT_DEINDEX)
      
        call RegisterNativeEvent(EVENT_ON_REINCARNATION_START, function thistype.onUnitDeath)
      
        call RegisterNativeEvent(EVENT_ON_RESURRECTION, function thistype.onUnitRevive)
        call RegisterNativeEvent(EVENT_ON_REINCARNATION_FINISH, function thistype.onUnitRevive)
        call RegisterNativeEvent(EVENT_ON_TRANSFORM, function thistype.onUnitTransform)
      
        call RegisterNativeEvent(EVENT_ON_PRE_DOCK, function thistype.onPreDockHandler)
        call RegisterNativeEvent(EVENT_ON_DOCK, function thistype.onDockHandler)
        call RegisterNativeEvent(EVENT_ON_UNDOCK, function thistype.onUndockHandler)      
    endmethod
  
    private static method init takes nothing returns nothing
        call thistype.initVar()
        call thistype.initListener()
    endmethod
  
    static method onGameStart takes nothing returns nothing
        local integer i = 1
        local player  p = Player(i - 1)
        local IntegerListItem firstIter = IntegerList(thistype.unitTypeList.integer[0]).first
        local IntegerListItem iter      = firstIter
      
        loop
            loop
                exitwhen iter == 0
              
                set BuildSystemEx.unitTypeLimit[i].integer[iter.data] = 0
                call SetPlayerTechMaxAllowed(p, iter.data, BuildSystemEx.unitTypeLimit[i].integer[iter.data])
              
                set iter = iter.next
            endloop
            set iter = firstIter
          
            exitwhen i >= bj_MAX_PLAYER_SLOTS
          
            set p = Player(i)
            set i = i + 1
        endloop
    endmethod
  
    implement Initializer
endstruct
  
//  This function is required. Don't edit this. (functionally private)
function InitTrig_CustomBuildSystem takes nothing returns nothing
    call ForForce(bj_FORCE_PLAYER[0], function BuildSystem.onGameStart)
endfunction

endlibrary


As it is right now, there are a lot of bugs to iron out. I still have to cover another case before going all out with imports and spells.
 
Last edited:
Oops, didn't realize I already had a second WIP.

Now, here are some more WIP's (of course, all models found therein are not set in stone)


View attachment 300638

Units before creation of another unit...

View attachment 300639

Units after creation of unit...


sprite2.png

2cp130.jpg



Anyway, I've discovered yet ANOTHER bug with my race. Had something to do with the old Seatapper's regeneration ability. With the ability removed and its triggers do not, the triggers does not have a valid condition, thus it runs on every spell cast that happens in the game, making mages have more regeneration than it should. I fixed it and uploaded the new map.
 
Quite the jolly banter, and I find the meme to be filled with the finest humors. :)

If I post any more WIPS, I will stamp them here:




CustomBuildSystem (Not much on how to use, so it's described here)
JASS:
library CustomBuildSystem /*

    */ requires /*
       
        Legend:
            #? -> optional resource
            #  -> required resource
           
        *||------------------------||*
        */  DockingSystem           /*
            #? SmartTrack
            #? Table
           
            -> Spellbound
           
            link: https://www.hiveworkshop.com/threads/dockingsystem-v1-05-2.299959/#resource-78986
           
        *||------------------------||*
       
        *||------------------------||*
        */  Table                   /*
            -> Bribe
           
            link: https://www.hiveworkshop.com/threads/snippet-new-table.188084/
        *||------------------------||*
       
        *||------------------------||*
        */  ListT                   /*
            # Table
            # Alloc
           
            -> Bannar
           
            link: https://www.hiveworkshop.com/threads/containers-list-t.249011/
        *||------------------------||*
   
        *||------------------------||*
        */  Alloc                   /*
            -> Sevion
           
            link: https://hiveworkshop.com/threads/snippet-alloc.192348/
        *||------------------------||*
       
        *||------------------------||*
        */  UnitDex                 /*
            -> TriggerHappy
           
            link: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
        *||------------------------||*
       
        *||------------------------||*
        */  RegisterPlayerUnitEvent /*
            # RegisterNativeEvent
           
            -> Bannar
           
            link: https://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
        *||------------------------||*
       
        *||------------------------||*
        */  UnitEventsEx            /*
            # UnitDex
            # RegisterPlayerUnitEvent
            #? ListT
            #? WorldBounds
           
            -> Spellbound
           
            link: https://www.hiveworkshop.com/threads/vjass-version-of-gui-unit-event.306289/#post-3270559
           
        *||------------------------||*
        */  SimError                /*
            -> Vexorian
           
            link: http://www.wc3c.net/showthread.php?t=101260
        *||------------------------||*
       
       
        *||------------------------||*
        */  BuilderTrace            /*
            # UnitDex
            # Table
            # OrderIndex
           
            -> MyPad
           
            link: Not yet defined...
    */
   
native GetUnitGoldCost takes integer unitid returns integer
native GetUnitWoodCost takes integer unitid returns integer

//! runtextmacro DEFINE_LIST("", "PlayerList", "player")
//! runtextmacro DEFINE_LIST("", "UnitGroupList", "group")

globals
    private group copyGroup_tempGroup = null
endglobals

private function OnCopyGroup takes nothing returns nothing
    call GroupAddUnit(copyGroup_tempGroup, GetEnumUnit())
endfunction

private function CopyGroup takes group whichGroup, group tempGroup returns nothing
    set copyGroup_tempGroup = whichGroup

    call GroupClear(whichGroup)
    call ForGroup(tempGroup, function OnCopyGroup)
endfunction

static if DEBUG_MODE then

private function printError takes string msg returns nothing
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "|cffff0000Error:|r |cffffcc00" + msg + "|r")
endfunction

endif

private module Initializer
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod   
endmodule

private struct BuildSystemEx extends array
    readonly static Table constructType                  = 0
    readonly static Table stationHolder                  = 0
    readonly static TableArray unitTypeLimit             = 0
   
    readonly static TableArray dataBase         = 0
   
    static method operator construct takes nothing returns integer
        return 1
    endmethod
   
    static method operator upgrade takes nothing returns integer
        return 2
    endmethod
   
    static method operator train takes nothing returns integer
        return 3
    endmethod
   
    static method getConstructType takes integer abilId returns integer
        return thistype.constructType.integer[abilId]
    endmethod
   
    static method increment takes player p, integer unitId returns nothing
        local integer pIndex = GetPlayerId(p) + 1
       
        set thistype.unitTypeLimit[pIndex].integer[unitId] = thistype.unitTypeLimit[pIndex].integer[unitId] + 1
        call SetPlayerTechMaxAllowed(p, unitId, thistype.unitTypeLimit[pIndex].integer[unitId])
    endmethod

    static method decrement takes player p, integer unitId returns nothing
        local integer pIndex = GetPlayerId(p) + 1
       
        set thistype.unitTypeLimit[pIndex].integer[unitId] = thistype.unitTypeLimit[pIndex].integer[unitId] - 1
        call SetPlayerTechMaxAllowed(p, unitId, thistype.unitTypeLimit[pIndex].integer[unitId])
    endmethod
   
    private static method init takes nothing returns nothing
        set thistype.stationHolder  = Table.create()
        set thistype.constructType  = Table.create()

        set thistype.dataBase       = TableArray[15]
        set thistype.unitTypeLimit  = TableArray[bj_MAX_PLAYER_SLOTS + 1]
    endmethod
   
    implement Initializer
endstruct

private struct SubBuildList extends array
    implement Alloc
   
    //  Ability is unique, so...
    private static Table abilityTable = 0
   
    readonly integer unitId
    readonly integer abilId
    readonly integer count
   
    readonly boolean consumesWorker
   
    static method getInstance takes integer abilId returns thistype
        return thistype.abilityTable.integer[abilId]
    endmethod
   
    static method create takes integer unitId, integer abilId, boolean consumesWorker, integer count returns thistype
        local thistype result = thistype.abilityTable.integer[abilId]
       
        if result == 0 then
            set result        = thistype.allocate()
           
            set result.unitId           = unitId
            set result.abilId           = abilId
            set result.count            = count
            set result.consumesWorker   =   consumesWorker
           
            set thistype.abilityTable.integer[abilId] = result
        endif
        return result
    endmethod
   
    private static method initVar takes nothing returns nothing
        set thistype.abilityTable = Table.create()
    endmethod
   
    private static method init takes nothing returns nothing
        call thistype.initVar()
    endmethod
   
    implement Initializer
endstruct

private struct BuildList extends array
    implement Alloc
   
    private static constant player NEUTRAL = Player(PLAYER_NEUTRAL_PASSIVE)
   
    private static Table holderTable            = 0
    private static Table subRefTable            = 0
   
    private static SubBuildList currentTemp     = 0
   
    private integer abilHolder
    private IntegerList subList
   
    static method create takes integer unitId, integer abilId, integer abilHolder, boolean consumesWorker, integer count returns thistype
        local thistype      result  = thistype.holderTable.integer[abilHolder]
        local SubBuildList  temp    = 0
       
        if result == 0 then
            set result              = thistype.allocate()
            set result.subList      = IntegerList.create()
            set result.abilHolder   = abilHolder
           
            set thistype.holderTable.integer[abilHolder] = result
        endif
       
        set temp = SubBuildList.getInstance(abilId)
        if temp == 0 then
            set temp = SubBuildList.create(unitId, abilId, consumesWorker, count)

            call result.subList.push(temp)
           
            set thistype.subRefTable.integer[temp] = result
        endif
       
        return result
    endmethod
   
    private static method onSpellCastHide takes nothing returns nothing
        local unit enum = GetEnumUnit()
       
        call SetUnitInvulnerable(enum, true)
       
        if thistype.currentTemp.consumesWorker then
            call SetUnitUseFood(enum, false)
        endif
       
        call PauseUnit(enum, true)
        call ShowUnit(enum, false)
       
        set enum = null
    endmethod
   
    private static method onSpellCastUndock takes nothing returns nothing
        call DockingSystem.undock(GetEnumUnit())
        call thistype.onSpellCastHide()
    endmethod
   
    private static method onSpellCast takes nothing returns nothing
        local unit station              = GetTriggerUnit()
        local unit plug
       
        local BuildSystem whichBuild   
       
        local SubBuildList temp         = SubBuildList.getInstance(GetSpellAbilityId())
        local thistype  handler         = thistype.subRefTable.integer[temp]
       
        local TableArray   trainList
       
        local integer constructType     = BuildSystemEx.getConstructType(handler.abilHolder)
       
        local integer stationHandle     = GetHandleId(station)
        local integer stationId         = GetUnitTypeId(station)
       
        local integer plugHandle       
        local integer plugCount         = 0
       
        local IntegerListItem iter
        local IntegerListItem iter2
       
        local player stationOwner       = GetOwningPlayer(station)
       
        local group grp
       
        set whichBuild = BuildSystem(BuildSystemEx.stationHolder.integer[stationId])
       
        if constructType == BuildSystemEx.construct then
            set BuildSystemEx.dataBase[1].player[stationHandle] = stationOwner
            set BuildSystemEx.dataBase[2].real[stationHandle]   = GetUnitX(station)
            set BuildSystemEx.dataBase[3].real[stationHandle]   = GetUnitY(station)
            set BuildSystemEx.dataBase[4].group[stationHandle]  = CreateGroup()
           
            set grp = CreateGroup()
           
            //  Get the number of available plugs...
            loop               
                exitwhen GetNumberOfOccupiedSockets(station) == 0
               
                set plug = GetSocketPlug(GetRandomSocketWithState(station, true))
               
                //  Remove the unit from the station
                call DockingSystem.undock(plug)
                call GroupAddUnit(grp, plug)
               
                if (plugCount < temp.count) then
                    call GroupAddUnit(BuildSystemEx.dataBase[4].group[stationHandle], plug)
                   
                    set plugCount = plugCount + 1
                endif
            endloop
           
            if plugCount < temp.count then
                call SimError(stationOwner, "Construction of " + GetObjectName(temp.unitId) + " requires "/*
                                       */ + "at least " + I2S(temp.count) + " " + GetObjectName(whichBuild.workerId))
               
                //  Redock the units...
                set plug = FirstOfGroup(grp)
                loop
                    exitwhen plug == null
                   
                    call DockingSystem.dock(plug, GetClosestSocketWithState(station, plug, false))
                    call GroupRemoveUnit(grp, plug)
                   
                    set plug = FirstOfGroup(grp)
                endloop
               
                call BuildSystemEx.dataBase[1].player.remove(stationHandle)
                call BuildSystemEx.dataBase[2].real.remove(stationHandle)
                call BuildSystemEx.dataBase[3].real.remove(stationHandle)
                call BuildSystemEx.dataBase[4].group.remove(stationHandle)
               
                call DestroyGroup(grp)
                call DestroyGroup(BuildSystemEx.dataBase[4].group[stationHandle])
               
                set grp = null
                set station = null
                set plug = null
               
                return
            endif
           
            call ShowUnit(station, false)
            call SetUnitX(station, WorldBounds.minX)
            call SetUnitY(station, WorldBounds.minY)
            call SetUnitInvulnerable(station, true)
           
            set plug = FirstOfGroup(BuildSystemEx.dataBase[4].group[stationHandle])
           
            call BuildSystemEx.increment(stationOwner, temp.unitId)
            if IssueBuildOrderById(plug, temp.unitId, BuildSystemEx.dataBase[2].real[stationHandle], BuildSystemEx.dataBase[3].real[stationHandle]) then               
                set thistype.currentTemp = temp
                call ForGroup(BuildSystemEx.dataBase[4].group[stationHandle], function thistype.onSpellCastUndock)
           
                call ShowUnit(plug, true)
                call PauseUnit(plug, false)
                               
                set plugHandle = GetHandleId(plug)
               
                //  Let the builder point at the station ...
                set BuildSystemEx.dataBase[5].unit[plugHandle]      = station
                set BuildSystemEx.dataBase[6].integer[plugHandle]   = temp
               
                call SetUnitX(plug, BuildSystemEx.dataBase[2].real[stationHandle])
                call SetUnitY(plug, BuildSystemEx.dataBase[3].real[stationHandle])
               
                //  The group is unnecessary at this point.
                call DestroyGroup(grp)
               
                set grp = null
            else
                call BuildSystemEx.decrement(stationOwner, temp.unitId)
                call DockingSystem.dock(plug, GetClosestSocketWithState(station, plug, false))
               
                loop
                    exitwhen plug == null
                   
                    call GroupRemoveUnit(grp, plug)
                    set plug = FirstOfGroup(grp)
                   
                    call DockingSystem.dock(plug, GetClosestSocketWithState(station, plug, false))
                endloop
               
                call SetUnitInvulnerable(station, false)
                call SetUnitX(station, BuildSystemEx.dataBase[2].real[stationHandle])
                call SetUnitY(station, BuildSystemEx.dataBase[3].real[stationHandle])
                call ShowUnit(station, true)

                call DestroyGroup(BuildSystemEx.dataBase[4].group[stationHandle])
                call DestroyGroup(grp)
               
                call BuildSystemEx.dataBase[1].player.remove(stationHandle)
                call BuildSystemEx.dataBase[2].real.remove(stationHandle)
                call BuildSystemEx.dataBase[3].real.remove(stationHandle)
                call BuildSystemEx.dataBase[4].group.remove(stationHandle)
               
                set grp = null
                set station = null
                set plug = null
               
                return
            endif
           
        elseif constructType == BuildSystemEx.upgrade then
            set BuildSystemEx.dataBase[7].player[stationHandle] = stationOwner
            set BuildSystemEx.dataBase[8].group[stationHandle]  = CreateGroup()
           
            set grp = BuildSystemEx.dataBase[8].group[stationHandle]
            //  Get the number of available plugs...
            loop
                exitwhen GetNumberOfOccupiedSockets(station) == 0 or (plugCount >= temp.count)
               
                set plug = GetSocketPlug(GetRandomSocketWithState(station, true))
                call DockingSystem.undock(plug)
                call GroupAddUnit(BuildSystemEx.dataBase[8].group[stationHandle], plug)
               
                set plugCount = plugCount + 1
            endloop
           
            if plugCount < temp.count then
                call SimError(stationOwner, "Upgrading to " + GetObjectName(temp.unitId) + " requires "/*
                                       */ + "at least " + I2S(temp.count) + " " + GetObjectName(whichBuild.workerId))
               
                set plug = FirstOfGroup(grp)
                loop
                    exitwhen plug == null
                   
                    call DockingSystem.dock(plug, GetClosestSocketWithState(station, plug, false))
                   
                    call GroupRemoveUnit(grp, plug)
                    set plug = FirstOfGroup(grp)
                endloop
               
                call DestroyGroup(BuildSystemEx.dataBase[8].group[stationHandle])
               
                call BuildSystemEx.dataBase[7].player.remove(stationHandle)
                call BuildSystemEx.dataBase[8].group.remove(stationHandle)
               
                set grp = null
                set station = null
                set plug = null
               
                return
            endif
           
            call UnitRemoveAbility(station, handler.abilHolder)
            call UnitAddAbility(station, handler.abilHolder)
           

            call PauseUnit(station, true)
            call IssueImmediateOrderById(station, 851972)
            call PauseUnit(station, false)

            call BuildSystemEx.increment(stationOwner, temp.unitId)

            if IssueImmediateOrderById(station, temp.unitId) then
                set thistype.currentTemp = temp
                call ForGroup(BuildSystemEx.dataBase[8].group[stationHandle], function thistype.onSpellCastUndock)
               
                set BuildSystemEx.dataBase[9].integer[stationHandle] = temp
                set BuildSystemEx.dataBase[10].integer[stationHandle] = stationId
               
                set grp = null
            else
                call BuildSystemEx.decrement(stationOwner, temp.unitId)
               
                loop
                    exitwhen plug == null
                   
                    call DockingSystem.dock(plug, GetClosestSocketWithState(station, plug, false))
                   
                    call GroupRemoveUnit(grp, plug)
                    set plug = FirstOfGroup(grp)
                endloop
               
                call DestroyGroup(grp)
               
                call BuildSystemEx.dataBase[7].player.remove(stationHandle)
                call BuildSystemEx.dataBase[8].group.remove(stationHandle)
               
                set grp = null
                set station = null
                set plug = null
               
                return
            endif
           
        elseif constructType == BuildSystemEx.train then
           
            if not BuildSystemEx.dataBase[12].player.has(stationHandle) then
                set BuildSystemEx.dataBase[12].player[stationHandle] = stationOwner
                set BuildSystemEx.dataBase[13].integer[stationHandle] = 0
            endif
           
            set grp = CreateGroup()
            loop
                exitwhen GetNumberOfOccupiedSockets(station) == 0 or (plugCount >= temp.count)
               
                set plug = GetSocketPlug(GetRandomSocketWithState(station, true))
                call DockingSystem.undock(plug)
                call GroupAddUnit(grp, plug)
               
                set plugCount = plugCount + 1
            endloop
           
            if plugCount < temp.count then
                call SimError(stationOwner, "Upgrading to " + GetObjectName(temp.unitId) + " requires "/*
                                       */ + "at least " + I2S(temp.count) + " " + GetObjectName(whichBuild.workerId))
               
                set plug = FirstOfGroup(grp)
                loop
                    exitwhen plug == null
                   
                    call DockingSystem.dock(plug, GetClosestSocketWithState(station, plug, false))
                   
                    call GroupRemoveUnit(grp, plug)
                    set plug = FirstOfGroup(grp)
                endloop
               
                call DestroyGroup(grp)
               
                if BuildSystemEx.dataBase[13].integer[stationHandle] == 0 then
                    call BuildSystemEx.dataBase[12].player.remove(stationHandle)
                    call BuildSystemEx.dataBase[13].integer.remove(stationHandle)
                endif
               
                set grp = null
                set station = null
                set plug = null
               
                return
            endif
           
            call PauseUnit(station, true)
            call IssueImmediateOrderById(station, 851972)
            call PauseUnit(station, false)
           
            call UnitRemoveAbility(station, handler.abilHolder)
            call UnitAddAbility(station, handler.abilHolder)
           
            //  Using a counter mechanism
            call BlzUnitDisableAbility(station, handler.abilHolder, false, false)
            call BlzUnitHideAbility(station, handler.abilHolder, true)
           
            call BuildSystemEx.increment(stationOwner, temp.unitId)
           
            if IssueImmediateOrderById(station, temp.unitId) then
                set thistype.currentTemp = temp
                call ForGroup(grp, function thistype.onSpellCastUndock)
                                             
                if not BuildSystemEx.dataBase[14].integer.has(stationHandle) then                   
                    set trainList = TableArray[3]
                    set trainList[1].integer[0] = IntegerList.create()
                    set trainList[2].integer[0] = IntegerList.create()
                    set trainList[1].integer[-2] = IntegerList.create()
                   
                    set BuildSystemEx.dataBase[14].integer[stationHandle] = integer(trainList)
                else
                    set trainList = BuildSystemEx.dataBase[14].integer[stationHandle]
                endif
               
                //  Store the size().
                set BuildSystemEx.dataBase[13].integer[stationHandle] = BuildSystemEx.dataBase[13].integer[stationHandle] + 1
               
                if not trainList[1].integer.has(temp.unitId) then
                    set trainList[1].integer[temp.unitId] = UnitGroupList.create()
                    set trainList[2].integer[temp.unitId] = temp
                   
                    call IntegerList(trainList[1].integer[0]).push(trainList[1].integer[temp.unitId])
                    call IntegerList(trainList[2].integer[0]).push(temp.unitId)
                endif
                               
                call UnitGroupList(trainList[1].integer[temp.unitId]).push(grp)
               
                if IntegerList(trainList[1].integer[-2]).find(handler.abilHolder) == 0 then
                    call IntegerList(trainList[1].integer[-2]).push(handler.abilHolder)
                endif
               
                set iter2 = IntegerList(trainList[1].integer[-2]).first
                loop
                    exitwhen iter2 == 0
                   
                    set iter = thistype(thistype.holderTable.integer[iter2.data]).subList.first
                    loop
                        exitwhen iter == 0
                       
                        call BlzUnitDisableAbility(station, SubBuildList(iter.data).abilId, false, false)
                        call BlzUnitHideAbility(station, SubBuildList(iter.data).abilId, true)
                       
                        set iter = iter.next
                    endloop
                   
                    set iter2 = iter2.next
                endloop
               
                set grp = null
            else
                call BuildSystemEx.decrement(stationOwner, temp.unitId)
               
                static if DEBUG_MODE then
                    call BJDebugMsg("Insufficient resources.")
                else
                    call SimError(stationOwner, "Insufficient resources.")
                endif
               
                set plug = FirstOfGroup(grp)
                loop
                    exitwhen plug == null
                   
                    call DockingSystem.dock(plug, GetClosestSocketWithState(station, plug, false))
                   
                    call GroupRemoveUnit(grp, plug)
                    set plug = FirstOfGroup(grp)
                endloop
               
                call DestroyGroup(grp)
               
                if trainList[1].integer.has(-2) then
                    set iter2 = IntegerList(trainList[1].integer[-2]).first
                    loop
                        exitwhen iter2 == 0
                       
                        set iter = thistype(thistype.holderTable.integer[iter2.data]).subList.first
                        loop
                            exitwhen iter == 0
                           
                            call BlzUnitDisableAbility(station, SubBuildList(iter.data).abilId, false, false)
                            call BlzUnitHideAbility(station, SubBuildList(iter.data).abilId, true)
                           
                            set iter = iter.next
                        endloop
                       
                        set iter2 = iter2.next
                    endloop
                endif
               
                if BuildSystemEx.dataBase[13].integer[stationHandle] == 0 then
                    if BuildSystemEx.dataBase[14].integer.has(stationHandle) then
                        set trainList = BuildSystemEx.dataBase[14].integer[stationHandle]
                       
                        if trainList[1].integer.has(0) then
                            call IntegerList(trainList[1].integer[0]).destroy()
                        endif
                        if trainList[2].integer.has(0) then
                            call IntegerList(trainList[2].integer[0]).destroy()
                        endif
                        if trainList[1].integer.has(-2) then
                            call IntegerList(trainList[1].integer[-2]).destroy()
                        endif
                       
                        call trainList.destroy()
                    endif
                   
                    call BuildSystemEx.dataBase[12].player.remove(stationHandle)
                    call BuildSystemEx.dataBase[13].integer.remove(stationHandle)
                    call BuildSystemEx.dataBase[14].integer.remove(stationHandle)
                endif
               
                set grp = null
                set station = null
                set plug = null
               
                return
            endif
        endif
       
        set station = null
    endmethod
   
    private static method initVar takes nothing returns nothing
        set thistype.holderTable = Table.create()
        set thistype.subRefTable = Table.create()
    endmethod
   
    private static method initListener takes nothing returns nothing
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onSpellCast)
    endmethod
   
    private static method init takes nothing returns nothing
        call thistype.initVar()
        call thistype.initListener()
    endmethod
   
    implement Initializer
endstruct

struct BuildSystem extends array
    implement Alloc
   
    private static constant integer DEFAULT_WORKER_COUNT    = 4
   
    private static constant real    DEFAULT_WORKER_DIST     = 45.
    private static constant real    DEFAULT_WORKER_HEIGHT   = 30.
    private static constant real    DEFAULT_WORKER_RAD      = 0.
    private static constant real    DEFAULT_DETECT_DIST     = 250.
   
    private static constant boolean DEFAULT_CONSUMES_WORKER = true
   
    //  These store the unit types which have been passed into the BuildSystem
    private static Table unitTypeList       = 0
    //  This stores the owner of the unit whose unit type has been registered
    private static Table unitOwner          = 0
    //  This stores the flag of the unit whether it triggered a unit death
    //  event or not
    private static Table unitDeathFlag      = 0
    //  This stores the flag for all stations...
    private static Table stationFlag        = 0
   
    //  Main members. Can be read, but not written.
    readonly integer workerId
    readonly integer buildingId
   
    private trigger onPredock
    private trigger onDock
    private trigger onUndock
   
    private IntegerList constructList
    private IntegerList upgradeList
    private IntegerList trainList
   
    integer workerCount
   
    real    workerHeight
    real    workerDist
    real    workerStartRad
    real    workerDetectDist
   
    boolean workerConsumed
   
    static method create takes integer workerId, integer buildingId returns thistype
        local thistype result = BuildSystemEx.stationHolder.integer[buildingId]
       
        if result == 0 then
            set result                  = thistype.allocate()
           
            set result.workerId         = workerId
            set result.buildingId       = buildingId
           
            set result.constructList    = IntegerList.create()
            set result.upgradeList      = IntegerList.create()
            set result.trainList        = IntegerList.create()
           
            set result.workerCount      = DEFAULT_WORKER_COUNT
            set result.workerHeight     = DEFAULT_WORKER_HEIGHT
            set result.workerDist       = DEFAULT_WORKER_DIST
            set result.workerStartRad   = DEFAULT_WORKER_RAD
            set result.workerDetectDist = DEFAULT_DETECT_DIST
            set result.workerConsumed   = DEFAULT_CONSUMES_WORKER
           
            set result.onPredock        = CreateTrigger()
            set result.onDock           = CreateTrigger()
            set result.onUndock         = CreateTrigger()
           
            set BuildSystemEx.stationHolder.integer[buildingId] = result
        endif
       
        return result
    endmethod
   
    private static method precheckAbil takes integer abilHolder returns boolean
        return BuildSystemEx.constructType.integer.has(abilHolder)
    endmethod
   
    private static method addUnitType takes integer unitId returns nothing
        if unitId == 0 then
            debug call printError("thistype.addUnitType: Invalid unit type!")
            return
        endif
       
        if not thistype.unitTypeList.integer.has(unitId) then
            set thistype.unitTypeList.integer[unitId] = IntegerList(thistype.unitTypeList.integer[0]).push(unitId).last
        endif
    endmethod
   
    private static method isUnitPhysical takes unit u returns boolean
        return (not IsUnitType(u, UNIT_TYPE_SUMMONED)) and (not IsUnitIllusion(u))
    endmethod
   
    method addConstructAbil takes integer abilHolder returns nothing
        if thistype.precheckAbil(abilHolder) then
            debug call printError("thistype.addConstructAbil: Ability type was already defined.")
            return
        endif
       
        call this.constructList.push(abilHolder)
        set BuildSystemEx.constructType.integer[abilHolder] = BuildSystemEx.construct
    endmethod

    method addUpgradeAbil takes integer abilHolder returns nothing
        if thistype.precheckAbil(abilHolder) then
            debug call printError("thistype.addUpgradeAbil: Ability type was already defined.")
            return
        endif
       
        call this.upgradeList.push(abilHolder)
        set BuildSystemEx.constructType.integer[abilHolder] = BuildSystemEx.upgrade
    endmethod
   
    method addTrainAbil takes integer abilHolder returns nothing
        if thistype.precheckAbil(abilHolder) then
            debug call printError("thistype.addTrainAbil: Ability type was already defined.")
            return
        endif
       
        call this.trainList.push(abilHolder)
        set BuildSystemEx.constructType.integer[abilHolder] = BuildSystemEx.train
    endmethod
   
    method addConstructUnit takes integer unitId, integer abilId, integer abilHolder, integer count returns nothing
        if BuildSystemEx.constructType.integer[abilHolder] != BuildSystemEx.construct then
            debug call printError("thistype.addConstructUnit: Ability id is not of type construct.")
            return
        endif
        call BuildList.create(unitId, abilId, abilHolder, this.workerConsumed, count)
        call thistype.addUnitType(unitId)
    endmethod

    method addUpgradeUnit takes integer unitId, integer abilId, integer abilHolder, integer count returns nothing
        if BuildSystemEx.constructType.integer[abilHolder] != BuildSystemEx.upgrade then
            debug call printError("thistype.addUpgradeUnit: Ability id is not of type upgrade.")
            return
        endif
        call BuildList.create(unitId, abilId, abilHolder, this.workerConsumed, count)
        call thistype.addUnitType(unitId)
    endmethod
   
    method addTrainUnit takes integer unitId, integer abilId, integer abilHolder, integer count returns nothing
        if BuildSystemEx.constructType.integer[abilHolder] != BuildSystemEx.train then
            debug call printError("thistype.addTrainUnit: Ability id is not of type train.")
            return
        endif
        call BuildList.create(unitId, abilId, abilHolder, this.workerConsumed, count)
        call thistype.addUnitType(unitId)
    endmethod
   
    method setOnDockEvent takes integer dockEvent, code whichHandler returns nothing
        if dockEvent == EVENT_ON_PRE_DOCK then
            call DestroyTrigger(this.onPredock)
           
            set this.onPredock = CreateTrigger()
            call TriggerAddCondition(this.onPredock, Condition(whichHandler))
           
        elseif dockEvent == EVENT_ON_DOCK then
            call DestroyTrigger(this.onDock)
           
            set this.onDock = CreateTrigger()
            call TriggerAddCondition(this.onDock, Condition(whichHandler))
           
        elseif dockEvent == EVENT_ON_UNDOCK then
            call DestroyTrigger(this.onUndock)
           
            set this.onUndock = CreateTrigger()
            call TriggerAddCondition(this.onUndock, Condition(whichHandler))           
        debug else
            debug call printError("thistype.setOnDockEvent: Unrecognized dock event!")
        endif
    endmethod
   
    private method initStation takes unit station returns nothing
        local integer i             = 0
        local integer stationHandle = GetHandleId(station)
       
        local real pSlice           = 2*bj_PI/(this.workerCount)
        local real stationX         = GetUnitX(station)
        local real stationY         = GetUnitY(station)
       
        if not thistype.stationFlag.boolean.has(stationHandle) then
            set thistype.stationFlag.boolean[stationHandle] = false
           
            call DockingSystem.createStation(station, this.workerDetectDist)
            loop
                exitwhen i >= this.workerCount
               
                call DockingSystem.addSocket(station, /*
                                            */stationX + this.workerDist*Cos(I2R(i)*pSlice + this.workerStartRad),/*
                                            */stationY + this.workerDist*Sin(I2R(i)*pSlice + this.workerStartRad),/*
                                            */this.workerHeight, (I2R(i)*pSlice + this.workerStartRad + (bj_PI/2))/*
                                            */ * bj_RADTODEG)
                set i = i + 1
            endloop
        endif
    endmethod
   
    private method removeStation takes unit station returns nothing
        local integer stationHandle = GetHandleId(station)
       
        if thistype.stationFlag.boolean.has(stationHandle) then
            call thistype.stationFlag.boolean.remove(stationHandle)
           
            call DockingSystem.terminateStation(station)
        endif
    endmethod
   
    /*
         -----------------------------------
        |   Handler methods                 |
         -----------------------------------
    */
   
    /*
          ------------------
         |                  |
         |  Constructs      |
         |                  |
          ------------------
    */
    private static method onConstructStart takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit builder              = GetBuildingUnit()
        local unit station
       
        local player buildingOwner      = GetOwningPlayer(building)
       
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
        local integer builderHandle     = GetHandleId(builder)
       
        local SubBuildList  buildList   = BuildSystemEx.dataBase[6].integer[buildingId]
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
       
        //  If building is a station...
        if temp != 0 then
            call temp.removeStation(building)
        endif
       
        if BuildSystemEx.dataBase[5].unit.has(builderHandle) then
            //  Move data reference to the building currently being built...
            set station = BuildSystemEx.dataBase[5].unit[builderHandle]
            set BuildSystemEx.dataBase[5].unit[buildingHandle]      = station
            set BuildSystemEx.dataBase[6].integer[buildingHandle]   = BuildSystemEx.dataBase[6].integer[builderHandle]
           
            call BuildSystemEx.dataBase[5].unit.remove(builderHandle)
            call BuildSystemEx.dataBase[6].integer.remove(builderHandle)
           
            //  Correct the incremental value...
            call BuildSystemEx.decrement(buildingOwner, buildingId)
           
            if buildList.consumesWorker then
                call UnitAddAbility(builder, 'Aloc')
            endif
        endif
       
        set builder  = null
        set building = null
    endmethod
   
    private static method onConstructCancel takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit builder              = GetBuildingUnit()
        local unit station
        local unit plug
       
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
       
        local integer builderHandle     = GetHandleId(builder)
       
        local integer stationHandle
       
        local real    stationX
        local real    stationY
       
        local player  stationOwner
        local group   stationGrp
       
        local trigger trig
       
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
        local SubBuildList  buildList   = 0
       
        //  Triggered by removal
        if GetTriggerEventId() == null then
            set building = GetIndexedUnit()
            set builder  = GetBuilder(building)
           
            set buildingId      = GetUnitTypeId(building)
            set buildingHandle  = GetHandleId(building)
           
            set builderHandle   = GetHandleId(builder)
        endif
       
        if BuildSystemEx.dataBase[5].unit.has(buildingHandle) then
            set station         = BuildSystemEx.dataBase[5].unit[buildingHandle]
           
            set stationHandle   = GetHandleId(station)
           
            set stationOwner    = BuildSystemEx.dataBase[1].player[stationHandle]
            set stationX        = BuildSystemEx.dataBase[2].real[stationHandle]
            set stationY        = BuildSystemEx.dataBase[3].real[stationHandle]
            set stationGrp      = BuildSystemEx.dataBase[4].group[stationHandle]
           
            set buildList       = BuildSystemEx.dataBase[6].integer[buildingHandle]
           
            call BuildSystemEx.dataBase[5].unit.remove(buildingHandle)
            call BuildSystemEx.dataBase[6].integer.remove(buildingHandle)
           
            if buildList.consumesWorker then
                call UnitRemoveAbility(builder, 'Aloc')
            endif
           
            call SetUnitInvulnerable(station, false)
            call SetUnitX(station, stationX)
            call SetUnitY(station, stationY)
            call ShowUnit(station, true)
           
            set plug = FirstOfGroup(stationGrp)
            loop
                exitwhen plug == null
               
                call ShowUnit(plug, true)
                call SetUnitInvulnerable(plug, false)
                call SetUnitPosition(plug, stationX, stationY)
                call PauseUnit(plug, false)
               
                if GetLocalPlayer() == stationOwner then
                    call SelectUnit(plug, true)
                endif
               
                if buildList.consumesWorker then
                    call SetUnitUseFood(plug, true)
                endif
               
                call GroupRemoveUnit(stationGrp, plug)
                set plug = FirstOfGroup(stationGrp)
            endloop
           
            call BuildSystemEx.dataBase[1].player.remove(stationHandle)
            call BuildSystemEx.dataBase[2].real.remove(stationHandle)
            call BuildSystemEx.dataBase[3].real.remove(stationHandle)
            call BuildSystemEx.dataBase[4].group.remove(stationHandle)
           
            call DestroyGroup(stationGrp)
           
            set station    = null
            set stationGrp = null
        endif
       
        set builder  = null
        set building = null
    endmethod
   
    private static method onConstructFinish takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit builder              = GetBuildingUnit()
        local unit station              = null
        local unit plug
       
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
       
        local integer builderHandle     = GetHandleId(builder)
       
        local integer stationHandle
       
        local real    stationX
        local real    stationY
       
        local player  stationOwner
        local group   stationTempGrp    = null
        local group   stationGrp
               
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
       
        local SubBuildList buildList    = 0
       
        if BuildSystemEx.dataBase[5].unit.has(buildingHandle) then
            set station         = BuildSystemEx.dataBase[5].unit[buildingHandle]
           
            set stationHandle   = GetHandleId(station)
           
            set stationOwner    = BuildSystemEx.dataBase[1].player[stationHandle]
            set stationX        = BuildSystemEx.dataBase[2].real[stationHandle]
            set stationY        = BuildSystemEx.dataBase[3].real[stationHandle]
            set stationGrp      = BuildSystemEx.dataBase[4].group[stationHandle]
           
            set buildList       = BuildSystemEx.dataBase[6].integer[stationHandle]
           
            set stationTempGrp  = CreateGroup()
            call CopyGroup(stationTempGrp, stationGrp)
        endif
       
        call thistype.onConstructCancel()
       
        if station != null then
            set plug = FirstOfGroup(stationTempGrp)
            loop
                exitwhen plug == null
               
                call GroupRemoveUnit(stationTempGrp, plug)
               
                if buildList.consumesWorker then
                    call RemoveUnit(plug)
                endif
               
                set plug = FirstOfGroup(stationTempGrp)
            endloop
           
            call RemoveUnit(station)
        endif
       
        if stationTempGrp != null then
            call DestroyGroup(stationTempGrp)
        endif
       
        if temp != 0 then
            call temp.initStation(building)
        endif
       
        set stationTempGrp  = null
        set station         = null
        set stationGrp      = null
       
        set builder         = null
        set building        = null
    endmethod
   
    /*
          ------------------
         |                  |
         |  Upgrades        |
         |                  |
          ------------------
    */
    private static method onUpgradeStart takes nothing returns nothing
        local unit building             = GetTriggerUnit()
       
        local player buildingOwner      = GetOwningPlayer(building)
       
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
       
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
           
        if temp != 0 then
            call DockingSystem.undockAll(building)
        endif
       
        if BuildSystemEx.dataBase[9].integer.has(buildingHandle) then
            //  Raise a flag telling the system that the unit is upgrading.
            set BuildSystemEx.dataBase[11].boolean[buildingHandle] = true
        endif
       
        set building = null
    endmethod
   
    private static method onUpgradeCancel takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit plug
       
        local real buildingX            = GetUnitX(building)
        local real buildingY            = GetUnitY(building)
       
        local group plugGroup
       
        local player buildingOwner      = GetOwningPlayer(building)
       
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
        local integer upgradeId         
       
        local SubBuildList buildList   
       
        local thistype temp             = BuildSystemEx.stationHolder.integer[buildingId]
       
        if GetTriggerEventId() == null then
            set building        = GetIndexedUnit()
           
            set buildingX       = GetUnitX(building)
            set buildingY       = GetUnitY(building)
           
            set buildingOwner   = GetOwningPlayer(building)
           
            set buildingId      = GetUnitTypeId(building)
            set buildingHandle  = GetHandleId(building)
        endif
       
        if BuildSystemEx.dataBase[9].integer.has(buildingHandle) then
            set plugGroup   = BuildSystemEx.dataBase[8].group[buildingHandle]
            set buildList   = SubBuildList(BuildSystemEx.dataBase[9].integer[buildingHandle])
            set upgradeId   = buildList.unitId
           
            call BuildSystemEx.decrement(buildingOwner, upgradeId)
           
            //  Show the units...
            set plug = FirstOfGroup(plugGroup)
            loop
                exitwhen plug == null
               
                call ShowUnit(plug, true)
                call SetUnitInvulnerable(plug, false)
                call SetUnitPosition(plug, buildingX, buildingY)
                call PauseUnit(plug, false)
               
                if buildList.consumesWorker then
                    call SetUnitUseFood(plug, true)
                endif
               
                call GroupRemoveUnit(plugGroup, plug)
                set plug = FirstOfGroup(plugGroup)
            endloop
           
            call DestroyGroup(plugGroup)
           
            set plugGroup = null
           
            call BuildSystemEx.dataBase[7].player.remove(buildingHandle)
            call BuildSystemEx.dataBase[8].group.remove(buildingHandle)
            call BuildSystemEx.dataBase[9].integer.remove(buildingHandle)
            call BuildSystemEx.dataBase[10].integer.remove(buildingHandle)
            call BuildSystemEx.dataBase[11].boolean.remove(buildingHandle)
        endif
       
        set building = null
    endmethod
   
    private static method onUpgradeFinish takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit plug
       
        local real buildingX            = GetUnitX(building)
        local real buildingY            = GetUnitY(building)
       
        local group plugGroup
        local group plugGroupCopy       = null
       
        local player buildingOwner      = GetOwningPlayer(building)
       
        local integer buildingId        = GetUnitTypeId(building)
        local integer buildingHandle    = GetHandleId(building)
        local integer upgradeId         
        local integer lastBuildingId   
       
        local SubBuildList buildList   
       
        local thistype temp             
       
        if BuildSystemEx.dataBase[3].integer.has(buildingHandle) then
            set plugGroup       = BuildSystemEx.dataBase[2].group[buildingHandle]
            set buildList       = SubBuildList(BuildSystemEx.dataBase[3].integer[buildingHandle])
            set upgradeId       = buildList.unitId
            set lastBuildingId  = BuildSystemEx.dataBase[4].integer[buildingHandle]
           
            set temp = BuildSystemEx.stationHolder.integer[lastBuildingId]
           
            set plugGroupCopy   = CreateGroup()
            call CopyGroup(plugGroupCopy, plugGroup)
           
            if temp != 0 then
                /*
                call temp.removeStation(building)
               
                //  Outsource it to transformation.
                if thistype.unitTypeList.has(lastBuildingId) then
                    call BuildSystemEx.decrement(buildingOwner, lastBuildingId)
                endif
                */
            endif
           
            //  Counter the effects of decrement when calling onUpgradeCancel...
            call BuildSystemEx.increment(buildingOwner, buildingId)
        endif
       
        call thistype.onUpgradeCancel()
       
        set plug = FirstOfGroup(plugGroupCopy)
        loop
            exitwhen plug == null
           
            call GroupRemoveUnit(plugGroupCopy, plug)
           
            if buildList.consumesWorker then
                call RemoveUnit(plug)
            endif
           
            set plug = FirstOfGroup(plugGroupCopy)
        endloop
       
        if plugGroupCopy != null then
            call DestroyGroup(plugGroupCopy)
        endif
       
        set plugGroupCopy = null
        set building = null
    endmethod
   
    /*
          ------------------
         |                  |
         |  Training        |
         |                  |
          ------------------
    */
    private static method onTrainCancel takes nothing returns nothing
        local unit building
        local unit plug
       
        local integer buildingHandle
        local integer buildingId
        local integer trainedType       = GetTrainedUnitType()
       
        local real buildingX
        local real buildingY
       
        local group buildGroup
       
        local TableArray buildTable
       
        local SubBuildList buildList
       
        local player buildingOwner     
       
        local thistype inst             
       
        set building = GetTriggerUnit()
       
        set buildingId      = GetUnitTypeId(building)
        set buildingHandle  = GetHandleId(building)
        set buildingOwner   = GetOwningPlayer(building)
       
        set buildingX       = GetUnitX(building)
        set buildingY       = GetUnitY(building)
       
        //  If there are units to be trained...
        if BuildSystemEx.dataBase[14].integer.has(buildingHandle) then
            set buildTable = BuildSystemEx.dataBase[14].integer[buildingHandle]
            set buildGroup = UnitGroupList(buildTable[1].integer[trainedType]).first.data
            set buildList  = SubBuildList(buildTable[2].integer[trainedType])
           
            call UnitGroupList(buildTable[1].integer[trainedType]).removeElem(buildGroup)
           
            call BuildSystemEx.decrement(buildingOwner, trainedType)
           
            //  Show the units...
            set plug = FirstOfGroup(buildGroup)
            loop
                exitwhen plug == null
               
                call ShowUnit(plug, true)
                call SetUnitInvulnerable(plug, false)
                call SetUnitPosition(plug, buildingX, buildingY)
                call PauseUnit(plug, false)
               
                if buildList.consumesWorker then
                    call SetUnitUseFood(plug, true)
                endif
               
                call GroupRemoveUnit(buildGroup, plug)
                set plug = FirstOfGroup(buildGroup)
            endloop
           
            call DestroyGroup(buildGroup)
           
            if UnitGroupList(buildTable[1].integer[trainedType]).size() == 0 then
                call IntegerList(buildTable[1].integer[0]).removeElem(buildTable[1].integer[trainedType])
                call IntegerList(buildTable[2].integer[0]).removeElem(trainedType)
               
                call buildTable[2].integer.remove(trainedType)
            endif
           
            set BuildSystemEx.dataBase[13].integer[buildingHandle] = BuildSystemEx.dataBase[13].integer[buildingHandle] - 1
           
            set buildGroup = null
        endif
    endmethod
   
    private static method onTrainFinish takes nothing returns nothing
        local unit building             = GetTriggerUnit()
        local unit plug
       
        local integer buildingHandle
        local integer buildingId
        local integer trainTypeId       = GetTrainedUnitType()
       
        local group buildGroup
       
        local TableArray trainTable
       
        local SubBuildList buildList
       
        local player buildingOwner     
       
        local thistype inst             
       
        local IntegerListItem iter
       
        if not thistype.unitTypeList.integer.has(trainTypeId) then
            set building = null
            return
        endif
       
        set buildingId      = GetUnitTypeId(building)
        set buildingHandle  = GetHandleId(building)
        set buildingOwner   = GetOwningPlayer(building)
       
        set buildGroup      = CreateGroup()
       
        set trainTable      = BuildSystemEx.dataBase[14].integer[buildingHandle]
        set buildList       = trainTable[2].integer[trainTypeId]

        //  Copy the group first, since it will get destroyed on thistype.onTrainCancel
        call CopyGroup(buildGroup, UnitGroupList(trainTable[1].integer[trainTypeId]).first.data)
       
        //  Negation of the decrement call is on onEnter
        call thistype.onTrainCancel()

        set plug = FirstOfGroup(buildGroup)
        loop
            exitwhen plug == null
           
            call GroupRemoveUnit(buildGroup, plug)
           
            if buildList.consumesWorker then
                call RemoveUnit(plug)
            endif
           
            set plug = FirstOfGroup(buildGroup)
        endloop
       
        call DestroyGroup(buildGroup)
        set buildGroup = null
       
        if BuildSystemEx.dataBase[13].integer[buildingHandle] == 0 then                   
            set iter = IntegerList(trainTable[1].integer[-2]).first
            loop
                exitwhen iter == 0
               
                call UnitRemoveAbility(building, iter.data)
                call UnitAddAbility(building, iter.data)
               
                set iter = iter.next
            endloop
           
            call IntegerList(trainTable[1].integer[0]).destroy()
            call IntegerList(trainTable[2].integer[0]).destroy()
            call IntegerList(trainTable[1].integer[-2]).destroy()
           
            call trainTable.destroy()
           
            call BuildSystemEx.dataBase[12].player.remove(buildingHandle)
            call BuildSystemEx.dataBase[13].integer.remove(buildingHandle)
            call BuildSystemEx.dataBase[14].integer.remove(buildingHandle)           
        endif
    endmethod
   
    private static method onTrainDeath takes unit building returns nothing
        local unit plug
       
        local integer buildingId                = GetUnitTypeId(building)
        local integer buildingHandle            = GetHandleId(building)
        local player buildingOwner              = GetOwningPlayer(building)
       
        local real buildingX                           = GetUnitX(building)
        local real buildingY                           = GetUnitY(building)
       
        local group trainGroup
       
        local SubBuildList buildList
       
        local TableArray trainTable
       
        local IntegerListItem iter
        local IntegerListItem iter2
       
        local UnitGroupListItem iterGrp
       
        set trainTable  = BuildSystemEx.dataBase[14].integer[buildingHandle]
        set iter        = IntegerList(trainTable[1].integer[0]).first
        set iter2       = IntegerList(trainTable[2].integer[0]).first
       
        loop
            exitwhen iter == 0
           
            set iterGrp = UnitGroupList(iter.data).first
            set buildList = SubBuildList(trainTable[2].integer[iter2.data])
           
            loop
                exitwhen iterGrp == 0
               
                set trainGroup = iterGrp.data
                call BuildSystemEx.decrement(buildingOwner, buildList.unitId)
               
                loop
                    set plug = FirstOfGroup(trainGroup)
                    exitwhen plug == null
                   
                    call ShowUnit(plug, true)
                    call SetUnitInvulnerable(plug, false)
                    call SetUnitPosition(plug, buildingX, buildingY)
                    call PauseUnit(plug, false)
                   
                    if buildList.consumesWorker then
                        call SetUnitUseFood(plug, true)
                    endif
                   
                    call GroupRemoveUnit(trainGroup, plug)
                endloop
               
                call DestroyGroup(trainGroup)
           
                set BuildSystemEx.dataBase[13].integer[buildingHandle] = BuildSystemEx.dataBase[13].integer[buildingHandle] - 1
           
                set iterGrp = iterGrp.next
            endloop
                       
            set iter    = iter.next
            set iter2   = iter2.next
        endloop
   
        if GetTriggerEventId() != null then
            set iter = IntegerList(trainTable[1].integer[-2]).first
            call PauseUnit(building, true)
            loop
                exitwhen iter == 0
               
                call UnitRemoveAbility(building, iter.data)
                call UnitAddAbility(building, iter.data)
               
                set iter = iter.next
            endloop
            call PauseUnit(building, false)
        endif
       
        call IntegerList(trainTable[1].integer[0]).destroy()
        call IntegerList(trainTable[2].integer[0]).destroy()
        call IntegerList(trainTable[1].integer[-2]).destroy()

        call trainTable.destroy()
       
        call BuildSystemEx.dataBase[12].player.remove(buildingHandle)
        call BuildSystemEx.dataBase[13].integer.remove(buildingHandle)
        call BuildSystemEx.dataBase[14].integer.remove(buildingHandle)
    endmethod
   
    private static method onUnitEnter takes nothing returns nothing
        local unit u             = GetIndexedUnit()
        local integer unitId     = GetUnitTypeId(u)
        local integer uHandle    = GetHandleId(u)
        local player uOwner      = GetOwningPlayer(u)
       
        local thistype inst      = BuildSystemEx.stationHolder.integer[unitId]
       
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
       
        //  Unit is a station.
        if inst != 0 then
            call inst.initStation(u)
        endif
       
        if thistype.unitTypeList.integer.has(unitId) then
            call BuildSystemEx.increment(uOwner, unitId)
               
            set thistype.unitOwner.player[uHandle]      = uOwner
            set thistype.unitDeathFlag.boolean[uHandle] = false
        endif
       
        set u = null
    endmethod
   
    private static method onUnitExit takes nothing returns nothing
        local unit u            = GetIndexedUnit()
        local integer unitId    = GetUnitTypeId(u)
        local integer uHandle   = GetHandleId(u)
        local player uOwner     = GetOwningPlayer(u)
       
        local thistype inst     = BuildSystemEx.stationHolder.integer[unitId]
       
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
       
        if inst != 0 then
            call inst.removeStation(u)
        endif

        //  Unit must have undergone construction when it was removed...
        if BuildSystemEx.dataBase[5].unit.has(uHandle) then
            call thistype.onConstructCancel()
        endif
       
        //  Unit must have undergone upgrade when it was removed...
        if BuildSystemEx.dataBase[9].integer.has(uHandle) then
            call thistype.onUpgradeCancel()
        endif
       
        //  Unit must have trained some units...
        if BuildSystemEx.dataBase[13].integer.has(uHandle) then
            call thistype.onTrainDeath(u)
        endif
       
        if thistype.unitTypeList.integer.has(unitId) then
            if not thistype.unitDeathFlag.boolean[uHandle] then
                call BuildSystemEx.decrement(uOwner, unitId)
            endif
           
            call thistype.unitOwner.player.remove(uHandle)
            call thistype.unitDeathFlag.boolean.remove(uHandle)
        endif
       
        set u = null
    endmethod
   
    private static method onUnitDeath takes nothing returns nothing
        local unit u            = GetTriggerUnit()
        local integer unitId    = GetUnitTypeId(u)
        local integer uHandle   = GetHandleId(u)
        local player uOwner     = GetOwningPlayer(u)
             
        local thistype inst      = BuildSystemEx.stationHolder.integer[unitId]

        if GetTriggerEventId() == null then
            //  Called on reincarnation
            set u = GetEventUnit()
        endif
       
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
       
        if inst != 0 then
            if GetNumberOfOccupiedSockets(u) != 0 then
                call DockingSystem.undockAll(u)
            endif
        endif
       
        //  Unit must have undergone construction when it was destroyed...
        if BuildSystemEx.dataBase[5].unit.has(uHandle) then
            call thistype.onConstructCancel()
        endif
       
        //  Unit must have trained some units beforehand...
        if BuildSystemEx.dataBase[13].integer.has(uHandle) then
            call thistype.onTrainDeath(u)
        endif
       
        if thistype.unitTypeList.integer.has(unitId) then
            if not thistype.unitDeathFlag.boolean[uHandle] then
                call BuildSystemEx.decrement(uOwner, unitId)
               
                set thistype.unitDeathFlag.boolean[uHandle] = true               
            endif           
        endif
       
        set u = null
    endmethod
   
    private static method onUnitChangeOwner takes nothing returns nothing
        local unit u            = GetTriggerUnit()
        local integer unitId    = GetUnitTypeId(u)
        local integer uHandle   = GetHandleId(u)
        local player uOwner     = GetOwningPlayer(u)
       
        local thistype inst     = BuildSystemEx.stationHolder.integer[unitId]
       
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
       
        if inst != 0 then
            call DockingSystem.undockAll(u)
        endif
       
        if thistype.unitTypeList.integer.has(unitId) then
            call BuildSystemEx.decrement(thistype.unitOwner.player[uHandle], unitId)
               
            set thistype.unitOwner.player[uHandle] = uOwner
           
            call BuildSystemEx.increment(thistype.unitOwner.player[uHandle], unitId)
        endif
       
        set u = null
    endmethod
   
    private static method onUnitRevive takes nothing returns nothing
        local unit u            = GetEventUnit()
        local integer unitId    = GetUnitTypeId(u)
        local integer uHandle   = GetHandleId(u)
        local player uOwner     = GetOwningPlayer(u)
       
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
       
        if thistype.unitTypeList.integer.has(unitId) then
            if thistype.unitDeathFlag.boolean[uHandle] then
                call BuildSystemEx.increment(uOwner, unitId)
               
                set thistype.unitDeathFlag.boolean[uHandle] = false
            endif
        endif
       
        set u = null
    endmethod
   
    private static method onUnitTransform takes nothing returns nothing
        local unit u                = GetEventUnit()
       
        local integer prevUnitId    = GetEventTransformType()
        local integer unitId        = GetUnitTypeId(u)
        local integer uHandle       = GetHandleId(u)
        local player uOwner         = GetOwningPlayer(u)
       
        local thistype inst         = BuildSystemEx.stationHolder.integer[prevUnitId]
       
        if not thistype.isUnitPhysical(u) then
            set u = null
            return
        endif
       
        if inst != 0 then
            call inst.removeStation(u)
        endif
       
        set inst = BuildSystemEx.stationHolder.integer[unitId]
       
        if inst != 0 then
            call inst.initStation(u)
        endif
       
        if thistype.unitTypeList.integer.has(GetEventTransformType()) then
            call BuildSystemEx.decrement(uOwner, GetEventTransformType())
        endif
           
        if thistype.unitTypeList.integer.has(unitId) then
            call BuildSystemEx.increment(uOwner, unitId)
        endif
       
        set u = null
    endmethod
   
    private static method onPreDockHandler takes nothing returns nothing
        local unit station          = GetStation()
        local unit plug             = GetPlug()
       
        local integer stationId     = GetUnitTypeId(station)
        local integer stationHandle = GetHandleId(station)
        local integer plugId        = GetUnitTypeId(plug)
        local integer plugHandle    = GetHandleId(plug)
       
        local player plugOwner      = GetOwningPlayer(plug)
       
        local thistype temp         = BuildSystemEx.stationHolder.integer[stationId]
       
        //  Nothing to do here...
        if (temp == 0) then
            // Don't interfere with other stations...
            set plug    = null
            set station = null
           
            return
        endif
       
        if (temp.workerId != plugId) then
            call InterruptDocking()
           
            set plug    = null
            set station = null
           
            return
        endif
               
        if (GetOwningPlayer(plug) != GetOwningPlayer(station)) then
            call InterruptDocking()
            call SimError(plugOwner, "Cannot dock into a station owned by another player!")
           
            set plug    = null
            set station = null
        endif
       
        //  Unit is being upgraded. Don't allow docking!
        if BuildSystemEx.dataBase[7].player.has(stationHandle) then
            call InterruptDocking()
            call SimError(plugOwner, "Upgrade is in progress... you cannot dock at this moment.")
           
            set plug    = null
            set station = null
           
            return
        endif

        call ConditionalTriggerExecute(temp.onPredock)
       
        set plug    = null
        set station = null
    endmethod
   
    private static method onDockHandler takes nothing returns nothing
        local thistype temp = BuildSystemEx.stationHolder.integer[GetUnitTypeId(GetStation())]
       
        if temp != 0 then
            call ConditionalTriggerExecute(temp.onDock)
        endif
    endmethod
   
    private static method onUndockHandler takes nothing returns nothing
        local thistype temp = BuildSystemEx.stationHolder.integer[GetUnitTypeId(GetStation())]
       
        if temp != 0 then
            call ConditionalTriggerExecute(temp.onUndock)
        endif
    endmethod
   
    private static method initVar takes nothing returns nothing
        set thistype.unitTypeList   = Table.create()
        set thistype.unitOwner      = Table.create()
        set thistype.unitDeathFlag  = Table.create()
        set thistype.stationFlag    = Table.create()
       
        set thistype.unitTypeList.integer[0] = IntegerList.create()
    endmethod
   
    private static method initListener takes nothing returns nothing
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_START, function thistype.onConstructStart)
        //call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, function thistype.onConstructCancel)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function thistype.onConstructFinish)
       
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_UPGRADE_START, function thistype.onUpgradeStart)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_UPGRADE_CANCEL, function thistype.onUpgradeCancel)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_UPGRADE_FINISH, function thistype.onUpgradeFinish)
       
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_TRAIN_CANCEL, function thistype.onTrainCancel)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_TRAIN_FINISH, function thistype.onTrainFinish)
       
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onUnitDeath)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CHANGE_OWNER, function thistype.onUnitChangeOwner)
       
        call RegisterUnitIndexEvent(Condition(function thistype.onUnitEnter), EVENT_UNIT_INDEX)
        call RegisterUnitIndexEvent(Condition(function thistype.onUnitExit), EVENT_UNIT_DEINDEX)
       
        call RegisterNativeEvent(EVENT_ON_REINCARNATION_START, function thistype.onUnitDeath)
       
        call RegisterNativeEvent(EVENT_ON_RESURRECTION, function thistype.onUnitRevive)
        call RegisterNativeEvent(EVENT_ON_REINCARNATION_FINISH, function thistype.onUnitRevive)
        call RegisterNativeEvent(EVENT_ON_TRANSFORM, function thistype.onUnitTransform)
       
        call RegisterNativeEvent(EVENT_ON_PRE_DOCK, function thistype.onPreDockHandler)
        call RegisterNativeEvent(EVENT_ON_DOCK, function thistype.onDockHandler)
        call RegisterNativeEvent(EVENT_ON_UNDOCK, function thistype.onUndockHandler)       
    endmethod
   
    private static method init takes nothing returns nothing
        call thistype.initVar()
        call thistype.initListener()
    endmethod
   
    static method onGameStart takes nothing returns nothing
        local integer i = 1
        local player  p = Player(i - 1)
        local IntegerListItem firstIter = IntegerList(thistype.unitTypeList.integer[0]).first
        local IntegerListItem iter      = firstIter
       
        loop
            loop
                exitwhen iter == 0
               
                set BuildSystemEx.unitTypeLimit[i].integer[iter.data] = 0
                call SetPlayerTechMaxAllowed(p, iter.data, BuildSystemEx.unitTypeLimit[i].integer[iter.data])
               
                set iter = iter.next
            endloop
            set iter = firstIter
           
            exitwhen i >= bj_MAX_PLAYER_SLOTS
           
            set p = Player(i)
            set i = i + 1
        endloop
    endmethod
   
    implement Initializer
endstruct
   
//  This function is required. Don't edit this. (functionally private)
function InitTrig_CustomBuildSystem takes nothing returns nothing
    call ForForce(bj_FORCE_PLAYER[0], function BuildSystem.onGameStart)
endfunction

endlibrary

Look at the create method in BuildSystem as it is the only public struct.

All public methods are public there.

BuilderTrace (Requirement for CustomBuildSystem)
JASS:
library BuilderTrace /*

    */ requires /*
   
        *||------------||*
        */ UnitDex      /*
            -> TriggerHappy
               
            link: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
               
            #? WorldBounds
            #? GroupUtils
        *||------------||*
       
        *||------------||*   
        */ Table   /*   
            -> Bribe
               
            link: https://www.hiveworkshop.com/threads/snippet-new-table.188084/#post-1835068
        *||------------||*
           
        *||------------||*
        */ OrderMatrix  /*
            -> MyPad
               
            link: Not yet included, will do so in the future.
               
            # Table
           
        */ optional TimerUtils /*
            -> Preferably by Magtheridon96, Bribe, and Vexorian.
           
        UnitDex -> Uses GetUnitId instead of GetHandleId, which is nicer in performance
        overall.
       
        Table   -> Makes attaching data to stuff a breeze.
       
        OrderMatrix ->   Stores the relevant data of an issued order in an issued order event.
       
        optional TimerUtils -> Only attach data to timers, which will clear out the first
        builder of the building.

        ----------------------------
        |Legend:                    |
        |    - #     -> Requirement |
        |    - #?    -> Optional    |
        ----------------------------
       
        ------------------------------------
        |   BuilderTrace                    |
        |   -   By MyPad                    |
        ------------------------------------
       
        Track the first builder of any building.
       
        -------------------------------------------------
            Approach:
        -------------------------------------------------
       
        Simply enumerate units with the appropriate order id in order to get the builder.
        From there, filter out the closest unit with that order id.
       
        This runs on a CONSTRUCT_START event, so we are assured that buildings created
        via CreateUnit or by training don't get included by mistake.
       
        -------------------------------------------------
            Functions:
        -------------------------------------------------
       
        function GetBuilder(unit whichBuilding) returns unit
            - Returns the first builder of the building. Will return null if the unit
            hasn't fired the CONSTRUCT_START event or 0 seconds after CONSTRUCT_FINISH,
            if the unit has fired said event.
           
        function GetBuildingUnit() returns unit
            - A wrapper function for GetBuilder(GetTriggerUnit()) which behaves like a
            native of sorts.
    */

private keyword BuilderTraceM

private struct BuilderTrace extends array
    private static Table      isIdBuilding = 0
    private static TableArray builderTable = 0
   
static if not LIBRARY_TimerUtils then
    private static Table newTimerData = 0
endif

    private static group enum         = null
    private static integer buildingId = 0
   
    private static method getUnitDist takes unit u1, unit u2 returns real
        return SquareRoot( (GetUnitX(u1) - GetUnitX(u2))*(GetUnitX(u1) - GetUnitX(u2)) + (GetUnitY(u1) - GetUnitY(u2))*(GetUnitY(u1) - GetUnitY(u2)) )
    endmethod
   
    private static method isBuildingId takes unit u returns boolean
        local integer uId = GetUnitTypeId(u)
       
        if not isIdBuilding.boolean.has(uId) then
            set isIdBuilding.boolean[uId] = IsUnitType(u, UNIT_TYPE_STRUCTURE)
        endif
        return isIdBuilding.boolean[uId]
    endmethod
   
    private static method onOrderMatchMisc takes unit u returns boolean
        return (852013 >= GetUnitCurrentOrder(u) and GetUnitCurrentOrder(u) >= 852008) or GetUnitCurrentOrder(u) == 852147
    endmethod
   
    private static method onOrderMatch takes nothing returns boolean
        local unit filterUnit = GetFilterUnit()
        local boolean result  = (GetUnitCurrentOrder(filterUnit) == buildingId) or onOrderMatchMisc(filterUnit)
       
        set filterUnit = null
        return result
    endmethod
   
    private static method onEnter takes nothing returns nothing
        local unit u            = GetTriggerUnit()
        local unit iter         = null
        local unit builder      = null
       
        local real dist         = 0.
        local real minDist      = 999999.
       
        local real orderDist    = 0.
        local real minOrderDist = 999999.
       
        local real cx           = GetUnitX(u)
        local real cy           = GetUnitY(u)
       
        if thistype.isBuildingId(u) then
            set buildingId = GetUnitTypeId(u)
           
            call GroupEnumUnitsOfPlayer(thistype.enum, GetOwningPlayer(u), Filter(function thistype.onOrderMatch))
            loop
                set iter = FirstOfGroup(thistype.enum)
                exitwhen iter == null
               
                set dist = thistype.getUnitDist(u, iter)
                if dist < minDist then
                    set orderDist = SquareRoot((cx - OrderMatrix[iter].getTargetX())*(cx - OrderMatrix[iter].getTargetX()) + (cy - OrderMatrix[iter].getTargetY())*(cy - OrderMatrix[iter].getTargetY())) 
                    if orderDist < minOrderDist then
                        set builder = iter
                        set minDist = dist
                        set minOrderDist = orderDist
                       
                        exitwhen minOrderDist <= 0
                    endif
                endif
               
                call GroupRemoveUnit(thistype.enum, iter)
            endloop
        endif
       
        set thistype.builderTable[1].unit[GetUnitId(u)] = builder
               
        set builder = null
        set u       = null
    endmethod
   
    private static method onClearData takes nothing returns nothing
        local timer t       = GetExpiredTimer()
        local integer data
       
    static if LIBRARY_TimerUtils then
        set data = ReleaseTimer(t)
    else
        set data = thistype.newTimerData.integer[GetHandleId(t)]
       
        call thistype.newTimerData.integer.remove(GetHandleId(t))
        call DestroyTimer(t)
    endif
       
        call thistype.builderTable[1].unit.remove(data)
        call thistype.builderTable[2].boolean.remove(data)
       
        set t = null
    endmethod
   
    private static method onCancel takes nothing returns nothing
        local unit u    = GetTriggerUnit()
        local timer t
       
        if (GetTriggerEventId() != EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL) and (GetTriggerEventId() != EVENT_PLAYER_UNIT_CONSTRUCT_FINISH) then
            set u = GetIndexedUnit()
        endif
       
    static if LIBRARY_TimerUtils then
        if not thistype.builderTable[2].boolean.has(GetUnitId(u)) then
            set t = NewTimerEx(GetUnitId(u))
        endif
    else
        set t = CreateTimer()
        set thistype.newTimerData.integer[GetHandleId(t)] = GetUnitId(u)       
    endif
       
        if not thistype.builderTable[2].boolean[GetUnitId(u)] then
            call TimerStart(t, 0., false, function thistype.onClearData)
            set thistype.builderTable[2].boolean[GetUnitId(u)] = true
        endif
       
        set t = null
        set u = null
    endmethod
   
    private static method onFinish takes nothing returns nothing
        call thistype.onCancel()
    endmethod
   
    private static method initVars takes nothing returns nothing
        set thistype.isIdBuilding = Table.create()
        set thistype.builderTable = TableArray[3]
       
    static if not LIBRARY_TimerUtils then
        set thistype.newTimerData = Table.create()
    endif
   
        set thistype.enum         = CreateGroup()
    endmethod
   
    private static method initHandlers takes nothing returns nothing
        local trigger array t
       
        set t[0] = CreateTrigger()
        set t[1] = CreateTrigger()
        set t[2] = CreateTrigger()
       
        call TriggerRegisterAnyUnitEventBJ(t[1], EVENT_PLAYER_UNIT_CONSTRUCT_START)
        call TriggerRegisterAnyUnitEventBJ(t[0], EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL)
        call TriggerRegisterAnyUnitEventBJ(t[2], EVENT_PLAYER_UNIT_CONSTRUCT_FINISH)
       
        call TriggerRegisterUnitIndexEvent(t[0], EVENT_UNIT_DEINDEX)
       
        call TriggerAddCondition(t[1], function thistype.onEnter)
        call TriggerAddCondition(t[0], function thistype.onCancel)
        call TriggerAddCondition(t[2], function thistype.onFinish)
       
        set t[2] = null
        set t[1] = null
        set t[0] = null
    endmethod
   
    private static method init takes nothing returns nothing
        call initVars()       
        call initHandlers()
    endmethod
   
    static method getBuilder takes unit u returns unit
        return thistype.builderTable[1].unit[GetUnitId(u)]
    endmethod
   
    implement BuilderTraceM
endstruct

private module BuilderTraceM
    private static method onInit takes nothing returns nothing
        call init()
    endmethod
endmodule

function GetBuilder takes unit building returns unit
    return BuilderTrace.getBuilder(building)
endfunction

function GetBuildingUnit takes nothing returns unit
    return GetBuilder(GetTriggerUnit())
endfunction

endlibrary

OrderMatrix (Actually, it is already up and running here, so I'll just leave the link)
Link: Here
 
Last edited:
Status
Not open for further replies.
Top