- Joined
- May 9, 2014
- Messages
- 1,820
Last edited:
library ResourceLibrary
//-------------------------------------------//
//---------CUSTOM RESOURCE SYSTEM------------//
//------------------BY-----------------------//
//--------------FINGOLFIN--------------------//
//-------------------------------------------//
globals
//DEFAULT MESSAGE STRINGS
private constant string MSG_ERROR = "|cffff0000ERROR: "
private constant string MSG_DEFAULT_NOTENOUGH = "|cffdaa520Not enough "
//SYSTEM FLAGS
private constant boolean USE_INDEXER = false
//HASHTABLE KEYS
private constant integer RESOURCE_KEY = StringHash("Resources")
private constant integer HARVESTER_KEY = StringHash("Harvester")
private constant integer SPAWNER_KEY = StringHash("Spawner")
private constant integer CONSTRUCTION_KEY = StringHash("Construction")
//SYSTEM CONSTANTS
private constant integer MAX_RESOURCES = 100
private constant integer ORDERID_CANCEL = 851976
private constant integer DROP_ABIL = 'drop'
private constant string DROP_ORDER = "curse"
private constant real INTERVAL = 0.33
private constant real SEARCH_RADIUS = 10000
private group ENUM_GROUP = CreateGroup()
private rect RECT = Rect(-1, -1, 1, 1)
private hashtable Hashtbl = InitHashtable()
//PLAYER RESOURCES
integer array PlayerResource[11][MAX_RESOURCES]
boolean array IsConstructing
endglobals
function IsUnitConstructing takes unit whichunit returns boolean
static if USE_INDEXER then
if IsConstructing[GetUnitUserData(whichunit)] != null then
return IsConstructing[GetUnitUserData(whichunit)]
endif
return false
else
if HaveSavedBoolean(Hashtbl, CONSTRUCTION_KEY, GetHandleId(whichunit)) then
return LoadBoolean(Hashtbl, CONSTRUCTION_KEY, GetHandleId(whichunit))
endif
return false
endif
endfunction
function RegisterCost takes integer unitid, Resource whichresource, integer amount returns nothing
call SaveInteger(Hashtbl, unitid, whichresource, amount)
endfunction
function GetUnitBuildTime takes integer unitid returns real
return 60.
endfunction
function GetCost takes integer unitid, Resource whichresource returns integer
//WORKS FOR UNITS, BUILDINGS, TECH ITEMS... ANYTHING, REALLY!
if HaveSavedInteger(Hashtbl, unitid, whichresource) then
return LoadInteger(Hashtbl, unitid, whichresource)
endif
return 0
endfunction
struct Resource
readonly string name
readonly string icon
readonly string color
readonly string anim
readonly string animtag
readonly string msg_not_enough = MSG_DEFAULT_NOTENOUGH
readonly integer orderid
readonly integer abilid = 0
readonly integer array max_carried[11]
readonly integer array harvest_rate[11]
readonly thistype next = 0
readonly thistype prev = 0
boolean useHitAnim = true
readonly static integer count = 0
private static thistype enum = 0
private static real min_x = 0
private static real min_y = 0
private static real min_dist = 0
private static unit min_unit = null
private static destructable min_dest = null
private static player min_player = Player(PLAYER_NEUTRAL_PASSIVE)
private static boolean useEnum = false
private static boolean doRefund = true
private static group cancelGroup
private static timer cancelTimer
private static sound snd_error
static method operator first takes nothing returns thistype
return thistype(0).next
endmethod
static method operator last takes nothing returns thistype
return thistype(0).prev
endmethod
static method create takes string name, string icon, string anim, string animtag, string color, integer harvestrate, integer maxcarried, integer abilid, string ordername returns thistype
local thistype this = thistype.allocate()
local integer i = 0
//ADD TO RESOURCE LIST
set last.next = this
set prev = last
set thistype(0).prev = this
set count = count + 1
//SETUP MEMBERS
set .name = name
set .icon = icon
set .color = color
set .anim = anim
set .animtag = animtag
set .abilid = abilid
set .orderid = OrderId(ordername)
loop
exitwhen i > 11
set .max_carried[i] = maxcarried
set .harvest_rate[i] = harvestrate
set i = i+1
endloop
return this
endmethod
static method get takes destructable d returns thistype
if HaveSavedInteger(Hashtbl, RESOURCE_KEY, GetDestructableTypeId(d)) then
return LoadInteger(Hashtbl, RESOURCE_KEY, GetDestructableTypeId(d))
endif
return 0
endmethod
method registerSource takes integer typeid returns nothing
call SaveInteger(Hashtbl, RESOURCE_KEY, typeid, this)
endmethod
method isDestructableSource takes destructable d returns boolean
return LoadInteger(Hashtbl, RESOURCE_KEY, GetDestructableTypeId(d)) == this
endmethod
method registerDropoffAbil takes integer abilid returns nothing
call SaveInteger(Hashtbl, RESOURCE_KEY, this, abilid)
endmethod
method isUnitDropoff takes unit u returns boolean
if HaveSavedInteger(Hashtbl, RESOURCE_KEY, this) then
return GetUnitAbilityLevel(u, LoadInteger(Hashtbl, RESOURCE_KEY, this)) > 0
endif
return false
endmethod
method setMaxCarriedForPlayer takes player p, integer value returns nothing
set max_carried[GetPlayerId(p)] = value
endmethod
method setHarvestRateForPlayer takes player p, integer value returns nothing
set harvest_rate[GetPlayerId(p)] = value
endmethod
method setInsufficientResourceMsg takes string msg returns nothing
set .msg_not_enough = msg
endmethod
private static method enumClosestDropoff takes nothing returns boolean
local thistype this = .enum
local unit u = GetFilterUnit()
local real dx = GetUnitX(u)-.min_x
local real dy = GetUnitY(u)-.min_y
local real dsq = dx*dx+dy*dy
if .isUnitDropoff(u) and GetOwningPlayer(u) == .min_player then
if dsq < .min_dist or .min_dist == 0 then
set .min_dist = dsq
set .min_unit = u
endif
endif
set u = null
return false
endmethod
method getClosestDropoff takes real x, real y, player p returns unit
local group g = CreateGroup()
set .enum = this
set .min_unit = null
set .min_x = x
set .min_y = y
set .min_dist = 0
set .min_player = p
call GroupEnumUnitsInRange(g, x, y, SEARCH_RADIUS, Filter(function thistype.enumClosestDropoff))
call DestroyGroup(g)
set g = null
return .min_unit
endmethod
private static method enumClosestDestructable takes nothing returns nothing
local destructable d = GetEnumDestructable()
local real dsq = 0
local real dx = 0
local real dy = 0
if GetDestructableLife(d) > 0 and (((not .useEnum) and Resource.get(d) != 0) or (.useEnum and enum.isDestructableSource(d))) then
set dx = GetDestructableX(d)-.min_x
set dy = GetDestructableY(d)-.min_y
set dsq = dx * dx + dy * dy
if dsq < .min_dist or .min_dist == 0 then
set .min_dist = dsq
set .min_dest = d
endif
endif
set d = null
endmethod
method getClosestSource takes real x, real y, real radius returns destructable
call SetRect(RECT, x-radius, y-radius, x+radius, y+radius)
set .enum = this
set .useEnum = true
set .min_x = x
set .min_y = y
set .min_dist = 0
set .min_dest = null
call EnumDestructablesInRect(RECT, null, function thistype.enumClosestDestructable)
return .min_dest
endmethod
static method getClosestDestructable takes real x, real y, real radius returns destructable
call SetRect(RECT, x-radius, y-radius, x+radius, y+radius)
set .useEnum = false
set .min_x = x
set .min_y = y
set .min_dist = 0
set .min_dest = null
call EnumDestructablesInRect(RECT, null, function thistype.enumClosestDestructable)
return .min_dest
endmethod
private static method enumCancelProd takes nothing returns nothing
set thistype.doRefund = false
if IssueImmediateOrderById(GetEnumUnit(), ORDERID_CANCEL) == false then
set thistype.doRefund = true
endif
call GroupRemoveUnit(thistype.cancelGroup, GetEnumUnit())
endmethod
private static method cancelProd takes nothing returns nothing
call ForGroup(thistype.cancelGroup, function thistype.enumCancelProd)
endmethod
static method unitCancelProd takes unit whichunit returns nothing
if IsUnitType(whichunit, UNIT_TYPE_STRUCTURE) then
call GroupAddUnit(thistype.cancelGroup, whichunit)
call TimerStart(thistype.cancelTimer, 0, false, function thistype.cancelProd)
else
call PauseUnit(whichunit, true)
call IssueImmediateOrder(whichunit, "stop")
call PauseUnit(whichunit, false)
endif
endmethod
static method enforceCost takes unit producer, integer objectid, boolean apply returns boolean
local thistype this = .first
local thistype array res_list
local integer array cost_list
local integer playerid = GetPlayerId(GetOwningPlayer(producer))
local integer cost = 0
local integer n = 0
loop
exitwhen this == 0
set cost = GetCost(objectid, this)
if cost > 0 then
if PlayerResource[playerid][this] >= cost then
set res_list[n] = this
set cost_list[n] = cost
set n = n+1
else
call .unitCancelProd(producer)
call DisplayTextToPlayer(Player(playerid), 0, 0, .msg_not_enough+.name+"!")
call StartSound(.snd_error)
return false
endif
endif
set this = .next
endloop
if apply then
//ONCE WE KNOW WE CAN AFFORD THE UNIT, WE SUBSTRACT THE RESORUCES
loop
set n = n-1
set PlayerResource[playerid][res_list[n]] = PlayerResource[playerid][res_list[n]] - cost_list[n]
exitwhen n == 0
endloop
endif
return true
endmethod
static method refundCost takes integer objectid, integer playerid returns nothing
local thistype this = .first
local integer cost = 0
loop
exitwhen this == 0
set cost = GetCost(objectid, this)
if cost > 0 then
set PlayerResource[playerid][this] = PlayerResource[playerid][this]+cost
endif
set this = .next
endloop
endmethod
private static method onOrder takes nothing returns boolean
local integer orderid = GetIssuedOrderId()
local unit producer = GetTriggerUnit()
//WE DON'T WANT TO APPLY THE COST OF STRUCTURES UNTIL THEY ARE PLACED
//HOWEVER, WE NEED TO APPLY COST TO STRUCTURES BEING UPGRADED!
local boolean applyCost = not((IsUnitType(producer, UNIT_TYPE_STRUCTURE) == false) and IsUnitIdType(orderid, UNIT_TYPE_STRUCTURE))
if orderid != ORDERID_CANCEL and orderid != OrderId("move") and orderid != OrderId("smart") and orderid != OrderId("stop") then
call .enforceCost(producer, orderid, applyCost)
endif
set producer = null
return false
endmethod
private static method onBuildingFinished takes nothing returns boolean
static if USE_INDEXER then
set IsConstructing[GetUnitUserData(GetConstructedStructure())] = false
else
call SaveBoolean(Hashtbl, CONSTRUCTION_KEY, GetHandleId(GetConstructedStructure()), false)
endif
return false
endmethod
private static method onBuildingConstructed takes nothing returns boolean
local unit constructed = GetTriggerUnit()
if thistype.enforceCost(constructed, GetUnitTypeId(constructed), true) == false then
call RemoveUnit(constructed)
set thistype.doRefund = true
endif
static if USE_INDEXER then
set IsConstructing[GetUnitUserData(constructed)] = true
else
call SaveBoolean(Hashtbl, CONSTRUCTION_KEY, GetHandleId(constructed), false)
endif
set constructed = null
return false
endmethod
private static method onUnitCanceled takes nothing returns boolean
if thistype.doRefund then
call .refundCost(GetTrainedUnitType(), GetPlayerId(GetTriggerPlayer()))
else
set thistype.doRefund = true
endif
return false
endmethod
private static method onResearchCanceled takes nothing returns boolean
if thistype.doRefund then
call .refundCost(GetResearched(), GetPlayerId(GetTriggerPlayer()))
else
set thistype.doRefund = true
endif
return false
endmethod
private static method onUpgradeCanceled takes nothing returns boolean
//WE ALWAYS REFUND CANCELED UPGRADES!
call .refundCost(GetUnitTypeId(GetTriggerUnit()), GetPlayerId(GetTriggerPlayer()))
set thistype.doRefund = true
return false
endmethod
private static method onBuildingCanceled takes nothing returns boolean
//WE ALWAYS REFUND CANCELED STRUCTURES!
call .refundCost(GetUnitTypeId(GetCancelledStructure()), GetPlayerId(GetTriggerPlayer()))
set thistype.doRefund = true
return false
endmethod
private static method onInit takes nothing returns nothing
local integer i = 0
local trigger t = CreateTrigger()
set thistype.cancelGroup = CreateGroup()
set thistype.cancelTimer = CreateTimer()
//REGISTER WHEN A UNIT STARTS SOME KIND OF ACTIVITY
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onOrder))
//UNIT CANCEL
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_TRAIN_CANCEL, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onUnitCanceled))
//BUILDING START
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_CONSTRUCT_START, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onBuildingConstructed))
//BUILDING START
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onBuildingFinished))
//BUILDING CANCEL
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onBuildingCanceled))
//UPGRADE CANCEL
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_UPGRADE_CANCEL, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onUpgradeCanceled))
//RESEARCH CANCEL
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_RESEARCH_CANCEL, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onResearchCanceled))
set .snd_error = CreateSound("Sound\\Interface\\Error.wav", false, false, false, 0, 0, "DefaultEAXON")
call SetSoundVolume(.snd_error, 127 )
endmethod
endstruct
struct Harvester
static integer count = 0
private boolean inlist = false
thistype next = 0
thistype prev = 0
unit u
Resource target_type = 0
Resource carried_type = 0
integer carried_amount = 0
destructable target = null
private static timer cooldown
private static thistype array harvesters //FOR INDEXER VERSION
static method operator first takes nothing returns thistype
return thistype(0).next
endmethod
static method operator last takes nothing returns thistype
return thistype(0).prev
endmethod
method listRemove takes nothing returns nothing
if not .inlist then
return
endif
set .inlist = false
set .prev.next = next
set .next.prev = prev
set .count = .count - 1
set .target_type = 0
if .count == 0 then
call PauseTimer(.cooldown)
endif
endmethod
static method create takes unit u returns thistype
local thistype this = thistype.allocate()
set .u = u
static if USE_INDEXER then
set .harvesters[GetUnitUserData(u)] = this
else
call SaveInteger(Hashtbl, HARVESTER_KEY, GetHandleId(u), this)
endif
return this
endmethod
static method get takes unit u returns thistype
static if USE_INDEXER then
if .harvesters[GetUnitUserData(u)] != 0 then
return .harvesters[GetUnitUserData(u)]
endif
else
if HaveSavedInteger(Hashtbl, HARVESTER_KEY, GetHandleId(u)) then
return LoadInteger(Hashtbl, HARVESTER_KEY, GetHandleId(u))
endif
endif
return 0
endmethod
static method isUnitHarvester takes unit u returns boolean
local Resource r = Resource.first
loop
exitwhen r == 0
if GetUnitAbilityLevel(u, r.abilid) > 0 then
return true
endif
set r = r.next
endloop
return false
endmethod
static method remove takes unit u returns boolean
local thistype this = thistype.get(u)
static if USE_INDEXER then
if this != 0 then
call .listRemove()
call .destroy()
call RemoveSavedInteger(Hashtbl, HARVESTER_KEY, GetHandleId(u))
return true
endif
else
if this != 0 then
call .listRemove()
call .destroy()
set .harvesters[GetUnitUserData(u)] = 0
return true
endif
endif
return false
endmethod
method returnResource takes nothing returns nothing
call .listRemove()
if not IssueTargetOrder(.u, DROP_ORDER, .carried_type.getClosestDropoff(GetUnitX(.u), GetUnitY(.u), GetOwningPlayer(.u))) then
debug call BJDebugMsg(MSG_ERROR+"Failed to cast drop ability!")
endif
endmethod
method setCarried takes Resource setTo returns nothing
call AddUnitAnimationProperties(.u, .carried_type.animtag, false)
call AddUnitAnimationProperties(.u, setTo.animtag, true)
set .carried_type = setTo
set .carried_amount = 0
endmethod
private method addTextTag takes nothing returns nothing
local texttag tt = CreateTextTag()
local real vel = 4.5 / 128
local real xvel = vel * Cos(45 * bj_DEGTORAD)
local real yvel = vel * Sin(45 * bj_DEGTORAD)
call SetTextTagText(tt, .carried_type.color+"+"+I2S(.carried_amount), 0.023)
call SetTextTagPos(tt, GetUnitX(.u), GetUnitY(.u), 100)
call SetTextTagColor(tt, 255, 215, 255, 255)
call SetTextTagVelocity(tt, xvel, yvel)
call SetTextTagLifespan(tt, 1)
call SetTextTagFadepoint(tt, 0)
call SetTextTagPermanent(tt, false)
set tt = null
endmethod
static method filterHarvester takes nothing returns boolean
if .isUnitHarvester(GetFilterUnit()) then
call thistype.create(GetFilterUnit())
endif
return false
endmethod
private static method registerInRect takes rect r returns nothing
call GroupEnumUnitsInRect(ENUM_GROUP, r, Filter(function thistype.filterHarvester))
endmethod
private static method registerAll takes nothing returns nothing
call DestroyTimer(GetExpiredTimer())
call thistype.registerInRect(bj_mapInitialPlayableArea)
endmethod
private static method onUpdate takes nothing returns nothing
local thistype this = .first
local thistype temp
//THIS IS NECESSARY IN CASE THE SPELL IS ON COOLDOWN
loop
exitwhen this == 0
set temp = .next
if .target == null or GetDestructableLife(.target) < 0.4 then
//TRY TO FIND ANOTHER SOURCE IF OURS IS INVALID
if .target_type != 0 then
set .target = .target_type.getClosestSource(GetUnitX(.u), GetUnitY(.u), 1000)
elseif .carried_type != 0 then
set .target = .carried_type.getClosestSource(GetUnitX(.u), GetUnitY(.u), 1000)
endif
endif
if .target == null then
//IF WE STILL COULDN'T FIND ANY, MIGHT ASWELL GO HOME
if .carried_amount > 0 then
call .returnResource()
else
call IssueImmediateOrder(.u, "stop")
//WE NO LONGER NEED TO CHECK THIS UNIT:
call .listRemove()
endif
else
if .target_type != 0 then
call IssueTargetOrderById(.u, .target_type.orderid, .target)
else
debug call BJDebugMsg(MSG_ERROR+"Target type undefined!")
endif
endif
set this = temp
endloop
endmethod
method listAdd takes nothing returns nothing
if .inlist or this==0 then
return
endif
set .inlist = true
set .last.next = this
set .prev = .last
set thistype(0).prev = this
set .next = thistype(0)
set .count = .count + 1
if .count == 1 then
call TimerStart(.cooldown, INTERVAL, true, function thistype.onUpdate)
endif
endmethod
private static method onHarvest takes nothing returns boolean
local thistype this = thistype.get(GetTriggerUnit())
local integer p = GetPlayerId(GetOwningPlayer(GetTriggerUnit()))
local destructable targ = GetSpellTargetDestructable()
local Resource r
if this == 0 or targ == null then
//INVALID HARVESTER OR TARGET!
return false
endif
set r = Resource.get(targ)
if r != 0 and GetSpellAbilityId() == r.abilid then
if .carried_type != r or .carried_amount == 0 then
call .setCarried(r)
endif
set .target = targ
if .carried_amount >= .carried_type.max_carried then
set .carried_amount = .carried_type.max_carried
call SetUnitAnimation(.u, "stand"+" "+.carried_type.animtag)
call .returnResource()
set targ = null
return false
else
call SetUnitAnimation(.u, .carried_type.anim+" "+.carried_type.animtag)
if not .inlist then
set .target_type = r
call .listAdd()
endif
endif
if GetWidgetLife(targ) < .carried_type.harvest_rate then
set .carried_amount = R2I(.carried_amount + GetWidgetLife(targ))
else
set .carried_amount = .carried_amount + .carried_type.harvest_rate
if r.useHitAnim then
call SetDestructableAnimation(.target, "stand hit")
call QueueDestructableAnimation(.target, "stand")
endif
endif
call SetWidgetLife(.target, GetWidgetLife(.target)-.carried_type.harvest_rate)
if GetWidgetLife(.target) < 0.4 and .carried_amount < .carried_type.max_carried then
set .target = .carried_type.getClosestSource(GetUnitX(.u), GetUnitY(.u), SEARCH_RADIUS)
endif
endif
set targ = null
return false
endmethod
private static method onOrder takes nothing returns boolean
local thistype this = thistype.get(GetTriggerUnit())
local destructable d = GetOrderTargetDestructable()
local unit u = GetOrderTargetUnit()
local integer orderid = GetIssuedOrderId()
local Resource r
if this == 0 then
set u = null
set d = null
return false
endif
if not(orderid == OrderId("smart") or orderid == OrderId("attack")) then
set r = Resource.first
loop
exitwhen r == 0
if orderid == r.orderid then
set u = null
set d = null
return false
endif
set r = r.next
endloop
//ORDER IS NOT RESOURCE RELATED - REMOVE FROM HARVESTER LOOP!
call .listRemove()
set u = null
set d = null
return false
endif
//TARGET IS UNIT
if u != null then
if .carried_type.isUnitDropoff(u) and GetOwningPlayer(u) == GetOwningPlayer(.u) then
if IsUnitConstructing(u) == false then
//THE TARGET IS A DROPOFF, DROP RESOURCES
call IssueTargetOrder(.u, DROP_ORDER, u)
endif
endif
set u = null
set d = null
return false
endif
//TARGET IS DESTRUCTABLE
if d == null then
//THIS FIXES A BUG CAUSED BY FOG OF WAR, WHERE A SMART ORDER WOULD RETURN NO DESTRUCTABLE.
set d = Resource.getClosestDestructable(GetOrderPointX(), GetOrderPointY(), 80)
endif
if d != null then
set r = Resource.get(d)
//IF WE FOUND A DESTRUCTABLE, BUT IT IS NOT A RESOURCE, WE CAN TERMINATE THE FUNCTION.
if r == 0 then
set u = null
set d = null
return false
endif
if GetUnitAbilityLevel(.u, r.abilid) == 0 or IssueTargetOrderById(.u, r.orderid, d) == false then
//WE NEED TO STOP UNITS FROM INSTINCTIVELY ATTACKING RESOURCES.
call PauseUnit(.u, true)
call IssueImmediateOrder(.u, "stop")
call PauseUnit(.u, false)
call SetUnitAnimation(.u, "stand")
call IssueTargetOrder(.u, "move", d)
if GetUnitAbilityLevel(.u, r.abilid) == 0 then
//call BJDebugMsg(MSG_ERROR+"Failed to target resource - Unit does not have required harvest ability.")
else
//call BJDebugMsg(MSG_ERROR+"Failed to target resource - Ability is on cooldown or cannot target destructable.")
endif
endif
if GetUnitAbilityLevel(.u, r.abilid) > 0 then
//IF TARGET IS A RESOURCE, ADD THE HARVESTER TO THE LOOP AGAIN:
set .target = d
set .target_type = r
call .listAdd()
else
call .listRemove()
endif
else
call .listRemove()
endif
set u = null
set d = null
return false
endmethod
private static method onDrop takes nothing returns boolean
local thistype this = thistype.get(GetTriggerUnit())
local integer p = GetPlayerId(GetOwningPlayer(GetTriggerUnit()))
local unit target = GetSpellTargetUnit()
if this == 0 or .carried_type == 0 or (GetOwningPlayer(GetTriggerUnit()) != GetOwningPlayer(target)) then
set target = null
return false
elseif GetSpellAbilityId() != DROP_ABIL or .carried_type.isUnitDropoff(target) == false or IsUnitConstructing(target) then
//NO NEED TO CONTINUE IF TARGET UNIT IS NOT A DROPOFF
set target = null
return false
endif
if .carried_amount > 0 then
//DROP RESOURCES IF WE HAVE ANY
set PlayerResource[.carried_type] = PlayerResource[.carried_type] + .carried_amount
call .addTextTag()
set .carried_amount = 0
call AddUnitAnimationProperties(.u, .carried_type.animtag, false)
endif
if .target == null or GetDestructableLife(.target) <= 0 then
//TRY TO FIND A NEW SOURCE IF THE OLD ONE IS GONE
set .target = .carried_type.getClosestSource(GetUnitX(.u), GetUnitY(.u), 1000)
endif
if .target != null then
call IssueTargetOrderById(.u, .carried_type.orderid, .target)
set .target_type = .carried_type
call .listAdd()
endif
set target = null
return false
endmethod
private static method onEnterMap takes nothing returns boolean
if .isUnitHarvester(GetTriggerUnit()) then
call .create(GetTriggerUnit())
endif
return false
endmethod
private static method onDeath takes nothing returns boolean
local thistype this = thistype.get(GetTriggerUnit())
if this != 0 then
call .remove(GetTriggerUnit())
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local region r = CreateRegion()
local integer i = 0
set .cooldown = CreateTimer()
//SOME TRIGGERS WE NEED:
//REGISTER ORDERS
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_CHANNEL, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onOrder))
//REGISTER HARVEST SPELL
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onHarvest))
//REGISTER DROPOFF SPELL
//I WAS NOT SURE IF ONE CONDITION RETURNING FALSE CAUSE THE GAME TO SKIP THE NEXT ONE, SO I SPLIT THIS INTO TWO TRIGGERS.
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onDrop))
//REGISTER HARVESTER DEATH
set i = 0
set t = CreateTrigger()
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
set i = i+1
endloop
call TriggerAddCondition(t, Condition(function thistype.onDeath))
//REGISTER HARVESTER ENTER MAP
set t = CreateTrigger()
call RegionAddRect(r, bj_mapInitialPlayableArea)
call TriggerRegisterEnterRegion(t, r, null)
call TriggerAddCondition(t, Condition( function thistype.onEnterMap))
//REGISTER ALL EXISTING HARVESTERS:
call TimerStart(CreateTimer(), 0, false, function thistype.registerAll)
set t = null
set r = null
endmethod
endstruct
endlibrary
library ResourceSetup initializer Init requires ResourceLibrary
/*
HOWTO:
//GET THE NAME OF YOUR RESOURCE
set udg_myString = MyResource.name
//Example:
set udg_woodName = Wood.name
//GET THE ICON OF YOUR RESOURCE
set udg_myString = MyResource.icon
//Example:
set udg_woodIcon = Wood.icon
//GET THE AMOUNT OF RESOURCE ACCUMULATED BY PlAYER
set udg_player_resource = PlayerResource[playerid][resource]
//Example:
set udg_player1_Wood = PlayerResource[0][Wood]
set udg_player2_Gold = PlayerResource[1][Gold]
//Note that player id:s start from 0 in JASS!
//This means Player 1 is 0, Player 2 is 1, etc.
*/
globals
//Here you can specify the names of your resources.
//Just type "resource", space, and the name you want it to have. You can have up to 20 resources,
//If you need more, you can change the "MAX_RESOURCES" constant in the resource library up to a
//maximum of 738 resources.
Resource Wood
Resource Food
Resource Gold
Resource Iron
Resource Stone
endglobals
private function Init takes nothing returns nothing
//This is where you create the actual resource.
//"harvest" is the raw ability ID of your harvest ability. I recommend that you simply copy the ability used in the test map.
//"ordername", in this case "cripple", is the order used to cast the "harvest" ability.
///static method create takes string name, string icon, string anim, string animtag, string color, integer harvestrate, integer maxcarried, integer harvest, string ordername returns resource
set Wood = Resource.create("Wood", "ReplaceableTextures\\CommandButtons\\BTNBundleOfLumber.blp", "work", "lumber", "|cff006400", 2, 10, 'harv', "cripple")
set Gold = Resource.create("Gold", "ReplaceableTextures\\CommandButtons\\BTNChestOfGold.blp", "work", "gold", "|cffffd700", 2, 10, 'harv', "cripple")
set Stone = Resource.create("Stone", "ReplaceableTextures\\CommandButtons\\BTNGolemThunderclap.blp", "work", "gold", "|cffa0a0a0", 2, 10, 'harv', "cripple")
set Food = Resource.create("Food", "ReplaceableTextures\\CommandButtons\\BTNMonsterLure.blp", "work", "gold", "|cffff4500", 2, 10, 'harv', "cripple")
//To make a unit into a dropoff point, you add a special ability to it. You can register a dropoff ability for each individual resource, or use the same, as you please.
//If you don't want the dropoff ability to be seen, just use one which doesn't shown an icon, such as "sphere", "channel", or "item armor bonus".
//This is how you registrer dropoff abilities:
call Wood.registerDropoffAbil('doff')
call Gold.registerDropoffAbil('doff')
call Stone.registerDropoffAbil('doff')
call Food.registerDropoffAbil('doff')
//This is how you register what destructables constitute your resource.
//The only requirement is that they are destructables, and that they don't use the "tree" classification.
call Wood.registerSource('tre0')
call Stone.registerSource('ston')
call Gold.registerSource('gold')
call Food.registerSource('shee')
call Food.registerSource('stag')
//The below line disables hit animations (such as tree shake) for a certain resource.
set Food.useHitAnim = false
//Finally, you might want to add your custom resource costs to your units and buildings. This is quite tedious work,
//but you will instead not have to bother with adding costs in the object editor. This is how you register your costs:
//takes integer unitid, resource whichresource, integer amount
//Peasant
call RegisterCost('hpea', Gold, 50)
call RegisterCost('hpea', Food, 50)
//Footman
call RegisterCost('hfoo', Gold, 75)
call RegisterCost('hfoo', Food, 50)
//Rifleman
call RegisterCost('hrif', Gold, 25)
call RegisterCost('hrif', Food, 50)
call RegisterCost('hrif', Wood, 25)
//Barracks
call RegisterCost('hbar', Gold, 100)
call RegisterCost('hbar', Wood, 220)
call RegisterCost('hbar', Stone, 50)
//Keep
call RegisterCost('hkee', Gold, 180)
call RegisterCost('hkee', Wood, 280)
call RegisterCost('hkee', Stone, 65)
//Town Hall
call RegisterCost('htow', Gold, 120)
call RegisterCost('htow', Wood, 240)
call RegisterCost('htow', Stone, 50)
//Town Hall
call RegisterCost('hbla', Gold, 80)
call RegisterCost('hbla', Wood, 180)
call RegisterCost('hbla', Stone, 35)
//Lumber Mill
call RegisterCost('hlum', Gold, 50)
call RegisterCost('hlum', Wood, 120)
//Scout Tower
call RegisterCost('hwtw', Gold, 50)
call RegisterCost('hwtw', Wood, 100)
call RegisterCost('hwtw', Stone, 50)
//Guard Tower
call RegisterCost('hgtw', Gold, 25)
call RegisterCost('hgtw', Wood, 50)
call RegisterCost('hgtw', Stone, 25)
//Farm
call RegisterCost('hhou', Gold, 35)
call RegisterCost('hhou', Wood, 90)
//Defend
call RegisterCost('Rhde', Gold, 100)
call RegisterCost('Rhde', Wood, 150)
//Long Rifles
call RegisterCost('Rhri', Gold, 150)
call RegisterCost('Rhri', Wood, 75)
//Setup starting resources
set PlayerResource[0][Food] = 200
set PlayerResource[0][Gold] = 300
set PlayerResource[0][Stone] = 100
set PlayerResource[0][Wood] = 300
endfunction
endlibrary
library ResourceMultiBoard initializer Init requires ResourceLibrary
globals
constant real UPDATE_INTERVAL = 0.5
multiboard array ResourceDisplay
endglobals
private function Update takes nothing returns nothing
local integer i = 0
local Resource r
local multiboarditem mb
loop
exitwhen i > 11
set r = Resource.first
loop
exitwhen r == 0
set mb = MultiboardGetItem(ResourceDisplay[i], r-1, 2)
call MultiboardSetItemValue(mb, I2S(PlayerResource[i][r]))
call MultiboardReleaseItem(mb)
set r = r.next
endloop
set i = i+1
endloop
set mb = null
endfunction
private function Setup takes nothing returns nothing
local integer i = 0
local Resource r
local multiboarditem mb
local timer t = CreateTimer()
call DestroyTimer(GetExpiredTimer())
loop
exitwhen i > 11
set ResourceDisplay[i] = CreateMultiboard()
call MultiboardSetRowCount(ResourceDisplay[i], Resource.count)
call MultiboardSetColumnCount(ResourceDisplay[i], 3)
call MultiboardSetTitleText(ResourceDisplay[i], "Resources:")
if i == 0 then
call MultiboardDisplay(ResourceDisplay[i], true)
else
call MultiboardDisplay(ResourceDisplay[i], false)
endif
set r = Resource.first
loop
exitwhen r == 0
set mb = MultiboardGetItem(ResourceDisplay[i], r-1, 0)
call MultiboardSetItemStyle(mb, false, true )
call MultiboardSetItemIcon(mb, r.icon)
call MultiboardSetItemWidth( mb, 0.015)
call MultiboardReleaseItem(mb)
set r = r.next
endloop
set r = Resource.first
loop
exitwhen r == 0
set mb = MultiboardGetItem(ResourceDisplay[i], r-1, 1)
call MultiboardSetItemStyle(mb, true, false)
call MultiboardSetItemValue(mb, r.name+":")
call MultiboardSetItemWidth(mb, 0.045)
call MultiboardReleaseItem(mb)
set r = r.next
endloop
set r = Resource.first
loop
exitwhen r == 0
set mb = MultiboardGetItem(ResourceDisplay[i], r-1, 2)
call MultiboardSetItemStyle(mb, true, false)
call MultiboardSetItemValue(mb, I2S(PlayerResource[i][r]))
call MultiboardSetItemWidth(mb, 0.03)
call MultiboardReleaseItem(mb)
set r = r.next
endloop
set i = i+1
endloop
call MultiboardDisplay(ResourceDisplay[0], true)
call TimerStart(t, UPDATE_INTERVAL, true, function Update)
set mb = null
set t = null
endfunction
private function Init takes nothing returns nothing
local timer t = CreateTimer()
call TimerStart(t, 0.5, false, function Setup)
set t = null
endfunction
endlibrary