- Joined
- Nov 2, 2004
- Messages
- 1,912
(1 ratings)
//===========================================================================
//
// Squad System v0.9.4
// by loktar and kokon
//
// -------
// * API *
// -------
// * Variables *
// -------------
// * integer array squadUnits
// - Unit types that generate a squad when trained or hired
//
// * integer array squadSize
// - Squad size for squads generated with squadUnits
//
// * integer squadUnits_max
// - Maximum index of squadUnits and squadSize
//
// * string squad_sfx_path
// - Default path for special effect to attach to squad units
// - Set to null to disable special effects
// - Default: null
//
// * string squad_sfx_attach
// - Default attachment point for special effect to attach to squad units
// - Default: "overhead"
//
// * string squad_leader_sfx_path
// - Default path for special effect to attach to squad leader
// - Set to null to use same effect as regular squad members
// - Default: null
//
// * string squad_leader_sfx_attach
// - Default attachment point for special effect to attach to squad leader
// - Default: "overhead"
//
// * integer squad_disband_multiplier
// - Multiplier that determines the chance of a squad disbanding when one of its units dies
// - Formula: squad_disband_multiplier/squad_size
// - E.g. 100: 50% chance when 2 units left, 33% chance when 3 units left
// - Set to 0 to disable disbanding
// - Default: 0
//
// * real squad_flee_distance
// - Distance units of a disbanded squad will flee in a random direction
// - set to 0 to disable fleeing
// - Default: 0
//
// * real squad_combat_overextension_distance
// - Squad members will try to come closer to each other if they are too far away during comabat
// - Default: 325
// (+ 25 per unit)
//
// * boolean leader_death_disband
// - If true, squads will disband if their leader dies
// - Default: false
//
// * real squad_shared_damage_activation_chance
// - Chance that part of the incoming attack damage is distributed between other squad members
// - Default: 0.50 (50%)
//
// * real squad_max_percent_of_shared_damage
// - Direct defender damage is reduced up to squad_max_percent_of_shared_damage
// - Shared damage does not cause death. Only DAMAGE_TYPE_NORMAL is split
// - Default: 0.50 (50%)
//
// * real squad_min_percent_of_shared_damage
// - Default: 0.25 (25%)
//
// * real squad_min_amount_of_shared_damage
// - If shared damage is less than squad_min_amount_of_shared_damage it will not be deducted from the original defender
// - Default: 3
//
// * Functions *
// -------------
// * SquadAddUnit(group squad, unit whichUnit)
// - Adds a unit to a squad
// - Will remove the unit from any squad it is already in
//
// * SquadRemoveUnit(group squad, unit whichUnit)
// - Removes a unit from a squad
// - Destroys squad if only 1 or 0 members remain
//
// * SquadSetLeader(group squad, unit whichUnit)
// - Sets the squad's leader
// - Adds the unit to squad if not already a member
//
// * unit SquadGetLeader(group squad)
// - Returns the leader of a squad
//
// * SquadSetSfx(group squad, string path, string attach)
// - Sets the special effect to be attached to units of the squad
// - Set to null to disable special effects for the squad
//
// * SquadSetLeaderSfx(group squad, string path, string attach)
// - Sets the special effect to be attached to the leader of the squad
// - Set to null to use same special effect as regular units
//
// * UnitCreateSquad(unit whichUnit)
// - If the unit is one of the squadUnits types, creates a squad
//
// * group UnitTypeCreateSquad(player whichPlayer, integer unitid, integer size, real x, real y, real facing)
// - Create a squad of unit type
// - Returns the squad
//
// * DisbandSquad(group squad)
// - Removes all units from the squad and destroys it
// - Does not cause fleeing
//
// * group UnitGetSquad(unit whichUnit)
// - Returns the unit's squad
// - Returns null if unit doesn't have a squad
//
// * IndividualOrderTarget(unit whichUnit, string order, widget target)
// - Issues target order to unit without ordering the squad
//
// * IndividualOrderImmediate(unit whichUnit, string order)
// - Issues immediate order to unit without ordering the squad
//
// * IndividualOrderPoint(unit whichUnit, string order, real x, real y)
// - Issues point order to unit without ordering the squad
//
//===========================================================================
Hmm, I'm not sure this is possible. Or at least, I don't know how to do it.1. When one squad memeber engages via aggro. Other squad member should try to engage too.
When one of the ai squad units gets magnetized to nearby fighting, other squad units should go more closely to the magnetized unit.
Ie they should go more like a group that has an attack order. May be a point attack order.
You can set the agro radius with this:2. Can we reduce agro radius for a squad? Ie enable defensive stance.
May be all units in a group should hold position except one unit. And when this unit starts moving hold position is cancelled for the rest.
Or hold position until enemy is really close and then issue an attack order.
call SetUnitAcquireRange(some_unit, 200.00)
Yes, you can do this with SquadAddUnit. I'll add an example in the map.3. Can I create a squad completely out of existing units? I could not find a way to do so.
That's a good idea, I'm not 100% sure it's possible with how the script it written at the moment, but maybe I can find a way around it.4. It could be useful if I could order a unit individually in a special way via Jass, circumventing a group order.
Oh I didn't know that existed lol. CountUnitsInGroup is not a native, but it's a built-in blizzard function.CountUnitsInGroup is it a wc3 native function btw? I see it in your library.
There is a BlzGroupGetSize that I usually use
There can be a check if one group members does damage, another one should come closer if their distance is more than 350.
It seem to be safe. Even if the group is null it will return 0>Oh I didn't know that existed lol. CountUnitsInGroup is not a native, but it's a built-in blizzard function.
Nice idea, I'll try to add it later. Maybe the squad leader can have a different special effect from the rest of the squad.6. If the strongest unit in a group dies. There can be a disband chance if the group is configured to have a leader.
//===========================================================================
//
// Squad System v0.9.3
// by loktar
//
// -------
// * API *
// -------
// * Variables *
// -------------
// * integer array squadUnits
// - Unit types that generate a squad when trained or hired
//
// * integer array squadSize
// - Squad size for squads generated with squadUnits
//
// * integer squadUnits_max
// - Maximum index of squadUnits and squadSize
//
// * string squad_sfx_path
// - Default path for special effect to attach to squad units
// - Set to null to disable special effects
// - Default: null
//
// * string squad_sfx_attach
// - Default attachment point for special effect to attach to squad units
// - Default: "overhead"
//
// * integer squad_disband_multiplier
// - Multiplier that determines the chance of a squad disbanding when one of its units dies
// - Formula: squad_disband_multiplier/squad_size
// - Set to 0 to disable disbanding
// - Default: 0
//
// * real squad_flee_distance
// - Distance units of a disbanded squad will flee in a random direction
// - set to 0 to disable fleeing
// - Default: 0
// * real squad_combat_overextension_distance
// - Squad members will try to come closer to each other if they are too far away during comabat
// - Default: 325
// (+ 25 per unit)
//
// * Functions *
// -------------
// * SquadAddUnit(group squad, unit whichUnit)
// - Adds a unit to a squad
// - Will remove the unit from any squad it is already in
//
// * SquadRemoveUnit(group squad, unit whichUnit)
// - Removes a unit from a squad
// - Destroys squad if only 1 or 0 members remain
//
// * SquadSetSfx(group squad, string path, string attach)
// - Sets the special effect to be attached to units of the squad
// - Set to null to disable special effects for the squad
//
// * UnitCreateSquad(unit whichUnit)
// - If the unit is one of the squadUnits types, creates a squad
//
// * group UnitTypeCreateSquad(player whichPlayer, integer unitid, integer size, real x, real y, real facing)
// - Create a squad of unit type
// - Returns the squad
//
// * DisbandSquad(group squad)
// - Removes all units from the squad and destroys it
// - Does not cause fleeing
//
// * group UnitGetSquad(unit whichUnit)
// - Returns the unit's squad
// - Returns null if unit doesn't have a squad
//
// * IndividualOrderTarget(unit whichUnit, string order, widget target)
// - Issues target order to unit without ordering the squad
//
// * IndividualOrderImmediate(unit whichUnit, string order)
// - Issues immediate order to unit without ordering the squad
//
// * IndividualOrderPoint(unit whichUnit, string order, real x, real y)
// - Issues point order to unit without ordering the squad
//
//===========================================================================
library SquadSystem initializer InitSquadSystem
globals
// Config
integer array squadUnits
integer array squadSize
integer squadUnits_max = -1
string squad_sfx_path = null
string squad_sfx_attach = "overhead"
integer squad_disband_multiplier = 0
real squad_flee_distance = 0
real squad_combat_overextension_distance = 325
// Misc
private group pauseSelection = CreateGroup()
private group pauseGroupOrder = CreateGroup()
// Unit hashtable
private hashtable htbUnit = InitHashtable()
private constant integer SQUAD = 0
private constant integer SFX = 1
private constant integer SELECTING_PLAYER = 2
// Squad hashtable
private hashtable htbSquad = InitHashtable()
private constant integer SFX_PATH = 0
private constant integer SFX_ATTACH = 1
endglobals
//=========================================================
// Util
//=========================================================
private function PlayerHasControl takes unit whichUnit, player whichPlayer returns boolean
local player owningPlayer = GetOwningPlayer(whichUnit)
local boolean result = owningPlayer == whichPlayer or GetPlayerAlliance(owningPlayer, whichPlayer, ALLIANCE_SHARED_CONTROL)
set owningPlayer = null
return result
endfunction
function MaxDistanceBetweenUnitsInGroup takes group g returns real
local unit u1
local unit u2
local real maxDistance = 0.0
local real dx
local real dy
local real distance
local integer i
local integer j
local integer groupSize = BlzGroupGetSize(g)
if groupSize < 2 then
return 0.0
endif
set i = 0
loop
exitwhen i >= groupSize - 1
set u1 = BlzGroupUnitAt(g, i)
set j = i + 1
loop
exitwhen j >= groupSize
set u2 = BlzGroupUnitAt(g, j)
set dx = GetUnitX(u2) - GetUnitX(u1)
set dy = GetUnitY(u2) - GetUnitY(u1)
set distance = SquareRoot(dx * dx + dy * dy)
if distance > maxDistance then
set maxDistance = distance
endif
set j = j + 1
endloop
set i = i + 1
endloop
return maxDistance
endfunction
//=========================================================
// Squad functions
//=========================================================
// Get a unit's squad
function UnitGetSquad takes unit whichUnit returns group
local integer handleId = GetHandleId(whichUnit)
if HaveSavedHandle(htbUnit, handleId, SQUAD) then
return LoadGroupHandle(htbUnit, handleId, SQUAD)
endif
return null
endfunction
// Remove unit from squad
function SquadRemoveUnit takes group squad, unit whichUnit returns nothing
local integer handleId = GetHandleId(whichUnit)
local integer squad_size
call GroupRemoveUnit(squad, whichUnit)
set squad_size = BlzGroupGetSize(squad)
if squad_size == 1 then // Remove last member if only 1 is left
call SquadRemoveUnit(squad, FirstOfGroup(squad))
elseif squad_size == 0 then // Destroy squad if empty
call FlushChildHashtable(htbSquad, GetHandleId(squad))
call DestroyGroup(squad)
endif
if HaveSavedHandle(htbUnit, handleId, SFX) then
call DestroyEffect(LoadEffectHandle(htbUnit, handleId, SFX))
endif
call FlushChildHashtable(htbUnit, handleId)
endfunction
// Add a unit to a squad
function SquadAddUnit takes group squad, unit whichUnit returns nothing
local trigger trg
local group oldSquad
local integer unitHandleId = GetHandleId(whichUnit)
local integer squadHandleId = GetHandleId(squad)
local string sfx_path = null
local string sfx_attach
set oldSquad = UnitGetSquad(whichUnit)
if oldSquad != null then
call SquadRemoveUnit(oldSquad, whichUnit)
endif
call GroupAddUnit(squad, whichUnit)
call SaveGroupHandle(htbUnit, unitHandleId, SQUAD, squad)
// Get SFX path
if HaveSavedString(htbSquad, squadHandleId, SFX_PATH) then
set sfx_path = LoadStr(htbSquad, squadHandleId, SFX_PATH)
else
set sfx_path = squad_sfx_path
endif
// Get SFX attachment point
if HaveSavedString(htbSquad, squadHandleId, SFX_ATTACH) then
set sfx_attach = LoadStr(htbSquad, squadHandleId, SFX_ATTACH)
else
set sfx_attach = squad_sfx_attach
endif
// Add effect
if sfx_path != null then
call SaveEffectHandle(htbUnit, unitHandleId, SFX, AddSpecialEffectTarget(sfx_path, whichUnit, sfx_attach))
endif
set trg = null
set oldSquad = null
endfunction
// Create squad from unit
function UnitCreateSquad takes unit whichUnit returns nothing
local group squad
local player owningPlayer
local integer typeId
local real x
local real y
local real facing
local boolean isSquadUnit = false
local integer index = 0
local integer size_index = 0
if squadUnits_max >= 0 then
set typeId = GetUnitTypeId(whichUnit)
loop
set isSquadUnit = squadUnits[index] == typeId
set index = index + 1
exitwhen index > squadUnits_max or isSquadUnit
endloop
if isSquadUnit then
set squad = CreateGroup()
set owningPlayer = GetOwningPlayer(whichUnit)
set x = GetUnitX(whichUnit)
set y = GetUnitY(whichUnit)
set facing = GetUnitFacing(whichUnit)
set size_index = index-1
set index = 0
call SquadAddUnit(squad, whichUnit)
loop
call SquadAddUnit(squad, CreateUnit(owningPlayer, typeId, x, y, facing))
set index = index + 1
exitwhen index == squadSize[size_index]-1
endloop
endif
endif
set squad = null
set owningPlayer = null
endfunction
// Create a squad with units of type for player
function UnitTypeCreateSquad takes player whichPlayer, integer unitid, integer size, real x, real y, real facing returns group
local group squad = CreateGroup()
local integer index = 0
loop
call SquadAddUnit(squad, CreateUnit(whichPlayer, unitid, x, y, facing))
set index = index + 1
exitwhen index == size
endloop
return squad
endfunction
// Remove all units from squad
function DisbandSquad takes group squad returns nothing
local unit pickedUnit
loop
set pickedUnit = FirstOfGroup(squad)
exitwhen pickedUnit == null
call SquadRemoveUnit(squad, pickedUnit)
endloop
set pickedUnit = null
endfunction
// Destroy existing effect and create/save new one
private function SetSfxEnum takes nothing returns nothing
local unit enumUnit = GetEnumUnit()
local integer unitHandleId = GetHandleId(enumUnit)
local integer squadHandleId = GetHandleId(UnitGetSquad(enumUnit))
local string path = LoadStr(htbSquad, squadHandleId, SFX_PATH)
if HaveSavedHandle(htbUnit, unitHandleId, SFX) then
call DestroyEffect(LoadEffectHandle(htbUnit, unitHandleId, SFX))
call RemoveSavedHandle(htbUnit, unitHandleId, SFX)
endif
if path != null then
call SaveEffectHandle(htbUnit, unitHandleId, SFX, AddSpecialEffectTarget(path, enumUnit, LoadStr(htbSquad, squadHandleId, SFX_ATTACH)))
endif
set enumUnit = null
endfunction
// Set the special effect for a squad
function SquadSetSfx takes group squad, string path, string attach returns nothing
local integer handleId = GetHandleId(squad)
call SaveStr(htbSquad, handleId, SFX_PATH, path)
call SaveStr(htbSquad, handleId, SFX_ATTACH, attach)
call ForGroup(squad, function SetSfxEnum)
endfunction
// Issue target order to unit without ordering the squad
function IndividualOrderTarget takes unit whichUnit, string order, widget target returns nothing
call GroupAddUnit(pauseGroupOrder, whichUnit)
call IssueTargetOrder(whichUnit, order, target)
call GroupRemoveUnit(pauseGroupOrder, whichUnit)
endfunction
// Issue immediate order to unit without ordering the squad
function IndividualOrderImmediate takes unit whichUnit, string order returns nothing
call GroupAddUnit(pauseGroupOrder, whichUnit)
call IssueImmediateOrder(whichUnit, order)
call GroupRemoveUnit(pauseGroupOrder, whichUnit)
endfunction
// Issue point order to unit without ordering the squad
function IndividualOrderPoint takes unit whichUnit, string order, real x, real y returns nothing
call GroupAddUnit(pauseGroupOrder, whichUnit)
call IssuePointOrder(whichUnit, order, x, y)
call GroupRemoveUnit(pauseGroupOrder, whichUnit)
endfunction
//=========================================================
// Triggers
//=========================================================
// Unit sold: create squad
private function UnitSold takes nothing returns boolean
call UnitCreateSquad(GetSoldUnit())
return false
endfunction
// Unit sold: create squad
private function UnitTrained takes nothing returns boolean
call UnitCreateSquad(GetTrainedUnit())
return false
endfunction
// Unit selected: modify selection
private function UnitSelected takes nothing returns nothing
local unit triggerUnit = GetTriggerUnit()
local player triggerPlayer = GetTriggerPlayer()
local group squad = UnitGetSquad(triggerUnit)
local group grp
local unit pickedUnit
// Only proceed if unit has squad and player has control over unit
if squad != null and not IsUnitInGroup(triggerUnit, pauseSelection) and PlayerHasControl(triggerUnit, triggerPlayer) then
call GroupAddGroup(squad, pauseSelection)
set grp = CreateGroup()
call GroupAddGroup(squad, grp)
loop
set pickedUnit = FirstOfGroup(grp)
exitwhen pickedUnit == null
call GroupRemoveUnit(grp, pickedUnit)
if pickedUnit != triggerUnit then
call SelectUnitRemoveForPlayer(pickedUnit, triggerPlayer)
endif
endloop
call DestroyGroup(grp)
call TriggerSleepAction(0.01) // this is necessary to prevent all selected squad units from firing the event
call GroupRemoveGroup(squad, pauseSelection)
endif
set triggerUnit = null
set triggerPlayer = null
set squad = null
set grp = null
set pickedUnit = null
endfunction
// Unit target order: order squad
private function SquadTargetOrder takes nothing returns boolean
local trigger trg
local string order
local unit orderedUnit = GetOrderedUnit()
local unit targetUnit
local group squad = UnitGetSquad(orderedUnit)
if squad != null and not IsUnitInGroup(orderedUnit, pauseGroupOrder) then
set trg = GetTriggeringTrigger()
set order = OrderId2String(GetIssuedOrderId())
set targetUnit = GetOrderTargetUnit()
call DisableTrigger(trg) // Prevent infinite loop
// smart order on friendly unit doesn't work
if order == "smart" and targetUnit != null and IsPlayerAlly(GetOwningPlayer(orderedUnit), GetOwningPlayer(targetUnit)) then
call GroupPointOrder(squad, order, GetUnitX(targetUnit), GetUnitY(targetUnit))
else
call GroupTargetOrder(squad, order, GetOrderTarget())
endif
call EnableTrigger(trg)
endif
set trg = null
set orderedUnit = null
set targetUnit = null
set squad = null
return false
endfunction
// Unit immediate order: order squad
private function SquadImmediateOrder takes nothing returns boolean
local trigger trg
local unit orderedUnit = GetOrderedUnit()
local group squad = UnitGetSquad(orderedUnit)
if squad != null and not IsUnitInGroup(orderedUnit, pauseGroupOrder) then
set trg = GetTriggeringTrigger()
call DisableTrigger(trg) // Prevent infinite loop
call GroupImmediateOrder(squad, OrderId2String(GetIssuedOrderId()))
call EnableTrigger(trg)
endif
set trg = null
set orderedUnit = null
set squad = null
return false
endfunction
// Unit point order: order squad
private function SquadPointOrder takes nothing returns boolean
local trigger trg
local unit orderedUnit = GetOrderedUnit()
local group squad = UnitGetSquad(orderedUnit)
if squad != null and not IsUnitInGroup(orderedUnit, pauseGroupOrder) then
set trg = GetTriggeringTrigger()
call DisableTrigger(trg) // Prevent infinite loop
call GroupPointOrder(squad, OrderId2String(GetIssuedOrderId()), GetOrderPointX(), GetOrderPointY())
call EnableTrigger(trg)
endif
set trg = null
set orderedUnit = null
set squad = null
return false
endfunction
// Make enumerated units flee to random point
private function FleeEnum takes nothing returns nothing
local unit enumUnit = GetEnumUnit()
local real x = GetRandomReal(-squad_flee_distance, squad_flee_distance)
local real y = SquareRoot(Pow(squad_flee_distance, 2) - Pow(x, 2))
// y is always positive, allow for negative value
if GetRandomInt(0, 1) == 0 then
set y = -y
endif
call IssuePointOrder(enumUnit, "move", GetUnitX(enumUnit)+x, GetUnitY(enumUnit)+y)
endfunction
// Unit dies: remove unit from squad and adjust selection
private function UnitDies takes nothing returns boolean
local unit dyingUnit = GetDyingUnit()
local group squad = UnitGetSquad(dyingUnit)
local group grp
local player indexPlayer
local integer squad_size
local integer index = 0
if squad != null then
set squad_size = BlzGroupGetSize(squad)
// Chance the squad disbands
if squad_disband_multiplier/(squad_size-1) >= GetRandomInt(1, 100) then
if squad_flee_distance > 0 then
set grp = CreateGroup()
call GroupAddGroup(squad, grp)
call GroupRemoveUnit(grp, dyingUnit)
endif
call DisbandSquad(squad)
if squad_flee_distance > 0 then
call ForGroup(grp, function FleeEnum)
call DestroyGroup(grp)
endif
// Adjust selection and remove unit from squad
else
if squad_size > 1 then
loop
set indexPlayer = Player(index)
if IsUnitSelected(dyingUnit, indexPlayer) and PlayerHasControl(dyingUnit, indexPlayer) then
set grp = CreateGroup()
call GroupAddGroup(squad, grp)
call GroupRemoveUnit(grp, dyingUnit)
call SelectUnitAddForPlayer(FirstOfGroup(grp), indexPlayer)
call DestroyGroup(grp)
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
endif
call SquadRemoveUnit(squad, dyingUnit)
endif
endif
set dyingUnit = null
set squad = null
set grp = null
set indexPlayer = null
return false
endfunction
// Unit is attacked: make sure attacking unit's squad follows through
private function UnitAttacked takes nothing returns boolean
local group squad = UnitGetSquad(GetAttacker())
if squad != null then
if MaxDistanceBetweenUnitsInGroup(squad) > squad_combat_overextension_distance + BlzGroupGetSize(squad) * 25 then
call GroupTargetOrder(squad, "attack", GetTriggerUnit())
//call GroupPointOrder(squad, "attack", GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()))
endif
endif
set squad = null
return false
endfunction
//=========================================================
// Initializer
//=========================================================
private function InitSquadSystem takes nothing returns nothing
local trigger trgUnitSold = CreateTrigger()
local trigger trgUnitTrained = CreateTrigger()
local trigger trgUnitSelected = CreateTrigger()
local trigger trgSquadTargetOrder = CreateTrigger()
local trigger trgSquadImmediateOrder = CreateTrigger()
local trigger trgSquadPointOrder = CreateTrigger()
local trigger trgUnitDies = CreateTrigger()
local trigger trgUnitAttacked = CreateTrigger()
local player indexPlayer
local integer index = 0
loop
set indexPlayer = Player(index)
call TriggerRegisterPlayerUnitEvent(trgUnitSold, indexPlayer, EVENT_PLAYER_UNIT_SELL, null)
call TriggerRegisterPlayerUnitEvent(trgUnitTrained, indexPlayer, EVENT_PLAYER_UNIT_TRAIN_FINISH, null)
call TriggerRegisterPlayerUnitEvent(trgUnitSelected, indexPlayer, EVENT_PLAYER_UNIT_SELECTED, null)
call TriggerRegisterPlayerUnitEvent(trgSquadTargetOrder, indexPlayer, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
call TriggerRegisterPlayerUnitEvent(trgSquadImmediateOrder, indexPlayer, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
call TriggerRegisterPlayerUnitEvent(trgSquadPointOrder, indexPlayer, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
call TriggerRegisterPlayerUnitEvent(trgUnitDies, indexPlayer, EVENT_PLAYER_UNIT_DEATH, null)
call TriggerRegisterPlayerUnitEvent(trgUnitAttacked, indexPlayer, EVENT_PLAYER_UNIT_ATTACKED, null)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(trgUnitSold, function UnitSold)
call TriggerAddCondition(trgUnitTrained, function UnitTrained)
call TriggerAddAction(trgUnitSelected, function UnitSelected)
call TriggerAddCondition(trgSquadTargetOrder, function SquadTargetOrder)
call TriggerAddCondition(trgSquadImmediateOrder, function SquadImmediateOrder)
call TriggerAddCondition(trgSquadPointOrder, function SquadPointOrder)
call TriggerAddCondition(trgUnitDies, function UnitDies)
call TriggerAddCondition(trgUnitAttacked, function UnitAttacked)
set trgUnitSold = null
set trgUnitTrained = null
set trgUnitSelected = null
set trgSquadTargetOrder = null
set trgSquadImmediateOrder = null
set trgSquadPointOrder = null
set trgUnitDies = null
set trgUnitAttacked = null
set indexPlayer = null
endfunction
endlibrary
Ah yes, I can check for distance when the trigger fires.attack order should not be spammed on every attack. it should only be triggered if overextension is in place
It's not possible to add buffs through triggers/scripts, only option is to add an aura ability, but that will also give buff to nearby non-squad units. Alternatively I can increase stats manually, but then there is no buff.How do you think we attach and remove buffs from squad members?
Ie Squad membership can provide additional regen and attack speed for example.
MaxDistanceBetweenUnitsInGroup already there.Ah yes, I can check for distance when the trigger fires.
It's not possible to add buffs through triggers/scripts, only option is to add an aura ability, but that will also give buff to nearby non-squad units. Alternatively I can increase stats manually, but then there is no buff.
Added it to the script. Also added Leader functionality. Squad can disband/flee when leader dies, leader can have different special effect, and leader will be the one selected when selecting multiple units of a squad (including leader).@loktar I've added
squad_combat_overextension_distance
attack order should not be spammed on every attack. it should only be triggered if overextension is in place
// * real squad_shared_damage_activation_chance
// - Chance that part of the incoming attack damage is distributed between other squad members
// - Defailt: 0.25 (25%)
// * real squad_max_percent_of_shared_damage
// . Direct defender damage is reduced up to squad_max_percent_of_shared_damage
// - Shared damage does not cause death. Only DAMAGE_TYPE_NORMAL is split
// - Default: 0.50 (50%)
// * real squad_min_percent_of_shared_damage
// - Defailt: 0.40 (40%)
// * real squad_min_amount_of_shared_damage
// - If shared damage is less than squad_min_amount_of_shared_damage it will not be deducted from the original defender
// - Default: 3
//===========================================================================
//
// Squad System v0.9.4
// by loktar
// and kokon
//
// -------
// * API *
// -------
// * Variables *
// -------------
// * integer array squadUnits
// - Unit types that generate a squad when trained or hired
//
// * integer array squadSize
// - Squad size for squads generated with squadUnits
//
// * integer squadUnits_max
// - Maximum index of squadUnits and squadSize
//
// * string squad_sfx_path
// - Default path for special effect to attach to squad units
// - Set to null to disable special effects
// - Default: null
//
// * string squad_sfx_attach
// - Default attachment point for special effect to attach to squad units
// - Default: "overhead"
//
// * integer squad_disband_multiplier
// - Multiplier that determines the chance of a squad disbanding when one of its units dies
// - Formula: squad_disband_multiplier/squad_size
// - Set to 0 to disable disbanding
// - Default: 0
//
// * real squad_flee_distance
// - Distance units of a disbanded squad will flee in a random direction
// - set to 0 to disable fleeing
// - Default: 0
// * real squad_combat_overextension_distance
// - Squad members will try to come closer to each other
// - Default: 300
// (+ 30 per unit)
// * real squad_shared_damage_activation_chance
// - Chance that part of the incoming attack damage is distributed between other squad members
// - Defailt: 0.50 (50%)
// * real squad_max_percent_of_shared_damage
// . Direct defender damage is reduced up to squad_max_percent_of_shared_damage
// - Shared damage does not cause death. Only DAMAGE_TYPE_NORMAL is split
// - Default: 0.50 (50%)
// * real squad_min_percent_of_shared_damage
// - Defailt: 0.25 (25%)
// * real squad_min_amount_of_shared_damage
// - If shared damage is less than squad_min_amount_of_shared_damage it will not be deducted from the original defender
// - Default: 3
//
// * Functions *
// -------------
// * SquadAddUnit(group squad, unit whichUnit)
// - Adds a unit to a squad
// - Will remove the unit from any squad it is already in
//
// * SquadRemoveUnit(group squad, unit whichUnit)
// - Removes a unit from a squad
// - Destroys squad if only 1 or 0 members remain
//
// * SquadSetSfx(group squad, string path, string attach)
// - Sets the special effect to be attached to units of the squad
// - Set to null to disable special effects for the squad
//
// * UnitCreateSquad(unit whichUnit)
// - If the unit is one of the squadUnits types, creates a squad
//
// * group UnitTypeCreateSquad(player whichPlayer, integer unitid, integer size, real x, real y, real facing)
// - Create a squad of unit type
// - Returns the squad
//
// * DisbandSquad(group squad)
// - Removes all units from the squad and destroys it
// - Does not cause fleeing
//
// * group UnitGetSquad(unit whichUnit)
// - Returns the unit's squad
// - Returns null if unit doesn't have a squad
//
// * IndividualOrderTarget(unit whichUnit, string order, widget target)
// - Issues target order to unit without ordering the squad
//
// * IndividualOrderImmediate(unit whichUnit, string order)
// - Issues immediate order to unit without ordering the squad
//
// * IndividualOrderPoint(unit whichUnit, string order, real x, real y)
// - Issues point order to unit without ordering the squad
//
//===========================================================================
library SquadSystem initializer InitSquadSystem
globals
// Config
integer array squadUnits
integer array squadSize
integer squadUnits_max = -1
string squad_sfx_path = null
string squad_sfx_attach = "overhead"
integer squad_disband_multiplier = 0
real squad_flee_distance = 0
real squad_combat_overextension_distance = 300.0
real squad_shared_damage_activation_chance = 0.5
real squad_max_percent_of_shared_damage = 0.5
real squad_min_percent_of_shared_damage = 0.25
real squad_min_amount_of_shared_damage = 3.0
// Misc
private group pauseSelection = CreateGroup()
private group pauseGroupOrder = CreateGroup()
// Unit hashtable
private hashtable htbUnit = InitHashtable()
private constant integer SQUAD = 0
private constant integer SFX = 1
private constant integer SELECTING_PLAYER = 2
// Squad hashtable
private hashtable htbSquad = InitHashtable()
private constant integer SFX_PATH = 0
private constant integer SFX_ATTACH = 1
endglobals
//=========================================================
// Util
//=========================================================
private function PlayerHasControl takes unit whichUnit, player whichPlayer returns boolean
local player owningPlayer = GetOwningPlayer(whichUnit)
local boolean result = owningPlayer == whichPlayer or GetPlayerAlliance(owningPlayer, whichPlayer, ALLIANCE_SHARED_CONTROL)
set owningPlayer = null
return result
endfunction
function MaxDistanceBetweenUnitsInGroup takes group g returns real
local unit u1
local unit u2
local real maxDistance = 0.0
local real dx
local real dy
local real distance
local integer i
local integer j
local integer groupSize = BlzGroupGetSize(g)
if groupSize < 2 then
return 0.0
endif
set i = 0
loop
exitwhen i >= groupSize - 1
set u1 = BlzGroupUnitAt(g, i)
set j = i + 1
loop
exitwhen j >= groupSize
set u2 = BlzGroupUnitAt(g, j)
set dx = GetUnitX(u2) -GetUnitX(u1)
set dy = GetUnitY(u2) -GetUnitY(u1)
set distance = SquareRoot(dx * dx + dy * dy)
if distance > maxDistance then
set maxDistance = distance
endif
set j = j + 1
endloop
set i = i + 1
endloop
return maxDistance
endfunction
function GetIndexOfLowestHPUnit takes group g returns integer
local integer lowestIndex = -1
local real lowestHP = 99999999.0
local integer i = 0
local integer groupSize = BlzGroupGetSize(g)
local unit u
local real hp
loop
exitwhen i >= groupSize
set u = BlzGroupUnitAt(g, i)
set hp = GetUnitState(u, UNIT_STATE_LIFE)
if hp < lowestHP then
set lowestHP = hp
set lowestIndex = i
endif
set i = i + 1
endloop
set u = null
return lowestIndex
endfunction
private function isRangedUnit takes unit u returns boolean
if BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 0) > 200 or BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 1) > 200 then
return true
endif
return false
endfunction
private function GetAverageHPInGroup takes group g returns real
local integer groupSize = BlzGroupGetSize(g)
local integer i = 0
local real totalHP = 0.0
local unit u
if groupSize == 0 then
return 0.0
endif
loop
exitwhen i >= groupSize
set u = BlzGroupUnitAt(g, i)
set totalHP = totalHP + GetUnitState(u, UNIT_STATE_LIFE)
set i = i + 1
endloop
set u = null
return totalHP / groupSize
endfunction
//=========================================================
// Squad functions
//=========================================================
// Get a unit's squad
function UnitGetSquad takes unit whichUnit returns group
local integer handleId = GetHandleId(whichUnit)
if HaveSavedHandle(htbUnit, handleId, SQUAD) then
return LoadGroupHandle(htbUnit, handleId, SQUAD)
endif
return null
endfunction
// Remove unit from squad
function SquadRemoveUnit takes group squad, unit whichUnit returns nothing
local integer handleId = GetHandleId(whichUnit)
local integer squad_size
call GroupRemoveUnit(squad, whichUnit)
set squad_size = BlzGroupGetSize(squad)
if squad_size == 1 then // Remove last member if only 1 is left
call SquadRemoveUnit(squad, FirstOfGroup(squad))
elseif squad_size == 0 then // Destroy squad if empty
call FlushChildHashtable(htbSquad, GetHandleId(squad))
call DestroyGroup(squad)
endif
if HaveSavedHandle(htbUnit, handleId, SFX) then
call DestroyEffect(LoadEffectHandle(htbUnit, handleId, SFX))
endif
call FlushChildHashtable(htbUnit, handleId)
endfunction
// Add a unit to a squad
function SquadAddUnit takes group squad, unit whichUnit returns nothing
local trigger trg
local group oldSquad
local integer unitHandleId = GetHandleId(whichUnit)
local integer squadHandleId = GetHandleId(squad)
local string sfx_path = null
local string sfx_attach
set oldSquad = UnitGetSquad(whichUnit)
if oldSquad != null then
call SquadRemoveUnit(oldSquad, whichUnit)
endif
call GroupAddUnit(squad, whichUnit)
call SaveGroupHandle(htbUnit, unitHandleId, SQUAD, squad)
// Get SFX path
if HaveSavedString(htbSquad, squadHandleId, SFX_PATH) then
set sfx_path = LoadStr(htbSquad, squadHandleId, SFX_PATH)
else
set sfx_path = squad_sfx_path
endif
// Get SFX attachment point
if HaveSavedString(htbSquad, squadHandleId, SFX_ATTACH) then
set sfx_attach = LoadStr(htbSquad, squadHandleId, SFX_ATTACH)
else
set sfx_attach = squad_sfx_attach
endif
// Add effect
if sfx_path != null then
call SaveEffectHandle(htbUnit, unitHandleId, SFX, AddSpecialEffectTarget(sfx_path, whichUnit, sfx_attach))
endif
set trg = null
set oldSquad = null
endfunction
// Create squad from unit
function UnitCreateSquad takes unit whichUnit returns nothing
local group squad
local player owningPlayer
local integer typeId
local real x
local real y
local real facing
local boolean isSquadUnit = false
local integer index = 0
local integer size_index = 0
if squadUnits_max >= 0 then
set typeId = GetUnitTypeId(whichUnit)
loop
set isSquadUnit = squadUnits[index] == typeId
set index = index + 1
exitwhen index > squadUnits_max or isSquadUnit
endloop
if isSquadUnit then
set squad = CreateGroup()
set owningPlayer = GetOwningPlayer(whichUnit)
set x = GetUnitX(whichUnit)
set y = GetUnitY(whichUnit)
set facing = GetUnitFacing(whichUnit)
set size_index = index - 1
set index = 0
call SquadAddUnit(squad, whichUnit)
loop
call SquadAddUnit(squad, CreateUnit(owningPlayer, typeId, x, y, facing))
set index = index + 1
exitwhen index == squadSize[size_index] -1
endloop
endif
endif
set squad = null
set owningPlayer = null
endfunction
// Create a squad with units of type for player
function UnitTypeCreateSquad takes player whichPlayer, integer unitid, integer size, real x, real y, real facing returns group
local group squad = CreateGroup()
local integer index = 0
loop
call SquadAddUnit(squad, CreateUnit(whichPlayer, unitid, x, y, facing))
set index = index + 1
exitwhen index == size
endloop
return squad
endfunction
// Remove all units from squad
function DisbandSquad takes group squad returns nothing
local unit pickedUnit
loop
set pickedUnit = FirstOfGroup(squad)
exitwhen pickedUnit == null
call SquadRemoveUnit(squad, pickedUnit)
endloop
set pickedUnit = null
endfunction
// Destroy existing effect and create/save new one
private function SetSfxEnum takes nothing returns nothing
local unit enumUnit = GetEnumUnit()
local integer unitHandleId = GetHandleId(enumUnit)
local integer squadHandleId = GetHandleId(UnitGetSquad(enumUnit))
local string path = LoadStr(htbSquad, squadHandleId, SFX_PATH)
if HaveSavedHandle(htbUnit, unitHandleId, SFX) then
call DestroyEffect(LoadEffectHandle(htbUnit, unitHandleId, SFX))
call RemoveSavedHandle(htbUnit, unitHandleId, SFX)
endif
if path != null then
call SaveEffectHandle(htbUnit, unitHandleId, SFX, AddSpecialEffectTarget(path, enumUnit, LoadStr(htbSquad, squadHandleId, SFX_ATTACH)))
endif
set enumUnit = null
endfunction
// Set the special effect for a squad
function SquadSetSfx takes group squad, string path, string attach returns nothing
local integer handleId = GetHandleId(squad)
call SaveStr(htbSquad, handleId, SFX_PATH, path)
call SaveStr(htbSquad, handleId, SFX_ATTACH, attach)
call ForGroup(squad, function SetSfxEnum)
endfunction
// Issue target order to unit without ordering the squad
function IndividualOrderTarget takes unit whichUnit, string order, widget target returns nothing
call GroupAddUnit(pauseGroupOrder, whichUnit)
call IssueTargetOrder(whichUnit, order, target)
call GroupRemoveUnit(pauseGroupOrder, whichUnit)
endfunction
// Issue immediate order to unit without ordering the squad
function IndividualOrderImmediate takes unit whichUnit, string order returns nothing
call GroupAddUnit(pauseGroupOrder, whichUnit)
call IssueImmediateOrder(whichUnit, order)
call GroupRemoveUnit(pauseGroupOrder, whichUnit)
endfunction
// Issue point order to unit without ordering the squad
function IndividualOrderPoint takes unit whichUnit, string order, real x, real y returns nothing
call GroupAddUnit(pauseGroupOrder, whichUnit)
call IssuePointOrder(whichUnit, order, x, y)
call GroupRemoveUnit(pauseGroupOrder, whichUnit)
endfunction
//=========================================================
// Triggers
//=========================================================
// Unit sold: create squad
private function UnitSold takes nothing returns boolean
call UnitCreateSquad(GetSoldUnit())
return false
endfunction
// Unit sold: create squad
private function UnitTrained takes nothing returns boolean
call UnitCreateSquad(GetTrainedUnit())
return false
endfunction
// Unit selected: modify selection
private function UnitSelected takes nothing returns nothing
local unit triggerUnit = GetTriggerUnit()
local player triggerPlayer = GetTriggerPlayer()
local group squad = UnitGetSquad(triggerUnit)
local group grp
local unit pickedUnit
// Only proceed if unit has squad and player has control over unit
if squad != null and not IsUnitInGroup(triggerUnit, pauseSelection) and PlayerHasControl(triggerUnit, triggerPlayer) then
call GroupAddGroup(squad, pauseSelection)
set grp = CreateGroup()
call GroupAddGroup(squad, grp)
loop
set pickedUnit = FirstOfGroup(grp)
exitwhen pickedUnit == null
call GroupRemoveUnit(grp, pickedUnit)
if pickedUnit != triggerUnit then
call SelectUnitRemoveForPlayer(pickedUnit, triggerPlayer)
endif
endloop
call DestroyGroup(grp)
call TriggerSleepAction(0.01) // this is necessary to prevent all selected squad units from firing the event
call GroupRemoveGroup(squad, pauseSelection)
endif
set triggerUnit = null
set triggerPlayer = null
set squad = null
set grp = null
set pickedUnit = null
endfunction
// Unit target order: order squad
private function SquadTargetOrder takes nothing returns boolean
local trigger trg
local string order
local unit orderedUnit = GetOrderedUnit()
local unit targetUnit
local group squad = UnitGetSquad(orderedUnit)
if squad != null and not IsUnitInGroup(orderedUnit, pauseGroupOrder) then
set trg = GetTriggeringTrigger()
set order = OrderId2String(GetIssuedOrderId())
set targetUnit = GetOrderTargetUnit()
call DisableTrigger(trg) // Prevent infinite loop
// smart order on friendly unit doesn't work
if order == "smart" and targetUnit != null and IsPlayerAlly(GetOwningPlayer(orderedUnit), GetOwningPlayer(targetUnit)) then
call GroupPointOrder(squad, order, GetUnitX(targetUnit), GetUnitY(targetUnit))
else
call GroupTargetOrder(squad, order, GetOrderTarget())
endif
call EnableTrigger(trg)
endif
set trg = null
set orderedUnit = null
set targetUnit = null
set squad = null
return false
endfunction
// Unit immediate order: order squad
private function SquadImmediateOrder takes nothing returns boolean
local trigger trg
local unit orderedUnit = GetOrderedUnit()
local group squad = UnitGetSquad(orderedUnit)
if squad != null and not IsUnitInGroup(orderedUnit, pauseGroupOrder) then
set trg = GetTriggeringTrigger()
call DisableTrigger(trg) // Prevent infinite loop
call GroupImmediateOrder(squad, OrderId2String(GetIssuedOrderId()))
call EnableTrigger(trg)
endif
set trg = null
set orderedUnit = null
set squad = null
return false
endfunction
// Unit point order: order squad
private function SquadPointOrder takes nothing returns boolean
local trigger trg
local unit orderedUnit = GetOrderedUnit()
local group squad = UnitGetSquad(orderedUnit)
if squad != null and not IsUnitInGroup(orderedUnit, pauseGroupOrder) then
set trg = GetTriggeringTrigger()
call DisableTrigger(trg) // Prevent infinite loop
call GroupPointOrder(squad, OrderId2String(GetIssuedOrderId()), GetOrderPointX(), GetOrderPointY())
call EnableTrigger(trg)
endif
set trg = null
set orderedUnit = null
set squad = null
return false
endfunction
// Make enumerated units flee to random point
private function FleeEnum takes nothing returns nothing
local unit enumUnit = GetEnumUnit()
local real x = GetRandomReal(-squad_flee_distance, squad_flee_distance)
local real y = SquareRoot(Pow(squad_flee_distance, 2) -Pow(x, 2))
// y is always positive, allow for negative value
if GetRandomInt(0, 1) == 0 then
set y = -y
endif
call IssuePointOrder(enumUnit, "move", GetUnitX(enumUnit) + x, GetUnitY(enumUnit) + y)
endfunction
// Unit dies: remove unit from squad and adjust selection
private function UnitDies takes nothing returns boolean
local unit dyingUnit = GetDyingUnit()
local group squad = UnitGetSquad(dyingUnit)
local group grp
local player indexPlayer
local integer squad_size
local integer index = 0
if squad != null then
set squad_size = BlzGroupGetSize(squad)
// Chance the squad disbands
if squad_disband_multiplier / (squad_size - 1) >= GetRandomInt(1, 100) then
if squad_flee_distance > 0 then
set grp = CreateGroup()
call GroupAddGroup(squad, grp)
call GroupRemoveUnit(grp, dyingUnit)
endif
call DisbandSquad(squad)
if squad_flee_distance > 0 then
call ForGroup(grp, function FleeEnum)
call DestroyGroup(grp)
endif
// Adjust selection and remove unit from squad
else
if squad_size > 1 then
loop
set indexPlayer = Player(index)
if IsUnitSelected(dyingUnit, indexPlayer) and PlayerHasControl(dyingUnit, indexPlayer) then
set grp = CreateGroup()
call GroupAddGroup(squad, grp)
call GroupRemoveUnit(grp, dyingUnit)
call SelectUnitAddForPlayer(FirstOfGroup(grp), indexPlayer)
call DestroyGroup(grp)
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
endif
call SquadRemoveUnit(squad, dyingUnit)
endif
endif
set dyingUnit = null
set squad = null
set grp = null
set indexPlayer = null
return false
endfunction
private function UnitDamaged takes nothing returns nothing
local integer i = 0
local unit defender = GetTriggerUnit()
local group squad = UnitGetSquad(defender)
local integer groupSize = BlzGroupGetSize(squad)
local real sharedDamage
local real ratio
local real squadUnit = 0
local integer currentIndex
local integer safetyIndex = 0
local real skipChance = 0.0
local unit currentUnit
local integer splitCount
local real averageHpInSquad
local real defenderLife
local integer appliedDamageSplitCount = 0
if groupSize < 2 then
return
endif
if BlzGetEventDamageType() != DAMAGE_TYPE_NORMAL then
return
endif
if GetRandomReal(0, 1) < squad_shared_damage_activation_chance then
return
endif
set defenderLife = GetUnitState(defender, UNIT_STATE_LIFE)
set averageHpInSquad = GetAverageHPInGroup(squad)
if defenderLife >= averageHpInSquad * 1.333 then
return
elseif defenderLife >= averageHpInSquad * 1.1 and GetRandomInt(0,2) == 2 then
return
endif
set ratio = GetRandomReal(squad_min_percent_of_shared_damage, squad_max_percent_of_shared_damage)
set sharedDamage = GetEventDamage() * ratio
if sharedDamage >= squad_min_amount_of_shared_damage and defenderLife > 0 then
set splitCount = groupSize - 1
if (splitCount >= 2) then
set splitCount = 2 - GetRandomInt(0, 1)
endif
loop
exitwhen appliedDamageSplitCount >= splitCount
set currentIndex = GetRandomInt(0, groupSize - 1)
set currentUnit = BlzGroupUnitAt(squad, currentIndex)
if currentUnit != defender and GetUnitState(currentUnit, UNIT_STATE_LIFE) > sharedDamage then
set skipChance = (1 - GetUnitState(currentUnit, UNIT_STATE_LIFE) / GetUnitState(currentUnit, UNIT_STATE_MAX_LIFE))
if GetIndexOfLowestHPUnit(squad) == currentIndex then
set skipChance = skipChance + 0.20
endif
if averageHpInSquad > GetUnitState(currentUnit, UNIT_STATE_LIFE) * 0.9 then
set skipChance = skipChance + 0.20
endif
if defenderLife > GetUnitState(currentUnit, UNIT_STATE_LIFE) then
set skipChance = skipChance + 0.20
endif
if isRangedUnit(currentUnit) == true then
set skipChance = skipChance + 0.20
endif
if GetRandomReal(0, 1.0) > skipChance then
call UnitDamageTarget(GetEventDamageSource(), currentUnit, sharedDamage / splitCount * 0.99, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNKNOWN, WEAPON_TYPE_WHOKNOWS)
set appliedDamageSplitCount = appliedDamageSplitCount + 1
endif
endif
set safetyIndex = safetyIndex + 1
exitwhen safetyIndex >= 12
endloop
if appliedDamageSplitCount > 0 then
call SetUnitState(defender, UNIT_STATE_LIFE, defenderLife + sharedDamage)
endif
endif
set squad = null
endfunction
// Unit is attacked: make sure attacking unit's squad follows through
private function UnitAttacked takes nothing returns boolean
local group squad = UnitGetSquad(GetAttacker())
if squad != null then
if MaxDistanceBetweenUnitsInGroup(squad) > squad_combat_overextension_distance + BlzGroupGetSize(squad) * 25 then
call GroupTargetOrder(squad, "attack", GetTriggerUnit())
//call GroupPointOrder(squad, "attack", GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()))
endif
endif
set squad = null
return false
endfunction
//=========================================================
// Initializer
//=========================================================
private function InitSquadSystem takes nothing returns nothing
local trigger trgUnitSold = CreateTrigger()
local trigger trgUnitTrained = CreateTrigger()
local trigger trgUnitSelected = CreateTrigger()
local trigger trgSquadTargetOrder = CreateTrigger()
local trigger trgSquadImmediateOrder = CreateTrigger()
local trigger trgSquadPointOrder = CreateTrigger()
local trigger trgUnitDies = CreateTrigger()
local trigger trgUnitAttacked = CreateTrigger()
local trigger trgUnitDamaging = CreateTrigger()
local player indexPlayer
local integer index = 0
loop
set indexPlayer = Player(index)
call TriggerRegisterPlayerUnitEvent(trgUnitSold, indexPlayer, EVENT_PLAYER_UNIT_SELL, null)
call TriggerRegisterPlayerUnitEvent(trgUnitTrained, indexPlayer, EVENT_PLAYER_UNIT_TRAIN_FINISH, null)
call TriggerRegisterPlayerUnitEvent(trgUnitSelected, indexPlayer, EVENT_PLAYER_UNIT_SELECTED, null)
call TriggerRegisterPlayerUnitEvent(trgSquadTargetOrder, indexPlayer, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
call TriggerRegisterPlayerUnitEvent(trgSquadImmediateOrder, indexPlayer, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
call TriggerRegisterPlayerUnitEvent(trgSquadPointOrder, indexPlayer, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
call TriggerRegisterPlayerUnitEvent(trgUnitDies, indexPlayer, EVENT_PLAYER_UNIT_DEATH, null)
call TriggerRegisterPlayerUnitEvent(trgUnitAttacked, indexPlayer, EVENT_PLAYER_UNIT_ATTACKED, null)
call TriggerRegisterPlayerUnitEvent(trgUnitDamaging, indexPlayer, EVENT_PLAYER_UNIT_DAMAGED, null)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(trgUnitSold, function UnitSold)
call TriggerAddCondition(trgUnitTrained, function UnitTrained)
call TriggerAddAction(trgUnitSelected, function UnitSelected)
call TriggerAddCondition(trgSquadTargetOrder, function SquadTargetOrder)
call TriggerAddCondition(trgSquadImmediateOrder, function SquadImmediateOrder)
call TriggerAddCondition(trgSquadPointOrder, function SquadPointOrder)
call TriggerAddCondition(trgUnitDies, function UnitDies)
call TriggerAddCondition(trgUnitAttacked, function UnitAttacked)
call TriggerAddAction(trgUnitDamaging, function UnitDamaged)
set trgUnitSold = null
set trgUnitTrained = null
set trgUnitSelected = null
set trgSquadTargetOrder = null
set trgSquadImmediateOrder = null
set trgSquadPointOrder = null
set trgUnitDies = null
set trgUnitAttacked = null
set indexPlayer = null
endfunction
endlibrary
Cool, I'll add it to the bundle and add you as author if you don't mind.I've added a feature that allows to split portion of incoming damage between squad members.
The formation is done automatically with object editor value Stats - Formation Rank. I don't think it's possible to turn this off through triggers.Can you add an option to turn formation on/off for a squad btw?
The formation is done automatically with object editor value Stats - Formation Rank. I don't think it's possible to turn this off through triggers.
Hm, should be doable. Loop through the group and give them separate orders.May be there should be an API function that commands all units of the group with a certain command but each of them recieve this order separately.
So just to have an option to move group in a non-organized way.
Weird, it works fine for me. Maybe it's the % sign?P.S.:
// - E.g. 100: 50% chance when 2 units left, 33% chance when 3 units left
my reforged editor breaks on it there is a broken symbol on this line or smth
Yeah shouldn't be a problem. I am worried that there are starting to be too many config variables haha.In a "Hunter + Pet" type of setup, I'd like to have the Pet(s) always follow the orders and commands of the Hunter, but never the reverse.
What are your thoughts on having a boolean flag that allows for a configuration where the Squad Members always follow the Leader's orders, but the Leader never follows the Squad Members?
squad_sfx_path
squad_sfx_attach
squad_leader_sfx_path
squad_leader_sfx_attach
squad_combat_overextension_distance
leader_death_disband
squad_shared_damage_activation_chance
squad_max_percent_of_shared_damage
squad_min_percent_of_shared_damage
squadUnits
squadSize
squadUnits_max
// By kokon
function MaxDistanceBetweenUnitsInGroup takes group g returns real
local unit u1
local unit u2
local real maxDistance = 0.0
local real dx
local real dy
local real distance
local integer i
local integer j
local integer groupSize = BlzGroupGetSize(g)
if groupSize < 2 then
return 0.0
endif
set i = 0
loop
exitwhen i >= groupSize - 1
set u1 = BlzGroupUnitAt(g, i)
set j = i + 1
loop
exitwhen j >= groupSize
set u2 = BlzGroupUnitAt(g, j)
set dx = GetUnitX(u2) - GetUnitX(u1)
set dy = GetUnitY(u2) - GetUnitY(u1)
set distance = SquareRoot(dx * dx + dy * dy)
if distance > maxDistance then
set maxDistance = distance
endif
set j = j + 1
endloop
set i = i + 1
endloop
set u1 = null
set u2 = null
return maxDistance
endfunction
exitwhen j >= groupSize
// By kokon
function GetIndexOfLowestHPUnit takes group g returns integer
local integer lowestIndex = -1
local real lowestHP = 99999999.0
local integer i = 0
local integer groupSize = BlzGroupGetSize(g)
local unit u
local real hp
loop
exitwhen i >= groupSize
set u = BlzGroupUnitAt(g, i)
set hp = GetUnitState(u, UNIT_STATE_LIFE)
if hp < lowestHP then
set lowestHP = hp
set lowestIndex = i
endif
set i = i + 1
endloop
set u = null
return lowestIndex
endfunction
exitwhen i >= groupSize
// By kokon
private function GetAverageHPInGroup takes group g returns real
local integer groupSize = BlzGroupGetSize(g)
local integer i = 0
local real totalHP = 0.0
local unit u
if groupSize == 0 then
return 0.0
endif
loop
exitwhen i >= groupSize
set u = BlzGroupUnitAt(g, i)
set totalHP = totalHP + GetUnitState(u, UNIT_STATE_LIFE)
set i = i + 1
endloop
set u = null
return totalHP / groupSize
endfunction
exitwhen i >= groupSize
// By kokon
private function IsRangedUnit takes unit u returns boolean
if BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 0) > 200 or BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 1) > 200 then
return true
endif
return false
endfunction
j// By kokon
private function IsRangedUnit takes unit u returns boolean
return BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 0) > 200 or BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 1) > 200
endfunction
// Add leader to squad
function SquadSetLeader takes group squad, unit whichUnit returns nothing
local string sfx_path
local string sfx_attach
local integer squadHandleId = GetHandleId(squad)
local integer unitHandleId = GetHandleId(whichUnit)
call SaveUnitHandle(htbSquad, squadHandleId, LEADER, whichUnit)
if not IsUnitInGroup(whichUnit, squad) then
call SquadAddUnit(squad, whichUnit)
endif
// Get SFX path
if HaveSavedString(htbSquad, squadHandleId, LEADER_SFX_PATH) then
set sfx_path = LoadStr(htbSquad, squadHandleId, LEADER_SFX_PATH)
else
set sfx_path = squad_leader_sfx_path
endif
// Get SFX attachment point
if HaveSavedString(htbSquad, squadHandleId, LEADER_SFX_ATTACH) then
set sfx_attach = LoadStr(htbSquad, squadHandleId, LEADER_SFX_ATTACH)
else
set sfx_attach = squad_leader_sfx_attach
endif
// Add/replace effect
if sfx_path != null then
if HaveSavedHandle(htbUnit, unitHandleId, SFX) then
call DestroyEffect(LoadEffectHandle(htbUnit, unitHandleId, SFX))
call RemoveSavedHandle(htbUnit, unitHandleId, SFX)
endif
call SaveEffectHandle(htbUnit, unitHandleId, SFX, AddSpecialEffectTarget(sfx_path, whichUnit, sfx_attach))
endif
endfunction
// Unit selected: modify selection
private function UnitSelected takes nothing returns nothing
local unit triggerUnit = GetTriggerUnit()
local player triggerPlayer = GetTriggerPlayer()
local group squad = UnitGetSquad(triggerUnit)
local unit pickedUnit
local unit leader
local integer squad_size
local integer index = 0
// Only proceed if unit has squad and player has control over unit
if squad != null and not IsUnitInGroup(triggerUnit, pauseSelection) and PlayerHasControl(triggerUnit, triggerPlayer) then
call TriggerSleepAction(0.01) // this is necessary to update selection in case leader was previously selected
call GroupAddGroup(squad, pauseSelection)
// If leader is selected, set triggerUnit to leader
set leader = SquadGetLeader(squad)
if leader != null and IsUnitSelected(leader, triggerPlayer) then
set triggerUnit = leader
endif
set squad_size = BlzGroupGetSize(squad)
loop
exitwhen index >= squad_size
set pickedUnit = BlzGroupUnitAt(squad, index)
if pickedUnit != triggerUnit then
call SelectUnitRemoveForPlayer(pickedUnit, triggerPlayer)
endif
set index = index + 1
endloop
call TriggerSleepAction(0.01) // this is necessary to prevent all selected squad units from firing the event
call GroupRemoveGroup(squad, pauseSelection)
endif
set triggerUnit = null
set triggerPlayer = null
set squad = null
set pickedUnit = null
set leader = null
endfunction
exitwhen index >= squad_size
must have -1. The group index starts at 0.local real y = SquareRoot(Pow(squad_flee_distance, 2) - Pow(x, 2))[/CODE][CODE]
SquareRoot(squad_flee_distance*squad_flee_distance - x*x)
// Unit dies: remove unit from squad and adjust selection
private function UnitDies takes nothing returns boolean
local unit dyingUnit = GetDyingUnit()
local group squad = UnitGetSquad(dyingUnit)
local group squad_copy
local player indexPlayer
local boolean isLeader
local integer squad_size
local integer index = 0
if squad != null then
set squad_size = BlzGroupGetSize(squad)-1
if squad_size > 0 then
// Get a copy of the squad in case only 1 unit is left and it is disbanded
set squad_copy = CreateGroup()
call GroupAddGroup(squad, squad_copy)
call GroupRemoveUnit(squad_copy, dyingUnit)
set isLeader = dyingUnit == SquadGetLeader(squad)
// Remove the unit from squad
call SquadRemoveUnit(squad, dyingUnit)
// Chance the squad disbands
if squad_disband_multiplier/squad_size >= GetRandomInt(1, 100) or (leader_death_disband and isLeader) then
if squad_flee_distance > 0 then
call GroupAddGroup(squad_copy, pauseGroupOrder)
call ForGroup(squad_copy, function FleeEnum)
call GroupRemoveGroup(squad_copy, pauseGroupOrder)
endif
call DisbandSquad(squad_copy)
// Adjust selection
else
loop
set indexPlayer = Player(index)
if IsUnitSelected(dyingUnit, indexPlayer) and PlayerHasControl(dyingUnit, indexPlayer) then
call SelectUnitAddForPlayer(FirstOfGroup(squad_copy), indexPlayer)
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
endif
endif
endif
set dyingUnit = null
set squad = null
set squad_copy = null
set indexPlayer = null
return false
endfunction
SquadRemoveUnit
after you find out its leader.if defenderLife >= averageHpInSquad * 1.333 then
skipChance = skipChance + 0.20
. SkipAmount or whatever you want you call it.jelseif defenderLife >= averageHpInSquad * 1.1 and GetRandomInt(0,2) == 2 then
sharedDamage / splitCount * 0.99
what's up with 0.99?exitwhen i >= groupSize - 1