Custom Resources 1.31

This bundle is marked as approved. It works and satisfies the submission rules.
Custom Resource System v1.30


You are no longer limited to gold and lumber. This system expand resources to unlimited number.
All custom resources are based on human lumber harvesting.
I made this system as a reply to UmbraUnda request.

Requirements:
GUI Unit Event v2.4.0.0
Order Ids
Player Resource Monitoring v1.01

Installation:
  • Copy&Paste unit "DummyTreeChecker" from demo-map. It should have abilities: 'Ahrl' 'Avul' 'Aloc'
  • Copy&Paste destructable "DummyTree" from demo-map.
  • Copy&Paste whole category "CustomResources" from demo-map.
  • Configuration is done in "CustomResourcesConfig" trigger.
  1. Prepare 1 harvest ability for *each* custom resource (for normal lumber-also). Base it on 'Ahrl' "Harvest (Ghouls Lumber)". You must use diffrent harvest ability for each custom resource.
    1. Prepare workers (max99 for each resource), returning-structures (max99 for each resource) and destructables based on tree (max99 for each resource). All registered workers should have one harvest ability from this system set it in ObjectEditor. If worker is able to harvest more then 1 resource, then this ability you set in OE will be default (after training/creating). All registered Buildings must have ability "Return Lumber" 'Arlm' or "Return Gold and Lumber" 'Argl'
    2. You can use the same Worker(s) in many custom resources.
    3. You can use the same Building(s) in many custom resources.
    4. You cannot use the same destructable in more then one custom resource.
  2. "ResourceWOOD" is obligatory trigger. It must exist. Define there all trees that will be harvest-able for lumber.
  3. Next triggers (4 in demo map) are examples. Every trigger represents one custom resource. Follow instruction inside triggers.
  4. Example1: To have only 1 custom resource: Set ability/workers/structures/destructables in "ResourceWOOD" trigger,rename "ResourceMUSHROOMS", set "Resource_Number=2" and set ability/workers/structures/destructables.Delete triggers "ResourceSTONES", "ResourceICEROCKS", "ResourcePLANTS".
  5. Example2: To have 5 custom resources:Edit existing triggers "ResourceWood/Mushrooms/Stones/Icerocks/Plants".Copy&paste "ResourcePLANTS" trigger. Rename it. Set "Resource_Number=5" and set ability/workers/structures/destructables inside this new trigger. Done.

Available functions:
AdjustPlayerCustomResource(player <p>, integer <resourceNumber>, integer <value>) returns nothing
use negative <value> to subtract or positive to add resource
GetPlayerCustomResource(player <p>, integer <resourceNumber>) returns integer
<resourceNumber> is an integer used in configuration ResourceSOMENAME triggers in first line (Set Resource_Number = X)

"Custom resource has been changed EVENT"
In GUI trigger use as event "Resource_Event" becomes equal to X (where X is Resource_Number defined in triggers "ResourceSOMENAME").
Variables available inside trigger: "Resource_Player" - owner whos resource nr X has been changed, value is: "Resource_Value" and "Resource_Number" - resource's number (defined in triggers "ResourceSOMENAME")

Tip: in game, to change workers ability to harvest diffrent resource-type: right-click on resource (left-click and order "harvest" targeted on new destructable-type is blocked).

Additionaly library "CustomResource" offers boolean variable that returns true for building under construction. Use like this:
if udg_isBuildingUnderConstruction[Custom Value of Unit] then



When worker brings resources to building he is ordered to "harvest", This order is issued excatly at the same time when player's lumber has changed. This is our event "worker brings resources". There is dummy tree in case if there are not resources left nearby (and then worker wont be ordered, dummy tree prevents that). I am manipulating and following worker orders)
There is one small problem that I cannot fix:
imagine that worker is harvesting, he is full and start walking to nearby valid delivery-structure (he is redirected by the system). If that structure get destroyed then worker will be internally redirected to closest player's structure with ability (Arlm) or (Argl). Resource-type will counted fine, only returning structure may be not valid

Addon library CustomResourcesCost
Allows to set prices for: training / building / upgrades / researches
This library does not support custom prices for item sell / unit sell.

Installation and configuration
Copy 2 triggers: "CustomResourcesCostConfig" and "CustomResourcesCost"
All configuration is done inside trigger "CustomResourcesCostConfig".
If you're going to use not more then 10 custom resources - use custom script. In this demo-map there are 4 custom resources (don't count normal WOOD): Mushrooms/Stones/Icerocks/Plants. They have numbers: Mushrooms-2/Stones-3/Icerocks-4/Plants-5.
For 4 resources to configure cost use function CR_SaveCost4, (for 6 resources use CR_SaveCost6, etc.)
So unit cost is set using 2 lines:
Set Resource_CostUnit = Peasant
Custom Script: call CR_SaveCost4(1, 0, 0, 1)
First CR_SaveCost4 parameter is Mushrooms price, then Stones price, Icerocks price and Plants price.
So Peasant costs: 1 Mushroom(number2), 0 Stones(number3), 0 Icerocks(number4) and 1 Plant(number5).
(there is alternative configuration trigger for users who needs more then 10 custom resources)

LIBRARY CustomResources configuration GUI trigger
  • CustomResourcesConfig
    • Events
      • Game - UnitIndexEvent becomes Equal to -1.00
    • Conditions
    • Actions
      • -------- Common configuration for whole CustomResources system --------
      • -------- ---------------------------- --------
      • -------- used to check if destructable is a tree, please copy from demo map, unit should have 3 abilities: 'Ahrl' 'Avul' 'Aloc' --------
      • Set Resource_Dummy = DummyTreeChecker
      • -------- ---------------------------- --------
      • -------- please copy this tree from demo map --------
      • Set Resource_DummyTree = DummyTree (CustomResourceSystem)
      • -------- ---------------------------- --------
      • -------- if "true" game will print warnings such as "returning building not found" & "unit cannot harvest this destructable" & "not enough resources" --------
      • Set Resource_PrintWarnings = True
      • -------- ---------------------------- --------
      • -------- if "true" game will print developing-mode messages ( for checking the system) --------
      • Set Resource_PrintMsgDeveloping = False
      • -------- ---------------------------- --------
      • -------- End of configuration --------
      • -------- ---------------------------- --------
      • -------- Please, don't touch following lines --------
      • Custom script: set g_resourceUnitTreeChecker = CreateUnit(Player(15), udg_Resource_Dummy, 0.00, 0.00, 0.00)
      • Custom script: call ShowUnit(g_resourceUnitTreeChecker, false)
      • -------- ---------------------------- --------
      • -------- Don't enable the following 4 lines as they exist simply to make copying the system easier --------
      • Set Resource_Event = 0.00
      • Set Resource_Player = Player 1 (Red)
      • Set Resource_Value = 0
      • Set isBuildingUnderConstruction[0] = False
LIBRARY CustomResources
JASS:
// CustomResources library requires: 
    // 1. Player Resources Monitoring ver 1.01
    // https://www.hiveworkshop.com/threads/player-resource-monitoring-v1-01.276158/
    // 2. GUI Unit Event by Bribe, version 2.4.0.0
    // https://www.hiveworkshop.com/threads/gui-unit-event-v2-4-0-0.201641/
    // 3. OrderList
    // https://www.thehelper.net/threads/order-ids.148097/

// Installation:
    // Copy&Paste unit "DummyTreeChecker" from demo-map. It should have abilities: 'Ahrl' 'Avul' 'Aloc'
    // Copy&Paste destructable "DummyTree" from demo-map.
    // Copy&Paste whole category "CustomResources" from demo-map.
    // Configuration is done in "CustomResourcesConfig" trigger. There is nothing to configure here.

    // Prepare 1 harvest ability for *each* custom resource (for normal lumber-also). Base it on 'Ahrl' "Harvest (Ghouls Lumber)"
    // You must use diffrent harvest ability for each custom resource.
    // Prepare workers (max99 for each resource), returning-structures (max99 for each resource) and 
    // destructables based on tree (max99 for each resource).

    // All registered workers should have one harvest ability from this system set it in ObjectEditor.
    // If worker is able to harvest more then 1 resource, then this ability you set in OE will be default (after training/creating).
    // All registered Buildings must have ability "Return Lumber" 'Arlm' or "Return Gold and Lumber" 'Argl'

    // You can use the same Worker(s) in many custom resources.
    // You can use the same Building(s) in many custom resources.
    // You -->cannot<-- use the same destructable in more then one custom resource.

    // "ResourceWOOD" is obligatory trigger. It must exist. Define there all trees that will be harvest-able for lumber.
    // Next triggers (4 in demo map) are examples. Edit them freely, add new triggers (as many as you want). 
    // Every trigger represents one resource. Follow instruction inside triggers.
    // Example1: To have only 1 custom resource:
    // Set ability/workers/structures/destructables in "ResourceWOOD" trigger
    // Rename "ResourceMUSHROOMS", use "Resource_Number=2" and set ability/workers/structures/destructables.
    // delete triggers "ResourceSTONES", "ResourceICEROCKS", "ResourcePlants".

    // Example2: To have 5 custom resources:
    // Edit existing triggers "ResourceWood/Mushrooms/Stones/Icerocks/Plants".
    // Copy/paste "ResourcePLANTS" trigger. Rename it. Set "Resource_Number=5" and 
    // set ability/workers/structures/destructables inside this new trigger. Done.

// Available functions:
    // AdjustPlayerCustomResource(player <p>, integer <resourceNumber>, integer <value>) returns nothing
        // use negative <value> to subtract or positive to add resource
    // GetPlayerCustomResource(player <p>, integer <resourceNumber>) returns integer
        // <resourceNumber> is an integer used in configuration ResourceSOMENAME triggers in first line (Set Resource_Number = X)

    // "Custom resource has been changed EVENT" 
    // use as event "Resource_Event" becomes equal to X (where X is Resource_Number defined in triggers "ResourceSOMENAME")
    // variables available inside trigger: "Resource_Player" - owner whos resource nr X has been changed, value is: "Resource_Value" and
    // "Resource_Number" - resource's number (defined in triggers "ResourceSOMENAME")

// Tip: in game, to change workers ability to harvest diffrent resource-type: 
// right-click on resource (left-click and order "harvest" targeted on new destructable-type is blocked)

// Additionaly library "CustomResource" offers boolean variable that returns true for building under construction. Use like this:
    // if udg_isBuildingUnderConstruction[Custom Value of Unit] then ...
    
library CustomResources initializer Init uses ORDER //v1.31 25-12-2017
globals
    public hashtable               g_hashCR
    private integer array           g_harvestAbility
    public integer                 g_customResourcesCount=0
    unit                    g_resourceUnitTreeChecker=null
    private real array              g_timeStampLastLumberRecived    // [player id]
    private integer array           g_lastLumberRecivedValue        // [player id]
    private timer                   g_stopWorkerTimer = CreateTimer()
    private timer                   g_gameTimer = CreateTimer()    
    private group                   g_workerToStopGroup = CreateGroup()
    private group                   g_workerRedirectGroup = CreateGroup()
    private timer                   g_workerRedirectTimer = CreateTimer()
    private real array            g_X // [GetUnitUserData(u)]
    private real array            g_Y // [GetUnitUserData(u)]
    private integer array           g_workerHarvestAbi              // [GetUnitUserData(u)]
    private unit                    g_closestStructure = null
    private unit                    g_returningWorker = null
    private real                    g_tempDistance2 = 0.00
    private group array             g_deliveryStructureGroup
    private destructable array      g_connectedDummyTree            // [structure's UnitUserData]
    public trigger                 g_trg_WorkerBringsResources = CreateTrigger()
    private trigger                 g_trg_ResumeharvestingRedirect = CreateTrigger()
    private trigger                 g_trg_DummyTreeDies = CreateTrigger()
    //hashtable keys:
    private constant integer    KEY_100 = 100 // total workers quantity, connected with harvest ability (abilityId as a parentKey)
    private constant integer    KEY_200 = 200 // total destructables quantity, connected with harvest ability (abilityId as a parentKey)
    private constant integer    KEY_300 = 300 // total structures quantity, connected with harvest ability (abilityId as a parentKey)
    private constant integer    KEY_400 = 400 // for saving abilityId able to harvest given destructable (destructableId as parentKey)
    private constant integer    KEY_401 = 401 // for saving boolean, as a sign that destructable is registered (destructableId as parentKey)
    private constant integer    KEY_410 = 410 // for saving boolean, as a sign that worker is registered (workerId as parentKey)
    private constant integer    KEY_420 = 420 // for saving integer udg_Resource_Number set up at ResourceXXXXX triggers (abilityId as a parentKey)
    private constant integer    KEY_430 = 430 // for saving boolean, as a sign that structure is registered (structureId as parentKey)
    private constant integer    KEY_500 = 500 // for saving each players custom resource value (abilityId as a parentKey)
    //used in "CustomResourcesCost" addon:
    public constant integer    KEY_600 = 600 // for saving prices, used as 600 + resourceNumber, (objectId as parentKey)
                                                                        // remember resource2 cost at 602 key, resource3 at 603 key, .. etc
    public constant integer    KEY_601 = 601 // for saving boolean, as a sign that object is registered (objectId as parentKey)
        

endglobals
//=============================================================

function MsgD takes string s returns nothing //for developing mode messages
    if udg_Resource_PrintMsgDeveloping then
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10.00, s)
    endif
endfunction
function Msg takes player p, string s returns nothing //warning messages
    if udg_Resource_PrintWarnings then
        call DisplayTextToPlayer(p, 0.00, 0.00, s)
    endif
endfunction

private function OnConstruct_Start_Cond takes nothing returns boolean
    set udg_isBuildingUnderConstruction[GetUnitUserData(GetConstructingStructure())]=true
    return false
endfunction

private function OnConstruct_Finish_Cond takes nothing returns boolean
    set udg_isBuildingUnderConstruction[GetUnitUserData(GetConstructedStructure())]=false
    return false
endfunction

private function OnBuilding_Enters_Cond takes nothing returns boolean
    if IsUnitType(udg_UDexUnits[udg_UDex], UNIT_TYPE_STRUCTURE) then
        set udg_isBuildingUnderConstruction[udg_UDex]=false
    endif
    return false
endfunction
//=============================================================

private function InitSettingGUIVariables takes nothing returns nothing // run at init
    local integer x=1
    loop
        exitwhen x>99
        set udg_Resource_Worker[x]=0
        set udg_Resource_Destructable[x]=0
        set udg_Resource_Structure[x]=0
        set x=x+1
    endloop
endfunction

//---------------------------------------------------------------------------------------
private function InitResource_SaveWorkers takes nothing returns nothing
    local integer x=1
    loop
        exitwhen udg_Resource_Worker[x]==0 or x>99
        call SaveInteger(g_hashCR, udg_Resource_Ability, KEY_100+x, udg_Resource_Worker[x]) // remember 1st worker "id" at 101 key
        call SaveBoolean(g_hashCR, udg_Resource_Worker[x], KEY_410, true) // save "true" as sign that worker is registered in this system
        set x=x+1
    endloop
    // abilityId as a parentKey , KEY_420 -- abilityId Number (integer)
    call SaveInteger(g_hashCR, udg_Resource_Ability, KEY_420, udg_Resource_Number)
            
    //save total workers quantity at "100 key" for this ability
    call SaveInteger(g_hashCR, udg_Resource_Ability, KEY_100, x-1) 
    //reset:
    loop
        exitwhen x==0
        set udg_Resource_Worker[x]=0
        set x=x-1
    endloop
endfunction
//--------------------------------------------------------------------------------
private function InitResource_SaveDestructables takes nothing returns nothing
    local integer x=1
    loop
        exitwhen udg_Resource_Destructable[x]==0 or x>99
        call SaveInteger(g_hashCR, udg_Resource_Ability, KEY_200+x, udg_Resource_Destructable[x]) // remember 1st destruct at 201 key
        call SaveInteger(g_hashCR, udg_Resource_Destructable[x], KEY_400, udg_Resource_Ability) // remember abiID to harvest this destr
        call SaveBoolean(g_hashCR, udg_Resource_Destructable[x], KEY_401, true) // for IsDestructableRegistered
        set x=x+1
    endloop
    //save total destructables quantity at "200 key" for this ability
    call SaveInteger(g_hashCR, udg_Resource_Ability, KEY_200, x-1) 
    //reset:
    loop
        exitwhen x==0
        set udg_Resource_Destructable[x]=0
        set x=x-1
    endloop
endfunction
//--------------------------------------------------------------------------------
private function InitResource_SaveStructures takes nothing returns nothing
    local integer x=1
    loop
        exitwhen udg_Resource_Structure[x]==0 or x>99
        call SaveInteger(g_hashCR, udg_Resource_Ability, KEY_300+x, udg_Resource_Structure[x]) // remember 1st structure at 301 key
        call SaveBoolean(g_hashCR, udg_Resource_Structure[x], KEY_430, true) // saves boolean for registered structures
        set x=x+1
    endloop
    //save total structures quantity at "300 key" for this ability
    call SaveInteger(g_hashCR, udg_Resource_Ability, KEY_300, x-1) 
    //reset:
    loop
        exitwhen x==0
        set udg_Resource_Structure[x]=0
        set x=x-1
    endloop
endfunction
//--------------------------------------------------------------------------------
function InitResource takes nothing returns nothing
    set g_harvestAbility[udg_Resource_Number] = udg_Resource_Ability
    if udg_Resource_Number > g_customResourcesCount then
        set g_customResourcesCount = udg_Resource_Number
    endif
    set g_deliveryStructureGroup[udg_Resource_Number] = CreateGroup()
    call InitResource_SaveWorkers()
    call InitResource_SaveDestructables()
    call InitResource_SaveStructures()
endfunction
//=============================================================

//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------    

/*    
HASHTABLE KEYS

    KEY_100 -- workers quantity (abilityId as a parentKey)
    101--199 - workers Id's (abilityId as a parentKey)
    
    KEY_200 -- destructables quantity (abilityId as a parentKey)
    201--299 - destructables Id's (abilityId as a parentKey)
    
    KEY_300 -- structures quantity (abilityId as a parentKey)
    301--399 - structures Id's (abilityId as a parentKey)
    
    KEY_400 -- destructableId as parentKey --> remembers abilityId to harvest this destructabe
    call SaveInteger(g_hashCR, destructableId, KEY_400, abiId) --> set abiId = LoadInteger(g_hashCR, destructableId, KEY_400)
    
    KEY_401 -- destructableId as parentKey --> remembers boolean true as sign that destructabe is registered in system
    call SaveBoolean(g_hashCR, destructableId, KEY_401, true) --> if LoadBoolean(g_hashCR, destructableId, KEY_401) then ...
    
    KEY_410 -- workerId as parentKey -- > saves boolean true for registered workers in this system
    call SaveBoolean(g_hashCR, workerId, KEY_410, true) -->  if LoadBoolean(g_hashCR, workerId, KEY_410) then ...

    abilityId as a parentKey
    KEY_420 -- abilityId Number - integer: udg_Resource_Number (set up at ResourceXXXXX triggers by a user)

    KEY_430 -- structureId as parentKey -- > saves boolean true for registered structures in this system
    call SaveBoolean(g_hashCR, structureId, KEY_430, true) --> if LoadBoolean(g_hashCR, structureId, KEY_430) then ...
    
    abilityId as a parentKey
    KEY_500+playerId => 500 ..to.. 511 - each players resource value
    
    //for Addon "CustomResourcesCost"
    KEY_601 -- units/structure/research/upgradeBuilding/items ID as parentKey -- > saves boolean true for registered objects for prices purposes
    call SaveBoolean(g_hashCR, objectId, KEY_601, true) --> if LoadBoolean(g_hashCR, objectId, KEY_601) then ...
    KEY_600+resourceNumber => 602 .. and up for prices -- object ID as parent key

*/
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
function IsTree takes destructable d returns boolean
    return (IssueTargetOrderById(g_resourceUnitTreeChecker, ORDER_harvest, d)) and (IssueImmediateOrderById(g_resourceUnitTreeChecker, ORDER_stop))
endfunction
//------------------------------------------------------------------------------------

//------------------------------------------------------------------------------------
// is harvest allowed by ability worker currently has:
//------------------------------------------------------------------------------------
private function IsHarvestAllowed takes unit u, integer destructableId returns boolean
    local integer x=1
    local integer abi = g_workerHarvestAbi[GetUnitUserData(u)] // ability unit has to harvest
    local integer count = LoadInteger(g_hashCR, abi, KEY_200) // gives total number of destructables quantity at 200 key
    loop
        exitwhen x>count
        if LoadInteger(g_hashCR, abi, KEY_200+x) == destructableId then
            return true
        endif
        set x=x+1
    endloop    
    return false
endfunction
//----------------------------------------------------------------------------------
// can worker change ability to new one
//----------------------------------------------------------------------------------
private function IsAbilityAllowedForWorker takes integer abi, integer workerId returns boolean
    local integer x=1
    local integer count = LoadInteger(g_hashCR, abi, KEY_100) // total number of workers at 100 key
    loop
        exitwhen x>count
        if LoadInteger(g_hashCR, abi, KEY_100+x) == workerId then
            return true
        endif
        set x=x+1
    endloop
    return false
endfunction
//----------------------------------------------------------------------------------
private function ChangeAbilityToHarvestDestructable takes unit u, integer destructableId returns boolean
    local integer abi = LoadInteger(g_hashCR, destructableId, KEY_400) //-- > gives abiId able to harvest given destructable
    local integer x=1
    
    if abi>0 and IsAbilityAllowedForWorker(abi, GetUnitTypeId(u)) then //can unit have this abi?
        
        call AddUnitAnimationProperties(u, "Lumber", false)
        set g_workerHarvestAbi[GetUnitUserData(u)] = abi
        
        loop //remove ALL other harvest abils
            exitwhen x>g_customResourcesCount
            call UnitRemoveAbility(u, g_harvestAbility[x])
            set x=x+1
        endloop
        call UnitAddAbility(u, abi)
        return true
    endif
    return false
endfunction
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
private function IsWorkerRegistered takes integer workerId returns boolean
    return LoadBoolean(g_hashCR, workerId, KEY_410)
endfunction
//----------------------------------------------------------------------------------
private function IsDestructableRegistered takes integer destructableId returns boolean
    return LoadBoolean(g_hashCR, destructableId, KEY_401)
endfunction
//----------------------------------------------------------------------------------
private function IsStructureRegistered takes integer structureId returns boolean
    return LoadBoolean(g_hashCR, structureId, KEY_430)
endfunction
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// returns udg_Resource_Number set up at ResourceXXXXX triggers by user    
private function GetHarvestAbilityNumber takes integer workerCurentAbi returns integer
    return LoadInteger(g_hashCR, workerCurentAbi, KEY_420)  
endfunction

//-----------------------------------------------------------------------------------------------------------
//------------------------------DELIVERY STRUCTURES---------------------------------------------------
//-----------------------------------------------------------------------------------------------------------

private function RemoveDeliveryStructureFromAllGroups takes unit structure returns nothing
    local integer c=1
    loop //loop over all groups
        exitwhen c>g_customResourcesCount
        call GroupRemoveUnit(g_deliveryStructureGroup[c], structure)
        set c=c+1
    endloop
endfunction
//----------------------------------------------------------------------------------
private function AddDeliveryStructureToGroups takes unit structure returns nothing
    local integer structureId = GetUnitTypeId(structure)
    local integer abi
    local integer count
    local integer x
    local integer c=1
    
    loop //loop over all harvest abiliies
        exitwhen c>g_customResourcesCount
        //inside loop begin
        set abi = g_harvestAbility[c]
        set count = LoadInteger(g_hashCR, abi, KEY_300) //  total number of structures for this abi (at 300 key)
        set x=1
        loop
            exitwhen x>count
            if LoadInteger(g_hashCR, abi, KEY_300+x) == structureId then
                call GroupAddUnit(g_deliveryStructureGroup[c], structure)
            endif
            set x=x+1
        endloop
        //inside loop end
        set c=c+1
    endloop
endfunction
//----------------------------------------------------------------------------------
private function IsDeliveryStructureValid takes unit u, unit structure returns boolean
    local integer abi = g_workerHarvestAbi[GetUnitUserData(u)] // current worker's ability to harvest
    local integer nr = GetHarvestAbilityNumber(abi)
    return IsUnitInGroup(structure, g_deliveryStructureGroup[nr])
endfunction

//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
//this not for a user:
private function AdjustPlayerCustomResource_ByAbility takes integer playerId, integer abi, integer value returns nothing
    local integer resourceNr = GetHarvestAbilityNumber(abi)
    local integer currentValue = LoadInteger(g_hashCR, abi, KEY_500+playerId)
    local integer newValue = currentValue+value
    if newValue<0 then //resource level cannot be negative
        set newValue=0
    endif
    call SaveInteger(g_hashCR, abi, KEY_500+playerId, newValue)
    // here FIRE EVENT (changed 22-12-2017)
    set udg_Resource_Player = Player(playerId)
    set udg_Resource_Value = value
    set udg_Resource_Number = resourceNr
    
    set udg_Resource_Event = 0.00
    set udg_Resource_Event = I2R(resourceNr)
    set udg_Resource_Event = 0.00
endfunction
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
//for user: (use negative "value" to subtract or positive to add resource)
function AdjustPlayerCustomResource takes player p, integer resourceNumber, integer value returns nothing
    call AdjustPlayerCustomResource_ByAbility(GetPlayerId(p), g_harvestAbility[resourceNumber], value)
endfunction
//----------------------------------------------------------------------------------
//for user:
function GetPlayerCustomResource takes player p, integer resourceNumber returns integer
    local integer abi = g_harvestAbility[resourceNumber]
    return LoadInteger(g_hashCR, abi, KEY_500+GetPlayerId(p))
endfunction


//===========================================================================
//===========================================================================
//-------------------------------TRIGGERS RELATED FUNCTIONS--------------------------------------------------------------
//===========================================================================
//===========================================================================

private function Trig_LumberChanged takes nothing returns nothing // "udg_PRM_EVENT", EQUAL, 2.00
    local integer i = GetPlayerId(udg_PRM_Player)
    if udg_PRM_Change > 0 then    
    //it fires as 1st, after this - "order harvest"  (or other) runs
    //remember time-stamp for this player and value:
    set g_timeStampLastLumberRecived[i] = TimerGetElapsed(g_gameTimer)
    set g_lastLumberRecivedValue[i] = udg_PRM_Change
    call MsgD("(PRM) time stamp: " + R2S(TimerGetElapsed(g_gameTimer)) + ", " + GetPlayerName(udg_PRM_Player) + ", value: " + I2S(udg_PRM_Change))
    endif
endfunction
//===========================================================================
// this is an EVENT "worker brings resources"
// we got unit "u", g_lastLumberRecivedValue[playerId], and g_workerHarvestAbi[GetunitUserData(u)] - integer abiId
private function Trig_WorkerBringsResources_Cond takes nothing returns boolean
    //if peasant recive ANY order like: harvest dummyTree, harvest normal resources , 
    //or player queued any order (stop, attack, move, etc) then:
    if TimerGetElapsed(g_gameTimer) == g_timeStampLastLumberRecived[GetPlayerId(GetOwningPlayer(GetOrderedUnit()))] then
        return IsWorkerRegistered(GetUnitTypeId(GetOrderedUnit()))
        // ordered unit is 'last lumber supplier'
    endif
    return false
endfunction
//------------------------------------------------------------------------------------
private function Trig_WorkerBringsResources_Act takes nothing returns nothing
    local unit u = GetOrderedUnit()
    local player pla = GetOwningPlayer(u)
    local integer i = GetPlayerId(pla)
    local integer abi = g_workerHarvestAbi[GetUnitUserData(u)]
    local integer resourceNr = GetHarvestAbilityNumber(abi) // new <---
    if not (abi == g_harvestAbility[1]) then // it is *custom resource* - subtract from normal wood!
        set udg_PRM_FireEvent = false
        call SetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER)-g_lastLumberRecivedValue[i])
        set udg_PRM_FireEvent = true
        call AdjustPlayerCustomResource_ByAbility(i, abi, g_lastLumberRecivedValue[i]) // writes value in hash (we're not writing "normal lumber")
        call MsgD(GetUnitName(u) + " brings resource nr(" + I2S(resourceNr) +"), value: " + I2S(g_lastLumberRecivedValue[i]) + ", time stamp: " + R2S(TimerGetElapsed(g_gameTimer)))
    endif //FIRE EVENT - MOVED 22-12-2017
    set u=null
    set pla=null
endfunction
//===========================================================================
private function StopWorker_Enum takes nothing returns nothing
    call IssueImmediateOrderById(GetEnumUnit(), ORDER_stop)
    call GroupRemoveUnit(g_workerToStopGroup, GetEnumUnit())
endfunction
private function StopWorker takes nothing returns nothing
    call DisableTrigger(g_trg_WorkerBringsResources) // protect agains double fire "worker brings resource" event
    call ForGroup(g_workerToStopGroup, function StopWorker_Enum)
    call EnableTrigger(g_trg_WorkerBringsResources)
endfunction
//--------------------------------------------------------------------------------
private function Trig_HarvestSmartOffset_Cond takes nothing returns boolean
    local integer ord=GetIssuedOrderId() // ORDER_OFFSET when building right-cick on destructable and trained worker start walking
    if ord==ORDER_harvest or ord==ORDER_smart or ord==ORDER_OFFSET then // 851970
        return GetOrderTargetDestructable() != null and IsWorkerRegistered(GetUnitTypeId(GetOrderedUnit()))
    endif
    return false
endfunction
//--------------------------------------------------------------------------------
private function Trig_HarvestSmartOffset_Act takes nothing returns nothing
    local unit u = GetOrderedUnit()
    local integer destructableId = GetDestructableTypeId(GetOrderTargetDestructable())
    local integer ord = GetIssuedOrderId()
    if ord==ORDER_harvest and (not IsHarvestAllowed(u, destructableId)) then
        // does current ability allow to harvest this destr? if no, then stop worker
        call GroupAddUnit(g_workerToStopGroup, u)
        call TimerStart(g_stopWorkerTimer, 0.00, false, function StopWorker)
    endif
    //  851970 --> when worker trained with rally-point set up on tree
    // 851970 or "smart" order --> need to change ability harvest for diffrent destructables
    if (ord==ORDER_smart or ord==ORDER_OFFSET) and IsTree(GetOrderTargetDestructable()) then
        if (not IsHarvestAllowed(u, destructableId)) then
            if not ChangeAbilityToHarvestDestructable(u, destructableId) then
                call Msg(GetOwningPlayer(u), GetUnitName(u) + " |cffff0000cannot|r harvest " + GetDestructableName(GetOrderTargetDestructable()))
                call GroupAddUnit(g_workerToStopGroup, u)
                call TimerStart(g_stopWorkerTimer, 0.00, false, function StopWorker)
            endif
        endif
    endif
    set u=null
endfunction
//---------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------
private function Trig_PointOrderHarvestSmartOffset_Cond takes nothing returns boolean
    local integer ord=GetIssuedOrderId() // ORDER_OFFSET = 851970 when building right-cick on destructable and trained worker start walking
    return (ord==ORDER_harvest or ord==ORDER_smart or ord==ORDER_OFFSET) and IsWorkerRegistered(GetUnitTypeId(GetOrderedUnit()))
endfunction
//---------------------------------------------------------------------------------------------------------------
private function RedirectWorker_Enum takes nothing returns nothing    
    call IssuePointOrder(GetEnumUnit(), "smart", g_X[GetUnitUserData(GetEnumUnit())], g_Y[GetUnitUserData(GetEnumUnit())]) //attack-move
    call GroupRemoveUnit(g_workerRedirectGroup, GetEnumUnit())
endfunction
private function RedirectWorker takes nothing returns nothing
    call ForGroup(g_workerRedirectGroup, function RedirectWorker_Enum)
endfunction
//------------------------------------
private function Trig_PointOrderHarvestSmartOffset_Act takes nothing returns nothing
    local unit u=GetOrderedUnit()
    if not IsVisibleToPlayer(GetOrderPointX(), GetOrderPointY(), GetOwningPlayer(u)) then
        set g_X[GetUnitUserData(u)]=GetOrderPointX()
        set g_Y[GetUnitUserData(u)]=GetOrderPointY()
        call GroupAddUnit(g_workerRedirectGroup, u)
        call TimerStart(g_workerRedirectTimer, 0.00, false, function RedirectWorker)
    endif
    set u=null
endfunction

//===========================================================================
private function FindClosestStructure_Enum takes nothing returns nothing
    local unit u = GetEnumUnit()
    local real dx = GetUnitX(g_returningWorker)-GetUnitX(u)
    local real dy = GetUnitY(g_returningWorker)-GetUnitY(u)
    local real dist2 = SquareRoot(dx * dx + dy * dy)
    if GetOwningPlayer(u)==GetOwningPlayer(g_returningWorker) and (not udg_isBuildingUnderConstruction[GetUnitUserData(u)]) then
        if dist2 < g_tempDistance2 then
            set g_tempDistance2 = dist2
            set g_closestStructure = u
        endif
    endif
    set u=null
endfunction
//-----------------------------------------------------------------------------
private function FindClosestStructure takes unit worker returns nothing
    local integer id = GetUnitUserData(worker)
    local integer nr = GetHarvestAbilityNumber(g_workerHarvestAbi[id])
    set g_tempDistance2 = 999999.00
    set g_closestStructure = null
    set g_returningWorker = worker
    call ForGroup(g_deliveryStructureGroup[nr], function FindClosestStructure_Enum)
endfunction
//-----------------------------------------------------------------------------
private function Trig_ResumeharvestingRedirect_Cond takes nothing returns boolean
    return GetIssuedOrderId()==ORDER_resumeharvesting and IsWorkerRegistered(GetUnitTypeId(GetOrderedUnit()))
endfunction
//-----------------------------------------------------------------------------
// when button "return resources" clicked  OR  auto come-back with resources
private function Trig_ResumeharvestingRedirect_Act takes nothing returns nothing
    local unit u = GetOrderedUnit()
    local integer id = GetUnitUserData(u)
    call FindClosestStructure(u) // it writes global "g_closestStructure" as a valid building to deliver resources
    if g_closestStructure != null then
        call DisableTrigger(g_trg_ResumeharvestingRedirect) // this trigger
        if IssueTargetOrderById(u, ORDER_resumeharvesting, g_closestStructure) then//and udg_Resource_PrintMsgDeveloping then
            call MsgD("Redirecting " + GetUnitName(u) + " to " + GetUnitName(g_closestStructure))
        endif
        call EnableTrigger(g_trg_ResumeharvestingRedirect)
    else //no valid structure..
        call Msg(GetOwningPlayer(u), GetUnitName(u) + ": |cffffff00 No proper building found to return. |r")
        call GroupAddUnit(g_workerToStopGroup, u)
        call TimerStart(g_stopWorkerTimer, 0.00, false, function StopWorker)
    endif
    set u=null
endfunction
//===========================================================================
private function Trig_ReturnSmartRestrictions_Cond takes nothing returns boolean
    if GetIssuedOrderId() == ORDER_smart then
        if GetOrderTargetUnit() != null and IsWorkerRegistered(GetUnitTypeId(GetOrderedUnit())) then
            return GetOwningPlayer(GetOrderedUnit()) == GetOwningPlayer(GetOrderTargetUnit())
        endif
    endif
    return false
endfunction
//-------------------------------------------------------------------------------------
//if player give order "smart" on Worker and if building is not valid to take resources then convert smart-->move order
private function Trig_ReturnSmartRestrictions_Act takes nothing returns nothing
    local unit u = GetOrderedUnit()
    local unit target = GetOrderTargetUnit()
    //check if worker is returning resources (it may also be order repair/finish construction)
    call DisableTrigger(g_trg_ResumeharvestingRedirect)
    if IssueTargetOrderById(u, ORDER_resumeharvesting, target) then //he has resources
        if (not IsDeliveryStructureValid(u, target)) then
            call IssueTargetOrderById(u, ORDER_move, target)
        endif
    endif
    call EnableTrigger(g_trg_ResumeharvestingRedirect)
    set u=null
    set target=null
endfunction
//===========================================================================
private function Trig_WorkerStructureCreated_Cond takes nothing returns boolean
    local integer unitId = GetUnitTypeId(udg_UDexUnits[udg_UDex])
    return IsStructureRegistered(unitId) or IsWorkerRegistered(unitId)
endfunction
//------------------------------------------------------------------------------------
private function Trig_WorkerStructureCreated_Act takes nothing returns nothing
    local integer x=1
    local integer unitId = GetUnitTypeId(udg_UDexUnits[udg_UDex])
    if IsStructureRegistered(unitId) then //BUILDING CREATED:
        //find x/y for DummyTree : item (Wirt's Leg) version
        set bj_lastCreatedItem = CreateItem('wtlg', GetUnitX(udg_UDexUnits[udg_UDex]), GetUnitY(udg_UDexUnits[udg_UDex]))
        set g_connectedDummyTree[udg_UDex] = CreateDestructable(udg_Resource_DummyTree, GetItemX(bj_lastCreatedItem), GetItemY(bj_lastCreatedItem), 0.00, 1, 0)
        call RemoveItem(bj_lastCreatedItem)        
        call TriggerRegisterDeathEvent(g_trg_DummyTreeDies, g_connectedDummyTree[udg_UDex]) //add event to DummyTreeDies
        call AddDeliveryStructureToGroups(udg_UDexUnits[udg_UDex]) //add to delivery group    
    elseif IsWorkerRegistered(unitId) then //WORKER CREATED:
        //------------------------------------------------------------------------------
        // all registered workers should have one harvest ability from this system (set in OE)
        //-----------------------------------------------------------------------------
        loop
            exitwhen x>g_customResourcesCount // loop over all harvest abilities and set variable
            if GetUnitAbilityLevel(udg_UDexUnits[udg_UDex], g_harvestAbility[x])>0 then
                set g_workerHarvestAbi[udg_UDex] = g_harvestAbility[x]
                return
            endif
            set x=x+1
        endloop
    endif
endfunction
//===========================================================================
private function Trig_StructureUpgraded_Act takes nothing returns nothing // runs when structure *finishes* upgrade
    call RemoveDeliveryStructureFromAllGroups(udg_UDexUnits[udg_UDex]) //remove from groups    
    if IsStructureRegistered(GetUnitTypeId(udg_UDexUnits[udg_UDex])) then // is upgraded building registered?
        call AddDeliveryStructureToGroups(udg_UDexUnits[udg_UDex]) // add this new upgraded building    
        //if connected dummy TREE is dead/none (means previous structure was not registered)
        if g_connectedDummyTree[udg_UDex]==null or GetDestructableLife(g_connectedDummyTree[udg_UDex]) <= 0 then
            //find x/y for DummyTree : item version
            set bj_lastCreatedItem = CreateItem('wtlg', GetUnitX(udg_UDexUnits[udg_UDex]), GetUnitY(udg_UDexUnits[udg_UDex]))
            set g_connectedDummyTree[udg_UDex] = CreateDestructable(udg_Resource_DummyTree, GetItemX(bj_lastCreatedItem), GetItemY(bj_lastCreatedItem), 0.00, 1, 0)
            call RemoveItem(bj_lastCreatedItem)            
            call TriggerRegisterDeathEvent(g_trg_DummyTreeDies, g_connectedDummyTree[udg_UDex]) //add event to DummyTreeDies
        endif    
    else // New (upgraded) structure is not registered --> remove connected dummy TREE
        call RemoveDestructable(g_connectedDummyTree[udg_UDex])
    endif
endfunction
//===========================================================================
private function Trig_RegisteredStructureDies_Cond takes nothing returns boolean
    if IsStructureRegistered(GetUnitTypeId(GetDyingUnit())) then
        call RemoveDeliveryStructureFromAllGroups(GetDyingUnit()) //remove from all groups
        call RemoveDestructable(g_connectedDummyTree[GetUnitUserData(GetDyingUnit())]) //remove connected dummy TREE
    endif
    return false
endfunction
//===========================================================================
private function Trig_DummyTreeDies_Actions takes nothing returns nothing
    call DestructableRestoreLife(GetDyingDestructable(), GetDestructableMaxLife(GetDyingDestructable()), false) //resurect
endfunction
//===========================================================================



//===========================================================================
//===========================================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i=0
    loop
        set g_timeStampLastLumberRecived[i] = 0.00
        set i=i+1
        exitwhen i==bj_MAX_PLAYERS //12
    endloop
    set udg_Resource_Event = 0.00

    set g_hashCR = InitHashtable()
    call TimerStart(g_gameTimer, 36000.00, false, null)
    call InitSettingGUIVariables()
    //moved to the trigger "CustomResourcesConfig"
    //set udg_UnitIndexerEnabled = false
    //set g_resourceUnitTreeChecker = CreateUnit(Player(15), udg_Resource_Dummy, 0.00, 0.00, 0.00)
    //set udg_UnitIndexerEnabled = true
    //call ShowUnit(g_resourceUnitTreeChecker, false)

    //--------for udg_isBuildingUnderConstruction check  [GetUnitUserData(u)] ------------
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_START)
    call TriggerAddCondition(t, Condition(function OnConstruct_Start_Cond))
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH)
    call TriggerAddCondition(t, Condition(function OnConstruct_Finish_Cond))
    set t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
    call TriggerAddCondition(t, Condition(function OnBuilding_Enters_Cond))
    //----for lumber changed trigger -----------------------------------------------------
    set t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_PRM_EVENT", EQUAL, 2.00)
    call TriggerAddAction(t, function Trig_LumberChanged)
    //-----------for worker-brings-resources event --------------------------------------
    call TriggerRegisterAnyUnitEventBJ(g_trg_WorkerBringsResources, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    call TriggerRegisterAnyUnitEventBJ(g_trg_WorkerBringsResources, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    call TriggerRegisterAnyUnitEventBJ(g_trg_WorkerBringsResources, EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(g_trg_WorkerBringsResources, Condition(function Trig_WorkerBringsResources_Cond))
    call TriggerAddAction(g_trg_WorkerBringsResources, function Trig_WorkerBringsResources_Act)
    // ----------harvest destructable, orders: harvest/smart/offset ---------------------
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    call TriggerAddCondition(t, Condition(function Trig_HarvestSmartOffset_Cond))
    call TriggerAddAction(t, function Trig_HarvestSmartOffset_Act)
    // point order on destructable - if point not visible redirect order to "smart" to prevent auto-harvest on not-valid destructables
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    call TriggerAddCondition(t, Condition(function Trig_PointOrderHarvestSmartOffset_Cond))
    call TriggerAddAction(t, function Trig_PointOrderHarvestSmartOffset_Act)
    
    //------ for order resumeharvesting  -> redirect -------------------------------------
    call TriggerRegisterAnyUnitEventBJ(g_trg_ResumeharvestingRedirect, EVENT_PLAYER_UNIT_ISSUED_ORDER) // when button "return resources" clicked
    call TriggerRegisterAnyUnitEventBJ(g_trg_ResumeharvestingRedirect, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER) // auto come-back    
    call TriggerAddCondition(g_trg_ResumeharvestingRedirect, Condition(function Trig_ResumeharvestingRedirect_Cond))
    call TriggerAddAction(g_trg_ResumeharvestingRedirect, function Trig_ResumeharvestingRedirect_Act)
    // if player give order "smart" on Worker and if building is not valid to take resources then convert smart-->move order
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    call TriggerAddCondition(t, Condition( function Trig_ReturnSmartRestrictions_Cond))
    call TriggerAddAction(t, function Trig_ReturnSmartRestrictions_Act)
    // worker / building created -----------------------------------------------------------
    set t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
    call TriggerAddCondition(t, Condition( function Trig_WorkerStructureCreated_Cond))
    call TriggerAddAction(t, function Trig_WorkerStructureCreated_Act)
    // building transformed (upgraded) ---------------------------------------------------
    set t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_UnitTypeEvent", EQUAL, 1.00)
    call TriggerAddAction(t, function Trig_StructureUpgraded_Act)
    // building dies ------------------------------------------------------------------------
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
    call TriggerAddCondition(t, Condition( function Trig_RegisteredStructureDies_Cond))
    // dummy tree dies -------------------------------------------------------------------
    set g_trg_DummyTreeDies = CreateTrigger()
    call TriggerAddAction(g_trg_DummyTreeDies, function Trig_DummyTreeDies_Actions)
    
    call MsgD("Init in library CS finished succefully")
    
    set t=null
endfunction
endlibrary

obligatory config triger for normal lumber harvesting:
  • ResourceWOOD
    • Events
      • Game - UnitIndexEvent becomes Equal to -1.00
    • Conditions
    • Actions
      • -------- THIS TRIGGER IS RESTRICTED TO "NORMAL WARCRAFT3 LUMBER HARVESTING" --------
      • -------- ---------------------------- --------
      • -------- start from "1", in next triggers use 2, 3, 4, 5 etc, no gaps please --------
      • Set Resource_Number = 1
      • Set Resource_Name[Resource_Number] = Lumber
      • -------- ---------------------------- --------
      • -------- do NOT use once declared harvest ability in other custom resources --------
      • Set Resource_Ability = Harvest_1 (trees)
      • -------- ---------------------------- --------
      • -------- start from "1", no gaps please --------
      • -------- you can use the same Worker in many custom resources --------
      • Set Resource_Worker[1] = Peasant
      • Set Resource_Worker[2] = Blood Mage
      • -------- ---------------------------- --------
      • -------- start from "1", no gaps please --------
      • -------- do NOT use once declared destructable in other custom resources --------
      • Set Resource_Destructable[1] = Summer Tree Wall
      • Set Resource_Destructable[2] = Cityscape Winter Tree Wall
      • Set Resource_Destructable[3] = Cityscape Snowy Tree Wall
      • -------- ---------------------------- --------
      • -------- start from "1", no gaps please --------
      • -------- you can use the same Building in many custom resources --------
      • Set Resource_Structure[1] = Town Hall
      • Set Resource_Structure[2] = Keep
      • Set Resource_Structure[3] = Castle
      • -------- ---------------------------- --------
      • -------- ---------------------------- --------
      • Custom script: call InitResource()

example custom resource configuration trigger:
  • ResourceMUSHROOMS
    • Events
      • Game - UnitIndexEvent becomes Equal to -1.00
    • Conditions
    • Actions
      • Set Resource_Number = 2
      • Set Resource_Name[Resource_Number] = Mushrooms
      • -------- ---------------------------- --------
      • -------- do NOT use once declared harvest ability in other custom resources --------
      • Set Resource_Ability = Harvest_2 (mushrums)
      • -------- ---------------------------- --------
      • -------- start from "1", no gaps please --------
      • Set Resource_Worker[1] = Peasant
      • Set Resource_Worker[2] = Priest
      • Set Resource_Worker[3] = Blood Mage
      • -------- ---------------------------- --------
      • -------- start from "1", no gaps please --------
      • Set Resource_Destructable[1] = Blue Mushrooms
      • Set Resource_Destructable[2] = Green Mushrooms
      • -------- ---------------------------- --------
      • -------- start from "1", no gaps please --------
      • Set Resource_Structure[1] = Keep
      • -------- ---------------------------- --------
      • -------- ---------------------------- --------
      • Custom script: call InitResource()
example "Custom resource has changed" event
  • UpdateLeaderboard
    • Events
      • Game - Resource_Event becomes Equal to 2.00
      • Game - Resource_Event becomes Equal to 3.00
      • Game - Resource_Event becomes Equal to 4.00
      • Game - Resource_Event becomes Equal to 5.00
    • Conditions
    • Actions
      • -------- there are 3 variables available to read --------
      • -------- "Resource_Player" - owner who recieved custom resource --------
      • -------- "Resource_Value" - how many resources have been delivered --------
      • -------- "Resource_Number" - delivered resource's number (defined in triggers "ResourceNAME"). It is usefull if you're using multiple events - like in this example. --------
      • -------- --- --------
      • Custom script: set udg_tempInt = GetPlayerCustomResource(Player(0), 2)
      • Leaderboard - Change the value for Player 6 (Orange) in (Last created leaderboard) to tempInt
      • Custom script: set udg_tempInt = GetPlayerCustomResource(Player(0), 3)
      • Leaderboard - Change the value for Player 1 (Red) in (Last created leaderboard) to tempInt
      • Custom script: set udg_tempInt = GetPlayerCustomResource(Player(0), 4)
      • Leaderboard - Change the value for Player 2 (Blue) in (Last created leaderboard) to tempInt
      • Custom script: set udg_tempInt = GetPlayerCustomResource(Player(0), 5)
      • Leaderboard - Change the value for Player 5 (Yellow) in (Last created leaderboard) to tempInt
      • -------- --- --------
      • -------- --- --------
      • Game - Display to (All players) the text: ((DemoTrigger) Player + ((Name of Resource_Player) + ( gets + ((String(Resource_Value)) + ( + (Resource_Name[Resource_Number] + <Empty String>))))))

configuration trigger for addon library CustomResourcesCost
  • CustomResourcesCostConfig
    • Events
      • Game - UnitIndexEvent becomes Equal to -1.00
    • Conditions
    • Actions
      • -------- UPGRADE --------
      • Set Resource_CostUnit = Keep
      • Custom script: call CR_SaveCost4(0, 0, 4, 0)
      • -------- ---------------------------- --------
      • -------- RESEARCH --------
      • Set Resource_CostResearch = Iron Forged Swords
      • Custom script: call CR_SaveCost4(0, 7, 1, 0)
      • -------- ---------------------------- --------
      • -------- UNIT TRAIN --------
      • Set Resource_CostUnit = Peasant
      • Custom script: call CR_SaveCost4(1, 0, 0, 1)
      • -------- ---------------------------- --------
      • Set Resource_CostUnit = Dragonhawk Rider
      • Custom script: call CR_SaveCost4(3, 0, 0, 2)
      • -------- ---------------------------- --------
      • -------- BUILD STRUCTURE --------
      • Set Resource_CostUnit = Farm
      • Custom script: call CR_SaveCost4(5, 3, 2, -5)
      • -------- ---------------------------- --------
      • -------- BUILD STRUCTURE (USING ITEM TINY FARM/etc) --------
      • Set Resource_CostItem = Tiny Farm
      • Custom script: call CR_SaveCost4(5, 3, 2, -5)
      • -------- ---------------------------- --------
addon library CustomResourcesCost
JASS:
// This is an addon to "CustomResources" library.
// Allows to set prices for: training / building / upgrades / researches
// This library does not support custom prices for item sell / unit sell.
//
// Installation and configuration
// Copy 2 triggers: "CustomResourcesCostConfig" and "CustomResourcesCost"
// All configuration is done inside trigger "CustomResourcesCostConfig".
// If you're going to use not more then 10 custom resources - use custom script
// in this demo-map there are 4 custom resources (don't count normal WOOD): Mushrooms/Stones/Icerocks/Plants
// they have numbers: Mushrooms-2/Stones-3/Icerocks-4/Plants-5
// for 4 resources to configure cost use function CR_SaveCost4, (for 6 resources use CR_SaveCost6, etc.)
// So unit cost is set using 2 lines:
// Set Resource_CostUnit = Peasant
// Custom Script: call CR_SaveCost4(1, 0, 0, 1)
// First CR_SaveCost4 parameter is Mushrooms price, then Stones price, Icerocks price and Plants price.
// So Peasant costs: 1 Mushroom(number2), 0 Stones(number3), 0 Icerocks(number4) and 1 Plant(number5).
//
// (there is alternative configuration trigger for users who needs more then 10 custom resources)
//
//=========================================================
library CustomResourcesCosts initializer Init uses CustomResources
//------------------------------------------------------------------------------------------------
globals
    private group                       g_groupCancel = CreateGroup()
    private timer                        g_timerCancel = CreateTimer()
    private integer array           g_upgradeTarget
    trigger                     g_triggerMultiOrderCancel = CreateTrigger()
endglobals
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
    // this Addon uses 2 hashtable keys
    // KEY_600 => for saving prices, used as 600 + resourceNumber, (objectId as parentKey)
                        // remember resource2 cost at 602 key, resource3 at 603 key, .. etc
    // KEY_601 => for saving boolean, as a sign that object is registered (objectId as parentKey)
//===============================================================================
//experimental version of saving cost 25-12-2017
function CR_SaveCost10 takes integer v2, integer v3, integer v4, integer v5, integer v6, integer v7, integer v8, integer v9, integer v10, integer v11 returns nothing
    local integer x=2 // we do not start from "1" as 1 is normal-wood 
    local integer parentKey // check if it's object or research or item?
    local integer array cost
    set cost[2]=v2
    set cost[3]=v3
    set cost[4]=v4
    set cost[5]=v5
    set cost[6]=v6
    set cost[7]=v7
    set cost[8]=v8
    set cost[9]=v9
    set cost[10]=v10
    set cost[11]=v11
    
    if udg_Resource_CostUnit>0 then
        set parentKey = udg_Resource_CostUnit
    elseif udg_Resource_CostResearch>0 then
        set parentKey = udg_Resource_CostResearch
    elseif udg_Resource_CostItem>0 then
        set parentKey = udg_Resource_CostItem        
    endif
    call SaveBoolean(CustomResources_g_hashCR, parentKey, CustomResources_KEY_601, true) // save "true" as sign that object is registered
    loop
        exitwhen x>CustomResources_g_customResourcesCount
        call SaveInteger(CustomResources_g_hashCR, parentKey, CustomResources_KEY_600+x, cost[x]) 
        // remember resource2 cost at 602 key, resource3 at 603 key, .. etc
        set x=x+1
    endloop    
    //reset integers for next call:
    set udg_Resource_CostUnit = 0
    set udg_Resource_CostResearch = 0    
    set udg_Resource_CostItem = 0    
endfunction
//-------------------------------------------------------------------------------------------------------------------------
function CR_SaveCost9 takes integer v2, integer v3, integer v4, integer v5, integer v6, integer v7, integer v8, integer v9, integer v10 returns nothing
    call CR_SaveCost10(v2,v3,v4,v5,v6,v7,v8,v9,v10,0)
endfunction
function CR_SaveCost8 takes integer v2, integer v3, integer v4, integer v5, integer v6, integer v7, integer v8, integer v9 returns nothing
    call CR_SaveCost10(v2,v3,v4,v5,v6,v7,v8,v9,0,0)
endfunction
function CR_SaveCost7 takes integer v2, integer v3, integer v4, integer v5, integer v6, integer v7, integer v8 returns nothing
    call CR_SaveCost10(v2,v3,v4,v5,v6,v7,v8,0,0,0)
endfunction
function CR_SaveCost6 takes integer v2, integer v3, integer v4, integer v5, integer v6, integer v7 returns nothing
    call CR_SaveCost10(v2,v3,v4,v5,v6,v7,0,0,0,0)
endfunction
function CR_SaveCost5 takes integer v2, integer v3, integer v4, integer v5, integer v6 returns nothing
    call CR_SaveCost10(v2,v3,v4,v5,v6,0,0,0,0,0)
endfunction
function CR_SaveCost4 takes integer v2, integer v3, integer v4, integer v5 returns nothing
    call CR_SaveCost10(v2,v3,v4,v5,0,0,0,0,0,0)
endfunction
function CR_SaveCost3 takes integer v2, integer v3, integer v4 returns nothing
    call CR_SaveCost10(v2,v3,v4,0,0,0,0,0,0,0)
endfunction
function CR_SaveCost2 takes integer v2, integer v3 returns nothing
    call CR_SaveCost10(v2,v3,0,0,0,0,0,0,0,0)
endfunction
function CR_SaveCost1 takes integer v2 returns nothing
    call CR_SaveCost10(v2,0,0,0,0,0,0,0,0,0)
endfunction


//-------------------------------------------------------------------------------------------------------------------------
function CustomResource_SaveCosts takes nothing returns nothing
    local integer x=2 // we do not start from "1" as 1 is normal-wood 
    local integer parentKey
    //is it object or research or item?
    if udg_Resource_CostUnit>0 then
        set parentKey = udg_Resource_CostUnit
    elseif udg_Resource_CostResearch>0 then
        set parentKey = udg_Resource_CostResearch
    elseif udg_Resource_CostItem>0 then
        set parentKey = udg_Resource_CostItem        
    endif
    
    call SaveBoolean(CustomResources_g_hashCR, parentKey, CustomResources_KEY_601, true) // save "true" as sign that object is registered
    loop
        exitwhen x>CustomResources_g_customResourcesCount
        call SaveInteger(CustomResources_g_hashCR, parentKey, CustomResources_KEY_600+x, udg_Resource_Cost[x]) 
        // remember resource2 cost at 602 key, resource3 at 603 key, .. etc
        //reset for next object:
        set udg_Resource_Cost[x] = 0
        set x=x+1
    endloop    
    //reset integers:
    set udg_Resource_CostUnit = 0
    set udg_Resource_CostResearch = 0    
    set udg_Resource_CostItem = 0    
endfunction
//------------------------------------------------------------------------------------------------
private function IsCustomCostDefined takes integer objectId returns boolean // object: unit/structure/upgrade-building/research
    return LoadBoolean(CustomResources_g_hashCR, objectId, CustomResources_KEY_601)
endfunction
//------------------------------------------------------------------------------------------------
private function GetObjectCost takes integer objectId, integer resourceNumber returns integer
    // resourceNumber = defined in ResourceMUSHROOM and so on triggers, as udg_Resource_Number
    // will not return value for resourceNumber=1 as it is normal war3 wood!
    return LoadInteger(CustomResources_g_hashCR, objectId, CustomResources_KEY_600+resourceNumber)
endfunction


//=========================================================
//------------------------TRIGGERS  RELATED---------------------------------------------------
//=========================================================

//------------------------------------------------------------------------------
private function CancelOrder_Enum takes nothing returns nothing
    local unit u = GetEnumUnit()
    call GroupRemoveUnit(g_groupCancel, u)
    call IssueImmediateOrderById(u, 851976) //upgrades , researches , trains
    call IssueImmediateOrderById(u, ORDER_stop) //for builders
    set u=null
endfunction

private function CancelOrder takes nothing returns nothing
    call DisableTrigger(g_triggerMultiOrderCancel)
    call DisableTrigger(CustomResources_g_trg_WorkerBringsResources) // protect agains double fire "worker brings resource" event
    call ForGroup(g_groupCancel, function CancelOrder_Enum)
    call EnableTrigger(CustomResources_g_trg_WorkerBringsResources)
    call EnableTrigger(g_triggerMultiOrderCancel)        
endfunction

private function UnitCancelOrder takes unit u returns nothing
    call GroupAddUnit(g_groupCancel, u)
    call TimerStart(g_timerCancel, 0.00, false, function CancelOrder)
endfunction
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
private function PlayerHasMoneyForObject takes player pla, integer objectId returns boolean
    local integer x=2
    local integer cost=0
    local string s = " "
    local boolean hasMoney = true
    loop
        exitwhen x>CustomResources_g_customResourcesCount
        set cost = GetObjectCost(objectId, x)
        if cost>0 then
            if cost <= GetPlayerCustomResource(pla, x) then
                set s = s + ("|cff00ff00"+udg_Resource_Name[x] + "|r(" + I2S(cost) + ") ") // green color
            else // not enough resource nr x
                set hasMoney=false
                set s = s + ("|cffff0000"+udg_Resource_Name[x] + "|r(" + I2S(cost) + ") ") // red color
            endif
        endif
        set x=x+1
    endloop
    
    if (not hasMoney) then//and udg_Resource_PrintWarnings then
        call Msg(pla, "Not enough resources: " + s)
    endif
    return hasMoney
endfunction
//-------------------------------------------------------------------------------------------
private function PlayerPayPriceForObject takes player pla, integer objectId returns nothing
    local integer x=2
    local integer cost=0
    loop
        exitwhen x>CustomResources_g_customResourcesCount
        set cost = GetObjectCost(objectId, x)
        //if cost>0 then
            call AdjustPlayerCustomResource(pla, x, - cost)
        //endif
        set x=x+1
    endloop
    call MsgD("CustomCost "+GetPlayerName(pla) + " just payed price for " + GetObjectName(objectId))
endfunction
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
private function Trig_MultiOrder_Cond takes nothing returns boolean
    return IsCustomCostDefined(GetIssuedOrderId())
endfunction
//------------------------------------------------------------------------------
private function Trig_MultiOrder_Act takes nothing returns nothing
    local integer h = GetHandleId(GetTriggerEventId())
    local unit u = GetOrderedUnit()
    local player pla = GetOwningPlayer(u)
    local integer ord = GetIssuedOrderId()
    local integer id = GetUnitUserData(u)
    
    if h==38 then // instant: TRAIN  UNIT or RESEARCH or UPGRADE
            if not PlayerHasMoneyForObject(pla, ord) then //no $$$ ?
                call UnitCancelOrder(u)
            else //has $?, pay price:
                set g_upgradeTarget[id] = ord //if it is UPGRADE save target-building (ord) as variable under number id
                call PlayerPayPriceForObject(pla, ord)
            endif
    
    elseif h==39 and (UnitId2String(ord) != null) then // point-order: BUILD  STRUCTURE
            if not PlayerHasMoneyForObject(pla, ord) then //no $$$ ?
                call UnitCancelOrder(u)
                call MsgD("CustomCost "+GetUnitName(u) + " canceled build structure - no money" )
            endif // paying is executed under event EVENT_PLAYER_UNIT_CONSTRUCT_START
    endif
    
    set u=null
    set pla=null
endfunction

//----------------------------------------------------------------------------------
//---------------------------trigger Begins Construction -------------------------
//----------------------------------------------------------------------------------
private function Trig_BeginsConstr_Cond takes nothing returns boolean                 
    return IsCustomCostDefined(GetUnitTypeId(GetConstructingStructure()))
endfunction
//------------------------------------------------------------------
private function Trig_BeginsConstr_Act takes nothing returns nothing
    local unit u = GetConstructingStructure()
    local player pla = GetOwningPlayer(u)
    local integer objectId = GetUnitTypeId(u)
    if not PlayerHasMoneyForObject(pla, objectId) then //no $$$ ?
        call UnitCancelOrder(u)  
    else //has $?, pay price:
        call PlayerPayPriceForObject(pla, objectId)
    endif
    set u=null
    set pla=null
endfunction

//---------------------------------------------------------------------------------------------
// --------------------------trigger CANCEL--------------------------------------------------
//---------------------------------------------------------------------------------------------
private function GiveMoneyBackOnCancel takes player pla, integer objectId returns nothing
    local integer x=2
    local integer cost=0
    loop
        exitwhen x>CustomResources_g_customResourcesCount
        set cost = GetObjectCost(objectId, x)
        call AdjustPlayerCustomResource(pla, x, cost)
        set x=x+1
    endloop
endfunction
//---------------------------------------------------------------------------------------------
private function Trig_MultiOrderCancel_Actions takes nothing returns nothing
    local integer h = GetHandleId(GetTriggerEventId())    
    local unit u = GetTriggerUnit() // the same for all 4 events
    local player pla = GetOwningPlayer(u) // the same for all 4 events
    local integer objectId
    //---------------------------------------------------------------------------
    if h==33 then // EVENT_PLAYER_UNIT_TRAIN_CANCEL
        set objectId = GetTrainedUnitType()
        if IsCustomCostDefined(objectId) then
            call GiveMoneyBackOnCancel(pla, objectId)
            call MsgD("CustomCost "+GetUnitName(u) + " canceled TRAIN" )
        endif        
    //---------------------------------------------------------------------------
    elseif h==27 then // EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL
        set objectId = GetUnitTypeId(u)
        if IsCustomCostDefined(objectId) then
            call GiveMoneyBackOnCancel(pla, objectId)
            call MsgD("CustomCost "+GetUnitName(u) + " canceled BUILD" )
        endif
    //---------------------------------------------------------------------------
    elseif h==36 then // EVENT_PLAYER_UNIT_RESEARCH_CANCEL
        set objectId = GetResearched()
        if IsCustomCostDefined(objectId) then
            call GiveMoneyBackOnCancel(pla, objectId)
            call MsgD("CustomCost "+GetPlayerName(pla) + " canceled RESEARCH" )
        endif
    //---------------------------------------------------------------------------
    elseif h==30 then // EVENT_PLAYER_UNIT_UPGRADE_CANCEL
        set objectId = g_upgradeTarget[GetUnitUserData(u)] // upgrade TARGET building 
                                                                        // (gives Keep - if Keep cancels upgrade and revert itself to TownHall)
        if IsCustomCostDefined(objectId) then
            call GiveMoneyBackOnCancel(pla, objectId)
            call MsgD("CustomCost "+GetPlayerName(pla) + " canceled UPGRADE BUILDING" )
        endif
    //---------------------------------------------------------------------------    
    endif
    set u=null
    set pla=null
endfunction
//---------------------------------------------------------------------------------------------
// items based on "Build Tiny Farm" etc:
private function Trig_ItemUsed takes nothing returns boolean
    local integer itemId=GetItemTypeId(GetManipulatedItem())
    local unit u=GetManipulatingUnit()
    local boolean backup=udg_Resource_PrintWarnings
    set udg_Resource_PrintWarnings=false
    if IsCustomCostDefined(itemId) and (not PlayerHasMoneyForObject(GetOwningPlayer(u), itemId)) then //refund lost item:
        set bj_lastCreatedItem = CreateItem(itemId, GetUnitX(u), GetUnitY(u))
        call UnitAddItem(u, bj_lastCreatedItem)
    endif
    set udg_Resource_PrintWarnings=backup
    set u=null
    return false
endfunction

//=========================================================
//=========================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_ISSUED_ORDER )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER )
    call TriggerAddCondition( t, Condition(function Trig_MultiOrder_Cond))
    call TriggerAddAction( t, function Trig_MultiOrder_Act )
    
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_CONSTRUCT_START )
    call TriggerAddCondition( t, Condition( function Trig_BeginsConstr_Cond ) )
    call TriggerAddAction( t, function Trig_BeginsConstr_Act )

    call TriggerRegisterAnyUnitEventBJ( g_triggerMultiOrderCancel, EVENT_PLAYER_UNIT_TRAIN_CANCEL )
    call TriggerRegisterAnyUnitEventBJ( g_triggerMultiOrderCancel, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL )
    call TriggerRegisterAnyUnitEventBJ( g_triggerMultiOrderCancel, EVENT_PLAYER_UNIT_RESEARCH_CANCEL )
    call TriggerRegisterAnyUnitEventBJ( g_triggerMultiOrderCancel, EVENT_PLAYER_UNIT_UPGRADE_CANCEL )
    call TriggerAddAction( g_triggerMultiOrderCancel, function Trig_MultiOrderCancel_Actions )

    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_USE_ITEM)
    call TriggerAddCondition(t, Condition(function Trig_ItemUsed))
    call MsgD("Init in library Cost finished succefully")
    set t=null
endfunction

endlibrary

update 01-06-2017
added Event "Custom resource was delivered", small code improvements
update 18-08-2017
fixed bug when resource was not visible it allows to harvest by not valid harvester
update 25-12-2017
fixing code to work properly with normal warcraft lumber, following DSG suggestions, merge CustomResources library with CustomResourcesCost library
Previews
Contents

Custom Resources 1.31 (Map)

Reviews
Dr Super Good
A useful custom resource system that seems pretty robust. Also easy to setup and configure for GUI users with limited JASS knowledge. Improvement Suggestions: Add features to the test map to demonstrate and show off the system more. Place floating...
Level 5
Joined
May 2, 2015
Messages
109
Cool idea! Here's my suggestion.

-this is vJass, so instead call ExecuteFunc("func_name"), just directly call func_name
-this system is about creating custom resources, instead using hashtable, try struct
-put uses after library SystemName and list down the required system
-better use Table instead hashtable.
-I believe there is IsDestructibleATree snippet out there, use it
-use TimerUtils when working with timers
-try use GetClosestWidget snippet
-
JASS:
private function InitSettingGUIVariables takes nothing returns nothing // run at init
    local integer x=1
    loop
        exitwhen x>99
        set udg_Resource_Worker[x]=0
        set udg_Resource_Destructable[x]=0
        set udg_Resource_Structure[x]=0
        set x=x+1
    endloop
endfunction
what is 99?
 
Level 17
Joined
Nov 21, 2012
Messages
836
-this is vJass, so instead
call ExecuteFunc("func_name"), just directly call func_name
I do not want to ask user to place library above configuration triggers. thats why ExecuteFunc has been used
this system is about creating custom resources, instead using hashtable, try struct
I do not understand structs fully to use it in public resource, sorry
put uses after library SystemName and list down the required system
I'll do that for library ORDER
I believe there is IsDestructibleATree snippet out there, use it
It's true, but since I am tracking/manipulating orders (like harvest, resumeharvesting and so on) it may interfere if end-user uses worker id in this system which is registered in external IsDestructibleATree library. Thats why I prefer to stay with unique unit for checking is destructable a tree.
use TimerUtils when working with timers
This system already requires 3 other system. Heh, are you serious to suggest to use TimerUtils instead of two (2) global timers? One timer runs as a game timer, I just read its elapsed time, no more. Second is used as 0sec duration timer to cancel some ordrers ;)
try use GetClosestWidget snippet
as above. And it is 20 lines of code now, GetClosestWidget is a little more. Plus I am searching for closest unit using ForGroup with condition isBuildingUnderConstruction
anyway thanks for input

edit
99 is max number of destructables, workers and structures for each custom resource
 
Level 5
Joined
May 2, 2015
Messages
109
I do not want to ask user to place library above configuration triggers. thats why ExecuteFunc has been used
You are using library dude, doesn't need to do that. Believe me, use call func_name

This system already requires 3 other system. Heh, are you serious to suggest to use TimerUtils instead of two (2) global timers? One timer runs as a game timer, I just read its elapsed time, no more. Second is used as 0sec duration timer to cancel some ordrers ;)
Explain to me why you use global timer instead local for the 0sec.

99 is max number of destructables, workers and structures for each custom resource
There's two option,
1. Learn struct and your work will become easier
2. Stay with hashtable and make the 99 configurable

I can't find any of the global variable being used outside the library so,
remove the prefix (g_) and add private
 
Level 10
Joined
Mar 31, 2016
Messages
658
Good news! Bribe updated his famous Unit Event (which is requirement here) with new event that this system needed. I made also small updated with call instead of ExecuteFunc (Reventhous, I really forgot that system was packed as library :D), variables are private (with one exception)

Nice - what does that affect in game play? or does it just run smoother now?

Also, will you be able to add unit cost and multiboard w/ icons later on?
 
Level 17
Joined
Nov 21, 2012
Messages
836
call vs ExecuteFunc is faster, but it won't affect game play as function InitResources() is called only once for each resource at map init.
Also, will you be able to add unit cost and multiboard w/ icons
I can make an addon for this library for training/build costs.
Displaying custom resources is not really connected with system's core and can be made as leaderboard (like in demo map) or multiboard or diffrent ways. In your Medieval Realms you're already using MB for displaying upgrading structure's parameters, and knowing that only 1 MB can be displayed for player it may be a problem. Leaderboard however can be displayed parallel with MB, I'm just not sure if leaderboard allows to display icons?
 
Level 10
Joined
Mar 31, 2016
Messages
658
call vs ExecuteFunc is faster, but it won't affect game play as function InitResources() is called only once for each resource at map init.
I can make an addon for this library for training/build costs.
Displaying custom resources is not really connected with system's core and can be made as leaderboard (like in demo map) or multiboard or diffrent ways. In your Medieval Realms you're already using MB for displaying upgrading structure's parameters, and knowing that only 1 MB can be displayed for player it may be a problem. Leaderboard however can be displayed parallel with MB, I'm just not sure if leaderboard allows to display icons?

Awesome, you're the best mate.

Oh, I see. Aw, really? Only one MB per player? Hmm, perhaps there's a way to add resource icons + amounts to the MB that's already there?
Otherwise, I'll be satisfied if I can add icons to the leaderboard - i'll tinker around and see if I find something in the GUI for that.

EDIT: Didn't find anything icon related to display on leaderboard. However I found 'Leaderboard - Show/Hide' line that works with a variable. Can variables be icons?
 
Last edited:
Level 17
Joined
Nov 21, 2012
Messages
836
"Custom resource was delivered EVENT"
look at 'food1' trigger as point of reference,
use as event "Resource_Event" becomes equal to X, where X is Resource_Number defined in triggers "ResourceNAME"
variables available inside trigger: "Resource_Player" - owner who recieved resource nr X, value is: "Resource_Value"

mostly this, also added Resource_Name (it is for warning messages for addon system ResourceCost)
for your needs: new event allows to update gold instead of displaying one custom resource on leaderboard
 
Level 10
Joined
Mar 31, 2016
Messages
658
"Custom resource was delivered EVENT"
look at 'food1' trigger as point of reference,
use as event "Resource_Event" becomes equal to X, where X is Resource_Number defined in triggers "ResourceNAME"
variables available inside trigger: "Resource_Player" - owner who recieved resource nr X, value is: "Resource_Value"

mostly this, also added Resource_Name (it is for warning messages for addon system ResourceCost)
for your needs: new event allows to update gold instead of displaying one custom resource on leaderboard

Not sure if I comprehend everything... I'll try to implement and see what happens.

EDIT: Ok, I get the first part.
What new event to allow gold update? and where is it?
Could you be more descriptive or post screenshots or use trigger/jass tags?
 
Last edited:
Level 10
Joined
Mar 31, 2016
Messages
658
upon an Event read this value (for food) GetPlayerCustomResource(player p, integer resourceNumber) returns integer and Set player's gold to read value

event allows you to not use periodic to update gold, do it just when event fires
ps you may want to try an addon Custom ResourcesCost

What? So after the first line in trigger Food1, I add that line as custom script? Can you jump on Hive chat?
EDIT: I got errors - idk what you mean by "upon an Event, read this value" and idk how to "Set player's gold to read value"
 
Level 5
Joined
May 2, 2015
Messages
109
Good news! Bribe updated his famous Unit Event (which is requirement here) with new event that this system needed. I made also small updated with
call
instead of
ExecuteFunc
(Reventhous, I really forgot that system was packed as library :D), variables are private (with one exception)
:) . The variable looks better now.
About the function, can you remove the trig_ because its not really neccesary to have them.
And can you make their name shorter.


Took me a while to find this line
set udg_Resource_Event = 5.00
because doesn't exist. :D
use as event "Resource_Event" becomes equal to X, where X is Resource_Number defined in triggers "ResourceNAME"
variables available inside trigger: "Resource_Player" - owner who recieved resource nr X, value is: "Resource_Value"

mostly this, also added Resource_Name (it is for warning messages for addon system ResourceCost)
I'm not sure about the "X" thing, better leave it to 5.00 or something constant.
 
Level 17
Joined
Nov 21, 2012
Messages
836
Took me a while to find this line
set udg_Resource_Event = 5.00

because doesn't exist. :D
hehe you right ;)
this does inside function Trig_WorkerBringsResources_Act
JASS:
set udg_Resource_Event = 0.00
set udg_Resource_Event = I2R(resourceNr)
set udg_Resource_Event = 0.00
we do not know how many resources user will add, so it may fire with diffrent numbers, its not constant. Event==5 means resource number5 was delivered, other numbers are for other resources
 
Level 5
Joined
May 2, 2015
Messages
109
I'm saying "not sure" because that method will requires user to have multiple event or trigger which is doesn't look... good, and I believe that method will be a problem for you to add another event.

So, my idea is change the event to 1.00 (constant), then add two integer variable named "Resource_LastCreatedType" and "Resource_EventId".
After that, make the InitResource function returns integer. In the end of the line in that function, add this
JASS:
set udg_Resource_LastCreatedType = theId
return udg_Resource_LastCreatedType
Now, the user will have the ability to do this.
  • Custom script: set udg_myCustomResource = InitResource()
or this
  • Custom script: call InitResource()
  • Set myCustomResource = Resource_LastCreatedType
In the Trig_WorkerBringsResources_Act add
set udg_Resource_EventId = currentId before event execution.

Finally,
  • ResourceDelivered
    • Events
      • Game - Resource_Event becomes Equal to 1.00
    • Conditions
      • Resource_EventId Equal to myCustomResource
    • Actions
      • Do anything
Done. :)
 
Last edited:
Level 17
Joined
Nov 21, 2012
Messages
836
to be honest I don't see advantages from adding 2 more variables,
that method will be a problem for you to add another event.
not at all, it already works 100% fine!
Let me describe it: Trig_WorkerBringsResources_Act fires event
JASS:
set udg_Resource_Event = 0.00
set udg_Resource_Event = I2R(resourceNr)
set udg_Resource_Event = 0.00
local integer resourceNr = GetHarvestAbilityNumber(abi) and GetHarvestAbilityNumber load integer from hashtable. this integer is resource_number defined by a user in configuration trigger(s) at map init:
  • Set Resource_Number = 2
  • Set Resource_Name[Resource_Number] = Mushrooms
  • Set Resource_Ability = Harvest_2 (mushrums)
InitResource is called once per resource only at map init.

when player right click on diffrent resource-type the ability on ordered worker is switched to match new destructable-type (if worker is allowed to harvest this of course). When worker brings harvested resources I'm reading his abilityId and fire event connected to ability have been read. (By the way the key to make it all works is event "worker brings lumber" which I developed here) I guess it is easier to understand if view in World Editor not only pure code ;)
 
Level 5
Joined
May 2, 2015
Messages
109
to be honest I don't see advantages from adding 2 more variables,
The independence to add more events is the advantage.

not at all, it already works 100% fine!
Well, if you so sure. What about 'a worker finish collecting resource' event and 'a resource is collapsed' event.

Let me describe it:
Trig_WorkerBringsResources_Act
fires event

local integer resourceNr = GetHarvestAbilityNumber(abi)
and GetHarvestAbilityNumber load integer from hashtable. this integer is resource_number defined by a user in configuration trigger(s) at map init:
I do know how it works, I'm just thinking is it worth for this big system to have just one event.
And what if user use same Resource_Number for different resource type?

By the way, I highly recommend you to learn struct. You won't regret it. :)
 
Level 17
Joined
Nov 21, 2012
Messages
836
'a resource is collapsed' event.
I guess it is impossible to do.
'a worker finish collecting resource' event
do you mean when worker is "full" and start walking back? very problematic to execute, moreover I cant imagine a reason to implement this event
And what if user use same Resource_Number for different resource type?
User is not allowed to do that. It is stated in description, we have to assume that user will read description ;)
By the way, I highly recommend you to learn struct.
thanks, maybe I try
 

Dr Super Good

Spell Reviewer
Level 59
Joined
Jan 18, 2005
Messages
26,620
A useful custom resource system that seems pretty robust. Also easy to setup and configure for GUI users with limited JASS knowledge.

Improvement Suggestions:
  • Add features to the test map to demonstrate and show off the system more. Place floating text signs above different resource sites suggesting to the player how to harvest them. Add some demonstration use for the resources, showing a potential practical application. The map is there to try to convince a perspective user to use it.
  • Place all variables that must be configured by the user in a separate global block at the top of the script, possibly above the bulk of documentation. Currently they are mixed in with system state variables which could confuse some less skilled users.
  • Use named constants for hashtable keys rather than hard coded magic numbers. This makes the code more readable and easier to maintain. One can alter the magic numbers in a single place rather than having to find and change them throughout the script and accidently missing one occurrence and causing a hard to find bug.
 
Level 2
Joined
Jul 2, 2018
Messages
5
An excellent resource system, quite simple and convenient. But the only question I have is ... Some units from the "CustomResourcesCostConfig" table are never built, no matter how long you click on their icon, but some can still be ordered without having custom resources.
 
Level 2
Joined
Jul 2, 2018
Messages
5
I've tweaked this useful thing a bit:
1) Cheat adding all custom resources:
1.1 Add this code in "CustomResources"
JASS:
function CheatRes takes integer value returns nothing
    call AdjustPlayerCustomResource_ByAbility(0, g_harvestAbility[2], value)
    call AdjustPlayerCustomResource_ByAbility(0, g_harvestAbility[3], value)
    call AdjustPlayerCustomResource_ByAbility(0, g_harvestAbility[4], value)
// all numbers or using cycle
endfunction
1.2 function call:
JASS:
call CheatRes(1000000)

2) Sale option:
2.1 Add variable "Normal_CostUnit" type "type object".
2.2 In trigger JASS "CustomResources" in global variables place add:
JASS:
    public constant integer    KEY_700 = 700 // for saving lumber prices, (objectId as parentKey)
    public constant integer    KEY_701 = 701 // for saving boolean, as a sign that object is registered (objectId as parentKey)
    public constant integer    KEY_702 = 702 // for saving gold prices, (objectId as parentKey)
2.3 In trigger "CustomResourcesCost" add this code:
function call:

JASS:
function CR_SaveNormalCost takes integer goldc integer lumberc returns nothing
    local integer parentKey
    local integer costg
    local integer costl
    set costg=goldc
    set costl=lumberc
    if udg_Normal_CostUnit>0 then
        set parentKey = udg_Normal_CostUnit
    endif
    call SaveBoolean(CustomResources_g_hashCR, parentKey, CustomResources_KEY_701, true) // save "true" as sign that object is registered
    call SaveInteger(CustomResources_g_hashCR, parentKey, CustomResources_KEY_700, costl)
    call SaveInteger(CustomResources_g_hashCR, parentKey, CustomResources_KEY_702, costg)
    //reset integers for next call:
    set udg_Normal_CostUnit = 0
endfunction
2.4 function call:
JASS:
set Normal_CostUnit= "YourUnit" // as in custom resources, but other variable
call CR_SaveNormalCost(150,50) //gold, lumber
2.5 In CustomResourcesCost before heading "TRIGGERS RELATED":
JASS:
private function GetObjectGold takes integer objectId returns integer
    return LoadInteger(CustomResources_g_hashCR, objectId, CustomResources_KEY_702)
endfunction
private function GetObjectLumber takes integer objectId returns integer
    return LoadInteger(CustomResources_g_hashCR, objectId, CustomResources_KEY_700)
endfunction
maybe you can return an array, I haven't tried...
2.6 In CustomResourcesCost before heading "trigger CANCEL":
JASS:
//----------------------------------------------------------------------------------
//---------------------------trigger Sell Building ---------------------------------
//----------------------------------------------------------------------------------
function SellBuilding takes nothing returns nothing
    local integer x=2
    local integer objectId
    local integer cost=0
    local unit u = GetSpellTargetUnit()
    local player pla = GetOwningPlayer(u)
    set objectId = GetUnitTypeId(u)
    loop
        exitwhen x>CustomResources_g_customResourcesCount
        set cost = GetObjectCost(objectId, x)
        call AdjustPlayerCustomResource(pla, x, R2I(udg_KoeffCost*cost))
        set x=x+1
    endloop
    call SetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER)+R2I(udg_KoeffCost*GetObjectLumber(objectId)))
endfunction
    call SetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD)+R2I(udg_KoeffCost*GetObjectGold(objectId)))
endfunction
2.7 Variable "KoeffCost" type "real" shows the percentage of the sale.
2.8 Selling trigger:
JASS:
function Trig_Selling_Conditions takes nothing returns boolean
    if ( not ( GetUnitCurrentOrder(GetTriggerUnit()) == String2OrderIdBJ("channel") ) ) then
        return false
    endif
    return true
endfunction

function Trig_Selling_Actions takes nothing returns nothing
    call SellBuilding()
    call KillUnit( GetSpellTargetUnit() )
endfunction

//===========================================================================
function InitTrig_Selling takes nothing returns nothing
    set gg_trg_Selling = CreateTrigger(  )
    call TriggerRegisterPlayerUnitEventSimple( gg_trg_Selling, Player(0), EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Selling, Condition( function Trig_Selling_Conditions ) )
    call TriggerAddAction( gg_trg_Selling, function Trig_Selling_Actions )
endfunction
2.9 I made an ability based on "channel", the target is units.

3. A small observation: CustomResourcesCostConfig can be split into several triggers, if, for example, the list of units/researches/upgrades is too large and causes lags.
 
Level 2
Joined
Jul 2, 2018
Messages
5
I forgot to say one more thing: someone here asked how to make resources like:
GoldOre
I took the last resource I needed, added buildings, ability, workers and doodads and edited in the trigger "CustomResources":
JASS:
private function Trig_WorkerBringsResources_Act takes nothing returns nothing
    local unit u = GetOrderedUnit()
    local player pla = GetOwningPlayer(u)
    local integer i = GetPlayerId(pla)
    local integer abi = g_workerHarvestAbi[GetUnitUserData(u)]
    local integer resourceNr = GetHarvestAbilityNumber(abi) // new <---
    if not (abi == g_harvestAbility[1]) then // it is *custom resource* - subtract from normal wood!
        set udg_PRM_FireEvent = false
        call SetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER)-g_lastLumberRecivedValue[i])
    if (abi == g_harvestAbility[5]) then
            call SetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD)+g_lastLumberRecivedValue[i])
    endif
        set udg_PRM_FireEvent = true
    if not (abi == g_harvestAbility[5]) then
            call AdjustPlayerCustomResource_ByAbility(i, abi, g_lastLumberRecivedValue[i]) // writes value in hash (we're not writing "normal lumber")
            call MsgD(GetUnitName(u) + " brings resource nr(" + I2S(resourceNr) +"), value: " + I2S(g_lastLumberRecivedValue[i]) + ", time stamp: " + R2S(TimerGetElapsed(g_gameTimer)))
    endif
    endif //FIRE EVENT - MOVED 22-12-2017
    set u=null
    set pla=null
endfunction
Now, the 5th (in my case) ability brings gold and DOES NOT give the 5th (non-existent) resource.
 
Level 2
Joined
Jul 2, 2018
Messages
5
CustomResourcesConfig for W3 1.29 and higher change this update for much MaxPlayerSlots:

Code:
set g_resourceUnitTreeChecker = CreateUnit(Player(24), udg_Resource_Dummy, 0.00, 0.00, 0.00)
 
Level 17
Joined
Nov 21, 2012
Messages
836
Lua:
--/////////////////////////////////////////////////////////
-- ArrayRemove by Mitch McMabers
-- https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating
function ArrayRemove(t, fnRemove)
    local j, n = 1, #t
    for i=1,n do
        if (fnRemove(t, i)) then --fnRemove(table, key) -- remove value
            t[i] = nil          
        else --keep value
            -- Move i's kept value to j's position, if it's not already there.
            if (i ~= j) then
                t[j] = t[i]
                t[i] = nil
            end
            j = j + 1 -- Increment position of where we'll place the next kept value.      
        end
    end
    return t
end
Lua:
--/////////////////////////////////////////////////////////
-- UnitIndexer Simple based on Bribe's work, by ZibitheWand3r3r
-- detects: create unit: UnitIndexEvent = 1.00
--            remove unit: UnitIndexEvent = 2.00
--             transform unit: UnitTypeEvent = 1.00 (does not work for upgrade building)
--            UnitIndexerMaxId - global variable aviable outside
--
-- call function UnitIndexer_Init() on GUI map init event
-- so it enumerates all units and sets their id's, then
-- at 0.00 sec of the game it fires UnitIndexEvent=1.00 for pre-placed units
-- GUI variables: UnitIndexEvent <real>, UnitTypeEvent <real>
-- UDex <integer>, UDexUnits <unit array>
-- UnitIndexEvent and UnitTypeEvent must be used in "dummy" GUI trigger, otherwise it won't work
do 
local TRANSFORM_DETECT =     FourCC("A001")
local REMOVE_DETECT =         FourCC("A000")
UnitIndexerMaxId = 0     -- max index from used indexes GLOBAL VAR
local inactive={}     -- array that keeps indexes from removed units, for re-use
local transforming = {}
local unitRemoved = {}
local fireEvent = false --for pre-placed units
-------------------------------------------------------
local function OnOrder()
    local u = GetFilterUnit()
    local id = GetUnitUserData(u)    
    local pdex = udg_UDex
    if (id>0) and (GetUnitAbilityLevel(u, TRANSFORM_DETECT) == 0) then
         
        if UnitAlive(u) and (not transforming[id]) then            
            transforming[id] = true
            local t=CreateTimer()
            TimerStart(t, 0.00, false, function()
                udg_UDex = id
                globals.udg_UnitTypeEvent = 0.00
                globals.udg_UnitTypeEvent = 1.00
                transforming[id] = false
                UnitAddAbility(u, TRANSFORM_DETECT)
                udg_UDex = pdex
                end)
            DestroyTimer(t)
            t=nil
        
        elseif (GetUnitAbilityLevel(u, REMOVE_DETECT)==0) and (not unitRemoved[id]) then
            --//Fire deindex event for UDex:
            unitRemoved[id] = true
            udg_UDex = id
            globals.udg_UnitIndexEvent = 0.00
            globals.udg_UnitIndexEvent = 2.00
            globals.udg_UnitIndexEvent = 0.00
            udg_UDex = pdex
            -- save index "id" for recycle
            table.insert(inactive, id)
        end
    end    
    return false
end


-------------------------------------------------------
local function UnitIndex()
    if GetUnitAbilityLevel(u, REMOVE_DETECT) == 0 then
        --look up for re-use numbers
        local a = #inactive -- ilosc elementow w zbiorze "inactive"
        local id --will be UnitUserData we're going to bind to the unit
        if a>0 then 
            id = inactive[a] -- value from the end of "inactive" array
            inactive[a] = nil
        else --no numbers for re-use, so generate new index
            UnitIndexerMaxId = UnitIndexerMaxId + 1
            id = UnitIndexerMaxId 
        end
        udg_UDexUnits[id] = GetFilterUnit()
        SetUnitUserData(udg_UDexUnits[id], id)
        UnitAddAbility(GetFilterUnit(), TRANSFORM_DETECT)
        transforming[id] = false
        unitRemoved[id] = false
        UnitAddAbility(GetFilterUnit(), REMOVE_DETECT)
        UnitMakeAbilityPermanent(GetFilterUnit(), true, REMOVE_DETECT)
        -- fire event:
        if fireEvent then
            local pdex = udg_UDex
            udg_UDex = id
            globals.udg_UnitIndexEvent = 0.00
            globals.udg_UnitIndexEvent = 1.00
            udg_UDex = pdex
        end
    end
    return false
end
-------------------------------------------------------

-------------------------------------------------------
-- run UnitIndexer_Init() on GUI map init (at top)
-------------------------------------------------------
function UnitIndexer_Init()
    local t = CreateTrigger()
    local re = CreateRegion() --region
    local r = GetWorldBounds() --rect
    local b = Filter(UnitIndex) --boolexpr
    local orderB = Filter(OnOrder)
    
    RegionAddRect(re, r)
    TriggerRegisterEnterRegion(CreateTrigger(), re, b)
    fireEvent = false
    for i = 0, (bj_MAX_PLAYER_SLOTS - 1) do
        SetPlayerAbilityAvailable(Player(i), TRANSFORM_DETECT, false)
        SetPlayerAbilityAvailable(Player(i), REMOVE_DETECT, false)
        GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(i), b)--only sets id (no event)
        TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, orderB)
    end
    
    TimerStart(CreateTimer(), 0.00, false, function()--fire udg_UnitIndexEvent for preplaced
        fireEvent = true                                -- at 0.00 sec of the game
        for i = 0, (bj_MAX_PLAYER_SLOTS - 1) do
            GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(i), Filter(function()        
            local pdex = udg_UDex
            udg_UDex = GetUnitUserData(GetFilterUnit())
            globals.udg_UnitIndexEvent = 0.00
            globals.udg_UnitIndexEvent = 1.00
            udg_UDex = pdex    
            return false
            end))    
        end    
    end)
    
end
-------------------------------------------------------
end --do

Lua:
--[[
//===============================================
// Player Resources Monitoring ver 1.00 for Lua uses ArrayRemove by Mitch McMabers
// runs registered function(s) when gold/lumber has changed
// API
// PRM_Init() -> run it on GUI map init
// functions supposed to run when resource changed must be registered
// RegisterPRM_GoldEvent(func)
// RegisterPRM_LumberEvent(func)
// UnregisterPRM_GoldEvent(func)
// UnregisterPRM_LumberEvent(func)
//
// It is save to add/subtract/set resource(s) inside registered functions.
// It is save to register/unregister function inside registered functions.
//
// function "func" must take 2 arguments: player & integer. These two are read-only.
// player=player whos gold has been changed, integer=resource change value (+ or -)
// example functions outside this system, they will run when resource changed:
//
// examples***

function CustomPlayerXGold_1(pla, value)
    local old = GetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD)
    if pla==Player(0) then    
        SetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD, old+1)
        print("+1 Gold ")
    end
end
function CustomPlayerXGold_2(pla, value)
    local i = GetPlayerId(pla)
    local old = GetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD)
    if old>600 then
        print(value .. " Player gets >100 gold.. unregistering.. ")
        UnregisterPRM_GoldEvent(CustomPlayerXGold_2)
    end
end
function CustomPlayerXGold_3(pla, value)
    local i = GetPlayerId(pla)
    print(value .. " Gold changed for player " .. i)
end
function CustomPlayerXLumber_1(pla, value)
    local i = GetPlayerId(pla)
    local old = GetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER)
    print(value .. " LUMBER changed for player " .. i)        
end

// end examples ***

//================================================
--]]

do
local g_level, l_level = {}, {}
local listFuncG, listFuncL = {}, {}
local fireEvent = true -- infinite loop protection

-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-- these are example function only:
function CustomPlayerXGold_1(pla, value)
    local old = GetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD)
    if pla==Player(0) then    
        SetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD, old+1)
        print("+1 Gold ")
    end
end
function CustomPlayerXGold_2(pla, value)
    local i = GetPlayerId(pla)
    local old = GetPlayerState(pla, PLAYER_STATE_RESOURCE_GOLD)
    if old>600 then
        print(value .. " Player gets >100 gold.. unregistering.. ")
        UnregisterPRM_GoldEvent(CustomPlayerXGold_2)
    end
end
function CustomPlayerXGold_3(pla, value)
    local i = GetPlayerId(pla)
    print(value .. " Gold changed for player " .. i)
end

function CustomPlayerXLumber_1(pla, value)
    local i = GetPlayerId(pla)
    local old = GetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER)
    print(value .. " LUMBER changed for player " .. i)        
end
-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

function RegisterPRM_GoldEvent(func)
    table.insert(listFuncG, func)
    --listFuncG[func] = true -- table key is a function, with "dummy" value=true
end
function RegisterPRM_LumberEvent(func)
    table.insert(listFuncL, func)
end

function UnregisterPRM_GoldEvent(func)
    TimerStart(CreateTimer(), 0.00, false, function()
        ArrayRemove(listFuncG,     function(listFuncG, k) --fnRemove(table, key)
                                    return (listFuncG[k] == func)
                                end)--function remove
    end)
    --if listFuncG[func] then listFuncG[func]=nil end
end

function UnregisterPRM_LumberEvent(func)
    TimerStart(CreateTimer(), 0.00, false, function()
        ArrayRemove(listFuncL,     function(listFuncL, k) --fnRemove(table, key)
                                    return (listFuncL[k] == func)
                                end)--function remove
    end)
end


local function PRM_Monitor()
    local p = GetTriggerPlayer()
    local id = GetPlayerId(p)
    local g = GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD)
    local l = GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER)
    local change=0
    
    --set udg_PRM_Player = p
    if g ~= g_level[id] then -- gold change detected
        change = g - g_level[id]
        g_level[id] = g
                        
        if fireEvent then
            fireEvent=false
            -- run registered functions for gold change event
            for key,value in ipairs(listFuncG) do 
                value(p, change)
            end
            fireEvent=true
        end
        
    elseif l ~= l_level[id] then -- lumber change detected
        change = l - l_level[id]
        l_level[id] = l
        
        if fireEvent then
            fireEvent=false
            -- run registered functions for lumber change event
            for key,value in ipairs(listFuncL) do 
                value(p, change)
            end                        
            fireEvent=true                      
        end

    end
    return false
end
--//================================================
--//================================================
function PRM_Init()
    local t = CreateTrigger()
    local p
    for i = 0, (bj_MAX_PLAYER_SLOTS - 1) do
        p = Player(i)
        g_level[i] = GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD)
        l_level[i] = GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER)
        
        TriggerRegisterPlayerStateEvent(t, p, PLAYER_STATE_RESOURCE_GOLD, NOT_EQUAL, 0.00)
        TriggerRegisterPlayerStateEvent(t, p, PLAYER_STATE_RESOURCE_GOLD, EQUAL, 0.00)
        TriggerRegisterPlayerStateEvent(t, p, PLAYER_STATE_RESOURCE_LUMBER, NOT_EQUAL, 0.00)
        TriggerRegisterPlayerStateEvent(t, p, PLAYER_STATE_RESOURCE_LUMBER, EQUAL, 0.00)        
    end
    TriggerAddCondition(t, Condition(PRM_Monitor))
end
end --do

Lua:
--[[
-- script: is building under construction
-- use boolean: underConstruction[unit user data]

--tested in war3 ver 1.32.10.17165, 03.05.2021:
--begins/cancel/finishes construction events may use GetTriggerUnit
--also build by item (tiny town hall item etc) may use in 3 events: GetTriggerUnit
--building created by a trigger: event from UnitIndexer always fires as first,
--then runs 'begins construction' event (tested)
--]]

do        
underConstruction = {}

function BuildingUnderConstruction_Init()
    local t = CreateTrigger()
    TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00) -- structure enters the map
    TriggerAddCondition(t, Condition(function()
        underConstruction[udg_UDex] = false
        --print("Enter " .. udg_UDex)
        return false
    end))

    t = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_START)     --26
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL)    --27
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH)    --28
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)                --20
    TriggerAddCondition(t, Condition(function()
        local h = GetHandleId(GetTriggerEventId())
        local id=GetUnitUserData(GetTriggerUnit())
        if h==26 then --Start construction event
            underConstruction[id] = true
            --print("START " .. id)
        else -- cancel/finish/dies events
            underConstruction[id] = false
            --print("cancel/finish/dies " .. id)
        end
        return false
    end))

end
end    --do

Lua:
--[[
Custom Resources Lua
uses Player Resources Monitoring, underConstruction, Unit Indexer Lua

the same ability/worker/structure can be set in many resources
only destructable can be set only in one resource
custom resource changed event: use SomeFunction(pla, resourceName, amount) - read-only parameters
register those function(s): CR_RegisterEventFunction(SomeFunction)
main public variable: CR_amount[player][resourceName]
--]]

do
local CR_DUMMYTREERES_STRING = "DummyTreeResource"
local CR_DUMMYTREE_STRING = "B000"
local CR_harvesterId = FourCC("h000") -- id of dummy harvester, should be diffrent from all registered workers
local CR_DUMMY_TREE_ID = FourCC(CR_DUMMYTREE_STRING)

CR_amount={}                 --nested tables. CR_amount[player][resourceName]
local CR_name = {}            --keeps resource names
local CR_abi = {}             --one for each resource
local CR_worker={}             --nested tables. CR_worker[resNo][workerTypeId]==true
local CR_structure = {}     --nested tables. CR_structure[resNo][strucTypeId]==true
local CR_D2ResourceNo = {}     --simple table.  CR_D2ResourceNo[destructableTypeId] = resource number
local CR_isRegistered={}     --for workers/structures/destructables. CR_isRegistered[objectId]==TYPE_WORKER
local TYPE_WORKER =        1    --or CR_isRegistered[objectId]==TYPE_STRUCTURE or TYPE_DESTRUCTABLE
local TYPE_STRUCTURE =     2
local TYPE_DESTRUCTABLE=3
local CR_count =         0     --resources count (2 more then declared: one for normal lumber, one for dummy tree)
local CR_normalLumberNo = 2

local CR_deliveryGroup={}    --unit group (structures), one for each resource. 
                            --CR_deliveryGroup[resNo] = unit group
local CR_dummyTree={}        --destructable array [unit user data of connected structure]
                            --CR_dummyTree[structure unit user data] = connected dummy tree (destructable)
                            
local CR_trg_dummyTreeDies = nil    --trigger
local CR_trg_WorkerBringsResources = nil
local CR_trg_ResumeharvestingRedirect = nil
local CR_workerResNo={}            --CR_workerResNo[worker's unit user data] = resource number
local CR_workerX={}                --CR_workerX[worker's unit user data], worker's last targeted tree x/y
local CR_workerY={}                --CR_workerY[worker's unit user data], worker's last targeted tree x/y
local CR_undefendCount = {}        -- to catch 2nd undefend order [unit user data]

local CR_timeStampLumber = {}     --last Time Stamp Lumber Recived [player Id]
local CR_timeStampValue = {}     --last Time Lumber Recived Value [player Id]
local CR_leftMouseClicked = {}    --remembers time when player clicks left mouse button [player Id]

CR_harvester = nil     --make it local after tests
CR_timer = nil        --make it local after tests
local CR_rect = nil
local CR_rectBig = nil
local CR_eventFunc = {}

local ORDER_stop=851972
local ORDER_move=851986
local ORDER_harvest=852018
local ORDER_resumeharvesting=852017
local ORDER_offset=851970
local ORDER_smart=851971
local ORDER_setrally=851980
local ORDER_channel=852600
local ORDER_undefend=852056
local ORDER_militia=852072
local ORDER_militiaconvert=852071

local printMsg=true
-- ////////////////////////////////////////////
local function GetResourceNumber(whatName)
    local i=0
    for key, value in ipairs(CR_name) do
        if (whatName==value) then i=key end    
    end
    return i
end
local function Msg(s)
    if printMsg then print(s) end
end
-- -----------------------------------------------------
-- start configuration functions -----------------------
-- -----------------------------------------------------
-- normal warcraft lumber must be set as first, its resNo=2 (resNo=1 for DummyTreeResource)
-- system will recognize 2nd resource as normal war3 lumber
function CR_SetResourceNameAbility(whatName, abilityId)--use ""
    table.insert(CR_name, whatName)
    local i = #CR_name
    CR_count = #CR_name
    CR_worker[i]={} --create tables
    CR_structure[i]={}
    CR_deliveryGroup[i] = CreateGroup()    
    table.insert(CR_abi, FourCC(abilityId)) -- FourCC("A001")
    --table for keep this resource amount
    for n = 0, (bj_MAX_PLAYER_SLOTS - 1) do
        CR_amount[Player(n)][whatName] = 0
    end
    Msg("saved resource " .. whatName .. ", resNo " .. i)
end


function CR_SetWorkers(whatResourceName, ...)
    local i = GetResourceNumber(whatResourceName)
    for k,v in ipairs({...}) do 
        CR_worker[i][FourCC(v)] = true -- "v" is workerId as a key with dummy value "true"
        CR_isRegistered[FourCC(v)]=TYPE_WORKER
        --Msg("saved worker " .. GetObjectName(FourCC(v)) .. " for resNo " .. i)
    end
end

function CR_SetStructures(whatResourceName, ...)
    local i = GetResourceNumber(whatResourceName)
    for k,v in ipairs({...}) do 
        CR_structure[i][FourCC(v)] = true -- "v" is buildingId as a key with dummy value "true"
        CR_isRegistered[FourCC(v)] = TYPE_STRUCTURE        
        --Msg("saved structure " .. GetObjectName(FourCC(v)) .. " for resNo " .. i)
    end    
end
function CR_SetDestructables(whatResourceName, ...)
    local i = GetResourceNumber(whatResourceName)
    for k,v in ipairs({...}) do 
        CR_D2ResourceNo[FourCC(v)] = i -- destrId as a key, resourceNo as value        
        CR_isRegistered[FourCC(v)]=TYPE_DESTRUCTABLE
    end    
end

-- function that run when custom resource changed
function CR_RegisterEventFunction(func)
    table.insert(CR_eventFunc, func)
end
function CR_UnregisterEventFunction(func)
    TimerStart(CreateTimer(), 0.00, false, function()
        ArrayRemove(CR_eventFunc,     function(CR_eventFunc, k) --fnRemove(table, key)
                                    return (CR_eventFunc[k] == func)
                                end)--function remove
    end)
end
-- -----------------------------------------------------
-- end configuration functions -------------------------
-- -----------------------------------------------------

local function CanWorkerHarvestResource(workerId, resourceNumber)
    return CR_worker[resourceNumber][workerId]
end
local function CanStructureGetResource(buildingId, resourceNumber)
    return CR_structure[resourceNumber][buildingId]
end


function TESTSelect()
    local u=GetTriggerUnit()
    local id=GetUnitUserData(u)
    local typeId=GetUnitTypeId(u)
    local msg=""
    local currentNo = CR_workerResNo[GetUnitUserData(u)]
    local x, y = CR_workerX[id], CR_workerY[id]
    local s = "Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl"
    for i = 1, CR_count do
        if CanWorkerHarvestResource(typeId, i) then
            msg=msg .. " resource: " .. i .. "-yes"
        else
            msg=msg .. " resource: " .. i .. "-no"                        
        end
    end
    DestroyEffect(AddSpecialEffect(s, x, y))
    Msg(currentNo)
end

-- *******************************************************
-- trigger related functions
-- *******************************************************
function RemoveDeliveryStructureFromAllGroups(u)
    for i = 1, CR_count do GroupRemoveUnit(CR_deliveryGroup[i], u) end
end

function IsDeliveryStructureValid(u, structure)
    local resNo = CR_workerResNo[GetUnitUserData(u)]
    local typeId = GetUnitTypeId(structure)
    return CanStructureGetResource(typeId, resNo)
end



local function Trig_Created() -- unit created (worker or delivery structure)
    local typeId = GetUnitTypeId(udg_UDexUnits[udg_UDex])
    local t = CR_isRegistered[typeId]
    if not t then return false end -- unit not registered in system
    if t==TYPE_WORKER then --read his harvest ability, and set variable
        for i = 1, CR_count do
            if GetUnitAbilityLevel(udg_UDexUnits[udg_UDex], CR_abi[i])>0 then
                if CanWorkerHarvestResource(typeId, i) then
                    CR_workerResNo[udg_UDex] = i
                    break
                    --Msg("registered worker typeId" .. udg_UDex .. ", resNo: " .. i)
                end
            end               
        end
        -- save x/y/ of worker last targeted tree, here initial values 
        CR_workerX[udg_UDex] = GetUnitX(udg_UDexUnits[udg_UDex])
        CR_workerY[udg_UDex] = GetUnitY(udg_UDexUnits[udg_UDex])
        CR_undefendCount[udg_UDex] = 0 --for undefend order (militia)
        
    elseif t==TYPE_STRUCTURE then
        bj_lastCreatedItem = CreateItem(FourCC("wtlg"), GetUnitX(udg_UDexUnits[udg_UDex]), GetUnitY(udg_UDexUnits[udg_UDex]))
        CR_dummyTree[udg_UDex] = CreateDestructable(CR_DUMMY_TREE_ID, GetItemX(bj_lastCreatedItem), GetItemY(bj_lastCreatedItem), 0.00, 1, 0)
        RemoveItem(bj_lastCreatedItem)        
        TriggerRegisterDeathEvent(CR_trg_dummyTreeDies, CR_dummyTree[udg_UDex])--add event   
        for i = 1, CR_count do --add to delivery group(s)
            if CR_structure[i][typeId] then
                GroupAddUnit(CR_deliveryGroup[i], udg_UDexUnits[udg_UDex])
                --Msg("adding " .. GetUnitName(udg_UDexUnits[udg_UDex]) .. " to group resNo " .. i) 
            end
        end
    end
    return false
end

local function Trig_Upgraded()
    local u = GetTriggerUnit()
    local id = GetUnitUserData(u)
    local typeId = GetUnitTypeId(u)
    --Remove Delivery Structure From All Groups
    for i = 1, CR_count do GroupRemoveUnit(CR_deliveryGroup[i], u) end --remove from all groups
    if CR_isRegistered[typeId]==TYPE_STRUCTURE then -- is upgraded building registered
        --Msg("upgrade finish: is registered")
        for i = 1, CR_count do --add to delivery group(s)
            if CR_structure[i][typeId] then
                GroupAddUnit(CR_deliveryGroup[i], u)
            end
        end
        --if connected dummy TREE is none (means previous structure was not registered)
        if not CR_dummyTree[id] then --find x/y for DummyTree
            bj_lastCreatedItem = CreateItem(FourCC("wtlg"), GetUnitX(u), GetUnitY(u))
            CR_dummyTree[id] = CreateDestructable(CR_DUMMY_TREE_ID, GetItemX(bj_lastCreatedItem), GetItemY(bj_lastCreatedItem), 0.00, 1, 0)
            RemoveItem(bj_lastCreatedItem)        
            TriggerRegisterDeathEvent(CR_trg_dummyTreeDies, CR_dummyTree[id])--add event
        end
    else --New (upgraded) structure is not registered --> remove connected dummy TREE
        --Msg("upgrade finish: NOT registered")
        RemoveDestructable(CR_dummyTree[id])
        CR_dummyTree[id] = nil
    end
    return false
end

-- *******************************************************
-- *******************************************************
local function IsTree(d)
    return (IssueTargetOrderById(CR_harvester, ORDER_harvest, d)) and (IssueImmediateOrderById(CR_harvester, ORDER_stop))
end

local function LumberChangedEvent(pla, change)
    local i = GetPlayerId(pla)
    if change <= 0 then return end
    --//it fires as 1st, after this - "order harvest"  (or other) runs CHECK IT (checked)
    CR_timeStampLumber[i] = TimerGetElapsed(CR_timer)
    CR_timeStampValue[i] = change
    --Msg("(PRM) time stamp: " .. TimerGetElapsed(CR_timer) .. ", " .. GetPlayerName(pla) .. ", value: " .. change)
end

-- this is an EVENT "worker brings resources"
local function Trig_WorkerBringsResources_Cond()
    local ord = GetIssuedOrderId()
    local id = GetUnitUserData(GetOrderedUnit())
    if not (CR_isRegistered[GetUnitTypeId(GetOrderedUnit())]==TYPE_WORKER) then return false end
    if not (TimerGetElapsed(CR_timer)==CR_timeStampLumber[GetPlayerId(GetOwningPlayer(GetOrderedUnit()))]) then return false end
    
    if ord==ORDER_militia then return false end --militia may run at the same time as undefend and mess the event
    if ord==ORDER_militiaconvert then return false end --the same if town hall orders call to arms
    
    if ord==ORDER_undefend then --undefend(from militia) runs twice
        if CR_undefendCount[id]>0 then
            --Msg("interrupted [" .. id .. "], undefend: " .. CR_undefendCount[id])
            CR_undefendCount[id] = 0
            return false
        elseif CR_undefendCount[id]==0 then
            CR_undefendCount[id] = CR_undefendCount[id] + 1
        end
    end
    -- if peasant recive ANY order like: harvest dummyTree, harvest normal resources, 
    -- or player queued any order (stop, attack, move, etc) then:
    --// ordered unit is 'last lumber supplier'
    --return result
    return true
end

local function Trig_WorkerBringsResources_Act()
    local u = GetOrderedUnit()
    local pla = GetOwningPlayer(u)
    local i = GetPlayerId(pla)
    local id = GetUnitUserData(u)
    local whatName = CR_name[CR_workerResNo[id]]
    local resourceNr = CR_workerResNo[id]
    if not (CR_workerResNo[id] == CR_normalLumberNo) then
        -- it is *custom resource* - subtract from normal wood!
        SetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(pla, PLAYER_STATE_RESOURCE_LUMBER)-CR_timeStampValue[i])
        CR_amount[pla][whatName] = CR_amount[pla][whatName] + CR_timeStampValue[i]
        Msg(GetUnitName(u) .. " [" .. id ..  "] brings resource nr(" .. CR_workerResNo[id] .. "), value: " .. CR_timeStampValue[i] .. ", time stamp: " .. TimerGetElapsed(CR_timer))
        -- run registered functions for gold change event
        for key,func in ipairs(CR_eventFunc) do 
            func(pla, whatName, CR_timeStampValue[i])
        end
    end
end

-- *******************************************************
-- here starts block where we order worker to do something
-- *******************************************************


-- offset and worker's smart or harvest (manually) on not-visible tree are point orders!
-- CONVERT point orders to target orders:
local function Trig_PointOrder_Cond()
    local ord=GetIssuedOrderId()
    local harvest = (ord==ORDER_harvest)
    local smartOffset = (ord==ORDER_smart or ord==ORDER_offset)        
    local x=GetOrderPointX()
    local y=GetOrderPointY()
    local u=GetOrderedUnit()
    local tree=nil
    local dist2Min = 1.00
    
    if not (harvest or smartOffset) then return false end
    if not (CR_isRegistered[GetUnitTypeId(u)]==TYPE_WORKER) then return false end
    if (smartOffset and IsVisibleToPlayer(x, y, GetOwningPlayer(u))) then return false end
    --harvest x/y can differ greatly(!) from targeted tree
    
    if harvest then dist2Min=1000000.00 end
    --find destructable on x/y START
    MoveRectTo(CR_rect, x, y)    
    EnumDestructablesInRect(CR_rect, Filter(function() -- hiden tree cannot be harvested 
        local d = GetFilterDestructable()
        if IsDestructableInvulnerable(d) then return false end -- "return false" from filter func
        if (GetDestructableLife(d) <= 0) then return false end
        if (GetDestructableTypeId(d) == CR_DUMMY_TREE_ID) then return false end 
        if not IsTree(d) then return false end
        local dx = x - GetDestructableX(d)
        local dy = y - GetDestructableY(d)
        local dist2 = (dx * dx + dy * dy)
        if dist2<dist2Min then
            dist2Min=dist2
            tree=d
        end        
        return false
    end), nil)
    --find destructable on x/y END
    
    if tree ~= nil then
        Msg("reordering point-->tree " .. GetDestructableName(tree))
        IssueTargetOrderById(u, ord, tree) --re-apply given order as a target-order
    elseif harvest then -- tree not found / harvest order. If tree=nil don't change smart/offset orders.
        Msg("reordering point Harvest-->Move")
        IssuePointOrderById(u, ORDER_move, x, y)
    end
    return false
end

-- //////////////////////////////////////////


local function CR_FindClosestStructure(u)
    local id = GetUnitUserData(u)
    local resNo = CR_workerResNo[id]
    local x = GetUnitX(u)
    local y = GetUnitY(u)
    local pla = GetOwningPlayer(u)
    local minDist = 999999
    local closestStructure = nil
    ForGroup(CR_deliveryGroup[resNo], function()
        local structure = GetEnumUnit()
        if (pla==GetOwningPlayer(structure)) and not underConstruction[GetUnitUserData(structure)] then
            local dx = GetUnitX(structure)-x
            local dy = GetUnitY(structure)-y
            local dist = math.sqrt(dx * dx + dy * dy)
            if dist < minDist then
                minDist = dist
                closestStructure = structure
            end
        end
    end)
    return closestStructure
end

local function CR_ChangeHarvestAbility(u, id, workerTypeId, x, y, resNo, printMsg)
    if CR_worker[resNo][workerTypeId] then -- worker can harvest this, so change his ability:
        --Msg("changing harvest ability")
        AddUnitAnimationProperties(u, "Lumber", false)
        UnitRemoveAbility(u, CR_abi[CR_workerResNo[id]])
        UnitAddAbility(u, CR_abi[resNo])
        CR_workerResNo[id] = resNo --update    
    else 
        IssuePointOrderById(u, ORDER_move, x, y)
        if printMsg then -- warning
            local t = GetUnitName(u) .. " |cffff0000cannot|r harvest " .. CR_name[resNo]
            DisplayTimedTextToPlayer(GetOwningPlayer(u), 0.00,  0.00, 2.00, t)
        end
    end
end

local function CR_GetTree(id)
    local x, y = CR_workerX[id], CR_workerY[id]
    local tree=nil
    local dist2Min=10000000.00
    --tree must be valid to: CR_workerResNo[id]
    --find destructable on x/y START
    MoveRectTo(CR_rectBig, x, y)    
    EnumDestructablesInRect(CR_rectBig, Filter(function() -- hiden tree cannot be harvested 
        local d = GetFilterDestructable()
        local treeTypeId = GetDestructableTypeId(d)
        if IsDestructableInvulnerable(d) then return false end -- "return false" from filter func
        if (GetDestructableLife(d) <= 0) then return false end
        if CR_workerResNo[id] == CR_normalLumberNo then -- "normal" trees are not registered:
            if not IsTree(d) then return false end
            if CR_isRegistered[treeTypeId]==TYPE_DESTRUCTABLE then return false end
        else
            if not (CR_D2ResourceNo[treeTypeId] == CR_workerResNo[id]) then return false end
        end
        local dx = x - GetDestructableX(d)
        local dy = y - GetDestructableY(d)
        local dist2 = (dx * dx + dy * dy)
        if dist2<dist2Min then
            dist2Min=dist2
            tree=d
        end        
        return false
    end), nil)
    --find destructable on x/y END
    return tree
end

-- //////////////////////////////////////////
-- on target orders: harvest, smart, offset
-- //////////////////////////////////////////
local function Trig_HarvestSmartOffset_Cond()
    local ord=GetIssuedOrderId()
    local d = GetOrderTargetDestructable()
    local u = GetOrderedUnit()
    local workerTypeId = GetUnitTypeId(u)
    local id = GetUnitUserData(u)
    local playerId = GetPlayerId(GetOwningPlayer(u))
    local isAutoHarvest = (TimerGetElapsed(CR_timer) == CR_timeStampLumber[playerId])
    local playerManuallyHarvest = (TimerGetElapsed(CR_timer) == CR_leftMouseClicked[playerId])
    local resNo = CR_normalLumberNo -- =2
    local structure = nil
    local b=nil
    
    if not (ord==ORDER_harvest or ord==ORDER_smart or ord==ORDER_offset) then return false end
    if (d == nil) or not (CR_isRegistered[workerTypeId]==TYPE_WORKER) then return false end
    if not IsTree(d) then return false end
    
    if CR_isRegistered[GetDestructableTypeId(d)]==TYPE_DESTRUCTABLE then
        resNo = CR_D2ResourceNo[GetDestructableTypeId(d)] --targeted custom resource or dummyTree
    end
    local x, y = GetDestructableX(d), GetDestructableY(d)
    
    if CR_workerResNo[id] == resNo then 
        CR_workerX[id], CR_workerY[id] = x, y --update worker target tree x/y
        return false 
    end
    
    -- ALL BELOW: worker and tree have >diffrent< numbers!
    local tree=nil
    if ord==ORDER_harvest then
        if playerManuallyHarvest then
            Msg("Player's order harvest (mouse detect)")
            CR_workerX[id], CR_workerY[id] = x, y
            CR_ChangeHarvestAbility(u, id, workerTypeId, x, y, resNo, true)
        else
            if isAutoHarvest then
                tree = CR_GetTree(id)
                DisableTrigger(CR_trg_WorkerBringsResources)--protect agains double fire "worker brings resource" event
                if tree==nil then
                    Msg("Auto-harvest upon delivery, stop.")
                    IssuePointOrderById(u, ORDER_move, GetUnitX(u), GetUnitY(u))--instead of "stop"                
                else
                    Msg("Auto-harvest upon delivery, redirect: " .. GetDestructableName(tree))
                    IssueTargetOrderById(u, ORDER_harvest, tree)
                end
                EnableTrigger(CR_trg_WorkerBringsResources)
            else
                structure = CR_FindClosestStructure(u)
                if structure==nil then
                    Msg("No place to return to..")
                    IssuePointOrderById(u, ORDER_move, GetUnitX(u), GetUnitY(u))
                else --closest structure found:
                    DisableTrigger(CR_trg_ResumeharvestingRedirect)
                    b = IssueTargetOrderById(u, ORDER_resumeharvesting, structure)
                    EnableTrigger(CR_trg_ResumeharvestingRedirect)
                end                
                if b then
                    --worker killed destructable and want auto-harvest another, diffrent-type,
                    --worker has resource in his hands.
                    Msg("Auto-harvest: after killed the tree, forced resumeharvesting.")
                else--worker is empty, tree dies before worker start gather it
                    tree = CR_GetTree(id)
                    DisableTrigger(CR_trg_WorkerBringsResources)--protect agains double fire "worker brings resource" event
                    if tree==nil then
                        Msg("Auto-harvest: worker empty, wrong tree, stop.")
                        IssuePointOrderById(u, ORDER_move, GetUnitX(u), GetUnitY(u))--instead of "stop"                
                    else
                        Msg("Auto-harvest: worker empty, wrong tree, redirect: " .. GetDestructableName(tree))
                        IssueTargetOrderById(u, ORDER_harvest, tree)
                    end
                    EnableTrigger(CR_trg_WorkerBringsResources)
                end    
                
            end
        end
        
    elseif ord==ORDER_smart then
        CR_workerX[id], CR_workerY[id] = x, y
        CR_ChangeHarvestAbility(u, id, workerTypeId, x, y, resNo, true)
    elseif ord==ORDER_offset then
        CR_workerX[id], CR_workerY[id] = x, y
        CR_ChangeHarvestAbility(u, id, workerTypeId, x, y, resNo, false)
    end
    return false
end

-- /////////////////////////////////////////////////
-- resumeharvesting --> redirect
-- when button "return resources" clicked  OR  auto come-back with resources
local function Trig_ResumeharvestingRedirect()
    if not (GetIssuedOrderId()==ORDER_resumeharvesting) then return false end
    if not (CR_isRegistered[GetUnitTypeId(GetOrderedUnit())]==TYPE_WORKER) then return false end    
    local u=GetOrderedUnit()
    local structure = CR_FindClosestStructure(u)
    if structure == nil then
        Msg(GetUnitName(u) .. ": |cffffff00 No proper building found to return. |r")
        IssuePointOrderById(u, ORDER_move, GetUnitX(u), GetUnitY(u))--instead of "stop"
    else --structure found, redirect:
        Msg("Redirecting " .. GetUnitName(u) .. " to " .. GetUnitName(structure))
        DisableTrigger(CR_trg_ResumeharvestingRedirect) --this trigger
        IssueTargetOrderById(u, ORDER_resumeharvesting, structure)
        EnableTrigger(CR_trg_ResumeharvestingRedirect)
    end
    --u=nil -- do not nil unit if you're using 0sec timer in function!
    return false
end

-- /////////////////////////////////////////////////
--if player give order "smart" on Worker and if building is not valid to take resources 
--then convert smart-->move order
local function Trig_ReturnSmartRestrictions()
    if not (GetIssuedOrderId() == ORDER_smart) then return false end
    if (GetOrderTargetUnit() == nil) then return false end
    if not (CR_isRegistered[GetUnitTypeId(GetOrderedUnit())]==TYPE_WORKER) then return false end    
    if not (GetOwningPlayer(GetOrderedUnit()) == GetOwningPlayer(GetOrderTargetUnit())) then return false end
    local u = GetOrderedUnit()
    local target = GetOrderTargetUnit()
    local resNo = CR_workerResNo[GetUnitUserData(u)]
    --check if worker is returning resources (it may also be order repair/finish construction)
    DisableTrigger(CR_trg_ResumeharvestingRedirect)
    if IssueTargetOrderById(u, ORDER_resumeharvesting, target) then --has resources
        if not CR_structure[resNo][GetUnitTypeId(target)] then
            IssueTargetOrderById(u, ORDER_move, target)
        end
    end
    EnableTrigger(CR_trg_ResumeharvestingRedirect)
    return false
end

-- remember time when player clicks left mouse button (to detect harvest)
function Trig_MouseButton()
    if BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_LEFT then
        CR_leftMouseClicked[GetPlayerId(GetTriggerPlayer())] = TimerGetElapsed(CR_timer)
    end
    return false
end


-- *******************************************************
-- *******************************************************
function CustomResources_Init()
    CR_rect = Rect(0.00, 0.00, 512.00, 512.00)
    CR_rectBig = Rect(0.00, 0.00, 1024.00, 1024.00)
    for i = 0, (bj_MAX_PLAYER_SLOTS - 1) do
        CR_timeStampLumber[i] = 0.00
        --tables for keep resource amount:
        CR_amount[Player(i)] = {}
    end
    CR_timer = CreateTimer()
    TimerStart(CR_timer, 36000.00, false, nil)
    
    CR_harvester = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CR_harvesterId, 0, 0, 0)
    UnitAddAbility(CR_harvester, FourCC("Ahrl")) -- ghoul harvest
    UnitAddAbility(CR_harvester, FourCC("Aloc"))
    ShowUnit(CR_harvester, false)
    --"register" dummyTree as a resource, resNo=1
    CR_SetResourceNameAbility(CR_DUMMYTREERES_STRING, "0000")
    CR_SetDestructables(CR_DUMMYTREERES_STRING, CR_DUMMYTREE_STRING)

    --player click left mouse button: (to detect manual harvest)
    local t = CreateTrigger()
    for i = 0, (bj_MAX_PLAYER_SLOTS - 1) do
        TriggerRegisterPlayerMouseEventBJ(t, Player(i), bj_MOUSEEVENTTYPE_DOWN)
    end
    TriggerAddCondition(t, Condition(Trig_MouseButton))


    t = CreateTrigger()         --worker or structure created
    TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00) 
    TriggerAddCondition(t, Condition(Trig_Created))
    t = CreateTrigger()         --structure finishes upgrade
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_FINISH)
    TriggerAddCondition(t, Condition(Trig_Upgraded))
    t = CreateTrigger()         -- structure dies
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
    TriggerAddCondition(t, Condition(function()
        local typeId = GetUnitTypeId(GetTriggerUnit())
        local id = GetUnitUserData(GetTriggerUnit())
        if CR_isRegistered[typeId]==TYPE_STRUCTURE then --remove from all groups
            for i = 1, CR_count do GroupRemoveUnit(CR_deliveryGroup[i], GetTriggerUnit()) end 
            RemoveDestructable(CR_dummyTree[id])
            CR_dummyTree[id] = nil
        end
        return false
    end))                
    
    CR_trg_dummyTreeDies = CreateTrigger() -- dummy tree dies
    TriggerAddAction(CR_trg_dummyTreeDies, function() --resurect    
        DestructableRestoreLife(GetDyingDestructable(), GetDestructableMaxLife(GetDyingDestructable()), false)
    end)
    -- ******************************************************
    
    RegisterPRM_LumberEvent(LumberChangedEvent) -- from Player-Resource-Monitoring system
    -- worker-brings-resources event:
    CR_trg_WorkerBringsResources = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(CR_trg_WorkerBringsResources, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    TriggerRegisterAnyUnitEventBJ(CR_trg_WorkerBringsResources, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    TriggerRegisterAnyUnitEventBJ(CR_trg_WorkerBringsResources, EVENT_PLAYER_UNIT_ISSUED_ORDER)
    TriggerAddCondition(CR_trg_WorkerBringsResources, Condition(Trig_WorkerBringsResources_Cond))
    TriggerAddAction(CR_trg_WorkerBringsResources, Trig_WorkerBringsResources_Act)

    -- point-order harvest/smart/offset on not visible point -> convert to target-order
    t = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    TriggerAddCondition(t, Condition(Trig_PointOrder_Cond))
    
    -- target orders: harvest/smart/offset
    t = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    TriggerAddCondition(t, Condition(Trig_HarvestSmartOffset_Cond))
    
    -- order resumeharvesting  -> redirect
    CR_trg_ResumeharvestingRedirect = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(CR_trg_ResumeharvestingRedirect, EVENT_PLAYER_UNIT_ISSUED_ORDER) --when button "return resources" clicked
    TriggerRegisterAnyUnitEventBJ(CR_trg_ResumeharvestingRedirect, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER) --auto come-back
    TriggerAddCondition(CR_trg_ResumeharvestingRedirect, Condition(Trig_ResumeharvestingRedirect))

    --if player give order "smart" on Worker and if building is not valid to take resources
    --convert smart-->move order
    t = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    TriggerAddCondition(t, Condition(Trig_ReturnSmartRestrictions))
    

--[[


 
 
--]]

end

end
 
Top