library ASS requires DamageEvent, UnitIndexer, SoundTools, Table, RegisterPlayerUnitEvent, TimerUtils, optional CombatDataStorage
// CONFIGURATION
globals
// Max interval between Multikills (seconds)
private constant real MULTI_TIME = 10
// Multikill Buffer Interval (seconds)
private constant real MULTI_UPDATE = 1
// Are sounds 3D?
private constant boolean S3D = false
// Firstblood bonus
private constant integer FIRST_GOLD = 200
// Duration of the text messages
private constant real TEXT_DURATION = 8.00
// Interval between streak and multikill sounds
private constant real INTERVAL = 0.50
// Assist Decay time (seconds)
private constant real ASSIST_DECAY = 45
// Can the heroes of leavers get streaks?
private constant boolean LEAVER_HEROES = false
// Leavers can assist?
private constant boolean LEAVER_ASSIST = false
// Initial Gold base
private integer baseGold = 210
// Gold increment according to formulas
private integer goldIncrement = 10
// Do you lose gold when you die?
private boolean loseGold = true
// Do you gain gold when you kill?
private boolean gainGold = true
// Do you want floating texts?
private constant boolean TEXTTAGS = true
// Give gold to assisters?
private constant boolean ASSIST_GOLD = true
// Does Suicide end a spree?
private constant boolean SUICIDE_ENDS = false
// Does a Death end a multikill?
private constant boolean DEATH_END_MULTI = true
endglobals
private module Configuration
private static method onInit takes nothing returns nothing
set firstSound = NewSound("Sound\\Interface\\GoodJob.wav", 2548, false, S3D)
// Feel free to configure these
call thistype.newStreak("", "", "", 0) // This was intended because I wanted to add 2 empty instances to the stack.
call thistype.newStreak("", "", "", 0) // Without these, 1 kill would count as a streak.
call thistype.newStreak("|cff80ff80killing spree|r", "is on a |cff80ff80killing spree|r!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newStreak("|cffff80c0dominating|r", "is |cffff80c0dominating|r!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newStreak("|cffffff80mega-kill|r", "has a |cffffff80mega-kill|r!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newStreak("|cffff8000unstoppable|r", "is |cffff8000unstoppable|r!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newStreak("|cff00ffa2wicked sick|r", "is |cff00ffa2wicked sick|r!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newStreak("|cffff0080monster kill|r", "has a |cffff0080monster kill|r!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newStreak("|cffff0000GODLIKE|r", "is |cffff0000GODLIKE|r!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newStreak("|cffffa500beyond Godlike|r", "is |cffffa500beyond Godlike. SOMEONE KILL HIM|r!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newMulti("got a |cff0028ffDouble Kill|r!!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newMulti("got a |cff40ff40Triple Kill|r!!!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newMulti("got an |cff00bfffUltra-Kill|r!!!!", "Sound\\Interface\\GoodJob.wav", 2548)
call thistype.newMulti("is on a |cff00ced1Rampage|r!!!!!", "Sound\\Interface\\GoodJob.wav", 2548)
set thistype.colors[0] = "|cffff0303"
set thistype.colors[1] = "|cff0042ff"
set thistype.colors[2] = "|cff1ce6b9"
set thistype.colors[3] = "|cff540081"
set thistype.colors[4] = "|cfffffc01"
set thistype.colors[5] = "|cfffeba0e"
set thistype.colors[6] = "|cff20c000"
set thistype.colors[7] = "|cffe55bb0"
set thistype.colors[8] = "|cff959697"
set thistype.colors[9] = "|cff7ebff1"
set thistype.colors[10] = "|cff106246"
set thistype.colors[11] = "|cff4e2a04"
endmethod
endmodule
private keyword Spree
// Configurable Functions:
private function GetBounty takes integer m, integer n, unit k, unit d returns integer
return baseGold + goldIncrement * Spree(n).streak + goldIncrement * Spree(m).streak
endfunction
private function GetLostGold takes integer g returns integer
return g / 3
endfunction
private function GetAssistGold takes integer g, integer n returns integer
return g / n
endfunction
private function KillingPlayerFilter takes integer playerId returns boolean
return playerId < 12
endfunction
private function AssistPlayerFilter takes integer playerId returns boolean
return playerId < 12
endfunction
private function DyingPlayerFilter takes integer playerId returns boolean
return playerId < 12
endfunction
// *******************************************
// DO NOT EDIT BELOW THIS LINE!! YOU HEAR ME??
// *******************************************
globals
// Table for Timer Data storage
private Table cache
endglobals
private keyword Assist
struct StreakSystem extends array
readonly static integer maxSpree = 0
readonly static integer minSpree = 1
readonly static integer maxMulti = 1
readonly static Sound firstSound
readonly static Sound array sounds
readonly static Sound array multiSound
readonly static string array strings
readonly static string array multiString
readonly static string array endString
readonly static string array colors
static boolean firstblood = false
static boolean enabled = true
static method newStreak takes string streakName, string streakDisplay, string soundPath, integer duration returns nothing
if streakName == "" then
set minSpree = minSpree + 1
set maxSpree = maxSpree + 1
return
endif
set maxSpree = maxSpree + 1
set strings[maxSpree] = " " + streakDisplay
set endString[maxSpree] = "'s " + streakName
set sounds[maxSpree] = NewSound(soundPath, duration, false, S3D)
endmethod
static method newMulti takes string text, string soundPath, integer duration returns nothing
set maxMulti = maxMulti + 1
set multiString[maxMulti] = " " + text
set multiSound[maxMulti] = NewSound(soundPath, duration, false, S3D)
endmethod
static method enableGoldGain takes nothing returns nothing
set gainGold = true
endmethod
static method disableGoldGain takes nothing returns nothing
set gainGold = false
endmethod
static method isGoldGainEnabled takes nothing returns boolean
return gainGold
endmethod
static method enableGoldLoss takes nothing returns nothing
set loseGold = true
endmethod
static method disableGoldLoss takes nothing returns nothing
set loseGold = false
endmethod
static method isGoldLossEnabled takes nothing returns boolean
return loseGold
endmethod
static method getBountyBase takes nothing returns integer
return baseGold
endmethod
static method setBountyBase takes integer i returns nothing
set baseGold = i
endmethod
static method getBountyIncrement takes nothing returns integer
return goldIncrement
endmethod
static method setBountyIncrement takes integer i returns nothing
set goldIncrement = i
endmethod
static method reset takes nothing returns nothing
local integer i = 11
loop
set Spree(i).streak = 0
set Spree(i).counter = 0
set Spree(i).multikills = 0
set Spree(i).off = false
exitwhen i == 0
set i = i - 1
endloop
call Assist.refresh()
endmethod
static method enable takes nothing returns nothing
set enabled = true
endmethod
static method disable takes nothing returns nothing
set enabled = false
endmethod
static method isEnabled takes nothing returns boolean
return enabled
endmethod
implement Configuration
endstruct
private struct Spree extends array
integer streak
integer multikills
boolean off
real counter
endstruct
// You mad Jesus4Lyf?
private struct Assist extends array
integer number
Table bool
Table decay
static thistype array next
static thistype array prev
method reset takes nothing returns nothing
set this.number = 0
call this.bool.flush()
call this.decay.flush()
endmethod
static method refresh takes nothing returns nothing
local thistype this = next[0]
loop
exitwhen this == 0
call this.reset()
set this = next[this]
endloop
endmethod
static method index takes nothing returns nothing
set next[GetIndexedUnitId()] = 0
set prev[GetIndexedUnitId()] = prev[0]
set next[prev[0]] = GetIndexedUnitId()
set prev[0] = GetIndexedUnitId()
set thistype(GetIndexedUnitId()).number = 0
set thistype(GetIndexedUnitId()).bool = Table.create()
set thistype(GetIndexedUnitId()).decay = Table.create()
endmethod
static method deindex takes nothing returns nothing
set next[prev[GetIndexedUnitId()]] = next[GetIndexedUnitId()]
set prev[next[GetIndexedUnitId()]] = prev[GetIndexedUnitId()]
set thistype(GetIndexedUnitId()).number = 0
call thistype(GetIndexedUnitId()).bool.destroy()
call thistype(GetIndexedUnitId()).decay.destroy()
endmethod
static method filter takes unit u returns boolean
return IsUnitType(u,UNIT_TYPE_HERO)
endmethod
implement UnitIndexStruct
endstruct
private function AssistBuffer takes nothing returns boolean
local integer id = GetPlayerId(GetOwningPlayer(DamageEvent.source))
static if LEAVER_ASSIST then
if not AssistPlayerFilter(id) or DamageEvent.source == null or not IsUnitType(DamageEvent.target,UNIT_TYPE_HERO) or Assist(DamageEvent.targetId).bool.boolean[id] then
return false
endif
else
if Spree(id).off or not AssistPlayerFilter(id) or DamageEvent.source == null or not IsUnitType(DamageEvent.target,UNIT_TYPE_HERO) or Assist(DamageEvent.targetId).bool.boolean[id] then
return false
endif
endif
set Assist(DamageEvent.targetId).bool.boolean[id] = true
set Assist(DamageEvent.targetId).number = Assist(DamageEvent.targetId).number + 1
set Assist(DamageEvent.targetId).decay.real[id] = ASSIST_DECAY
return false
endfunction
private function MultiBuffer takes nothing returns nothing
local integer i = 11
local integer k = 11
loop
set Spree(i).counter = Spree(i).counter - 1
if Spree(i).counter == 0 then
set Spree(i).multikills = 0
endif
exitwhen i == 0
set i = i - 1
endloop
set i = Assist.next[0]
loop
exitwhen i == 0
loop
set Assist(i).decay[k] = Assist(i).decay[k] - 1
if Assist(i).decay[k] == 0 then
set Assist(i).bool.boolean[k] = false
set Assist(i).number = Assist(i).number - 1
endif
exitwhen k == 0
set k = k - 1
endloop
set k = 11
set i = Assist.next[i]
endloop
endfunction
static if LEAVER_HEROES then
private function OnLeave takes nothing returns boolean
set Spree(GetPlayerId(GetTriggerPlayer())).off = true
return false
endfunction
endif
// Wrapper used for readability/configuration
private function Print takes string s returns nothing
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, TEXT_DURATION, s)
endfunction
// I can make this function uglier if you want..
private function DoMulti takes nothing returns nothing
local timer tmr = GetExpiredTimer()
local integer timerId = GetHandleId(tmr)
local integer timerData = GetTimerData(tmr)
if 0 < cache.real[timerId] and timerData > StreakSystem.maxMulti then
call Print(cache.string[timerId] + StreakSystem.multiString[StreakSystem.maxMulti])
call RunSound(StreakSystem.multiSound[StreakSystem.maxMulti])
elseif 0 < cache.real[timerId] and timerData > 1 and timerData <= StreakSystem.maxMulti then
call Print(cache.string[timerId] + StreakSystem.multiString[timerData])
call RunSound(StreakSystem.multiSound[timerData])
endif
call ReleaseTimer(tmr)
set tmr = null
endfunction
private function Process takes player killingPlayer, player dyingPlayer, unit killingUnit, unit dyingUnit returns nothing
local timer delayTimer = NewTimer()
local integer index = 11
local integer killingId = GetPlayerId(killingPlayer)
local integer dyingId = GetPlayerId(dyingPlayer)
local integer killingGold = GetPlayerState(killingPlayer, PLAYER_STATE_RESOURCE_GOLD)
local integer timerId = GetHandleId(delayTimer)
local integer bounty = GetBounty(killingId, dyingId, killingUnit, dyingUnit)
local string assistString = ""
local string killingName = StreakSystem.colors[killingId] + GetPlayerName(killingPlayer) + "|r"
local string dyingName = StreakSystem.colors[dyingId] + GetPlayerName(dyingPlayer) + "|r"
local player tempPlayer = null
local integer unitId = GetUnitUserData(dyingUnit)
static if TEXTTAGS then
local real cameraCenter = GetCameraField(CAMERA_FIELD_TARGET_DISTANCE) / 2
local texttag tag
endif
if Assist(unitId).number > 1 then
set assistString = " Assists: "
loop
set tempPlayer = Player(index)
if IsPlayerEnemy(tempPlayer, dyingPlayer) and Assist(unitId).bool.boolean[index] and index != killingId and index != dyingId and not Spree(index).off then
set assistString = assistString + StreakSystem.colors[index] + GetPlayerName(tempPlayer) + "|r/"
static if LIBRARY_CombatDataStorage then
call IncreasePlayerAssistsById(index)
endif
static if ASSIST_GOLD then
call SetPlayerState(tempPlayer, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(tempPlayer, PLAYER_STATE_RESOURCE_GOLD) + GetAssistGold(bounty, Assist(unitId).number))
static if TEXTTAGS then
set tag = CreateTextTag()
call SetTextTagText(tag, "|cffffcc00+" + I2S(GetAssistGold(bounty, Assist(unitId).number)), 0.024)
call SetTextTagPos(tag, GetCameraEyePositionX(), GetCameraEyePositionY() + cameraCenter, 16.0)
call SetTextTagVelocity(tag, 0.0, 0.0355)
call SetTextTagVisibility(tag, GetLocalPlayer() == tempPlayer)
call SetTextTagFadepoint(tag, 1.5)
call SetTextTagLifespan(tag, 2.0)
call SetTextTagPermanent(tag, false)
set tag = null
endif
endif
endif
exitwhen index == 0
set index = index - 1
endloop
set assistString = SubString(assistString, 0, StringLength(assistString) - 1)
endif
call Assist(unitId).reset()
if gainGold then
call SetPlayerState(killingPlayer, PLAYER_STATE_RESOURCE_GOLD, killingGold + bounty)
static if TEXTTAGS then
set tag = CreateTextTag()
call SetTextTagText(tag, "|cffffcc00+" + I2S(bounty), 0.024)
call SetTextTagPos(tag, GetCameraEyePositionX(), GetCameraEyePositionY() + cameraCenter, 16.0)
call SetTextTagVelocity(tag, 0.0, 0.0355)
call SetTextTagVisibility(tag, GetLocalPlayer() == killingPlayer)
call SetTextTagFadepoint(tag, 1.5)
call SetTextTagLifespan(tag, 2.0)
call SetTextTagPermanent(tag, false)
set tag = null
endif
endif
if loseGold then
call SetPlayerState(dyingPlayer, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(dyingPlayer, PLAYER_STATE_RESOURCE_GOLD) - GetLostGold(bounty))
endif
if StreakSystem.firstblood then
if Spree(dyingId).streak >= StreakSystem.minSpree and Spree(dyingId).streak <= StreakSystem.maxSpree then
call Print(dyingName + StreakSystem.endString[Spree(dyingId).streak] + " streak has been ended by " + killingName + " for |cffffcc00" + I2S(bounty) + "|r gold." + assistString)
elseif Spree(dyingId).streak > StreakSystem.maxSpree then
call Print(dyingName + StreakSystem.endString[StreakSystem.maxSpree] + " streak has been ended by " + killingName + " for |cffffcc00" + I2S(bounty) + "|r gold." + assistString)
else
call Print(killingName + " pwned " + dyingName + "'s head for |cffffcc00" + I2S(bounty) + "|r gold." + assistString)
endif
if Spree(killingId).streak >= StreakSystem.minSpree and Spree(killingId).streak <= StreakSystem.maxSpree then
call Print(killingName + StreakSystem.strings[Spree(killingId).streak])
call RunSound(StreakSystem.sounds[Spree(killingId).streak])
elseif Spree(killingId).streak > StreakSystem.maxSpree then
call Print(killingName + StreakSystem.strings[StreakSystem.maxSpree])
call RunSound(StreakSystem.sounds[StreakSystem.maxSpree])
endif
else
set StreakSystem.firstblood = true
call Print(killingName + " pwned " + dyingName + "'s head for |cffffcc00" + I2S(bounty) + "|r gold." + assistString + "\n" + killingName + " just drew |cffff0000firstblood|r!")
call RunSound(StreakSystem.firstSound)
call SetPlayerState(killingPlayer, PLAYER_STATE_RESOURCE_GOLD, killingGold + FIRST_GOLD)
endif
set Spree(killingId).multikills = Spree(killingId).multikills + 1
set Spree(dyingId).streak = 0
call SetTimerData(delayTimer, Spree(killingId).multikills)
set cache.real[timerId] = Spree(killingId).counter
set cache.string[timerId] = killingName
call TimerStart(delayTimer, INTERVAL, false, function DoMulti)
set tempPlayer = null
set delayTimer = null
endfunction
private function Main takes nothing returns nothing
local unit dyingUnit = GetTriggerUnit()
local unit killingUnit = GetKillingUnit()
local player dyingPlayer = GetTriggerPlayer()
local player killingPlayer = GetOwningPlayer(killingUnit)
local integer killingId = GetPlayerId(killingPlayer)
local integer dyingId = GetPlayerId(dyingPlayer)
loop
exitwhen not StreakSystem.enabled or not IsUnitType(dyingUnit, UNIT_TYPE_HERO)
static if LEAVER_STREAK then
if not KillingPlayerFilter(killingId) or not DyingPlayerFilter(dyingId) or killingUnit == null or killingPlayer == null then
call Assist(GetUnitUserData(dyingUnit)).reset()
exitwhen true
endif
else
if not KillingPlayerFilter(killingId) or not DyingPlayerFilter(dyingId) or killingUnit == null or killingPlayer == null or Spree(killingId).off then
call Assist(GetUnitUserData(dyingUnit)).reset()
exitwhen true
endif
endif
if IsPlayerAlly(killingPlayer, dyingPlayer) and killingPlayer != dyingPlayer then
call Print(StreakSystem.colors[killingId] + GetPlayerName(killingPlayer) + "|r has denied his ally " + StreakSystem.colors[dyingId] + "|r!")
call Assist(GetUnitUserData(dyingUnit)).reset()
exitwhen true
elseif dyingPlayer == killingPlayer then
call Print(StreakSystem.colors[killingId] + GetPlayerName(dyingPlayer) + "|r has killed himself!")
static if SUICIDE_ENDS then
set Spree(killingId).streak = 0
endif
call Assist(GetUnitUserData(dyingUnit)).reset()
exitwhen true
endif
set Spree(killingId).streak = Spree(killingId).streak + 1
set Spree(killingId).counter = MULTI_TIME
static if DEATH_END_MULTI then
set Spree(dyingId).multikills = 0
set Spree(dyingId).counter = MULTI_TIME
endif
call Process(killingPlayer, dyingPlayer, killingUnit, dyingUnit)
exitwhen true
endloop
set dyingUnit = null
set killingUnit = null
set dyingPlayer = null
set killingPlayer = null
endfunction
private module Init
private static method onInit takes nothing returns nothing
static if LEAVER_HEROES then
local trigger t = CreateTrigger()
local integer i = 15
loop
call TriggerRegisterPlayerEvent(t,Player(i),EVENT_PLAYER_LEAVE)
exitwhen i == 0
set i = i - 1
endloop
set t = null
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function Main)
call DamageEvent.ANY.register(Condition(function AssistBuffer))
call TimerStart(CreateTimer(), MULTI_UPDATE, true, function MultiBuffer)
set cache = Table.create()
endmethod
endmodule
private struct Inits extends array
implement Init
endstruct
endlibrary