// Baradé's Black Arrow System 1.1
//
// Supports Black Arrow abilities for target units with levels greater than 5.
// The standard Black Arrow abilities from Warcraft only work with target units up to level 5.
//
// Usage:
// - Copy this code into your map script or a trigger converted into code.
// - Copy the custom buff ability "Black Arrow Buff" (A000) into your map and adapt the raw code in the constant BUFF_ABILITY_ID to the new raw code in your map.
// - Optional: Add all preplaced units in your map with enabled Black Arrow auto casting using the function BlackArrowAddAutoCaster.
// - Optional: Use the API functions to register custom abilities and item types.
// - Optional: Create triggers and register Black Arrow events for further custom actions.
//
// Design:
// Auto casters are detected by issued orders. Preplaced units are created in the generated map script function CreateAllUnits which is called in the generated
// method main before the initialization of this system. This means that issued orders from preplaced units won't be detected by the system's order triggers.
// Hence, you have to use the function BlackArrowAddAutoCaster to add all preplaced units in your map with enabled Black Arrow auto casting.
//
// API:
//
// function BlackArrowAddAbility takes integer abilityId, integer level, integer summonedUnitTypeId, integer summonedUnitsCount, real summonedUnitDuration, real durationHero, real durationUnit, integer buffId returns integer
//
// Adds another custom ability to the system with the given configuration. Whenever a unit with the added ability at the given level kills a target with a level greater than 5, it will automatically summon the minions
// with the given unit type for the given amount of time.
// The function returns a unique index refering to the added ability. The first index starts at 1.
//
// function BlackArrowAddItemTypeId takes integer itemTypeId, integer abilityIndex returns integer
//
// Adds an item type which has the Black Arrow ability with the given index. You can combine this function with BlackArrowAddAbility and directly add the ability when adding the item type.
// Whenever a unit with carrying an item with the added item type kills a target unit with a level greater than 5, it will automatically summon the minions with the given configuration from the given ability.
// The function returns a unique index refering to the added item type. The first index starts at 1.
//
//
// function BlackArrowAddAutoCaster takes unit whichUnit returns nothing
//
// Adds the given unit as auto caster. This is required to detect damage caused by auto casters and cast the Black Arrow effect.
// All preplaced units with an enabled Black Arrow ability in the map must be added manually with this function.
//
// function BlackArrowRemoveAutoCaster takes unit whichUnit returns nothing
//
// Removes the given unit from the group of auto casters.
//
// function BlackArrowIsAutoCaster takes unit which returns boolean
//
// Returns true if the given unit is an auto caster. Otherwise, it returns false.
//
// function TriggerRegisterBlackArrowEvent takes trigger whichTrigger returns nothing
//
// Registers a Black Arrow event for the given callback trigger. This means that the trigger is evaluated and executed whenever an added Black Arrow ability is casted for a target unit above level 5.
// For the standard ability casts, you have to use the standard ability cast events instead.
//
// function GetTriggerBlackArrowCaster takes nothing returns unit
//
// Returns the casting unit for the current callback trigger.
//
// function GetTriggerBlackArrowTarget takes nothing returns unit
//
// Returns the target unit for the current callback trigger.
//
// function GetTriggerBlackArrowSummonedUnits takes nothing returns group
//
// Returns all summoned minions for the current callback trigger. Never destroy this group since it is basically bj_lastCreatedGroup and does not leak.
//
// function GetTriggerBlackArrowAbilityId takes nothing returns integer
//
// Returns the ability ID of the casted Black Arrow ability.
//
library BlackArrowSystem
globals
public constant integer BUFF_ABILITY_ID = 'A0FU'
//public constant integer BUFF_ABILITY_ID = 'A000'
public constant string ORDER_ON = "blackarrowon"
public constant string ORDER_OFF = "blackarrowoff"
public constant boolean ADD_STANDARD_OBJECT_DATA = true
public constant boolean ADD_ALL_UNITS_WITH_ORBS = true
private integer array BlackArrowAbiliyId
private integer array BlackArrowAbiliyLevel
private integer array BlackArrowAbiliySummonedUnitTypeId
private integer array BlackArrowAbiliySummonedUnitsCount
private real array BlackArrowAbiliySummonedUnitDuration
private real array BlackArrowAbiliyDurationHero
private real array BlackArrowAbiliyDurationUnit
private integer array BlackArrowAbiliyBuffId
private integer BlackArrowAbilityCounter = 1
private integer array BlackArrowItemTypeId
private integer array BlackArrowItemTypeAbilityIndex
private integer BlackArrowItemTypeCounter = 1
private hashtable BlackArrowHashTable = InitHashtable()
private group BlackArrowTargets = CreateGroup()
private group BlackArrowAutoCasters = CreateGroup()
private group BlackArrowItemUnits = CreateGroup()
private trigger BlackArrowDamageTrigger = CreateTrigger()
private trigger BlackArrowDeathTrigger = CreateTrigger()
private trigger BlackArrowOrderTrigger = CreateTrigger()
private trigger BlackArrowItemPickupTrigger = CreateTrigger()
private trigger BlackArrowItemDropTrigger = CreateTrigger()
// callbacks
private unit BlackArrowCaster = null
private unit BlackArrowTarget = null
private group BlackArrowSummonedUnits = null
private integer BlackArrowAbilityId = 0
private trigger array BlackArrowCallbackTrigger
private integer BlackArrowCallbackTriggerCounter = 0
private boolean hookEnabled = true
endglobals
function GetTriggerBlackArrowCaster takes nothing returns unit
return BlackArrowCaster
endfunction
function GetTriggerBlackArrowTarget takes nothing returns unit
return BlackArrowTarget
endfunction
function GetTriggerBlackArrowSummonedUnits takes nothing returns group
return BlackArrowSummonedUnits
endfunction
function GetTriggerBlackArrowAbilityId takes nothing returns integer
return BlackArrowAbilityId
endfunction
function TriggerRegisterBlackArrowEvent takes trigger whichTrigger returns nothing
set BlackArrowCallbackTrigger[BlackArrowCallbackTriggerCounter] = whichTrigger
set BlackArrowCallbackTriggerCounter = BlackArrowCallbackTriggerCounter + 1
endfunction
function BlackArrowAddAbility takes integer abilityId, integer level, integer summonedUnitTypeId, integer summonedUnitsCount, real summonedUnitDuration, real durationHero, real durationUnit, integer buffId returns integer
set BlackArrowAbiliyId[BlackArrowAbilityCounter] = abilityId
set BlackArrowAbiliyLevel[BlackArrowAbilityCounter] = level
set BlackArrowAbiliySummonedUnitTypeId[BlackArrowAbilityCounter] = summonedUnitTypeId
set BlackArrowAbiliySummonedUnitsCount[BlackArrowAbilityCounter] = summonedUnitsCount
set BlackArrowAbiliySummonedUnitDuration[BlackArrowAbilityCounter] = summonedUnitDuration
set BlackArrowAbiliyDurationHero[BlackArrowAbilityCounter] = durationHero
set BlackArrowAbiliyDurationUnit[BlackArrowAbilityCounter] = durationUnit
set BlackArrowAbiliyBuffId[BlackArrowAbilityCounter] = buffId
set BlackArrowAbilityCounter = BlackArrowAbilityCounter + 1
return BlackArrowAbilityCounter - 1
endfunction
function BlackArrowAddItemTypeId takes integer itemTypeId, integer abilityIndex returns integer
set BlackArrowItemTypeId[BlackArrowItemTypeCounter] = itemTypeId
set BlackArrowItemTypeAbilityIndex[BlackArrowItemTypeCounter] = abilityIndex
set BlackArrowItemTypeCounter = BlackArrowItemTypeCounter + 1
return BlackArrowItemTypeCounter - 1
endfunction
function BlackArrowAddAutoCaster takes unit whichUnit returns nothing
call GroupAddUnit(BlackArrowAutoCasters, whichUnit)
endfunction
function BlackArrowRemoveAutoCaster takes unit whichUnit returns nothing
call GroupRemoveUnit(BlackArrowAutoCasters, whichUnit)
endfunction
function BlackArrowIsAutoCaster takes unit which returns boolean
return IsUnitInGroup(which, BlackArrowAutoCasters)
endfunction
function BlackArrowPrintDebug takes nothing returns nothing
call BJDebugMsg("Targets: " + I2S(CountUnitsInGroup(BlackArrowTargets)))
call BJDebugMsg("Auto Casters: " + I2S(CountUnitsInGroup(BlackArrowAutoCasters)))
call BJDebugMsg("Item Units: " + I2S(CountUnitsInGroup(BlackArrowItemUnits)))
endfunction
private function GetMatchingBlackArrowAbilityIndex takes unit caster returns integer
local integer result = 0
local integer i = 1
loop
exitwhen (i >= BlackArrowAbilityCounter or result > 0)
if (GetUnitAbilityLevel(caster, BlackArrowAbiliyId[i]) == BlackArrowAbiliyLevel[i]) then
set result = i
endif
set i = i + 1
endloop
return result
endfunction
private function GetMatchingBlackArrowItemTypeIndex takes integer itemTypeId returns integer
local integer result = 0
local integer i = 1
loop
exitwhen (i >= BlackArrowItemTypeCounter or result > 0)
if (BlackArrowItemTypeId[i] == itemTypeId) then
set result = i
endif
set i = i + 1
endloop
return result
endfunction
private function TimerFunctionBlackArrowBuffExpires takes nothing returns nothing
local unit target = LoadUnitHandle(BlackArrowHashTable, GetHandleId(GetExpiredTimer()), 0)
call FlushChildHashtable(BlackArrowHashTable, GetHandleId(target))
call UnitRemoveAbility(target, BUFF_ABILITY_ID)
call GroupRemoveUnit(BlackArrowTargets, target)
set target = null
endfunction
private function MarkTarget takes integer abilityIndex, unit source, unit target returns nothing
local timer whichTimer = LoadTimerHandle(BlackArrowHashTable, 0, GetHandleId(target))
//call BJDebugMsg("Marking Black Arrow target " + GetUnitName(GetTriggerUnit()))
if (whichTimer != null) then
call FlushChildHashtable(BlackArrowHashTable, GetHandleId(whichTimer))
call PauseTimer(whichTimer)
call DestroyTimer(whichTimer)
set whichTimer = null
endif
set whichTimer = CreateTimer()
call SaveUnitHandle(BlackArrowHashTable, GetHandleId(whichTimer), 0, target)
call SaveTimerHandle(BlackArrowHashTable, GetHandleId(target), 0, whichTimer)
call SaveUnitHandle(BlackArrowHashTable, GetHandleId(target), 1, source)
call SaveInteger(BlackArrowHashTable, GetHandleId(target), 2, abilityIndex)
if (IsUnitType(target, UNIT_TYPE_HERO)) then
call TimerStart(whichTimer, BlackArrowAbiliyDurationHero[abilityIndex], false, function TimerFunctionBlackArrowBuffExpires)
else
call TimerStart(whichTimer, BlackArrowAbiliyDurationUnit[abilityIndex], false, function TimerFunctionBlackArrowBuffExpires)
endif
call UnitAddAbility(target, BUFF_ABILITY_ID)
if (not IsUnitInGroup(target, BlackArrowTargets)) then
call GroupAddUnit(BlackArrowTargets, target)
endif
endfunction
private function ExecuteCallbackTriggers takes unit source, unit target, group summonedUnits, integer abilityId returns nothing
local integer i = 0
set BlackArrowCaster = source
set BlackArrowTarget = target
set BlackArrowSummonedUnits = summonedUnits
set BlackArrowAbilityId = abilityId
loop
exitwhen (i == BlackArrowCallbackTriggerCounter)
call TriggerExecute(BlackArrowCallbackTrigger[i])
set i = i + 1
endloop
endfunction
private function SummonEffect takes integer abilityIndex, unit source, unit target returns group
local location tmpLocation = GetUnitLoc(target)
// Does not leak since it uses bj_lastCreatedGroup:
local group summonedUnits = CreateNUnitsAtLoc(BlackArrowAbiliySummonedUnitsCount[abilityIndex], BlackArrowAbiliySummonedUnitTypeId[abilityIndex], GetOwningPlayer(source), tmpLocation, GetUnitFacing(target))
local integer i = 0
loop
exitwhen (i == BlzGroupGetSize(summonedUnits))
call SetUnitAnimation(BlzGroupUnitAt(summonedUnits, i), "Birth")
call UnitApplyTimedLife(BlzGroupUnitAt(summonedUnits, i), BlackArrowAbiliyBuffId[abilityIndex], BlackArrowAbiliySummonedUnitDuration[abilityIndex])
set i = i + 1
endloop
call ExecuteCallbackTriggers(source, target, summonedUnits, BlackArrowAbiliyId[abilityIndex])
return summonedUnits
endfunction
private function Effect takes unit target returns group
local timer whichTimer = LoadTimerHandle(BlackArrowHashTable, GetHandleId(target), 0)
local unit source = LoadUnitHandle(BlackArrowHashTable, GetHandleId(target), 1)
local integer abilityIndex = LoadInteger(BlackArrowHashTable, GetHandleId(target), 2)
local group summonedUnits = SummonEffect(abilityIndex, source, target)
//call BJDebugMsg("Black Arrow effect on target " + GetUnitName(target) + " with ability level " + I2S(BlackArrowAbiliyLevel[abilityIndex]) + " summoning units of type " + GetObjectName(BlackArrowAbiliySummonedUnitTypeId[abilityIndex]))
if (whichTimer != null) then
call FlushChildHashtable(BlackArrowHashTable, GetHandleId(whichTimer))
call PauseTimer(whichTimer)
call DestroyTimer(whichTimer)
set whichTimer = null
endif
call FlushChildHashtable(BlackArrowHashTable, GetHandleId(target))
call UnitRemoveAbility(target, BUFF_ABILITY_ID)
call GroupRemoveUnit(BlackArrowTargets, target)
// remove the decaying corpse
call RemoveUnit(target)
set target = null
set source = null
return summonedUnits
endfunction
private function TriggerConditionDamage takes nothing returns boolean
return not IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_SUMMONED) and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_MECHANICAL) and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE) and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_RESISTANT) and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_MAGIC_IMMUNE) and GetUnitLevel(GetTriggerUnit()) > 5 and ((IsUnitInGroup(GetEventDamageSource(), BlackArrowAutoCasters) and GetMatchingBlackArrowAbilityIndex(GetEventDamageSource()) > 0) or IsUnitInGroup(GetEventDamageSource(), BlackArrowItemUnits))
endfunction
function BlackArrowUnitGetOrbItem takes unit whichUnit, item excludeItem returns integer
local integer i = 0
loop
exitwhen (i >= UnitInventorySize(whichUnit))
if ((excludeItem == null or UnitItemInSlot(whichUnit, i) != excludeItem) and GetMatchingBlackArrowItemTypeIndex(GetItemTypeId(UnitItemInSlot(whichUnit, i))) > 0) then
return i
endif
set i = i + 1
endloop
return -1
endfunction
private function TriggerActionDamage takes nothing returns nothing
local integer itemIndex = BlackArrowUnitGetOrbItem(GetEventDamageSource(), null)
local integer abilityIndex = GetMatchingBlackArrowAbilityIndex(GetEventDamageSource())
if (itemIndex != -1 and abilityIndex == 0) then
call MarkTarget(BlackArrowItemTypeAbilityIndex[itemIndex], GetEventDamageSource(), GetTriggerUnit())
else
call MarkTarget(abilityIndex, GetEventDamageSource(), GetTriggerUnit())
endif
endfunction
private function TriggerConditionDeath takes nothing returns boolean
return IsUnitInGroup(GetTriggerUnit(), BlackArrowTargets)
endfunction
private function TriggerActionDeath takes nothing returns nothing
call Effect(GetTriggerUnit())
endfunction
private function TriggerConditionOrder takes nothing returns boolean
return GetIssuedOrderId() == OrderId(ORDER_ON) or GetIssuedOrderId() == OrderId(ORDER_OFF)
endfunction
private function TriggerActionOrder takes nothing returns nothing
if (GetIssuedOrderId() == OrderId(ORDER_ON)) then
if (not BlackArrowIsAutoCaster(GetTriggerUnit())) then
call BlackArrowAddAutoCaster(GetTriggerUnit())
//call BJDebugMsg("Adding unit " + GetUnitName(caster) + " to casters.")
endif
else
if (BlackArrowIsAutoCaster(GetTriggerUnit())) then
call BlackArrowRemoveAutoCaster(GetTriggerUnit())
//call BJDebugMsg("Removing unit " + GetUnitName(GetTriggerUnit()) + " from casters.")
endif
endif
endfunction
private function TriggerConditionPickupItem takes nothing returns boolean
return not IsUnitInGroup(GetTriggerUnit(), BlackArrowItemUnits) and GetMatchingBlackArrowItemTypeIndex(GetItemTypeId(GetManipulatedItem())) > 0
endfunction
private function TriggerActionPickupItem takes nothing returns nothing
call GroupAddUnit(BlackArrowItemUnits, GetTriggerUnit())
//call BJDebugMsg("Unit " + GetUnitName(GetTriggerUnit()) + " picked up a Black Arrow orb item.")
endfunction
private function TriggerConditionDropItem takes nothing returns boolean
local boolean result = IsUnitInGroup(GetTriggerUnit(), BlackArrowItemUnits) and GetMatchingBlackArrowItemTypeIndex(GetItemTypeId(GetManipulatedItem())) > 0
if (result) then
// we need to exclude the dropped item since it is not dropped yet
return BlackArrowUnitGetOrbItem(GetTriggerUnit(), GetManipulatedItem()) == -1
endif
return result
endfunction
private function TriggerActionDropItem takes nothing returns nothing
call GroupRemoveUnit(BlackArrowItemUnits, GetTriggerUnit())
//call BJDebugMsg("Unit " + GetUnitName(GetTriggerUnit()) + " dropped the final Black Arrow orb item.")
endfunction
private function AddStandardObjectData takes nothing returns nothing
call BlackArrowAddAbility('ANba', 1, 'ndr1', 1, 80.0, 0.0, 2.0, 'BNdm')
call BlackArrowAddAbility('ANba', 2, 'ndr2', 1, 80.0, 0.0, 2.0, 'BNdm')
call BlackArrowAddAbility('ANba', 3, 'ndr3', 1, 80.0, 0.0, 2.0, 'BNdm')
call BlackArrowAddAbility('ACbk', 1, 'ndr1', 1, 80.0, 0.0, 2.0, 'BNdm')
call BlackArrowAddItemTypeId('odef', BlackArrowAddAbility('ANbs', 1, 'ndr1', 1, 80.0, 0.0, 2.0, 'BNdm'))
endfunction
private function FilterForUnitWithOrb takes nothing returns boolean
return UnitInventorySize(GetFilterUnit()) > 0 and BlackArrowUnitGetOrbItem(GetFilterUnit(), null) != -1
endfunction
private function AddAllUnitsWithOrbs takes nothing returns nothing
local group whichGroup = CreateGroup()
call GroupEnumUnitsInRect(whichGroup, GetPlayableMapRect(), Filter(function FilterForUnitWithOrb))
set bj_wantDestroyGroup = true
call GroupAddGroup(whichGroup, BlackArrowItemUnits)
//call BJDebugMsg("Units with orbs size " + I2S(CountUnitsInGroup(BlackArrowItemUnits)))
set whichGroup = null
endfunction
private module Init
private static method onInit takes nothing returns nothing
call TriggerRegisterAnyUnitEventBJ(BlackArrowDamageTrigger, EVENT_PLAYER_UNIT_DAMAGED)
call TriggerAddCondition(BlackArrowDamageTrigger, Condition(function TriggerConditionDamage))
call TriggerAddAction(BlackArrowDamageTrigger, function TriggerActionDamage)
call TriggerRegisterAnyUnitEventBJ(BlackArrowDeathTrigger, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(BlackArrowDeathTrigger, Condition(function TriggerConditionDeath))
call TriggerAddAction(BlackArrowDeathTrigger, function TriggerActionDeath)
call TriggerRegisterAnyUnitEventBJ(BlackArrowOrderTrigger, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerAddCondition(BlackArrowOrderTrigger, Condition(function TriggerConditionOrder))
call TriggerAddAction(BlackArrowOrderTrigger, function TriggerActionOrder)
call TriggerRegisterAnyUnitEventBJ(BlackArrowItemPickupTrigger, EVENT_PLAYER_UNIT_PICKUP_ITEM)
call TriggerAddCondition(BlackArrowItemPickupTrigger, Condition(function TriggerConditionPickupItem))
call TriggerAddAction(BlackArrowItemPickupTrigger, function TriggerActionPickupItem)
call TriggerRegisterAnyUnitEventBJ(BlackArrowItemDropTrigger, EVENT_PLAYER_UNIT_DROP_ITEM)
call TriggerAddCondition(BlackArrowItemDropTrigger, Condition(function TriggerConditionDropItem))
call TriggerAddAction(BlackArrowItemDropTrigger, function TriggerActionDropItem)
static if (ADD_STANDARD_OBJECT_DATA) then
call AddStandardObjectData()
endif
static if (ADD_ALL_UNITS_WITH_ORBS) then
call AddAllUnitsWithOrbs()
endif
endmethod
endmodule
private struct S
implement Init
endstruct
// ChangeLog:
//
// 1.1 2022-09-24:
// - Use vJass and a library, many private declarations and with early automatic initialization in a module.
// - Add options ADD_STANDARD_OBJECT_DATA and ADD_ALL_UNITS_WITH_ORBS.
// - Add function BlackArrowIsAutoCaster.
// - BlackArrowAbiliyDurationHero is used for target heroes now.
// - Add API documentation with usable functions.
// - Add event handling functions which allow adding actions to Black Arrow events.
// - Refactor some functions.
endlibrary