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

Can somebody make a Regional/Leaderboard income system for me?

Status
Not open for further replies.
Level 2
Joined
Feb 25, 2008
Messages
9
I'm doing my best to create the trigger myself but I fail with triggers. Just need a simple Leaderboard income system based off how many regions you control. Control is based by who owns the territoy and to own it you must have units on it. Every 30 seconds you gain gold based on how many regions are under control.
 
Level 2
Joined
Feb 25, 2008
Messages
9
I will try a multiboard. Yes if you have units in a territory/region you gain gold, if your enemy is also in it then it's who has the most units in the territory/region. If nobody has units in the territory/region then it is neutral and gives nobody gold. I want people to invest in defending forces for my map.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
That only has one counter for all of the rects. He said that if a player has more units than the other unit, then that specific region should be given to a specific player with more units. I'm guessing if they each have the same amount of units, nobody should get income for that region until one unit from either team dies.

At no point do I see you counting any units of any players. This would also give two players gold in the case that they are both in the region, but that's not what he wanted.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
JASS:
library ZoneBasedIncome initializer Init 


globals
    private timer               incomeTimer     = CreateTimer()

    private group               unitGroupA      = CreateGroup()
    private group               unitGroupB      = CreateGroup()
    
    private player              filterPlayer    
    private integer             groupCounter   
    
    private IncomeZone array    Zone
    private integer             ZoneCount       = 0
    
endglobals


struct IncomeZone
    private player mOwner = null
    private integer incomeBase
    
    private region zoneRegion = null
    private rect zoneRect = null
    
    private integer array unitsOfPlayer [16]
    
    // static members:
    private static hashtable reftable = InitHashtable()
    private static trigger enterZone  = CreateTrigger()
    private static trigger leaveZone  = CreateTrigger()
    
    static method operator [] takes region r returns thistype
        return LoadInteger(reftable, GetHandleId(r), 0)
    endmethod
    
    // utility methods for zone ownership:
    //  @ IsOwned       - returns true if the zone is owned by a player
    //  @ SetOwner      - sets the owner of the zone
    //  @ GetOwner      - returns the owner of the zone
    //  @ ClearOwner    - removes the presence of an owner
    method IsOwned takes nothing returns boolean
        return (mOwner != null)
    endmethod
    method SetOwner takes player p returns nothing
        set mOwner = p
    endmethod
    method GetOwner takes nothing returns player
        return mOwner
    endmethod
    method ClearOwner takes nothing returns nothing
        set mOwner = null
    endmethod
    
    // getter for region/rect members:
    //      accesses the region/rect members of the IncomeZone.
    method GetRegion takes nothing returns region
        return zoneRegion
    endmethod
    method GetRect takes nothing returns rect
        return zoneRect
    endmethod
    
    // GiveIncome:
    //      adds the specified income to the owning player's gold pool
    method GiveIncome takes nothing returns nothing
        if (IsOwned()) then
            call SetPlayerState(mOwner, PLAYER_STATE_RESOURCE_GOLD, /*
                */ GetPlayerState(mOwner, PLAYER_STATE_RESOURCE_GOLD) + incomeBase)
        endif
    endmethod
    
    // GetPlayerWithMostUnits
    //      returns the player's id with the most amount of units in the zone
    //
    //      if the player with the most units has the same amount of units as
    //      someone else then returns -1. this covers the situation where the
    //      most units any player has in that zone is 0.
    method GetPlayerWithMostUnits takes nothing returns integer
        local integer i = 0
        local integer m = 0
        loop
            exitwhen (i == 16)
            if ((i != m) and (unitsOfPlayer[i] > unitsOfPlayer[m])) then
                set m = i 
            endif
            set i = i + 1
        endloop
        loop
            set i = i - 1
            if ((i != m) and (unitsOfPlayer[m] == unitsOfPlayer[i])) then
                return -1
            endif
            exitwhen (i == 0)
        endloop
        return m
    endmethod
    
    // constructor:
    //      creates a zone with a rect and an income base to be given to 
    //      owning players. the rect is converted to a region and registered to
    //      a trigger for enter/leave events.
    static method create takes rect zoneRect, integer incomeBase returns thistype
        local thistype zone = allocate()
        set zone.zoneRect   = zoneRect
        set zone.zoneRegion = CreateRegion()
        call RegionAddRect(zone.zoneRegion, zoneRect)
        call TriggerRegisterEnterRegion(enterZone, zone.zoneRegion, null)
        call TriggerRegisterLeaveRegion(enterZone, zone.zoneRegion, null)
        
        set zone.incomeBase = incomeBase
        call SaveInteger(reftable, GetHandleId(zone.zoneRegion), 0, zone)
        set Zone[ZoneCount] = zone
        set ZoneCount = ZoneCount + 1
        return zone
    endmethod
    
    
    // onEnterZone()
    //      executes whenever a unit enters a defined zone
    private static method onEnterZone takes nothing returns nothing
        local IncomeZone entered = IncomeZone[GetTriggeringRegion()]
        local integer playerId = GetPlayerId(GetOwningPlayer(GetTriggerUnit()))
        local integer owner
        
        set entered.unitsOfPlayer[playerId] = entered.unitsOfPlayer[playerId] + 1
        set owner = entered.GetPlayerWithMostUnits()
        
        if (owner != -1) then
            call entered.SetOwner(Player(owner))
        else
            call entered.ClearOwner()
        endif
    endmethod
    
    
    // onLeaveZone()
    //      executes whenever a unit leaves the zone
    private static method onLeaveZone takes nothing returns nothing
        local IncomeZone leaving = IncomeZone[GetTriggeringRegion()]
        local integer playerId = GetPlayerId(GetOwningPlayer(GetTriggerUnit()))
        local integer owner
        
        set leaving.unitsOfPlayer[playerId] = leaving.unitsOfPlayer[playerId] - 1
        set owner = leaving.GetPlayerWithMostUnits()
        
        if (owner != -1) then
            call leaving.SetOwner(Player(owner))
        else
            call leaving.ClearOwner()
        endif
    endmethod
    
    
    // initializer method:
    //      initializes components
    private static method onInit takes nothing returns nothing
        call TriggerAddAction(enterZone, function thistype.onEnterZone)
        call TriggerAddAction(leaveZone, function thistype.onLeaveZone)
    endmethod
endstruct




// function OnIncome
//      executes every timer interval to generate income for players
//      who are controlling zones. 
//
//      the control of each zone is based on the algorithms in the
//      IncomeZone struct which determine who actually owns the zone
//      in certain situations
private function OnIncome takes nothing returns nothing
    local integer i = 0 // loop through array of zones
    loop
        exitwhen (i == ZoneCount)
        if (Zone[i].IsOwned()) then
            call Zone[i].GiveIncome()
        endif
        set i = i + 1
    endloop
endfunction


// initialization:
//      initializes the income timer
private function Init takes nothing returns nothing
    call IncomeZone.create(gg_rct_Region_000, 5)
    call IncomeZone.create(gg_rct_Region_001, 5)
    call IncomeZone.create(gg_rct_Region_002, 5)
    call IncomeZone.create(gg_rct_Region_003, 5)

    call TimerStart(incomeTimer, 1, true, function OnIncome)
endfunction



endlibrary

That's what I got so far. Because I just have counters on enter/leave region (beats enumerations every time someone leaves/enters) it still counts dead units as units, which it should not. I'll have to do something about that still.

Oh, and to implement you would pretty much just do:
JASS:
call IncomeZone.create( gg_rct_Your_Region_001, 10 ) // the 10 represents the income gained for that territory

That would make your region named "Your Region 001" act as an income-zone, and supply the specified 10 income to the player who is in control of that territory when the income timer expires. This would typically be performed on map initialization unless you're having these zones dynamically created (which is good as long as you're not destroying them).

Something worth mentioning, you would probably only be able to have about 511 of these zones because they have an integer array of size 16 in each struct. This is to store the amount of units that are in that zone by any player from 1 to 16.

If you are planning on doing illogical things, the same region can be created as an IncomeZone[/b] but it will function as if there is only one income-zone there, because it uses the handle ID of the region as a base for the income zone.
 
Level 7
Joined
Apr 30, 2011
Messages
359
berb makes a system so fast 0_0 !!!
well, i hope he knows what you mean ~.~

and is it instant? or it needs some time before capturing the region?
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
It looks up the IncomeZone by associating it with the region (though the rect is also stored for other purposes) because the region is accessed in the event callback as GetTriggeringRegion. I did not see any such function for a Rect.

I also didn't mention where I got the rects from. They are actually the rects in the test-map that was posted above. If you just paste this in that test-map it will work. So no matter what you do, you've got to check out his stuff too : ) (Well actually you can totally get it to work without looking at his stuff but whatever)

JASS:
library ZoneBasedIncome initializer Init 


globals
    private timer               incomeTimer     = CreateTimer()

    private group               unitGroupA      = CreateGroup()
    private group               unitGroupB      = CreateGroup()
    
    private player              filterPlayer    
    private integer             groupCounter   
    
    private IncomeZone array    Zone
    private integer             ZoneCount       = 0
    
endglobals


struct IncomeZone
    private player mOwner = null
    private integer incomeBase
    
    private region zoneRegion = null
    private rect zoneRect = null
    
    private integer array unitsOfPlayer [16]
    
    // static members:
    private static hashtable reftable = InitHashtable()
    private static trigger enterZone  = CreateTrigger()
    private static trigger leaveZone  = CreateTrigger()
    private static trigger onDeath    = CreateTrigger()
    
    static method operator [] takes region r returns thistype
        return LoadInteger(reftable, GetHandleId(r), 0)
    endmethod
    
    // utility methods for zone ownership:
    //  @ IsOwned       - returns true if the zone is owned by a player
    //  @ SetOwner      - sets the owner of the zone
    //  @ GetOwner      - returns the owner of the zone
    //  @ ClearOwner    - removes the presence of an owner
    method IsOwned takes nothing returns boolean
        return (mOwner != null)
    endmethod
    method SetOwner takes player p returns nothing
        set mOwner = p
    endmethod
    method GetOwner takes nothing returns player
        return mOwner
    endmethod
    method ClearOwner takes nothing returns nothing
        set mOwner = null
    endmethod
    
    // getter for region/rect members:
    //      accesses the region/rect members of the IncomeZone.
    method GetRegion takes nothing returns region
        return zoneRegion
    endmethod
    method GetRect takes nothing returns rect
        return zoneRect
    endmethod
    
    // GiveIncome:
    //      adds the specified income to the owning player's gold pool
    method GiveIncome takes nothing returns nothing
        if (IsOwned()) then
            call SetPlayerState(mOwner, PLAYER_STATE_RESOURCE_GOLD, /*
                */ GetPlayerState(mOwner, PLAYER_STATE_RESOURCE_GOLD) + incomeBase)
        endif
    endmethod
    
    // GetPlayerWithMostUnits
    //      returns the player's id with the most amount of units in the zone
    //
    //      if the player with the most units has the same amount of units as
    //      someone else then returns -1. this covers the situation where the
    //      most units any player has in that zone is 0.
    method GetPlayerWithMostUnits takes nothing returns integer
        local integer i = 0
        local integer m = 0
        loop
            exitwhen (i == 16)
            if ((i != m) and (unitsOfPlayer[i] > unitsOfPlayer[m])) then
                set m = i 
            endif
            set i = i + 1
        endloop
        loop
            set i = i - 1
            if ((i != m) and (unitsOfPlayer[m] == unitsOfPlayer[i])) then
                return -1
            endif
            exitwhen (i == 0)
        endloop
        return m
    endmethod
    
    // CalculateOwner:
    //      calculates the player that should be in control of the zone
    //      based on the player with the most units in the zone
    //
    //      if there is no "highest" amount of units (two players are tied)
    //      then it becomes idle, owned by no one, otherwise that player
    //      is returned
    //
    //      the owner is set to the calculated value
    private method CalculateOwner takes nothing returns nothing
        local integer owner = GetPlayerWithMostUnits()
        if (owner != -1) then
            call SetOwner(Player(owner))
        else
            call ClearOwner()
        endif
    endmethod
    
    // constructor:
    //      creates a zone with a rect and an income base to be given to 
    //      owning players. the rect is converted to a region and registered to
    //      a trigger for enter/leave events.
    static method create takes rect zoneRect, integer incomeBase returns thistype
        local thistype zone = allocate()
        set zone.zoneRect   = zoneRect
        set zone.zoneRegion = CreateRegion()
        call RegionAddRect(zone.zoneRegion, zoneRect)
        call TriggerRegisterEnterRegion(enterZone, zone.zoneRegion, null)
        call TriggerRegisterLeaveRegion(enterZone, zone.zoneRegion, null)
        
        set zone.incomeBase = incomeBase
        call SaveInteger(reftable, GetHandleId(zone.zoneRegion), 0, zone)
        set Zone[ZoneCount] = zone
        set ZoneCount = ZoneCount + 1
        return zone
    endmethod
    
    
    // onEnterZone()
    //      executes whenever a unit enters a defined zone
    private static method onEnterZone takes nothing returns nothing
        local IncomeZone entered
        local integer playerId
        local integer owner
        
        if (not IsUnitType(GetTriggerUnit(), UNIT_TYPE_DEAD)) then
            set entered = IncomeZone[GetTriggeringRegion()]
            set playerId = GetPlayerId(GetOwningPlayer(GetTriggerUnit()))
            
            set entered.unitsOfPlayer[playerId] = entered.unitsOfPlayer[playerId] + 1
            call entered.CalculateOwner()
        endif
    endmethod
    
    
    // onLeaveZone()
    //      executes whenever a unit leaves the zone
    private static method onLeaveZone takes nothing returns nothing
        local IncomeZone leaving
        local integer playerId 
        local integer owner
        
        if (not IsUnitType(GetTriggerUnit(), UNIT_TYPE_DEAD)) then
            set leaving = IncomeZone[GetTriggeringRegion()]
            set playerId = GetPlayerId(GetOwningPlayer(GetTriggerUnit()))
        
            set leaving.unitsOfPlayer[playerId] = leaving.unitsOfPlayer[playerId] - 1
            call leaving.CalculateOwner()
        endif
    endmethod
    
    
    // onUnitDeath:
    //      i hope this doesn't cause too much overhead when units are dying
    //      in the region because it has to loop through the array of zones
    //      to determine if the unit is contained in any of them
    private static method onUnitDeath takes nothing returns nothing
        local integer i = 0 
        local integer owner
        local unit trigunit = GetTriggerUnit()
        loop
            exitwhen (i == ZoneCount) 
            if (IsUnitInRegion(Zone[i].GetRegion(), trigunit)) then
                // reduce the count of units owned by the owning player by one
                set Zone[i].unitsOfPlayer[GetPlayerId(GetOwningPlayer(trigunit))] = /*
                        */ Zone[i].unitsOfPlayer[GetPlayerId(GetOwningPlayer(trigunit))] - 1
                        
                // calculate new owner of the zone
                call Zone[i].CalculateOwner()
            endif
            set i = i + 1 // loop through zones
        endloop
        set trigunit = null
    endmethod
    
    
    // initializer method:
    //      initializes components
    private static method onInit takes nothing returns nothing
        call TriggerRegisterAnyUnitEventBJ(onDeath, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddAction(onDeath, function thistype.onUnitDeath)
    
        call TriggerAddAction(enterZone, function thistype.onEnterZone)
        call TriggerAddAction(leaveZone, function thistype.onLeaveZone)
    endmethod
endstruct




// function OnIncome
//      executes every timer interval to generate income for players
//      who are controlling zones. 
//
//      the control of each zone is based on the algorithms in the
//      IncomeZone struct which determine who actually owns the zone
//      in certain situations
private function OnIncome takes nothing returns nothing
    local integer i = 0 // loop through array of zones
    loop
        exitwhen (i == ZoneCount)
        if (Zone[i].IsOwned()) then
            call Zone[i].GiveIncome()
        endif
        set i = i + 1
    endloop
endfunction


// initialization:
//      initializes the income timer
private function Init takes nothing returns nothing
    call IncomeZone.create(gg_rct_Region_000, 5)
    call IncomeZone.create(gg_rct_Region_001, 5)
    call IncomeZone.create(gg_rct_Region_002, 5)
    call IncomeZone.create(gg_rct_Region_003, 5)

    call TimerStart(incomeTimer, 1, true, function OnIncome)
endfunction



endlibrary

Actually there is still a bug with that. It will subtract one from the counter when the unit dies in the region but then again when the unit is removed from the game (and leaves the region). Hm..

Oh, duh, I can just check it the unit is dead when it leaves. I'll put the filter on entering the region too so dead units being moved into the region do not count for anything. Thar ya go sonny!

Actually this leaves it open for units to be revived while they are in the region (which would not trigger the entering region event). If this happens, there are a few bugs that could happen. The unit could leave the region (which will subtract 1 even though it would have already been subtracted when the unit died) or the unit could stay in the region but would not count as if he were there at all.
 
Last edited:
Level 2
Joined
Feb 25, 2008
Messages
9
Ty for the help sorry if i didn't specify what kind of game the trigger will be used for. It is a trench warfare type map based on ww-1 so there will be no revives you simply hold the high ground for strategic/income purposes. And this game will deal with massive numbers of units. But they will also be dying in masses. The longer you hold trenches the more units you can produce. Don't really know anything about jass but I will attempt to implement it.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
there will be no revives

That solves one problem.

But they will also be dying in masses.

The amount of units dying shouldn't really be a problem. If you've got a metric shit-ton of regions then it may be a problem, because every time a unit dies it has to loop through each zone to determine if this unit was contained in that zone.

Checking to see if the unit is contained in that zone should be a really quick operation, so the only time consuming operation when a unit dies is looping through the existing zones. I could probably speed this operation up by including groups (not sure how fast IsUnitInGroup is though).

but I will attempt to implement it.

Just ask me if you need any help. The main implementation is simply calling these lines of custom script on map initialization:
  • Custom script: call IncomeZone.create(gg_rct_THE_REGION, 10)
In this example, your region would be named THE REGION, but to convert this to JASS you need to add gg_rct_ before it and replace spaces in the name with underscores. The value 10 is the amount of gold that will be given to that player.

Currently it just gives the specified amount of gold every second, but I can throw some constants at the top for you to play around with that if you want.
 
Level 2
Joined
Feb 25, 2008
Messages
9
The gold gained was going to increase on how much closer the trench is to the enemy, obviously the middle trench would be worth the most, but if you can gain an enemy trench way in the back it would highly benifit you.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
But those trenches are all laid out by you right? You know which ones are 25 and which ones are 5 or whatever. So when you're registering the regions using that custom script line I gave you just give your regions that are closer to the "middle" a higher value.

  • Custom script: call IncomeZone.create(gg_rct_FAR_REGION, 5)
  • Custom script: call IncomeZone.create(gg_rct_CLOSER_REGION, 15)
  • Custom script: call IncomeZone.create(gg_rct_MIDDLE_REGION, 30)
Where your names are FAR REGION, CLOSER REGION, and MIDDLE REGION. You can obviously change these to names that you like.

I can only hope that you use Jass NewGen Pack.
 
instead of this...
JASS:
private function Init takes nothing returns nothing
    call IncomeZone.create(gg_rct_Region_000, 5)
    call IncomeZone.create(gg_rct_Region_001, 5)
    call IncomeZone.create(gg_rct_Region_002, 5)
    call IncomeZone.create(gg_rct_Region_003, 5)

    call TimerStart(incomeTimer, 1, true, function OnIncome)
endfunction

why dont you make it like this...
JASS:
function RegisterRegion takes rect r, integer income returns nothing
    call IncomeZone.create(r, income)
endfunction

private function Init takes nothing returns nothing
    call TimerStart(incomeTimer, 1, true, function OnIncome)
endfunction
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
What's the difference?

Oh, I guess it makes the Init function for the system automatically initialize them. If you were saying that I should split the two up, I most definitely should.
 
Status
Not open for further replies.
Top