//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
scope StartGame initializer Init
globals
Camera array PlayerCamera
unit array PlayerHero
endglobals
private function StartGame takes nothing returns nothing
local trigger trig
local integer i = 0
local User user
local Equipment equipment
local Inventory inv
local integer array urace
set urace[1] = 'Hpal'
set urace[2] = 'Obla'
set urace[3] = 'Ulic'
set urace[4] = 'Edem'
loop
exitwhen i == User.AmountPlaying
set user = User.fromPlaying(i)
set PlayerCamera[user.id] = Camera.create()
// create hero
set equipment = equipment.create.evaluate(CreateUnitAtLoc(user.handle, urace[GetHandleId(GetPlayerRace(user.handle))], GetStartLocationLoc(GetPlayerStartLocation(user.handle)), 180))
call UnitAddAbility(equipment.unit, 'A001')
call UnitAddAbility(equipment.unit, 'A002')
if (User.Local == user.handle) then
call SelectUnit(equipment.unit, true)
call PanCameraToTimed(GetUnitX(equipment.unit), GetUnitY(equipment.unit), 0)
endif
set inv = Inventory.create(equipment.unit)
set PlayerHero[user.id] = equipment.unit
call SetPlayerAllianceStateBJ(Player(bj_PLAYER_NEUTRAL_EXTRA), user.handle, bj_ALLIANCE_ALLIED_VISION)
call SetPlayerAllianceStateBJ(user.handle, Player(bj_PLAYER_NEUTRAL_EXTRA), bj_ALLIANCE_ALLIED_VISION)
set i = i + 1
endloop
endfunction
private function Init takes nothing returns nothing
call SetSkyModel("Environment\\Sky\\Sky\\SkyLight.mdl")
call SetFloatGameState(GAME_STATE_TIME_OF_DAY, 22.00)
call StartGame()
endfunction
endscope
library InventoryCore initializer Init requires UserInterface, InventoryItem, PlayerUtils, TimerUtils
struct Inventory
//
// configuration
//
// location of inventory
static real X = 0.61
static real Y = -0.3
// size of the background wwindow
static real SCALE = 0.228
// location of the tooltip information
static real TOOLTIP_X = -0.7
static real TOOLTIP_Y = 0.0
// max # of pages the inventory can have
static integer MAX_PAGES = 5
// how many slots are on each row/column
static constant integer BUTTON_ROWS = 6
static constant integer BUTTON_COLS = 5
// how fast the UI updates
static constant real UI_REFRESH_RATE = 0.01
// show errors
static constant boolean SHOW_ERRORS = true
// icon size
static constant real SLOT_WIDTH = 0.065
static constant real SLOT_HEIGHT = 0.065 * SCREEN_ASPECT_RATIO
static constant real SLOT_SPACING = 1.00 // 0%
// icon offsets (from X,Y)
static constant real SLOT_OFFSET_X = -0.0485
static constant real SLOT_OFFSET_Y = 0.072
// raw id's
static constant integer ICON_EMPTY = 'dbnk'
static constant integer ICON_TRANSPARENT = 'B00S'
static constant integer ICON_PAGEUP = 'B00U'
static constant integer ICON_PAGEDOWN = 'B00T'
static constant integer ICON_SELL = 'B011'
static constant integer ICON_DROP = 'B00N'
static constant integer RACE_BORDERS_START = 'D201'
static constant integer BACKGROUND = 'bwn2'
//
// don't edit below here unless you know what you're doing
//
static constant integer MAX_SLOTS = BUTTON_ROWS * BUTTON_COLS
static constant integer MAX_ITEMS = MAX_SLOTS * MAX_PAGES
readonly static hashtable Hashtable = InitHashtable()
readonly static trigger ExecL = CreateTrigger()
readonly static trigger ExecR = CreateTrigger()
readonly static integer DisplayCount = 0
readonly static timer UpdateTimer = CreateTimer()
readonly static thistype array PlayerCurrent
readonly static UIButton array buttons
readonly integer itemCount
readonly thistype next
readonly thistype prev
readonly integer pid
readonly User user
readonly unit owner
static UIPicture array background
static UIText array pageNum
static UIButton array btnPageUp
static UIButton array btnPageDown
static UIButton array btnSell
static UIButton array btnDrop
static UIPicture array selector
Camera camera
Table countTable
integer currentPage
readonly boolean displayed
readonly integer freeSlot
public static trigger onSell
public static trigger onDrop
public static key KEY_ITEMS
public static key KEY_ITEM_ID
public static key KeyUnit
public static constant integer KEY_UNIT = INVENTORY_KEY_START + KeyUnit
public static key KeyItemCount
public static constant integer KEY_ITEM_COUNT = INVENTORY_KEY_START + KeyItemCount
static method localInt takes integer pid, integer value, integer other returns integer
if (User.Local != User(pid).handle) then
set value = other
endif
return value
endmethod
static method err takes player p, string s returns nothing
static if (Inventory.SHOW_ERRORS) then
static if (LIBRARY_SimError) then
call SimError(p, s)
else
call DisplayTimedTextToPlayer(p, 0, 0, 15, "Error: " + s)
endif
endif
endmethod
static method operator [] takes unit u returns thistype
return thistype(LoadInteger(.Hashtable, KEY_UNIT, GetHandleId(u)))
endmethod
static method operator []= takes unit u, thistype value returns nothing
call SaveInteger(.Hashtable, KEY_UNIT, GetHandleId(u), value)
endmethod
method getButton takes integer index returns UIButton
return this.buttons[(this.pid * MAX_SLOTS) + index]
endmethod
method setButton takes integer index, UIButton value returns nothing
set this.buttons[(this.pid * MAX_SLOTS) + index] = value
endmethod
method getItem takes integer index returns InvItem
return LoadInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEMS) + index)
endmethod
method itemsOfType takes integer id returns integer
return this.countTable[id]
endmethod
static method addLeftClickHook takes code func returns nothing
call TriggerAddAction(ExecL, func)
endmethod
static method addRightClickHook takes code func returns nothing
call TriggerAddAction(ExecR, func)
endmethod
method getItemId takes integer index returns integer
return LoadInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEM_ID) + index)
endmethod
method setItem takes integer index, InvItem itm returns nothing
local UIButton btn
local integer slot = index
local integer id = itm.id
local integer icon = GetItemIcon(id)
local InvItem oldItem
if (this.currentPage > 0) then
set slot = index - (.MAX_SLOTS*this.currentPage)
endif
set btn = this.getButton(slot)
set oldItem = this.getItem(index)
call SaveInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEMS) + index, itm)
if (itm.tempCustomId > 0) then
call SaveInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEM_ID) + index, itm.tempCustomId)
endif
set itm.tempCustomId = 0
if (id == 0 and HaveSavedInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEMS) + index)) then
set id = oldItem.id
set this.itemCount = this.itemCount - 1
set this.countTable[id] = this.countTable[id] - 1
call RemoveSavedInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEMS) + index)
// remove ability
if (oldItem.ability > 0 and itemsOfType(id) <= 0) then
call UnitRemoveAbility(this.owner, oldItem.ability)
endif
elseif (id > 0) then
set this.itemCount = this.itemCount + 1
set this.countTable[id] = this.countTable[id] + 1
// add ability
if (itm.ability > 0 and itemsOfType(id) == 1) then
call UnitAddAbility(this.owner, itm.ability)
endif
endif
call btn.setTexture(.localInt(this.pid, icon, ICON_TRANSPARENT))
call btn.showPlayer(this.user.handle, this.displayed, this.camera)
endmethod
method findFreeSlot takes integer page returns integer //O(n)
local integer i = 0
loop
exitwhen i == MAX_SLOTS
if (this.getItem((MAX_SLOTS*page)+i) == 0) then
return i
endif
set i = i + 1
endloop
return -1
endmethod
method createButtons takes nothing returns nothing
local integer row = 0
local integer col = 0
local UIButton btn = 0
local integer i = 0
if (btnPageUp[this.pid] == 0) then
set btnPageUp[this.pid] = UIButton.create(X - 0.15, (Y + SLOT_OFFSET_Y) - (col * (SLOT_HEIGHT*SLOT_SPACING)), .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, thistype.localInt(this.pid, ICON_PAGEUP, ICON_TRANSPARENT))
set btnPageDown[this.pid] = UIButton.create(X - 0.15, (Y + SLOT_OFFSET_Y) - ((.BUTTON_COLS-1) * (SLOT_HEIGHT*SLOT_SPACING)), .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, thistype.localInt(this.pid, ICON_PAGEDOWN, ICON_TRANSPARENT))
set btnSell[this.pid] = UIButton.create(X - 0.15, (Y + SLOT_OFFSET_Y) - .300, .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, thistype.localInt(this.pid, ICON_SELL, ICON_TRANSPARENT))
set btnDrop[this.pid] = UIButton.create(X - 0.15, (Y + SLOT_OFFSET_Y) - .150, .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, thistype.localInt(this.pid, ICON_DROP, ICON_TRANSPARENT))
set btnPageUp[this.pid].onLeftClick = InvFuncLClickSlot
set btnPageDown[this.pid].onLeftClick = InvFuncLClickSlot
set btnSell[this.pid].onLeftClick = InvFuncLClickSlot
set btnDrop[this.pid].onLeftClick = InvFuncLClickSlot
set btnPageUp[this.pid].selectUnit = this.owner
set btnPageDown[this.pid].selectUnit = this.owner
set btnSell[this.pid].selectUnit = this.owner
set btnDrop[this.pid].selectUnit = this.owner
set selector[this.pid] = UIPicture.createEx(X - 0.15, (Y + SLOT_OFFSET_Y) - .250, 0, .55, 'e000', 1, 1, 0)
set selector[this.pid].animIndex = 56
call selector[this.user.id].show(false, this.camera)
call AddSpecialEffectTarget("UI\\TRSHerolevel.mdx", selector[this.pid].picture, "origin")
endif
loop
exitwhen col == BUTTON_COLS
set row = 0
loop
exitwhen row == BUTTON_ROWS
if (this.getButton(i) == 0) then
set btn = UIButton.create( (X + SLOT_OFFSET_X) + (row * (SLOT_WIDTH*SLOT_SPACING)), (Y + SLOT_OFFSET_Y) - (col * (SLOT_HEIGHT*SLOT_SPACING)), .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, GetItemIcon(this.getItem(i).id))
set btn.customValue = i
set btn.selectUnit = this.owner
set btn.onLeftClick = InvFuncLClickSlot
set btn.onRightClick = InvFuncRClickSlot
call this.setButton(i, btn)
//call SetUnitVertexColor(.background[this.pid].picture, 255, 255, 255, thistype.localInt(this.pid, 255, 0))
endif
set i = i + 1
set row = row + 1
endloop
set col = col + 1
endloop
endmethod
implement InvPlugins
static method create takes unit owner returns thistype
local thistype this = thistype.allocate()
local boolean showLocally
local integer raceId
local integer i = 0
set this.itemCount = 0
set this.owner = owner
set this.freeSlot = 0
set this.pid = GetPlayerId(GetOwningPlayer(this.owner))
set this.user = User(this.pid)
set this.countTable=Table.create()
set raceId = GetHandleId(GetPlayerRace(User(this.pid).handle))-1
set showLocally = (User.Local == User(this.pid).handle)
call this.createButtons()
static if (thistype.createTooltip.exists) then
call thistype.createTooltip(this, raceId)
endif
if (this.pageNum[this.pid] == 0) then
set this.pageNum[this.pid] = UIText.createEx(this.user.toPlayer(), X + 0.300, Y + .10, 1)
endif
set Inventory[this.owner] = this
if (.background[this.pid] == 0) then
set .background[this.pid] = UIPicture.createEx(X, Y, 2, SCALE, BACKGROUND, 70., 60, .localInt(this.pid, RACE_BORDERS_START + raceId, ICON_TRANSPARENT))
endif
set thistype(0).next.prev = this
set this.next = thistype(0).next
set thistype(0).next = this
set this.prev = 0
return this
endmethod
method addItem takes InvItem itm returns boolean
local integer slot
if (itm <= 0 or itm.id <= 0) then
return false
endif
set slot = findFreeSlot(this.currentPage)
if (slot == -1) then
call err(User(this.pid).handle, "There are no free slots on this inventory page.")
return false
endif
if (itm.icon == ICON_EMPTY) then
return false
endif
call this.setItem((.MAX_SLOTS * this.currentPage) + slot, itm)
return true
endmethod
method destroy takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
call this.deallocate()
endmethod
private static method onDisplay takes nothing returns nothing
local thistype inv
local real x
local real y
local real z
local User user = User(User.LocalId)
set inv = Inventory.PlayerCurrent[user.id]
if (inv == 0 or not inv.displayed) then
return
endif
set x = GetUnitX(inv.owner)
set y = GetUnitY(inv.owner)
set z = GetTerrainZ(x, y) + GetUnitDefaultFlyHeight(inv.owner)
call inv.camera.setPosition(x, y, z)
if inv.camera.applyCameraForPlayer(user.handle, false) then
call Interface.updateAll(true, true, true)
endif
endmethod
method show takes boolean flag, Camera cam returns nothing
local integer i
local thistype equip
local real timeout
if (this.currentPage >= .MAX_PAGES) then
set this.currentPage = 0
elseif (this.currentPage < 0) then
set this.currentPage = .MAX_PAGES - 1
endif
if (flag) then
if (not this.displayed) then
set .DisplayCount = .DisplayCount + 1
endif
if (PlayerCurrent[this.pid] > 0 and PlayerCurrent[this.pid] != this) then
call PlayerCurrent[this.pid].show(false, cam)
endif
set PlayerCurrent[this.pid] = this
call PauseTimer(.UpdateTimer)
call TimerStart(.UpdateTimer, UI_REFRESH_RATE, true, function thistype.onDisplay)
else
set .DisplayCount = .DisplayCount - 1
if (DisplayCount == 0) then
call PauseTimer(.UpdateTimer)
else
call TimerStart(.UpdateTimer, UI_REFRESH_RATE, true, function thistype.onDisplay)
endif
if (User.LocalId == this.pid) then
call ResetToGameCamera(0)
endif
set PlayerCurrent[this.pid] = 0
endif
set this.displayed = flag
set this.camera = cam
call this.btnPageUp[this.pid].show(flag, cam)
call this.btnSell[this.pid].show(flag, cam)
call this.btnDrop[this.pid].show(flag, cam)
//call this.selector[this.pid].show(flag, cam)
call this.btnPageDown[this.pid].show(flag, cam)
call this.background[this.pid].show(flag, cam)
static if thistype.showTooltip.exists then
if (not flag) then
call thistype.showTooltip(this, flag)
endif
endif
call SetTextTagText(this.pageNum[this.pid].text, I2S(this.currentPage + 1) + "/" + I2S(.MAX_PAGES), 10 * 0.0023)
call this.pageNum[this.pid].show(flag, cam)
set i = 0
loop
exitwhen i == .MAX_SLOTS
call this.getButton(i).setTexture(thistype.localInt(this.pid, GetItemIcon(this.getItem((.MAX_SLOTS*this.currentPage) + i).id), ICON_TRANSPARENT))
call this.getButton(i).showPlayer(this.user.handle, flag, this.camera)
call this.getButton(i).showPlayer(this.user.handle, flag, this.camera)
set i = i + 1
endloop
endmethod
/*private static method onInit takes nothing returns nothing
endmethod*/
endstruct
public function GracePeriod takes nothing returns nothing
local timer t = GetExpiredTimer()
local UIButton but = UIButton(GetTimerData(t))
set InvButtonDisabled[but] = false
call ReleaseTimer(t)
endfunction
function DisableButtonIfUsingTimer takes UIButton but, real time returns nothing
static if (Inventory.USE_LEFT_CLICK_TIMER) then
call TimerStart(NewTimerEx(but), time, false, function GracePeriod)
set InvButtonDisabled[but] = true
endif
endfunction
private function LClickItemSlot takes nothing returns boolean
local UIButton but = GetTriggerButton()
local player p = GetClickingPlayer()
local integer pid = GetPlayerId(p)
local Inventory inv = Inventory.PlayerCurrent[pid]
local integer slot = but.customValue
local integer itemId = 0
local integer icon = 0
local integer last = InvPlayerLastSlot[inv.pid] - 1
local integer lastItemId
local InvItem lastItem
local UIButton lastButton = InvPlayerLastButton[inv.pid]
if (User.Local == p) then
call SelectUnit(but.picture, false)
call SelectUnit(inv.owner, true)
endif
if (InvButtonDisabled[but]) then
return false
endif
// sell item
if (but == Inventory.btnSell[inv.pid]) then
set last = InvPlayerLastSlot[inv.pid] - 1
if (last < 0) then
return false
endif
if (p != User(inv.pid).handle) then
return false
endif
// run onSell event
set InvEventPlayer = User(inv.pid).handle
set InvEventItem = inv.getItem((Inventory.MAX_SLOTS*inv.currentPage)+last)
set InvEventSlot = (Inventory.MAX_SLOTS*inv.currentPage)+last
if (Inventory.onSell != null and TriggerEvaluate(Inventory.onSell)) then
call TriggerExecute(Inventory.onSell)
endif
call DisableButtonIfUsingTimer(but, 2)
return false
elseif (but == Inventory.btnDrop[inv.pid]) then
set last = InvPlayerLastSlot[inv.pid] - 1
if (last < 0) then
return false
endif
if (p != User(inv.pid).handle) then
return false
endif
// run onSell event
set InvEventPlayer = User(inv.pid).handle
set InvEventItem = inv.getItem((Inventory.MAX_SLOTS*inv.currentPage)+last)
set InvEventSlot = (Inventory.MAX_SLOTS*inv.currentPage)+last
if (Inventory.onDrop != null and TriggerEvaluate(Inventory.onDrop)) then
call TriggerExecute(Inventory.onDrop)
return false
endif
call DisableButtonIfUsingTimer(but, 2)
elseif (but == Inventory.btnPageDown[inv.pid]) then
if (p != User(inv.pid).handle) then
return false
endif
set inv.currentPage = inv.currentPage + 1
call inv.show(true, inv.camera)
call DisableButtonIfUsingTimer(but, .6)
return false
elseif (but == Inventory.btnPageUp[inv.pid]) then
if (p != User(inv.pid).handle) then
return false
endif
set inv.currentPage = inv.currentPage - 1
call inv.show(true, inv.camera)
call DisableButtonIfUsingTimer(but, .6)
return false
else
call Inventory.selector[inv.pid].show(true, inv.camera)
call Inventory.selector[pid].setPosition(but.centerx - 0.004, but.centery + 0.0262)
call Inventory.selector[pid].showPlayer(Player(pid), true, inv.camera)
endif
if (p != User(inv.pid).handle) then
return false
endif
if (last + 1 != slot) then
call inv.showLines(inv.pid, false, inv.camera)
endif
call TriggerExecute(Inventory.ExecL)
// get item in slot
set itemId = inv.getItem((Inventory.MAX_SLOTS*inv.currentPage)+slot).id
// check for switch
if (last+1 != 0) then
call SetUnitVertexColor(lastButton.picture, 255, 255, 255, 255)
call SetUnitVertexColor(but.picture, 255, 255, 255, 255)
set InvPlayerLastSlot[inv.pid] = 0
set InvPlayerLastButton[inv.pid] = but
if (itemId == 0) then
set last = (Inventory.MAX_SLOTS * inv.currentPage) + last
set lastItem = inv.getItem(last)
set lastItemId = lastItem.id
set lastItem.tempCustomId = inv.getItemId(last)
call inv.setItem((Inventory.MAX_SLOTS*inv.currentPage)+slot, lastItem)
call inv.setItem(last, 0)
call DisableButtonIfUsingTimer(but, 2)
elseif (inv.getItem((Inventory.MAX_SLOTS * inv.currentPage) + last).isSocket) then
set last = (Inventory.MAX_SLOTS * inv.currentPage) + last
set lastItem = inv.getItem(last)
set lastItem.tempCustomId = inv.getItemId(last)
set slot = (Inventory.MAX_SLOTS*inv.currentPage)+slot
if (Equipment.onSocket != null) then
if (lastItem.sockets <= lastItem.maxSockets) then
set SocketInv = inv
set SocketLast = last
set SocketSlot = slot
if (not inv.getItem(slot).isSocket and Equipment.onSocket != null and TriggerEvaluate(Equipment.onSocket)) then
call TriggerExecute(Equipment.onSocket)
return false
endif
endif
else
set inv.getItem(slot).tempCustomId = inv.getItemId(slot)
if (inv.getItem(slot).addSocket(lastItem)) then
call inv.setItem(last, 0)
endif
endif
endif
else//if (itemId != 0) then
set InvPlayerLastSlot[inv.pid] = 0
set InvPlayerLastButton[inv.pid] = but
endif
if (itemId == 0 or InvPlayerLastSlot[inv.pid] != 0) then
else
set InvPlayerLastSlot[inv.pid] = slot + 1
endif
if (but != lastButton) then
call SetUnitVertexColor(lastButton.picture, 255, 255, 255, 255)
call SetUnitVertexColor(but.picture, 175, 175, 175, 255)
endif
if (itemId > 0) then
call inv.setTooltipTitle(GetObjectName(itemId))
static if (InvItem.buildDescription.exists) then
set GetInvItem(itemId).tempCustomId = inv.getItemId((Inventory.MAX_SLOTS*inv.currentPage)+slot)
call GetInvItem(itemId).buildDescription(inv.owner)
else
call inv.setTooltipInfo(GetItemDescription(itemId))
endif
call inv.setTooltipCost("|cffffcc00" + I2S(GetInvItem(itemId).cost) + "|r")
call inv.setTooltipIcon(inv.localInt(inv.pid, GetItemIcon(itemId), Inventory.ICON_TRANSPARENT))
endif
set Inventory.TOOLTIP_X = but.minx - 0.4
set Inventory.TOOLTIP_Y = but.maxy + 0.4
call inv.showTooltip(inv, itemId > 0)
return false
endfunction
private function RClickItemSlot takes nothing returns boolean
local UIButton but = GetTriggerButton()
local player p = GetClickingPlayer()
local integer pid = GetPlayerId(p)
local Inventory inv = Inventory.PlayerCurrent[pid]
local integer slot = but.customValue
local integer itemId
if (p != User(inv.pid).handle) then
return false
endif
call TriggerExecute(Inventory.ExecR)
set slot = (Inventory.MAX_SLOTS * inv.currentPage) + slot
set itemId = inv.getItem(slot).id
if (itemId == 0) then
return false
endif
call inv.showTooltip(inv, false)
/*call SetItemUserData(CreateItem(itemId, GetUnitX(inv.owner), GetUnitY(inv.owner)), inv.getItemId(slot))
call inv.setItem(slot, 0)*/
return false
endfunction
private function Init takes nothing returns nothing
set InvFuncLClickSlot = Filter(function LClickItemSlot)
set InvFuncRClickSlot = Filter(function RClickItemSlot)
endfunction
endlibrary
library InventoryItem uses Table, optional InventoryBonus
globals
public constant integer SEED_START = 0
public constant integer SEED_END = 999
endglobals
struct InvItem
readonly static Table Table
integer id
integer icon
integer cost
integer ability
string info
integer tempCustomId
implement InvItemPlugins
static if not thistype.buildDescriptionLines.exists then
method buildDescriptionLines takes integer pid, integer startLine returns integer
return buildDescriptionLines(GetPlayerId(GetOwningPlayer(forUnit)), 0)
endmethod
endif
static if not thistype.buildDescription.exists then
method buildDescription takes unit forUnit returns string
return buildDescriptionLines(GetPlayerId(GetOwningPlayer(forUnit)), 0)
endmethod
endif
private static method onInit takes nothing returns nothing
set InvItem.Table = Table.create()
endmethod
endstruct
function GetInvItem takes integer id returns InvItem
return InvItem(InvItem.Table[id])
endfunction
function GetItemInfo takes integer id returns string
return InvItem(InvItem.Table[id]).info
endfunction
function GetItemDescription takes integer id returns string
return InvItem(InvItem.Table[id]).info
endfunction
function GetItemIcon takes integer id returns integer
local integer icon = InvItem(InvItem.Table[id]).icon
if (icon == 0) then
return Inventory.ICON_EMPTY
endif
return icon
endfunction
function CreateInvItem takes integer id, integer icon, integer cost, integer abil, string info returns InvItem
local InvItem itm = InvItem.create()
set itm.id = id
set itm.icon = icon
set itm.cost = cost
set itm.info = info
set itm.ability = abil
set InvItem.Table[itm.id] = itm
return itm
endfunction
struct InvCustomItem extends array
static integer Counter = 1
static Table SeedTable
static method create takes nothing returns thistype
local thistype this = Counter
set SeedTable[this] = GetRandomInt(SEED_START, SEED_END)
set Counter = Counter + 1
return this
endmethod
method seed takes nothing returns integer
return SeedTable[this]
endmethod
static method onInit takes nothing returns nothing
set SeedTable = SeedTable.create()
endmethod
endstruct
endlibrary
library InventoryPlugins
// add plugins here
module InvPlugins
implement InvTooltip
endmodule
module InvItemPlugins
implement InvItemTooltip
implement InvItemEquipment
implement InvItemSocket
endmodule
endlibrary
library InventoryTooltip
module InvTooltip
//
// configuration
//
// about the length of tooltip line.
// line lengths are resolution and char-width
// dependent for textags, so a new text tag
// is created per-line.
static constant integer TOOLTIP_LINE_LENGTH = 32
static constant integer TOOLTIP_LINES_MAX = 16
static constant integer TOOLTIP_TEXTURE = 'B014'
static constant real TOOLTIP_LINE_SPACING = .05
static constant real TOOLTIP_FONT_SIZE = 7.5
static constant integer ICON_TOOLTIP_GOLD = 'B00H'
static constant integer TOOLTIP_BACKGROUND = 'ttip'
//
// end config
//
readonly static UIText array ItemInfoTags[15][.TOOLTIP_LINES_MAX]
static integer array RightSideStart
static UIText array infoBoxTitle
static UIText array infoBoxCost
static UIText array infoBoxDesc
static UIPicture array infoBox
static UIPicture array infoIcon
static UIPicture array costIcon
method setTooltipTitle takes string title returns nothing
call SetTextTagText(thistype.infoBoxTitle[this.pid].text, title, 10 * 0.0023)
endmethod
method setTooltipInfo takes string desc returns nothing
call SetTextTagText(thistype.infoBoxTitle[this.pid].text, desc, TOOLTIP_FONT_SIZE * 0.0023)
endmethod
method setTooltipCost takes string value returns nothing
call SetTextTagText(thistype.infoBoxCost[this.pid].text, value, 8 * 0.0023)
endmethod
method setTooltipIcon takes integer id returns nothing
call this.infoIcon[this.pid].setTexture(id)
endmethod
static method setTooltipLine takes integer pid, integer line, string str returns nothing
call SetTextTagText(Inventory.ItemInfoTags[pid][line].text, str, Inventory.TOOLTIP_FONT_SIZE * 0.0023)
endmethod
static method setTooltipSlot takes integer pid, integer line, string str returns nothing
call SetTextTagText(Inventory.ItemInfoTags[pid][TOOLTIP_LINES_MAX-1].text, str, Inventory.TOOLTIP_FONT_SIZE * 0.0023)
endmethod
method showLines takes integer pid, boolean flag, Camera cam returns nothing
local integer i = 0
loop
exitwhen i == TOOLTIP_LINES_MAX
if (not flag) then
call SetTextTagText(Inventory.ItemInfoTags[pid][i].text, "", Inventory.TOOLTIP_FONT_SIZE * 0.0023)
endif
call Inventory.ItemInfoTags[pid][i].show(flag, cam)
set i = i + 1
endloop
endmethod
static method clearLines takes integer pid returns nothing
local integer i = 0
loop
exitwhen i == TOOLTIP_LINES_MAX
call SetTextTagText(Inventory.ItemInfoTags[pid][i].text, "", Inventory.TOOLTIP_FONT_SIZE * 0.0023)
set i = i + 1
endloop
endmethod
static method updateLines takes integer pid returns nothing
local integer i = 0
loop
exitwhen i == TOOLTIP_LINES_MAX
call Inventory.ItemInfoTags[pid][i].update()
set i = i + 1
endloop
endmethod
static method createTooltip takes thistype this, integer raceId returns nothing
local boolean showLocally = (User.Local == User(this.pid).handle)
local integer i = 0
//if (showLocally) then
if (thistype.infoBoxTitle[this.pid] == null) then
set thistype.infoBoxTitle[this.pid] = UIText.createEx(this.user.toPlayer(), TOOLTIP_X - 0.1, TOOLTIP_Y + .22, 1)
set thistype.infoBoxDesc[this.pid] = UIText.createEx(this.user.toPlayer(), TOOLTIP_X - 0.1, TOOLTIP_Y + .25, 1)
endif
if (thistype.infoBoxCost[this.pid] == null) then
set thistype.infoBoxCost[this.pid] = UIText.createEx(this.user.toPlayer(), TOOLTIP_X - (0.065 - 0.), TOOLTIP_Y + .15, 1)
endif
// description lines
if (ItemInfoTags[this.pid][0] == 0) then
loop
exitwhen i == TOOLTIP_LINES_MAX
if (i == TOOLTIP_LINES_MAX-1) then
set ItemInfoTags[this.pid][i] = UIText.createEx(this.user.toPlayer(), TOOLTIP_X + 0.05, TOOLTIP_Y + .16, 1)
else
set ItemInfoTags[this.pid][i] = UIText.createEx(this.user.toPlayer(), TOOLTIP_X - 0.18, TOOLTIP_Y - (-.05 + (TOOLTIP_LINE_SPACING*i)), 1)
endif
set i = i + 1
endloop
endif
//endif
if (.infoIcon[this.pid] == 0) then
set .infoIcon[this.pid] = UIPicture.create(TOOLTIP_X - 0.18, TOOLTIP_Y + .28, 0.07, 0.07 * SCREEN_ASPECT_RATIO, 0.1, 0)
set .costIcon[this.pid] = UIPicture.create(TOOLTIP_X - 0.1, TOOLTIP_Y + .215, 0.03, 0.03 * SCREEN_ASPECT_RATIO, 0, 0)
set .infoBox[this.pid] = UIPicture.createEx(TOOLTIP_X, TOOLTIP_Y, 0.20, 0.10, TOOLTIP_BACKGROUND, 120., 60., .localInt(this.pid, TOOLTIP_TEXTURE, ICON_TRANSPARENT))
endif
call .costIcon[this.pid].setTexture(Inventory.localInt(this.pid, ICON_TOOLTIP_GOLD, ICON_TRANSPARENT))
call .infoIcon[this.pid].setTexture(Inventory.localInt(this.pid, ICON_TOOLTIP_GOLD, ICON_TRANSPARENT))
call SetUnitVertexColor(.infoBox[this.pid].picture, 255, 255, 255, Inventory.localInt(this.pid, 255, 0))
endmethod
static method showTooltip takes thistype this, boolean flag returns nothing
local integer i = 0
loop
exitwhen i == TOOLTIP_LINES_MAX
if (i < Inventory.RightSideStart[this.pid]) then
call ItemInfoTags[this.pid][i].setPosition(TOOLTIP_X - 0.15, (TOOLTIP_Y - 0.05) - ((TOOLTIP_LINE_SPACING*i)))
else
call ItemInfoTags[this.pid][i].setPosition(TOOLTIP_X + 0.175, (TOOLTIP_Y - 0.05) - ((TOOLTIP_LINE_SPACING*(i-Inventory.RightSideStart[this.pid]))))
endif
set i = i + 1
endloop
call Inventory.ItemInfoTags[pid][TOOLTIP_LINES_MAX-1].setPosition(TOOLTIP_X + 0.175, TOOLTIP_Y + 0.05)
call .infoBox[this.pid].setPosition(TOOLTIP_X, TOOLTIP_Y)
call .infoBoxTitle[this.pid].setPosition(TOOLTIP_X - 0.15, TOOLTIP_Y + 0.1)
call .infoBoxCost[this.pid].setPosition(TOOLTIP_X - 0.11, TOOLTIP_Y + 0.022)
call .costIcon[this.pid].setPosition(TOOLTIP_X - 0.15 , TOOLTIP_Y + 0.1)
call .infoBox[this.pid].show(flag, this.camera)
//call .infoIcon[this.pid].show(flag, this.camera)
call .infoBoxTitle[this.pid].show(flag, this.camera)
call this.showLines(this.pid, flag, this.camera)
call .infoBoxCost[this.pid].show(flag, this.camera)
call .costIcon[this.pid].show(flag, this.camera)
endmethod
static method updateTooltip takes thistype this returns nothing
local integer i = 0
call .infoBox[this.pid].update()
call .infoIcon[this.pid].update()
call .infoBoxTitle[this.pid].update()
call this.updateLines(this.pid)
call .infoBoxCost[this.pid].update()
call .costIcon[this.pid].update()
endmethod
endmodule
module InvItemTooltip
//
// Tooltip building
//
private static method getStringLine takes string str, integer line returns string
local integer i = 0
local integer l = StringLength(str)
local string o = ""
local integer c = 0
loop
exitwhen i > l
if (SubString(str, i, i + 1) == "\n") then
set c = c + 1
if (c == line) then
return o
else
set o = ""
endif
endif
set o = o + SubString(str, i, i + 1)
set i = i + 1
endloop
return ""
endmethod
method lTrim takes string s returns string
local integer i = 0
loop
exitwhen SubString(s, i, i + 1) != " "
set s = SubString(s, 1, 9999)
set i = i + 1
endloop
return s
endmethod
method buildDescriptionLines takes integer pid, integer startLine returns integer
local integer i = 0
local integer l = StringLength(this.info)
local integer c = 0
local integer j = 0
local integer safeSpot = 0
local string line = ""
local string safe = ""
local string char
if (StringLength(.lTrim(this.info)) <= 0) then
return startLine - 1
endif
loop
exitwhen i > l
set char = SubString(this.info, i, i + 1)
if (char == "\n" or c >= Inventory.TOOLTIP_LINE_LENGTH) then
if (char != " " and char != "\n") then
set line = safe
set i = safeSpot
endif
call Inventory.setTooltipLine(pid, startLine + j, .lTrim(line))
set line = ""
if (char != "\n") then
set i = i - 1
endif
set char = ""
set c = 0
set j = j + 1
endif
if (char == " ") then
set safe = line
set safeSpot = i
endif
set line = line + char
set c = c + 1
set i = i + 1
endloop
if (j == 0) then
call Inventory.setTooltipLine(pid, startLine, .lTrim(line))
elseif (line != "") then
call Inventory.setTooltipLine(pid, startLine + j, .lTrim(line))
endif
return (startLine + j) + 1
endmethod
endmodule
endlibrary
library InventoryBonuses requires optional BonusMod, optional UnitMaxState
endlibrary
globals
filterfunc InvFuncLClickSlot = null
filterfunc InvFuncRClickSlot = null
integer array InvPlayerLastSlot
UIButton array InvPlayerLastButton
boolean array InvButtonDisabled
player InvEventPlayer = null
InvItem InvEventItem = 0
integer InvEventSlot = 0
constant integer INVENTORY_KEY_START = 10000 // above 8191
endglobals
library EquipmentCore initializer Init requires InventoryCore, EquipmentItem
globals
public filterfunc FuncLClickSlot = null
public filterfunc FuncRClickSlot = null
public integer array PlayerLastSlot
endglobals
public /*constant*/ function HERO_WINDOW_NAME takes unit u returns string
return User[GetOwningPlayer(u)].nameColored
// return GetHeroProperName(u)
endfunction
struct Equipment
//
// configuration
//
static constant real X = 0.425
static constant real Y = .93
static constant real WINDOW_SIZE = 0.28
static constant real SLOT_OFFSET_Y = 0.065
static constant real SLOT_OFFSET_ROWRIGHT_X = 0.460
static constant real SLOT_OFFSET_ROWLEFT_X = 0.105
static constant real HERO_NAME_Y = 0.9
static constant real CHARMODEL_OFFSET_X = X + 0.32
static constant real CHARMODEL_OFFSET_Y = 0.200
static constant integer MAX_SLOTS = 14
static constant real SLOT_WIDTH = 0.090
static constant real SLOT_HEIGHT = 0.090 * SCREEN_ASPECT_RATIO
static constant integer MODEL_DUMMY = 'e000' // for character model
static constant integer WINDOW_DUMMY = 'ewin'
//
// end config
//
readonly static boolean Initialized = false
readonly static integer DisplayCount = 0
readonly static hashtable Hashtable
readonly static timer UpdateTimer
readonly static unit array PlayerCurrentUnit
readonly static thistype array UnitsIndex
static UIButton array buttons[.MAX_SLOTS]
static UIPicture array slotButton[.MAX_SLOTS]
InvItem array item[.MAX_SLOTS]
integer array itemId[.MAX_SLOTS]
static UIPicture array pictures[.MAX_SLOTS]
static UIPicture array selector
static UIText array title
static trigger onSocket
UIPicture charModel
Camera camera
unit unit
player player
User user
readonly boolean displayed
readonly thistype next
readonly thistype prev
static method operator [] takes unit u returns thistype
return .UnitsIndex[GetUnitUserData(u)]
endmethod
method getButton takes integer index returns UIButton
return this.buttons[(this.user.id * .MAX_SLOTS) + index]
endmethod
method setButton takes integer index, UIButton value returns nothing
set this.buttons[(this.user.id * .MAX_SLOTS) + index] = value
endmethod
static method create takes unit u returns thistype
local thistype this = thistype.allocate()
local real x1 = X + SLOT_OFFSET_ROWLEFT_X
local real x2 = X + SLOT_OFFSET_ROWRIGHT_X
local real y1 = Y - SLOT_OFFSET_Y
local integer i = 0
set this.unit = u
set this.player = GetOwningPlayer(u)
set this.user = User[this.player]
set .UnitsIndex[GetUnitUserData(u)] = this
//
// GEAR SLOTS
//
if (this.getButton(0) == 0) then
set selector[this.user.id] = UIPicture.createEx(X - 0.15, (Y + SLOT_OFFSET_Y) - .250, 0, .70, 'e000', 1, 1, 0)
set selector[this.user.id].animIndex = 56
call selector[this.user.id].show(false, this.camera)
call AddSpecialEffectTarget("UI\\TRSHerolevel.mdx", selector[this.user.id].picture, "origin")
// middle
call this.setButton(12, UIButton.create(x1 + (.SLOT_WIDTH*1.5), y1 - (.SLOT_HEIGHT*5), .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'B005'))
call this.setButton(13, UIButton.create(x2 - (.SLOT_WIDTH*1.5), y1 - (.SLOT_HEIGHT*5), .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'B00B'))
// left side
call this.setButton(0, UIButton.create(x1, y1, .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'B003'))
call this.setButton(1, UIButton.create(x1, y1 - (.SLOT_HEIGHT*1), .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'B004'))
call this.setButton(2, UIButton.create(x1, y1 - (.SLOT_HEIGHT*2), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'B006'))
call this.setButton(3, UIButton.create(x1, y1 - (.SLOT_HEIGHT*3), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'B007'))
call this.setButton(4, UIButton.create(x1, y1 - (.SLOT_HEIGHT*4), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'B008'))
call this.setButton(5, UIButton.create(x1, y1 - (.SLOT_HEIGHT*5), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'D05W'))
// right side
call this.setButton(6, UIButton.create(x2, y1, .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'B009'))
call this.setButton(7, UIButton.create(x2, y1 - (.SLOT_HEIGHT*1), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'B00A'))
call this.setButton(8, UIButton.create(x2, y1 - (.SLOT_HEIGHT*2), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'B00C'))
call this.setButton(9, UIButton.create(x2, y1 - (.SLOT_HEIGHT*3), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'B00D'))
call this.setButton(10, UIButton.create(x2, y1 - (.SLOT_HEIGHT*4), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'B00E'))
call this.setButton(11, UIButton.create(x2, y1 - (.SLOT_HEIGHT*5), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'B00F'))
loop
exitwhen i == 14
set this.getButton(i).customValue = i
set this.getButton(i).selectUnit = this.unit
set this.getButton(i).onLeftClick = FuncLClickSlot
set this.getButton(i).onRightClick = FuncRClickSlot
set this.slotButton[(this.user.id * MAX_SLOTS) + i] = UIPicture.create(this.getButton(i).minx + (.SLOT_WIDTH/6.3), this.getButton(i).maxy - 0.022, .SLOT_WIDTH * .7, .SLOT_HEIGHT * .7, 9, Inventory.ICON_EMPTY)
set this.slotButton[(this.user.id * MAX_SLOTS) + i].customValue = i
set i = i + 1
endloop
set .pictures[(this.user.id * MAX_SLOTS) + 0] = UIPicture.createEx(X, Y, 11, WINDOW_SIZE, .WINDOW_DUMMY, 140., 130., Inventory.localInt(this.user.id, Inventory.RACE_BORDERS_START+(GetHandleId(GetPlayerRace(this.player))-1), Inventory.ICON_TRANSPARENT))
endif
// unit model
set i = EquipGetHeroModel(GetUnitTypeId(this.unit))
set .charModel = UIPicture.createEx(CHARMODEL_OFFSET_X, .CHARMODEL_OFFSET_Y, 5, HeroModelData(i).scale, MODEL_DUMMY, 1, 1, 0)
set .charModel.animIndex = 140
call SetUnitColor(.charModel.picture, this.user.color)
call AddSpecialEffectTarget(HeroModelData(i).path, .charModel.picture, "origin")
set .title[this.user.id] = UIText.createEx(this.user.toPlayer(), X/1.4, 0.1, 1)
set thistype(0).next.prev = this
set this.next = thistype(0).next
set thistype(0).next = this
set this.prev = 0
return this
endmethod
method equip takes InvItem itm, integer equipSlot returns boolean
local integer slot = itm.slot - 1
if (slot < 0) then
return false
endif
if (this.item[equipSlot] != 0) then
return false
endif
if (itm.slotAlt - 1 > 0) then
if (equipSlot != slot and equipSlot != itm.slotAlt -1) then
call Inventory.err(this.player, "The item doesn't belong in that slot.")
return false
endif
else
if (equipSlot != slot) then
call Inventory.err(this.player, "The item doesn't belong in that slot.")
return false
endif
endif
if (itm.reqUnitType > 0 and GetUnitTypeId(this.unit) != itm.reqUnitType) then
call Inventory.err(this.player, "Your unit type cannot equip this item.")
return false
endif
if (itm.reqAbility > 0 and GetUnitAbilityLevel(this.unit, itm.reqAbility) < 0) then
call Inventory.err(this.player, "Failed requirements.")
return false
endif
if (GetHeroLevel(this.unit) < itm.reqLevel and GetUnitLevel(this.unit) < itm.reqLevel) then
call Inventory.err(this.player, "Your level is too low to equip that item.")
return false
endif
if (itm.equipCondition != null) then
call TriggerClearConditions(InvItem.equipEval)
call TriggerAddCondition(InvItem.equipEval, itm.equipCondition)
set InvItem.eventUnit = this.unit
if (not TriggerEvaluate(InvItem.equipEval)) then
return false
endif
endif
set slot = equipSlot
if (this.item[slot] != 0) then
return false
endif
if (itm.equipAbility > 0) then
call UnitAddAbility(this.unit, itm.equipAbility)
endif
set this.item[slot] = itm
set this.itemId[slot] = itm.tempCustomId
call this.slotButton[(this.user.id * .MAX_SLOTS) + slot].setTexture(Inventory.localInt(this.user.id, itm.icon, Inventory.ICON_TRANSPARENT))
call this.slotButton[(this.user.id * .MAX_SLOTS) + slot].show(true, this.camera)
set InvEventPlayer = this.player
set InvEventItem = itm
set InvEventSlot = equipSlot
if (InvItem.onEquip != null and TriggerEvaluate(InvItem.onEquip)) then
call TriggerExecute(InvItem.onEquip)
endif
// add bonuses
call itm.applyBonuses(this.unit)
set itm.tempCustomId = 0
return true
endmethod
method unequip takes InvItem itm, integer slot returns boolean
local InvItem i2
local integer i = 0
local integer cid = itm.tempCustomId
if (slot != itm.slot -1 and slot != itm.slotAlt - 1) then
return false
endif
if (itm.equipAbility > 0) then
call UnitRemoveAbility(this.unit, itm.equipAbility)
endif
// run unequip event
set InvEventPlayer = this.player
set InvEventItem = this.item[slot]
set InvEventSlot = slot
if (InvItem.onUnequip != null and TriggerEvaluate(InvItem.onUnequip)) then
call TriggerExecute(InvItem.onUnequip)
endif
set this.item[slot] = 0
call this.slotButton[(this.user.id * .MAX_SLOTS) + slot].setTexture(Inventory.ICON_EMPTY)
call this.slotButton[(this.user.id * .MAX_SLOTS) + slot].show(false, this.camera)
// remove bonuses
call itm.removeBonuses(this.unit)
return true
endmethod
method destroy takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
call this.charModel.destroy()
call this.deallocate()
endmethod
private static method onDisplay takes nothing returns nothing
local User user = User(User.LocalId)
local thistype equipment = Equipment[Equipment.PlayerCurrentUnit[user.id]]
local real x
local real y
local real z
if (equipment == 0 or not equipment.displayed or Inventory.PlayerCurrent[equipment.user.id] > 0 or User.Local != user.handle) then
return
endif
set x = GetUnitX(equipment.unit)
set y = GetUnitY(equipment.unit)
set z = GetTerrainZ(x, y) + GetUnitDefaultFlyHeight(equipment.unit)
call equipment.camera.setPosition(x, y, z)
if equipment.camera.applyCameraForPlayer(user.handle, false) then
call Interface.updateAll(true, true, true)
endif
endmethod
method show takes boolean flag, Camera cam returns nothing
local integer i = 0
local thistype equip = 0
local integer len
local real timeout
set this.displayed = flag
set this.camera = cam
if (flag) then
set .DisplayCount = .DisplayCount + 1
if (DisplayCount >= 1) then
call PauseTimer(.UpdateTimer)
call TimerStart(.UpdateTimer, Inventory.UI_REFRESH_RATE, true, function thistype.onDisplay)
endif
if (.PlayerCurrentUnit[this.user.id] != null) then
set equip = Equipment[.PlayerCurrentUnit[this.user.id]]
endif
if (.PlayerCurrentUnit[this.user.id] != null and .PlayerCurrentUnit[this.user.id] != this.unit and equip != this) then
call equip.show(false, this.camera)
endif
set .PlayerCurrentUnit[this.user.id] = this.unit
if (Inventory.PlayerCurrent[this.user.id] > 0 and Inventory.PlayerCurrent[this.user.id].owner != this.unit) then
call Inventory.PlayerCurrent[this.user.id].show(false, this.camera)
endif
else
set .DisplayCount = .DisplayCount - 1
set .PlayerCurrentUnit[this.user.id] = null
if (DisplayCount == 0) then
call PauseTimer(.UpdateTimer)
else
call PauseTimer(.UpdateTimer)
call TimerStart(.UpdateTimer, Inventory.UI_REFRESH_RATE, true, function thistype.onDisplay)
endif
if (User.Local == this.player) then
call ResetToGameCamera(0)
endif
endif
call this.charModel.showPlayer(this.user.toPlayer(), flag, this.camera)
//call this.selector[this.user.id].show(flag, cam)
call SetUnitColor(.charModel.picture, this.user.color)
// todo: make one line / recode
set len = StringLength(this.user.name)
call .title[this.user.id].setPosition(X + 0.10, HERO_NAME_Y)
call SetTextTagText(.title[this.user.id].text, HERO_WINDOW_NAME(this.unit), 8 * 0.0023)
call this.title[this.user.id].show(flag, this.camera)
set i = 0
loop
exitwhen i == thistype.MAX_SLOTS
if (.getButton(i) != 0) then
call .getButton(i).showPlayer(this.user.handle, flag, this.camera)
endif
if (this.pictures[(this.user.id * .MAX_SLOTS) + i] != 0) then
call .pictures[(this.user.id * .MAX_SLOTS) + i].showPlayer(this.user.handle, flag, this.camera)
endif
if (this.item[i] > 0) then
call slotButton[(this.user.id * .MAX_SLOTS) + i].showPlayer(this.user.handle, flag, this.camera)
endif
set i = i + 1
endloop
endmethod
private static method onInit takes nothing returns nothing
set thistype.Hashtable = InitHashtable()
set thistype.UpdateTimer = CreateTimer()
set thistype.Initialized = true
endmethod
endstruct
private function OnInventoryItemClick takes nothing returns nothing
local UIButton but = GetTriggerButton()
local player p = GetClickingPlayer()
local integer pid = GetPlayerId(p)
local Inventory inv = Inventory.PlayerCurrent[pid]
local integer slot = but.customValue
local integer itemId = 0
local integer last = InvPlayerLastSlot[inv.pid] - 1
local integer lastItemId
local UIButton lastButton = InvPlayerLastButton[inv.pid]
local Equipment gear = Equipment[Equipment.PlayerCurrentUnit[inv.pid]]
local integer gearSlot
local InvItem itm
call Equipment.selector[inv.pid].show(false, inv.camera)
if (gear <= 0) then
return
endif
if (PlayerLastSlot[inv.pid] > 0) then
set gearSlot = PlayerLastSlot[inv.pid] - 1
if (inv.getItem(slot) == 0) then
set gear.item[gearSlot].tempCustomId = gear.itemId[gearSlot]
call inv.setItem(slot, gear.item[gearSlot])
set gear.item[gearSlot].tempCustomId = gear.itemId[gearSlot]
call gear.unequip(gear.item[gearSlot], gearSlot)
endif
set PlayerLastSlot[inv.pid] = 0
else
set itm = inv.getItem(slot)
if (itm > 0 and not itm.isSocket) then
set but = gear.getButton(itm.slot - 1)
call Equipment.selector[inv.pid].show(true, inv.camera)
call Equipment.selector[inv.pid].setPosition(but.centerx - 0.007, but.centery + 0.0272)
call Equipment.selector[inv.pid].showPlayer(Player(inv.pid), true, inv.camera)
endif
endif
endfunction
private function OnInventoryItemRightClick takes nothing returns nothing
local UIButton but = GetTriggerButton()
local player p = GetClickingPlayer()
local integer pid = GetPlayerId(p)
local Inventory inv = Inventory.PlayerCurrent[pid]
local integer slot = but.customValue
local Equipment gear = Equipment[Equipment.PlayerCurrentUnit[inv.pid]]
local InvItem itm = inv.getItem(slot)
local integer gearSlot = itm.slot - 1
local integer unequippedSlot = 0
local InvItem unequipItem = 0
local integer unequipId = 0
if (gear <= 0 or itm <= 0) then
return
endif
set itm.tempCustomId = inv.getItemId(slot)
if (gear.item[gearSlot] > 0) then
set unequipId = gear.itemId[gearSlot]
set gear.item[gearSlot].tempCustomId = unequipId
call gear.unequip(gear.item[gearSlot], gearSlot)
set unequippedSlot = InvEventSlot
set unequipItem = InvEventItem
endif
set itm.tempCustomId = inv.getItemId(slot)
if (gear.equip(itm, itm.slot - 1)) then
call inv.setItem(slot, 0)
set unequipItem.tempCustomId = unequipId
call inv.addItem(unequipItem)
elseif (unequipItem > 0) then
call gear.equip(unequipItem, unequippedSlot)
endif
endfunction
private function RClickItemSlot takes nothing returns boolean
local UIButton but = GetTriggerButton()
local unit u = Equipment.PlayerCurrentUnit[GetPlayerId(GetClickingPlayer())]
local Equipment gear = Equipment[u]
local Inventory inv = Inventory[u]
local integer slot = but.customValue
local integer itemId
local InvItem itm
local integer cid
if (GetClickingPlayer() != User(inv.pid).handle or gear.item[slot] <= 0) then
return false
endif
// clear tooltip
call inv.showLines(inv.pid, false, inv.camera)
set itm = gear.item[slot]
set itemId = itm.id
set cid = gear.itemId[slot]
set gear.item[slot].tempCustomId = cid
if (gear.unequip(itm, slot)) then
set itm.tempCustomId = cid
if (not inv.addItem(itm)) then
set gear.item[slot].tempCustomId = cid
call gear.equip(itm, slot)
return false
endif
else
return false
endif
call Inventory.showTooltip(inv, false)
return false
endfunction
private function LClickItemSlot takes nothing returns boolean
local UIButton but = GetTriggerButton()
local player p = GetClickingPlayer()
local Equipment equip = Equipment[Equipment.PlayerCurrentUnit[GetPlayerId(p)]]
local Inventory inv = Inventory.PlayerCurrent[equip.user.id]
local integer slot = but.customValue
local integer last = InvPlayerLastSlot[inv.pid] - 1
local integer itemId = 0
local UIButton lastButton = InvPlayerLastButton[equip.user.id]
local InvItem itm
if (User.Local == p) then
call SelectUnit(but.picture, false)
call SelectUnit(equip.unit, true)
endif
if (p != equip.user.handle) then
return false
endif
if (InvButtonDisabled[but]) then
return false
endif
call inv.showLines(inv.pid, false, inv.camera)
set PlayerLastSlot[equip.user.id] = slot + 1
// equip item
if (inv > 0 and last+1 > 0) then
set last = (Inventory.MAX_SLOTS * inv.currentPage) + last
set itm = inv.getItem(last)
if (itm.slot - 1 == slot or itm.slotAlt - 1 == slot) then
set itm.tempCustomId = inv.getItemId(last)
if (equip.equip(itm, slot)) then
set PlayerLastSlot[inv.pid] = 0
call inv.setItem(last, 0)
call TimerStart(NewTimerEx(but), 2, false, function InventoryCore_GracePeriod)
set InvButtonDisabled[but] = true
endif
endif
endif
set InvPlayerLastButton[equip.user.id] = but
set InvPlayerLastSlot[equip.user.id] = 0
if (but != lastButton) then
call SetUnitVertexColor(lastButton.picture, 255, 255, 255, 255)
call SetUnitVertexColor(but.picture, 175, 175, 175, 255)
endif
set itemId = equip.item[slot].id
call Inventory.selector[inv.pid].show(false, inv.camera)
call Equipment.selector[inv.pid].setPosition(but.centerx - 0.007, but.centery + 0.0272)
call Equipment.selector[inv.pid].showPlayer(Player(inv.pid), true, inv.camera)
if (itemId > 0) then
set equip.item[slot].tempCustomId = equip.itemId[slot]
call inv.setTooltipTitle(GetObjectName(itemId))
call GetInvItem(itemId).buildDescription(inv.owner)
call inv.setTooltipCost("|cffffcc00" + I2S(GetInvItem(itemId).cost) + "|r")
call inv.setTooltipIcon(inv.localInt(inv.pid, GetItemIcon(itemId), Inventory.ICON_TRANSPARENT))
set equip.item[slot].tempCustomId = 0
endif
// display tooltip
set Inventory.TOOLTIP_X = but.minx - 0.4
set Inventory.TOOLTIP_Y = but.miny - 0.08
call Inventory.showTooltip(inv, itemId > 0)
return false
endfunction
private function Init takes nothing returns nothing
set FuncLClickSlot = Filter(function LClickItemSlot)
set FuncRClickSlot = Filter(function RClickItemSlot)
call Inventory.addLeftClickHook(function OnInventoryItemClick)
call Inventory.addRightClickHook(function OnInventoryItemRightClick)
endfunction
endlibrary
library EquipmentItem initializer Init requires BonusMod, InventoryCore
globals
public string array BonusString
endglobals
public function Init takes nothing returns nothing
set BonusString[BONUS_DAMAGE] = "Damage"
set BonusString[BONUS_ARMOR] = "Armor"
set BonusString[BONUS_STRENGTH] = "Strength"
set BonusString[BONUS_AGILITY] = "Agility"
set BonusString[BONUS_INTELLIGENCE] = "Intelligence"
set BonusString[BONUS_ATTACK_SPEED] = "Attack Speed"
set BonusString[BONUS_SIGHT_RANGE] = "Visibility"
set BonusString[BONUS_LIFE_REGEN] = "Life Regen"
set BonusString[BONUS_MANA_REGEN_PERCENT] = "% Mana Regen"
set BonusString[BONUS_MOVEMENT_SPEED] = "Movement Speed"
set BonusString[BONUS_LIFE] = "Life"
set BonusString[BONUS_MANA] = "Mana"
endfunction
module InvItemEquipment
//
// configuration
//
public static constant integer MAX_BONUSES = 1000 // can be any number
//
// end config
//
static trigger onEquip = null
static trigger onUnequip = null
public static trigger equipEval = CreateTrigger()
public static unit eventUnit = null
integer slot
integer slotAlt
integer equipAbility
integer reqLevel
integer reqUnitType
integer reqAbility
string class
boolexpr equipCondition
string equipConditionStr
string equipConditionStrPrefix
static if (LIBRARY_BonusMod) then
integer bonuses
public static key KeyBonusType
public static key KeyBonusAmount
public static key KeyBonusMin
public static key KeyBonusMax
public static key KeyBonusRand
public static integer KEY_BONUS_TYPE = INVENTORY_KEY_START + KeyBonusType
public static integer KEY_BONUS_AMOUNT = INVENTORY_KEY_START + KeyBonusAmount
public static integer KEY_BONUS_MIN = INVENTORY_KEY_START + KeyBonusMin
public static integer KEY_BONUS_MAX = INVENTORY_KEY_START + KeyBonusMax
public static integer KEY_BONUS_RND = INVENTORY_KEY_START + KeyBonusRand
method addBonusRange takes Bonus bonusType, integer base, integer min, integer max returns nothing
call SaveInteger(Inventory.Hashtable, KEY_BONUS_TYPE,(this*.MAX_BONUSES)+this.bonuses, bonusType)
call SaveInteger(Inventory.Hashtable, KEY_BONUS_AMOUNT,(this*.MAX_BONUSES)+this.bonuses, base)
call SaveInteger(Inventory.Hashtable, KEY_BONUS_MIN,(this*.MAX_BONUSES)+this.bonuses, min)
call SaveInteger(Inventory.Hashtable, KEY_BONUS_MAX,(this*.MAX_BONUSES)+this.bonuses, max)
set this.bonuses = this.bonuses + 1
endmethod
method addBonus takes Bonus bonusType, integer amount returns nothing
call SaveInteger(Inventory.Hashtable, KEY_BONUS_TYPE,(this*.MAX_BONUSES)+this.bonuses, bonusType)
call SaveInteger(Inventory.Hashtable, KEY_BONUS_AMOUNT,(this*.MAX_BONUSES)+this.bonuses, amount)
set this.bonuses = this.bonuses + 1
endmethod
method getBonus takes integer index returns Bonus
return LoadInteger(Inventory.Hashtable, KEY_BONUS_TYPE, (this*.MAX_BONUSES)+index)
endmethod
method getBonusAmount takes integer index returns integer
return LoadInteger(Inventory.Hashtable, KEY_BONUS_AMOUNT, (this*.MAX_BONUSES)+index)
endmethod
method getBonusMin takes integer index returns integer
return LoadInteger(Inventory.Hashtable, KEY_BONUS_MIN, (this*.MAX_BONUSES)+index)
endmethod
method getBonusMax takes integer index returns integer
return LoadInteger(Inventory.Hashtable, KEY_BONUS_MAX, (this*.MAX_BONUSES)+index)
endmethod
static method Bonus2S takes Bonus b returns string
return BonusString[b]
endmethod
method getBonusTotal takes integer index returns integer
local integer total = getBonusAmount(index)
set bj_cineModeSavedSeed = GetRandomInt(0, 1000000)
call SetRandomSeed(InvCustomItem(this.tempCustomId).seed())
set total = total + (GetRandomInt(this.getBonusMin(index), this.getBonusMax(index)))
call SetRandomSeed(bj_cineModeSavedSeed)
return total
endmethod
method addBonusesMultiplier takes unit u, integer m returns nothing
local integer i = 0
local integer n = 0
local integer total
local InvItem itm
loop
exitwhen i >= this.bonuses
call BonusModStruct.addBonus(u, this.getBonus(i), this.getBonusTotal(i)*m)
set i = i + 1
endloop
set i = 0
loop
exitwhen i >= this.sockets
set itm = this.getSocket(i)
set itm.tempCustomId = this.socketId(i)
set n = 0
loop
exitwhen n == itm.bonuses
call BonusModStruct.addBonus(u, itm.getBonus(n), itm.getBonusTotal(n)*m)
set n = n + 1
endloop
set i = i + 1
endloop
endmethod
method applyBonuses takes unit u returns nothing
call addBonusesMultiplier(u, 1)
endmethod
method removeBonuses takes unit u returns nothing
call addBonusesMultiplier(u, -1)
endmethod
endif
static method GearSlotName takes integer slot returns string
if (slot == 1) then
return "Head"
elseif (slot == 2) then
return "Shoulder"
elseif (slot == 8) then
return "Earring"
elseif (slot == 3) then
return "Chest"
elseif (slot == 4) then
return "Belt"
elseif (slot == 5) then
return "Pants"
elseif (slot == 6 or slot == 12) then
return "Ring"
elseif (slot == 7) then
return "Amulet"
elseif (slot == 9) then
return "Gloves"
elseif (slot == 10) then
return "Bracers"
elseif (slot == 11) then
return "Boots"
elseif (slot == 13) then
return "Weapon"
elseif (slot == 14) then
return "Offhand"
endif
return ""
endmethod
method buildDescription takes unit forUnit returns string
local integer i = 0
local integer n = 0
local string out = ""
local string bonusName
local integer lines = 0
local integer pid = GetPlayerId(GetOwningPlayer(forUnit))
local integer total
local InvItem itm
call EquipmentItem_Init.execute()
call Inventory.clearLines(pid)
// slot type
set out = GearSlotName(this.slot)
if (out != "") then
set out = "|cffffcc00Slot:|r " + out
call Inventory.setTooltipSlot(pid, lines, out)
call Inventory.ItemInfoTags[pid][Inventory.TOOLTIP_LINES_MAX].show(true, Inventory.PlayerCurrent[pid].camera)
endif
// item description
set lines = this.buildDescriptionLines(pid, 0)
if (lines != 0) then
set lines = lines + 1
endif
// item ability
if (this.ability > 0) then
set out = "|cfffedaac" + GetObjectName(this.ability) + "|r (Does not stack)\n"
call Inventory.setTooltipLine(pid, lines, out)
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
// equip ability
if (this.equipAbility > 0) then
set out = "|cfffedaac" + GetObjectName(this.equipAbility) + "|r\n"
call Inventory.setTooltipLine(pid, lines, out)
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
// equip bonuses
static if (LIBRARY_BonusMod) then
loop
exitwhen i >= this.bonuses
set bonusName = .Bonus2S(this.getBonus(i))
if (bonusName != null and bonusName != "") then
set total = this.getBonusTotal(i)
if (total > 0) then
set out = "|cfffedaac+" + I2S(total) + " to " + bonusName + "|r"
call Inventory.setTooltipLine(pid, lines, out)
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
endif
set i = i + 1
endloop
endif
set Inventory.RightSideStart[pid] = lines
// level requirement
if (this.reqLevel > 0) then
if (GetHeroLevel(forUnit) < this.reqLevel and GetUnitLevel(forUnit) < this.reqLevel) then
set out = "|cffffcc00Level Req:|r |cffff0000" + I2S(this.reqLevel) + "|r"
else
set out = "|cffffcc00Level Req:|r " + I2S(this.reqLevel)
endif
call Inventory.setTooltipLine(pid, lines, out)
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
// unit type requirement
if (this.reqUnitType > 0) then
if (GetUnitTypeId(forUnit) != this.reqUnitType) then
set out = "|cffffcc00Usable:|r |cffff0000" + GetObjectName(this.reqUnitType) + "|r"
else
set out = "|cffffcc00Usable:|r |cff2eb82e" + GetObjectName(this.reqUnitType) + "|r"
endif
call Inventory.setTooltipLine(pid, lines, out)
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
// ability requirement
if (this.reqAbility > 0) then
if (GetUnitAbilityLevel(forUnit, this.reqAbility) < 0) then
set out = "|cffff0000Requires:|r |cffff0000" + GetObjectName(this.reqAbility) + "|r"
else
set out = "|cffffcc00Requires:|r " + GetObjectName(this.reqAbility)
endif
call Inventory.setTooltipLine(pid, lines, out)
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
// custom
if (this.equipCondition != null and this.equipConditionStr != null and this.equipConditionStr != "") then
call TriggerClearConditions(InvItem.equipEval)
call TriggerAddCondition(InvItem.equipEval, this.equipCondition)
set InvItem.eventUnit = forUnit
if (not TriggerEvaluate(InvItem.equipEval)) then
set out = "|cffffcc00" + this.equipConditionStrPrefix + ":|r " + this.equipConditionStr
else
set out = "|cffffcc00" + this.equipConditionStrPrefix + ":|r " + this.equipConditionStr
endif
call Inventory.setTooltipLine(pid, lines, out)
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
// sockets
set i = 0
if (this.maxSockets > 0) then
if (Inventory.RightSideStart[pid] != lines) then
call Inventory.setTooltipLine(pid, lines, "")
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
call Inventory.setTooltipLine(pid, lines, "Sockets: " + I2S(this.sockets) + "/" + I2S(this.maxSockets))
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
loop
exitwhen i >= this.maxSockets
set itm = this.getSocket(i)
if (itm > 0) then
set itm.tempCustomId = this.socketId(i)
set n = 0
loop
exitwhen n >= itm.bonuses
set bonusName = .Bonus2S(itm.getBonus(n))
if (bonusName != null and bonusName != "") then
set total = itm.getBonusTotal(n)
if (total > 0) then
set out = "|cff5566bb+" + I2S(total) + " to " + bonusName + "|r\n"
call Inventory.setTooltipLine(pid, lines, out)
call Inventory.ItemInfoTags[pid][lines].show(true, Inventory.PlayerCurrent[pid].camera)
set lines = lines + 1
endif
endif
set n = n + 1
endloop
endif
set i = i + 1
endloop
return out
endmethod
endmodule
function SetEquipItemStats takes integer slot, integer s2, integer ab, integer lvl, integer utype, integer rab, integer dmg, integer def, integer str, integer agi, integer it, integer as, integer sight, integer lreg, integer mreg, InvItem itm returns InvItem
set itm.slot = slot
set itm.slotAlt = s2
set itm.reqUnitType = utype
set itm.equipAbility = ab
set itm.reqLevel = lvl
set itm.reqUnitType = utype
set itm.reqAbility = rab
if (dmg != 0) then
call itm.addBonus(BONUS_DAMAGE, dmg)
endif
if (def != 0) then
call itm.addBonus(BONUS_ARMOR, def)
endif
if (str != 0) then
call itm.addBonus(BONUS_STRENGTH, str)
endif
if (agi != 0) then
call itm.addBonus(BONUS_AGILITY, agi)
endif
if (it != 0) then
call itm.addBonus(BONUS_INTELLIGENCE, it)
endif
if (as != 0) then
call itm.addBonus(BONUS_ATTACK_SPEED, as)
endif
if (sight != 0) then
call itm.addBonus(BONUS_SIGHT_RANGE, sight)
endif
if (lreg != 0) then
call itm.addBonus(BONUS_LIFE_REGEN, lreg)
endif
if (mreg != 0) then
call itm.addBonus(BONUS_MANA_REGEN_PERCENT, lreg)
endif
return itm
endfunction
endlibrary
library EquipmentSockets
module InvItemSocket
integer maxSockets
boolean isSocket
static hashtable SocketTable = InitHashtable()
method operator sockets takes nothing returns integer
return LoadInteger(.SocketTable, this.tempCustomId, KEY_SOCKETS_COUNT*100)
endmethod
method operator sockets= takes integer val returns nothing
call SaveInteger(.SocketTable, this.tempCustomId, KEY_SOCKETS_COUNT*100, val)
endmethod
method socketId takes integer index returns integer
return LoadInteger(.SocketTable, this.tempCustomId, (KEY_SOCKETS_ID*100) + index)
endmethod
private static key KEY_SOCKETS
private static key KEY_SOCKETS_ID
private static key KEY_SOCKETS_COUNT
method addSocket takes InvItem socket returns boolean
if this.isSocket then
return false
endif
if (this.sockets >= this.maxSockets) then
return false
endif
call SaveInteger(.SocketTable, this.tempCustomId, (KEY_SOCKETS*100) + this.sockets, socket)
call SaveInteger(.SocketTable, this.tempCustomId, (KEY_SOCKETS_ID*100) + this.sockets, socket.tempCustomId)
set this.sockets = this.sockets + 1
return true
endmethod
method getSocket takes integer index returns InvItem
if this.isSocket then
return 0
endif
return LoadInteger(.SocketTable, this.tempCustomId, (KEY_SOCKETS*100) + index)
endmethod
endmodule
endlibrary
library EquipHeroModels initializer Init
globals
private Table HeroModels
private string array Path
private integer Models = 0
endglobals
struct HeroModelData
string path
real scale
endstruct
function EquipSetHeroModel takes integer utype, real scale, string path returns nothing
local HeroModelData data = HeroModelData.create()
set data.path = path
set data.scale = scale
set HeroModels[utype] = data
endfunction
function EquipGetHeroModel takes integer id returns HeroModelData
return HeroModels[id]
endfunction
private function Init takes nothing returns nothing
set HeroModels=Table.create()
//
// configuration
//
call EquipSetHeroModel('Hpal', 0.10, "units\\human\\HeroPaladin\\HeroPaladin.mdl")
call EquipSetHeroModel('hfoo', 0.08, "units\\human\\Footman\\Footman.mdl")
call EquipSetHeroModel('Ulic', 0.08, "units\\undead\\HeroLich\\HeroLich.mdl")
call EquipSetHeroModel('Obla', 0.08, "units\\orc\\HeroBladeMaster\\HeroBladeMaster.mdl")
call EquipSetHeroModel('Edem', 0.08, "units\\nightelf\\HeroDemonHunter\\HeroDemonHunter.mdl")
//
// end config
//
endfunction
endlibrary
library EquipItemParser requires EquipmentItem
//
// configuration
//
public function ParseTypeToSlot takes InvItem itm, string t returns nothing
if (t == "weapon") then
set itm.slot = 13
elseif(t == "shield") then
set itm.slot = 14
elseif(t == "armor") then
set itm.slot = 3
elseif(t == "boots") then
set itm.slot = 11
elseif(t == "bracers") then
set itm.slot = 10
elseif(t == "gloves") then
set itm.slot = 9
elseif(t == "belt") then
set itm.slot = 4
elseif(t == "helm") then
set itm.slot = 1
elseif(t == "pauldron") then
set itm.slot = 2
set itm.slotAlt = 9
elseif(t == "amulet") then
set itm.slot = 7
elseif(t == "pants") then
set itm.slot = 5
elseif(t == "ring") then
set itm.slot = 6
set itm.slotAlt = 12
elseif(t == "shield") then
set itm.slot = 14
elseif(t == "socket" or t == "gem") then
set itm.isSocket = true
endif
endfunction
public function ParseClass takes InvItem itm, string c returns nothing
if (c == "1h") then
set itm.slotAlt = 14
endif
endfunction
public function ParseBonusParam takes InvItem itm, string param, string value, string min, string max returns nothing
if (param == "dmg") then
call itm.addBonusRange(BONUS_DAMAGE, S2I(value), S2I(min), S2I(max))
elseif (param == "def") then
call itm.addBonusRange(BONUS_ARMOR, S2I(value), S2I(min), S2I(max))
elseif (param == "str") then
call itm.addBonusRange(BONUS_STRENGTH, S2I(value), S2I(min), S2I(max))
elseif (param == "agi") then
call itm.addBonusRange(BONUS_AGILITY, S2I(value), S2I(min), S2I(max))
elseif (param == "int") then
call itm.addBonusRange(BONUS_INTELLIGENCE, S2I(value), S2I(min), S2I(max))
elseif (param == "ias") then
call itm.addBonusRange(BONUS_ATTACK_SPEED, S2I(value), S2I(min), S2I(max))
elseif (param == "hp" or param == "life") then
call itm.addBonusRange(BONUS_LIFE, S2I(value), S2I(min), S2I(max))
elseif (param == "mp" or param == "mana") then
call itm.addBonusRange(BONUS_MANA, S2I(value), S2I(min), S2I(max))
elseif (param == "manareg") then
call itm.addBonusRange(BONUS_MANA_REGEN, S2I(value), S2I(min), S2I(max))
elseif (param == "sight") then
call itm.addBonusRange(BONUS_SIGHT_RANGE, S2I(value), S2I(min), S2I(max))
elseif (param == "ms" or param == "move") then
call itm.addBonusRange(BONUS_MOVEMENT_SPEED, S2I(value), S2I(min), S2I(max))
elseif (param == "lifereg") then
call itm.addBonusRange(BONUS_LIFE_REGEN, S2I(value), S2I(min), S2I(max))
endif
endfunction
//
// end
//
// String2Rawcode:
public function Char2Id takes string c returns integer
local integer i = 0
local string abc = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
local string t
loop
set t = SubString(abc,i,i + 1)
exitwhen t == null or t == c
set i = i + 1
endloop
if i < 10 then
return i + 48
elseif i < 36 then
return i + 65 - 10
endif
return i + 97 - 36
endfunction
public function String2Id takes string s returns integer
return ((Char2Id(SubString(s,0,1)) * 256 + Char2Id(SubString(s,1,2))) * 256 + Char2Id(SubString(s,2,3))) * 256 + Char2Id(SubString(s,3,4))
endfunction
public function Id2Char takes integer i returns string
local string abc = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
if i >= 97 then
return SubString(abc,i - 97 + 36,i - 96 + 36)
elseif i >= 65 then
return SubString(abc,i - 65 + 10,i - 64 + 10)
endif
return SubString(abc,i - 48,i - 47)
endfunction
public function Id2String takes integer id returns string
local integer t = id / 256
local string r = Id2Char(id - 256 * t)
set id = t / 256
set r = Id2Char(t - 256 * id) + r
set t = id / 256
return Id2Char(t) + Id2Char(id - 256 * t) + r
endfunction
// end
public function ParseRawCodeStr takes string value returns integer
local integer l = StringLength(value)
if (l == 4 or l == 1 or l == 3 or l == 6) then
if (SubString(value, 0, 1) == "'") then
set value = SubString(value, 1, 9999)
set l = l - 1
endif
if (SubString(value, l-1, l) == "'") then
set value = SubString(value, 0, l-1)
endif
return String2Id(value)
endif
return S2I(value)
endfunction
private function ParseParam takes InvItem itm, string param, string value returns nothing
if (param == "lvl") then
set itm.reqLevel = S2I(value)
elseif (param == "class") then
call ParseClass(itm, value)
elseif(param == "icon") then
set itm.icon = ParseRawCodeStr(value)
elseif(param == "utype") then
set itm.reqUnitType = ParseRawCodeStr(value)
elseif(param == "abil") then
set itm.reqAbility = ParseRawCodeStr(value)
elseif(param == "cost") then
set itm.cost = S2I(value)
elseif(param == "type") then
call ParseTypeToSlot(itm, value)
elseif(param == "maxsock") then
set itm.maxSockets = S2I(value)
endif
endfunction
globals
public string TypeSlot
endglobals
function InitEquipItem takes integer itemId, string data returns InvItem
local InvItem itm = InvItem.create()
local integer len = StringLength(data)
local integer i = 0
local string desc = ""
local string param = ""
local string array value
local integer curVal = 0
local string s = ""
local integer step = 0
set itm.id = itemId
set InvItem.Table[itm.id] = itm
loop
exitwhen i >= len
set s = SubString(data, i, i + 1)
if (step > 0 and s == " ") then
// do nothing
elseif (step == 0) then
if (s == "^") then
set step = 1
else
set desc = desc + s
endif
elseif (step == 1) then
if (s == "^") then
set step = 3
set param = ""
elseif (s == "=") then
set step = 2
else
set param = param + s
endif
elseif (step == 2) then
if (s == ";") then
call ParseParam(itm, StringCase(param, false), value[0])
set param = ""
set value[0] = ""
set step = 1
else
set value[0] = value[0] + s
endif
elseif (step == 3) then
if (s == "=") then
set step = 4
else
set param = param + s
endif
elseif (step == 4) then
if (s == ";") then
set curVal = curVal + 1
if (curVal >= 3) then
if (value[0] == "") then
set value[0] = "0"
elseif (value[1] == "") then
set value[0] = "0"
elseif (value[2] == "") then
set value[0] = "0"
endif
call ParseBonusParam(itm, param, value[0], value[1], value[2])
set step = 3
set curVal = 0
set value[0]=""
set value[1]=""
set value[2]=""
set param=""
endif
else
set value[curVal] = value[curVal] + s
endif
endif
set i = i + 1
endloop
set itm.info = desc
return itm
endfunction
endlibrary
scope InvItemSetups initializer Init
function CustomItemCondition takes nothing returns boolean
local integer id = GetUnitTypeId(InvItem.eventUnit)
return (id == 'Hpal' or id == 'Obla') // Paladin or Blademaster
endfunction
private function Init takes nothing returns nothing
local InvItem itm
// The more verbose way to delcare items:
// Crown of Kings +5
set itm = CreateInvItem('ckng', 'B00G', 1000, 0, "Increases the Strength, Intelligence, and Agility of the Hero by 5 when worn.")
set itm.slot = 1
set itm.reqUnitType = 'Hpal'
set itm.reqLevel = 3
set itm.maxSockets = 1
call itm.addBonus(BONUS_STRENGTH, 5)
call itm.addBonus(BONUS_AGILITY, 5)
call itm.addBonus(BONUS_INTELLIGENCE, 5)
// with string parser
// Claws of Attack +15
call InitEquipItem.execute('ratf', "Increases the attack damage of the Hero by 15 when worn.^type=weapon; class=1h; maxsock=1; icon='D009'; cost=28; ^dmg=15;0;0;")
// Robe of the Last Guardian
call InitEquipItem.execute('I007', "Powerful magic robe.^type=armor; lvl=10; icon=B00Z; utype='Ulic'; cost=600; maxsock=2; ^def=0;10;20; int=20;0;5; life=300;0;0;")
// Gems
call InitEquipItem.execute('I009', "Can be socketed..^type=gem; icon=B012; cost=600; ^life=0;10;50;")
call InitEquipItem.execute('I00A', "Can be socketed..^type=gem; icon=B013; cost=600; ^mana=0;5;35;")
// two lines
// Mask of Death
set itm = CreateInvItem('modt', 'D02Z', 1000, 0, "While wearing this mask, a Hero will recover hit points equal to 50% of the attack damage dealt to an enemy unit.")
set itm = SetEquipItemStats(/*slot=*/1, /*slot2=*/0, /*abil=*/'AIva', /*lvl=*/0, /*utype=*/0, /*reqAbil=*/0, /*dmg=*/0, /*def=*/0, /*str=*/0, /*agi=*/0, /*int=*/0, /*atckspd=*/0, /*sight=*/0, /*lifereg=*/0, /*manareg=*/0, itm)
set itm.maxSockets = 1
// Orb of Frost
set itm = CreateInvItem('ofro', 'D04U', 1000, 'A003', " Adds 6 bonus cold damage to the attack of a Hero when carried. The Hero's attacks also become ranged when attacking air and slow the movement speed and attack rate of the enemy for 1 second.")
// Boots of Quel'Thalas +6
set itm = CreateInvItem('belv', 'B00I', 500, 0, " Increases the Agility of the Hero by 6 when worn.")
set itm.slot = 11
call itm.addBonus(BONUS_AGILITY, 6)
set itm.maxSockets = 1
// Belt of Giant Strength +6
set itm = CreateInvItem('bgst', 'B00J', 500, 0, " Increases the Strength of the Hero by 6 when worn.")
set itm.slot = 4
call itm.addBonus(BONUS_STRENGTH, 6)
set itm.maxSockets = 1
// Gauntlets of Ogre Strength +3
set itm = CreateInvItem('rst1', 'B00K', 100, 0, " Increases the Strength of the Hero by 3 when worn.")
set itm.slot = 9
call itm.addBonus(BONUS_STRENGTH, 3)
set itm.maxSockets = 1
// Necklace of Spell Immunity
set itm = CreateInvItem('nspi', 'B00L', 1000, 0, " Renders the Hero invulnerable to magic.")
set itm.slot = 7
set itm.equipAbility = 'AImx'
set itm.maxSockets = 1
// Runed Bracers
set itm = CreateInvItem('brac', 'B00M', 400, 0, " Reduces Magic damage dealt to the Hero by 33%.")
set itm.slot = 10
set itm.equipAbility = 'AIsr'
set itm.maxSockets = 1
// Ring of Protection +4
set itm = CreateInvItem('rde3', 'B00O', 500, 0, " Increases the armor of the Hero by 4 when worn.")
set itm.slot = 6
set itm.slotAlt = 12
call itm.addBonus(BONUS_ARMOR, 4)
set itm.maxSockets = 1
// Pauldron
set itm = CreateInvItem('I000', 'B00P', 600, 0, " Increases the armor of the Hero by 4 when worn.")
set itm.slot = 2
set itm.slotAlt = 8
call itm.addBonus(BONUS_ARMOR, 4)
set itm.maxSockets = 1
// Battlescarred Shoulderplate
set itm = CreateInvItem('I003', 'B00V', 600, 0, " Increases the armor by 4 and strenght by 2 when worn.")
set itm.slot = 2
set itm.slotAlt = 8
set itm.reqUnitType = 'Hpal'
set itm.reqLevel = 3
call itm.addBonus(BONUS_ARMOR, 4)
call itm.addBonus(BONUS_STRENGTH, 2)
set itm.maxSockets = 1
// Lich Mantle
set itm = CreateInvItem('I006', 'B00Y', 600, 0, " Increases Intelligence by 3 and Health by 50 when worn.")
set itm.slot = 2
set itm.slotAlt = 8
set itm.reqUnitType = 'Ulic'
set itm.reqLevel = 3
call itm.addBonus(BONUS_INTELLIGENCE, 3)
call itm.addBonus(BONUS_LIFE, 50)
set itm.maxSockets = 1
// Armor
set itm = CreateInvItem('I001', 'B00Q', 600, 0, " Increases armor by 8 and life by 100 when worn.")
set itm.slot = 3
call itm.addBonus(BONUS_ARMOR, 8)
call itm.addBonus(BONUS_LIFE, 100)
set itm.maxSockets = 1
// Silverhand Cuirass
set itm = CreateInvItem('I004', 'B00W', 600, 0, " Increases armor by 10 and life by 150 when worn.")
set itm.slot = 3
set itm.reqUnitType = 'Hpal'
set itm.reqLevel = 2
call itm.addBonus(BONUS_ARMOR, 10)
call itm.addBonus(BONUS_LIFE, 150)
set itm.maxSockets = 2
// Pants
set itm = CreateInvItem('I002', 'B00R', 600, 0, "Increases Strength by 3 when worn.")
set itm.slot = 5
call itm.addBonus(BONUS_STRENGTH, 3)
set itm.maxSockets = 1
// Golden Legguards
set itm = CreateInvItem('I005', 'B00X', 600, 0, "Increases Strength by 3 and Armor by 1 when worn.")
set itm.slot = 5
set itm.reqUnitType = 'Hpal'
call itm.addBonus(BONUS_STRENGTH, 2)
call itm.addBonus(BONUS_ARMOR, 1)
set itm.maxSockets = 2
// Kel'Thuzard's Robe
set itm = CreateInvItem('I008', 'B010', 600, 0, "Increases Intelligence by 5, Health by 40, and Mana by 50 when worn.")
set itm.slot = 3
set itm.reqUnitType = 'Ulic'
call itm.addBonus(BONUS_INTELLIGENCE, 5)
call itm.addBonus(BONUS_LIFE, 40)
call itm.addBonus(BONUS_MANA, 50)
set itm.maxSockets = 1
// Searing Blade
set itm = CreateInvItem('srbd', 'B000', 1650, 0, "Adds 10 bonus fire damage to the attack of a Hero when carried. The Hero's attacks also do splash damage to nearby enemy units.")
set itm.slot = 13
set itm.equipAbility = 'AIfw'
set itm.equipCondition = Filter(function CustomItemCondition)
set itm.equipConditionStrPrefix = "Usable"
set itm.equipConditionStr = "PL or BM"
set itm.maxSockets = 3
// Shield of Honor
set itm = CreateInvItem('shhn', 'B001', 3350, 0, "|cff8b00ffUnique|r\nGrants nearby friendly units a 10% bonus to attack damage. Also increases the armor of the Hero by 8 when worn.")
set itm.slot = 14
set itm.equipAbility = 'AIcd'
call itm.addBonus(BONUS_ARMOR, 8)
set itm.maxSockets = 2
// Dagger of Escape
set itm = CreateInvItem('desc', 'B002', 400, 0, "Allows the Hero to teleport a short distance.")
set itm.slot = 13
set itm.slotAlt = 14
set itm.reqUnitType = 'Edem'
set itm.equipAbility = 'AIbk'
set itm.maxSockets = 1
endfunction
endscope
function Trig_show_equip_Conditions takes nothing returns boolean
if ( not ( GetSpellAbilityId() == 'A001' ) ) then
return false
endif
return true
endfunction
function Trig_show_equip_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer pid = GetPlayerId(GetOwningPlayer(u))
local boolean flag = not Equipment[u].displayed
call Equipment[u].show(flag, PlayerCamera[pid])
set u = null
endfunction
//===========================================================================
function InitTrig_Show_Equipment takes nothing returns nothing
set gg_trg_Show_Equipment = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Show_Equipment, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_Show_Equipment, Condition( function Trig_show_equip_Conditions ) )
call TriggerAddAction( gg_trg_Show_Equipment, function Trig_show_equip_Actions )
endfunction
function Trig_show_bag_Conditions takes nothing returns boolean
if ( not ( GetSpellAbilityId() == 'A002' ) ) then
return false
endif
return true
endfunction
function Trig_show_bag_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local Inventory inv = Inventory[u]
local integer pid = GetPlayerId(GetOwningPlayer(u))
local unit u2 = Equipment[Equipment.PlayerCurrentUnit[pid]].unit
call inv.show(not inv.displayed, PlayerCamera[pid])
if (u2 != null and u2 != u) then
call Equipment[Equipment.PlayerCurrentUnit[pid]].show(false, PlayerCamera[pid])
endif
set u = null
endfunction
//===========================================================================
function InitTrig_Show_Inventory takes nothing returns nothing
set gg_trg_Show_Inventory = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Show_Inventory, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_Show_Inventory, Condition( function Trig_show_bag_Conditions ) )
call TriggerAddAction( gg_trg_Show_Inventory, function Trig_show_bag_Actions )
endfunction
scope AcquireItem initializer Init
private function Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local item itm = GetManipulatedItem()
local Inventory inv = Inventory[u]
local InvItem itemData
local InvCustomItem data
if (inv == 0) then
return
endif
set itemData = GetInvItem(GetItemTypeId(itm))
if (itemData <= 0) then
return
endif
set data = GetItemUserData(itm)
if (data == 0) then
set data = InvCustomItem.create()
call SetItemUserData(itm, data)
endif
set itemData.tempCustomId = data
if (inv.addItem(itemData)) then
call RemoveItem(itm)
else
call UnitRemoveItem(u, itm)
endif
set itm = null
set u = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_PICKUP_ITEM)
call TriggerAddAction(t, function Actions)
endfunction
endscope
scope EquipItem initializer Init
function IsInvItm2H takes InvItem itm returns boolean
return (itm.slot == 13 and itm.slotAlt == 0)
endfunction
private function Actions takes nothing returns nothing
local InvItem itm = InvEventItem
local player p = InvEventPlayer
local unit u = Equipment.PlayerCurrentUnit[User[p].id]
local Inventory inv = Inventory[u]
local Equipment gear = Equipment[u]
local boolean is2h = IsInvItm2H(itm)
// 2H weapon conditions
if (is2h) then
if (gear.item[14-1] > 0) then
call SimError(p, "You cannot equip a two-handed weapon with an offhand item.")
call gear.unequip(gear.item[itm.slot-1], itm.slot-1)
if (inv > 0) then
call inv.addItem(itm)
else
call SetItemUserData(UnitAddItemById(u, itm.id), itm.tempCustomId)
set itm.tempCustomId = 0
endif
endif
elseif (itm.slot == 14 or itm.slotAlt == 14) then
if (gear.item[13-1] > 0 and IsInvItm2H(gear.item[13-1])) then
call SimError(p, "You cannot equip an offhand item with a two-handed weapon.")
call gear.unequip(gear.item[14-1], 14-1)
if (inv > 0) then
call inv.addItem(itm)
else
call SetItemUserData(UnitAddItemById(u, itm.id), itm.tempCustomId)
set itm.tempCustomId = 0
endif
endif
endif
set u = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
set InvItem.onEquip = CreateTrigger()
call TriggerAddAction(InvItem.onEquip, function Actions)
endfunction
endscope
scope UnequipItem initializer Init
private function Actions takes nothing returns nothing
//call BJDebugMsg(GetPlayerName(InvEquipPlayer) + " unequipped " + GetObjectName(InvEquipItem.id))
endfunction
//===========================================================================
private function Init takes nothing returns nothing
set InvItem.onUnequip = CreateTrigger()
call TriggerAddAction(InvItem.onUnequip, function Actions)
endfunction
endscope
scope SellItem initializer Init
globals
private InvItem array SellItem
private integer array SellSlot
private Inventory array SellInv
endglobals
private function OnAgree takes nothing returns boolean
local User user = User[GetTriggerPlayer()]
local InvItem itm = SellItem[user.id]
local integer slot = SellSlot[user.id]
local Inventory inv = SellInv[user.id]
call inv.setItem(slot, 0)
call SetPlayerState(inv.user.toPlayer(), PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(inv.user.toPlayer(), PLAYER_STATE_RESOURCE_GOLD) + itm.cost)
call DisplayTimedTextToPlayer(inv.user.toPlayer(), 0, 0, 15, "You sold \"" + GetObjectName(itm.id) + "\" for |cffffcc00" + I2S(itm.cost) + "|r gold.")
return false
endfunction
private function Actions takes nothing returns nothing
local InvItem itm = InvEventItem
local player p = InvEventPlayer
local Inventory inv = Inventory.PlayerCurrent[User[p].id]
local integer slot = InvEventSlot
if (itm <= 0) then
return
endif
set SellItem[inv.user.id] = itm
set SellSlot[inv.user.id] = slot
set SellInv[inv.user.id] = inv
call inv.showTooltip(inv, false)
call ShowYesNoDialog("Sell " + GetObjectName(itm.id) + "?", inv.user, Filter(function OnAgree))
endfunction
//===========================================================================
private function Init takes nothing returns nothing
set Inventory.onSell = CreateTrigger()
call TriggerAddAction(Inventory.onSell, function Actions)
endfunction
endscope
scope DropItem initializer Init
globals
private InvItem array DropItem
private integer array DropSlot
private Inventory array DropInv
endglobals
private function OnAgree takes nothing returns boolean
local User user = User[GetTriggerPlayer()]
local InvItem itm = DropItem[user.id]
local integer slot = DropSlot[user.id]
local Inventory inv = DropInv[user.id]
call SetItemUserData(CreateItem(itm.id, GetUnitX(inv.owner), GetUnitY(inv.owner)), inv.getItemId(slot))
call inv.setItem(slot, 0)
call DisplayTimedTextToPlayer(user.toPlayer(), 0, 0, 15, "You dropped \"" + GetObjectName(itm.id) + "\".")
return false
endfunction
private function Actions takes nothing returns nothing
local InvItem itm = InvEventItem
local player p = InvEventPlayer
local Inventory inv = Inventory.PlayerCurrent[User[p].id]
local integer slot = InvEventSlot
if (itm <= 0) then
return
endif
set DropItem[inv.user.id] = itm
set DropSlot[inv.user.id] = slot
set DropInv[inv.user.id] = inv
call inv.showTooltip(inv, false)
call ShowYesNoDialog("Drop " + GetObjectName(itm.id) + "?", inv.user, Filter(function OnAgree))
endfunction
//===========================================================================
private function Init takes nothing returns nothing
set Inventory.onDrop = CreateTrigger()
call TriggerAddAction(Inventory.onDrop, function Actions)
endfunction
endscope
scope SocketItem initializer Init
globals
private InvItem array SockItem
private integer array SockSlot
private Inventory array SockInv
private integer array SockLast
integer SocketLast
integer SocketSlot
Inventory SocketInv
endglobals
private function OnAgree takes nothing returns boolean
local User user = User[GetTriggerPlayer()]
local InvItem itm = SockItem[user.id]
local integer slot = SockSlot[user.id]
local Inventory inv = SockInv[user.id]
local InvItem socketItm = inv.getItem(slot)
set socketItm.tempCustomId = inv.getItemId(slot)
if (socketItm.addSocket(itm)) then
static if (InvItem.buildDescription.exists) then
set socketItm.tempCustomId = inv.getItemId((Inventory.MAX_SLOTS*inv.currentPage)+slot)
call socketItm.buildDescription(inv.owner)
else
call inv.setTooltipInfo(GetItemDescription(socketItm.id))
endif
call inv.setTooltipTitle(GetObjectName(socketItm.id))
call inv.setTooltipCost("|cffffcc00" + I2S(GetInvItem(socketItm.id).cost) + "|r")
call inv.setTooltipIcon(inv.localInt(inv.pid, GetItemIcon(socketItm.id), Inventory.ICON_TRANSPARENT))
call inv.showTooltip(inv, true)
call inv.setItem(SockLast[user.id], 0)
endif
return false
endfunction
private function Actions takes nothing returns nothing
local Inventory inv = SocketInv
local InvItem itm = inv.getItem(SocketLast)
local InvItem socketItm = inv.getItem(SocketSlot)
set SockItem[inv.user.id] = itm
set SockSlot[inv.user.id] = SocketSlot
set SockInv[inv.user.id] = SocketInv
set SockLast[inv.user.id] = SocketLast
call inv.showTooltip(inv, false)
call ShowYesNoDialog("Socket " + GetObjectName(itm.id) + " into " + GetObjectName(socketItm.id) + "?", inv.user, Filter(function OnAgree))
endfunction
//===========================================================================
private function Init takes nothing returns nothing
set Equipment.onSocket = CreateTrigger()
call TriggerAddAction(Equipment.onSocket, function Actions)
endfunction
endscope
scope HideInventory initializer Init
private function OnIndex takes nothing returns boolean
local unit u = GetIndexedUnit()
local Inventory inv = Inventory[u]
local unit u2 = Equipment[Equipment.PlayerCurrentUnit[inv.pid]].unit
if (u == u2 and u != null) then
call Equipment[u2].show(false, PlayerCamera[inv.pid])
endif
if (Inventory.PlayerCurrent[inv.pid] == inv) then
call inv.show(false, PlayerCamera[inv.pid])
endif
return false
endfunction
private function OnDeath takes nothing returns nothing
local unit u = GetTriggerUnit()
local Inventory inv = Inventory[u]
local unit u2 = Equipment[Equipment.PlayerCurrentUnit[inv.pid]].unit
if (u == u2 and u != null) then
call Equipment[u2].show(false, PlayerCamera[inv.pid])
endif
if (Inventory.PlayerCurrent[inv.pid] == inv) then
call inv.show(false, PlayerCamera[inv.pid])
endif
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddAction(t, function OnDeath)
call OnUnitDeindex(function OnIndex)
endfunction
endscope
library YesNoDialog requires UserInterface
globals
dialog array Dialog
private button array YesBtn
private filterfunc array YesCallback
player YesNoPlayer
endglobals
private function OnClick takes nothing returns nothing
local User user = User.first
local button btn = GetClickedButton()
loop
exitwhen user == User.NULL
if (YesCallback[user.id] != null and YesBtn[user.id] == btn) then
set YesNoPlayer = user.toPlayer()
call UserInterface_Eval(YesCallback[user.id])
endif
set user = user.next
endloop
endfunction
private keyword INITS
struct YesNoDialog extends array
implement INITS
endstruct
function ShowYesNoDialog takes string title, User user, filterfunc callback returns nothing
set YesCallback[user.id] = callback
call DialogSetMessage(Dialog[user.id], title)
call DialogDisplay(user.toPlayer(), Dialog[user.id], true)
endfunction
private module INITS
private static method onInit takes nothing returns nothing
local User user = User.first
local trigger t = CreateTrigger()
loop
exitwhen user == User.NULL
set YesCallback[user.id] = null
set Dialog[user.id] = DialogCreate()
set YesBtn[user.id] = DialogAddButton(Dialog[user.id], "Yes", 89)
call DialogAddButton(Dialog[user.id], "No", 0)
call TriggerRegisterDialogEvent(t, Dialog[user.id])
set user = user.next
endloop
call TriggerAddAction(t, function OnClick)
endmethod
endmodule
endlibrary
library UserInterface initializer Init requires optional UnitDex/*or any unit indexer*/, Camera
/***************************************************************
*
* v1.0.6, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*
* - Create UI elements based on dummy units.
*
* - Elements are overlay onto the current camera keeping you in gameplay.
*
* - The camera can be modified to any view including third person, however
* it must be locked.
*
* - Dynamic textures are applied to dummies through an ability based off
* off War Clib, using destructables with their replaceable texture field
* set to the desired texture. (more: http://www.wc3c.net/showthread.php?p=1043980)
*
* - Detect left and right clicks.
*
* - Works in multiplayer (and single) without lag.
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Copy the script to your map and save it (requires JassHelper *or* JNGP)
* 2. Copy the object editor data and paste the correct raw codes into the configuration.
* _________________________________________________________________________
* 2. API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* struct UIButton
*
* static method create takes real minx, real maxy, real W, real H, real z, integer texture returns thistype
* static method updateAll takes nothing returns nothing
* static method click takes unit u, boolean isLeft returns boolean
* static method clickEx takes real x, real y, boolean isLeft returns boolean
* static method clickPeriodicSelect takes player p, boolean isLeft returns boolean
*
* method destroy takes nothing returns nothing
* method update takes nothing returns nothing
* method show takes boolean flag, Camera Cam returns nothing
* method showPlayer takes player p, boolean flag, Camera cam returns nothing
* method isClicked takes real x, real y returns boolean
* method clickL takes nothing returns nothing
* method clickR takes nothing returns nothing
* method setPosition takes real minx, real maxy returns nothing
* method setTexture takes integer texture returns nothing
*
* -----------------------
*
* integer customValue
* integer animIndex
* trigger triggerL
* trigger triggerR
* filterfunc onLeftClick
* filterfunc onRightClick
*
*
* struct UIPicture
*
* static method create takes real minx, real maxy, real w, real h, real z, integer texture returns thistype
* static method createEx takes real minx, real maxy, real z, real scale, integer unitId, real modelWidth, real modelHeight, integer texture returns thistype
* static method updateAll takes nothing returns nothing
* method destroy takes nothing returns nothing
* method update takes nothing returns nothing
* method show takes boolean show, Camera Cam returns nothing
* method showPlayer takes player p, boolean flag, Camera cam returns nothing
* method setPosition takes real minx, real maxy returns nothing
* method setTexture takes integer texture returns nothing
*
* -----------------------
*
* integer customValue
* integer animIndex
*
* struct UIText
*
* static method create takes real minx, real maxy, real z returns thistype
* static method updateAll takes nothing returns nothing
*
* method destroy takes nothing returns nothing
* method update takes nothing returns nothing
* method setPosition takes real minx, real maxy returns nothing
* method show takes boolean show, Camera Cam returns nothing
*
* -----------------------
*
* integer customValue
*
* function GetTriggerButton takes nothing returns UIButton
* function GetClickingPlayer takes nothing returns player
* function GetCameraDeltaZ takes nothing returns real
* function CreateWindow takes real x, real y, real size, real width, real height, race playerRace returns UIPicture
*
* public function ApplySkin takes unit u, integer texture returns boolean
*
***************************************************************/
// configuration
private module InterfaceConfig
// dummy unit with pitch angle animations
static constant integer DUMMY_TYPE = 'uidm'
// model used in CreateWindow function
static constant integer WINDOW_DUMMY = 'uiwn'
// destructable ID of the human border texture. the
// rest of the races should have consecutive ids.
static constant integer RACE_BORDERS_START = 'D201'
// run a timer to check for unit selections.
// faster but more cpu intensive.
static constant boolean LEFT_CLICK_TIMER = true
// really low timer intervals remove the flicker
// effect, most of the time.
static constant real LEFT_CLICK_TIMER_RATE = 0.01
// in multiplayer, IsUnitSelected is synchronous
// so it doesn't instantly return false when you use
// SelectUnit(u, false). So we must disable the button
// for the clicking player for a brief period or else
// the event will be spammed.
static constant real LEFT_CLICK_DELAY = 0.6
// ability id's
static constant integer DESTROYER_FORM_ID = 'Aave'
static constant integer LOCUST_ID = 'Aloc'
// ability used to change dummy unit textures
static constant integer SKIN_CHANGE_ABILITY = 'Skin'
// removes pathing and possibly other things from the unit.
// similar to locust, but allows clicking.
static constant integer GHOST_ABILITY = 'Aeth'
// dummy player
static constant player PLAYER = CAMERA_DUMMY_PLAYER // from Camera library
endmodule
// end configuration
globals
public destructable DummyTree = null
private trigger Trig = CreateTrigger()
private UIButton ClickedButton = 0
private player ClickingPlayer = null
private location DummyLoc = Location(0,0)
endglobals
function GetTriggerButton takes nothing returns UIButton
return ClickedButton
endfunction
function GetClickingPlayer takes nothing returns player
return ClickingPlayer
endfunction
public function Eval takes filterfunc func returns boolean
call TriggerClearConditions(Trig)
call TriggerAddCondition(Trig, func)
return TriggerEvaluate(Trig)
endfunction
public function GetTerrainZ takes real x, real y returns real
call MoveLocation(DummyLoc, x, y)
return GetLocationZ(DummyLoc)
endfunction
// adjusts
public function FindModelAnimIndex takes real w, real h, real z returns integer
local real W = w*SCREEN_WIDTH*z
local real H = h*SCREEN_HEIGHT*z
local real anim
if (H<W) then
set anim = 10*(W/H-1)
return UIMath_R2I_N(anim)
else
set anim = 10*(H/W-1)
if anim >= 0.5 then
return 100+UIMath_R2I_N(anim)
endif
endif
return 0
endfunction
public function FindModelSize takes real w, real h, real z returns real
local real W = w*SCREEN_WIDTH*z
local real H = h*SCREEN_HEIGHT*z
if (H<W) then
return 0.5*H
endif
return 0.5*W
endfunction
private function LocalReal takes player p, real forPlayer, real default returns real
if (User.Local == p) then
return forPlayer
endif
return default
endfunction
public function ApplySkin takes unit u, integer texture returns boolean
local boolean out = false
if (texture != 0) then
call UnitAddAbility(u, Interface.SKIN_CHANGE_ABILITY)
set DummyTree = CreateDestructable(texture,GetUnitX(u),GetUnitY(u),0,0,1)
set out = IssueTargetOrder(u, "grabtree", DummyTree)
call RemoveDestructable(DummyTree)
set DummyTree = null
call UnitRemoveAbility(u, Interface.SKIN_CHANGE_ABILITY)
endif
return out
endfunction
struct UIButton
integer customValue
integer animIndex
trigger triggerL
trigger triggerR
filterfunc onLeftClick
filterfunc onRightClick
boolean disabled
unit selectUnit
static boolean useDelay = false
readonly static thistype array AllShow
readonly static integer CountShow = 0
readonly integer index
readonly Camera camera
readonly real scale
readonly real centerx
readonly real centery
readonly real minx
readonly real maxx
readonly real miny
readonly real maxy
readonly real width
readonly real height
readonly real z
readonly unit picture
readonly effect model
readonly boolean displayed
readonly integer texture
private integer unitId
private static thistype array Unit2Button
static method create takes real minx, real maxy, real W, real H, real z, integer texture returns thistype
local thistype this = thistype.allocate()
set .texture = texture
set .customValue = 0
set .camera = 0
set .width = W
set .height = H
set .minx = minx
set .maxx = minx+W
set .maxy = maxy
set .miny = maxy-H
set .z = 100.2+z
set .centerx = minx+W/2.0
set .centery = maxy-H/2.0
set .displayed = false
set .scale = FindModelSize(W, H, .z)
set .triggerL = null
set .triggerR = null
set .onLeftClick = null
set .onRightClick = null
set .selectUnit = null
set .picture = CreateUnit(Interface.PLAYER, Interface.DUMMY_TYPE, 0, 0, 0)
// apply texture
call ApplySkin(.picture, texture)
set .animIndex = FindModelAnimIndex(W, H, .z)
call SetUnitAnimationByIndex(.picture, .animIndex)
call UnitAddAbility(.picture, Interface.DESTROYER_FORM_ID)
call UnitRemoveAbility(.picture, Interface.DESTROYER_FORM_ID)
call UnitAddAbility(.picture, Interface.SKIN_CHANGE_ABILITY)
call UnitAddAbility(.picture, Interface.GHOST_ABILITY)
set this.unitId = GetUnitUserData(.picture)
set Unit2Button[this.unitId] = this
call SetUnitScale(.picture, 0, 0, 0)
return this
endmethod
method destroy takes nothing returns nothing
call RemoveUnit(.picture)
if .displayed then
set .AllShow[.index] = .AllShow[.CountShow]
set .AllShow[.index].index = .index
set .CountShow = .CountShow - 1
endif
endmethod
method update takes nothing returns nothing
local VECTOR3 Pos
if (.displayed) then
set Pos = .camera.win2World(.centerx, .centery, .z)
call SetUnitX(.picture, Pos.x)
call SetUnitY(.picture, Pos.y)
call SetUnitFlyHeight(.picture, Pos.z-GetTerrainZ(Pos.x, Pos.y), 0)
call Pos.destroy()
endif
endmethod
method show takes boolean flag, Camera Cam returns nothing
if Cam != -1 then
set .camera = Cam
endif
if flag != .displayed then
if flag then
set .AllShow[.CountShow] = this
set .index = .CountShow
set .CountShow = .CountShow + 1
call .update()
call SetUnitAnimationByIndex(.picture, .animIndex)
call SetUnitScale(.picture, .scale, 0, 0)
else
set .CountShow = .CountShow - 1
set .AllShow[.index] = .AllShow[.CountShow]
set .AllShow[.index].index = .index
call SetUnitX(.picture, 0)
call SetUnitY(.picture, 0)
call SetUnitScale(.picture, 0, 0, 0)
endif
endif
set .displayed = flag
endmethod
method showPlayer takes player p, boolean flag, Camera cam returns nothing
call .show(flag, cam)
if (flag) then
call SetUnitScale(.picture, LocalReal(p, .scale, 0), 0, 0)
endif
endmethod
method isClicked takes real x, real y returns boolean
return .minx < x and x < .maxx and .miny < y and y < .maxy
endmethod
method clickL takes nothing returns nothing
set ClickedButton = this
if (.triggerL != null and TriggerEvaluate(.triggerL)) then
call TriggerExecute(.triggerL)
endif
if (.onLeftClick != null) then
call Eval(.onLeftClick)
endif
endmethod
method clickR takes nothing returns nothing
set ClickedButton = this
if (.triggerR != null and TriggerEvaluate(.triggerR)) then
call TriggerExecute(.triggerR)
endif
if (.onRightClick != null) then
call Eval(.onRightClick)
endif
endmethod
method setPosition takes real minx, real maxy returns nothing
set .minx = minx
set .maxx = minx+.width
set .maxy = maxy
set .miny = maxy-.height
set .centerx = minx+.width/2.0
set .centery = maxy-.height/2.0
if .displayed then
call .update()
endif
endmethod
method setTexture takes integer texture returns nothing
call SetUnitX(.picture, 0)
call SetUnitY(.picture, 0)
call ApplySkin(.picture, texture)
if .displayed then
call .update()
endif
set .texture = texture
endmethod
static method updateAll takes nothing returns nothing
local integer i = .CountShow
loop
exitwhen i < 0
call .AllShow[i].update()
set i = i - 1
endloop
endmethod
static method click takes unit u, boolean isLeft returns boolean
local thistype b
if GetUnitTypeId(u) == Interface.DUMMY_TYPE then
set b = Unit2Button[GetUnitUserData(u)]
if b > 0 and b.displayed then
call SelectUnit(u, false)
if isLeft then
call b.clickL()
else
call b.clickR()
endif
return true
endif
endif
return false
endmethod
static method clickEx takes real x, real y, boolean isLeft returns boolean
local integer i = .CountShow
loop
exitwhen i < 0
if .AllShow[i].isClicked(x, y) then
if isLeft then
call .AllShow[i].clickL()
else
call .AllShow[i].clickR()
endif
return true
endif
set i = i - 1
endloop
return false
endmethod
private static method enableButtonCallback takes nothing returns nothing
local timer t = GetExpiredTimer()
static if (LIBRARY_TimerUtils) then
local thistype btn = thistype(GetTimerData(t))
else
local thistype btn = thistype(R2I(TimerGetRemaining(t) + 0.5))
endif
set btn.disabled = false
set t = null
endmethod
static method clickPeriodicSelect takes player p, boolean isLeft returns boolean
local integer i = .CountShow
local timer t
loop
exitwhen i < 0
if (IsUnitSelected(.AllShow[i].picture, p)) then
if (not .AllShow[i].disabled) then
if isLeft then
call .AllShow[i].clickL()
else
call .AllShow[i].clickR()
endif
if (thistype.useDelay) then // static if doesn't work..
set .AllShow[i].disabled = true
static if (LIBRARY_TimerUtils) then
call TimerStart(NewTimerEx(.AllShow[i]), Interface.LEFT_CLICK_DELAY, false, function thistype.enableButtonCallback)
else
set t = CreateTimer()
call TimerStart(t, .AllShow[i], false, null)
call PauseTimer(t)
call TimerStart(t, Interface.LEFT_CLICK_DELAY, false, function thistype.enableButtonCallback)
set t = null
endif
endif
return true
endif
call SelectUnit(.AllShow[i].picture, false)
if (.AllShow[i].selectUnit != null and User.Local == p) then
call SelectUnit(.AllShow[i].selectUnit, true)
endif
endif
set i = i - 1
endloop
return false
endmethod
endstruct
struct UIPicture
integer customValue
integer animIndex
readonly static thistype array AllShow
readonly static integer CountShow = 0
readonly integer index
readonly Camera camera
readonly real scale
readonly real centerx
readonly real centery
readonly real width
readonly real height
readonly real z
readonly unit picture
readonly boolean displayed
readonly integer texture
static method create takes real minx, real maxy, real w, real h, real z, integer texture returns thistype
local thistype this = thistype.allocate()
set .texture = texture
set .customValue = 0
set .camera = 0
set .width = w
set .height = h
set .centerx = minx+w/2.0
set .centery = maxy-h/2.0
set .z = 100.2+z
set .displayed = false
set .picture = CreateUnit(Interface.PLAYER, Interface.DUMMY_TYPE, 0, 0, 0)
set .animIndex = FindModelAnimIndex(w, h, .z)
set .scale = FindModelSize(w, h, .z)
call ApplySkin(.picture, texture)
call SetUnitAnimationByIndex(.picture, .animIndex)
call UnitAddAbility(.picture, Interface.DESTROYER_FORM_ID)
call UnitRemoveAbility(.picture, Interface.DESTROYER_FORM_ID)
call UnitAddAbility(.picture, Interface.LOCUST_ID)
call UnitRemoveAbility(.picture, Interface.LOCUST_ID)
// hide unit
call SetUnitScale(.picture, 0, 0, 0)
return this
endmethod
static method createEx takes real minx, real maxy, real z, real scale, integer unitId, real modelWidth, real modelHeight, integer texture returns thistype
local thistype this = thistype.allocate()
set .customValue = 0
set .camera = 0
set .z = 100.2 + z
set .width = (modelWidth*scale) / (SCREEN_WIDTH*.z)
set .height = (modelHeight*scale) / (SCREEN_HEIGHT*.z)
set .scale = scale
set .centerx = minx+.width/2.0
set .centery = maxy-.height/2.0
set .displayed = false
set .picture = CreateUnit(Interface.PLAYER, unitId, 0, 0, 270)
call ApplySkin(.picture, texture)
call UnitAddAbility(.picture, Interface.DESTROYER_FORM_ID)
call UnitRemoveAbility(.picture, Interface.DESTROYER_FORM_ID)
call UnitAddAbility(.picture, Interface.LOCUST_ID)
call UnitRemoveAbility(.picture, Interface.LOCUST_ID)
// hide unit
call SetUnitScale(.picture, 0, 0, 0)
return this
endmethod
method destroy takes nothing returns nothing
call RemoveUnit(.picture)
if .displayed then
set .AllShow[.index] = .AllShow[.CountShow]
set .AllShow[.index].index = .index
set .CountShow = .CountShow - 1
endif
endmethod
method update takes nothing returns nothing
local VECTOR3 Pos
if (this.displayed) then
set Pos = this.camera.win2World(.centerx, .centery, .z)
call SetUnitX(.picture, Pos.x)
call SetUnitY(.picture, Pos.y)
call SetUnitFlyHeight(.picture, Pos.z - GetTerrainZ(Pos.x, Pos.y), 0)
call Pos.destroy()
endif
endmethod
method show takes boolean show, Camera Cam returns nothing
if Cam != -1 then
set this.camera = Cam
endif
if show != .displayed then
if show then
set .AllShow[.CountShow] = this
set .index = .CountShow
set .CountShow = .CountShow + 1
call .update()
call SetUnitAnimationByIndex(.picture, .animIndex)
call SetUnitScale(.picture, .scale, 0, 0)
else
set .CountShow = .CountShow - 1
set .AllShow[.index] = .AllShow[.CountShow]
set .AllShow[.index].index = .index
call SetUnitX(.picture, 0)
call SetUnitY(.picture, 0)
call SetUnitScale(.picture, 0, 0, 0)
endif
endif
set .displayed = show
endmethod
method showPlayer takes player p, boolean flag, Camera cam returns nothing
call .show(flag, cam)
if (flag) then
call SetUnitScale(.picture, LocalReal(p, .scale, 0), 0, 0)
endif
endmethod
method setPosition takes real minx, real maxy returns nothing
set .centerx = minx+.width/2.0
set .centery = maxy-.height/2.0
if .displayed then
call .update()
endif
endmethod
method setTexture takes integer texture returns nothing
call SetUnitX(.picture, 0)
call SetUnitY(.picture, 0)
call ApplySkin(.picture, texture)
if .displayed then
call .update()
endif
set .texture = texture
endmethod
static method updateAll takes nothing returns nothing
local integer i = .CountShow
loop
exitwhen i < 0
call .AllShow[i].update()
set i = i - 1
endloop
endmethod
endstruct
struct UIText
integer customValue
readonly unit dummy
readonly static thistype array AllShow
readonly static integer CountShow = 0
readonly integer index
readonly Camera camera
readonly real minx
readonly real maxy
readonly real z
readonly texttag text
readonly boolean displayed
static method create takes real minx, real maxy, real z returns thistype
local thistype this = thistype.allocate()
set .customValue = 0
set .camera = 0
set .minx = minx
set .maxy = maxy
set .z = 100 + z
set .displayed = false
set .text = CreateTextTag()
set .dummy = CreateUnit(Interface.PLAYER, Interface.DUMMY_TYPE, 0, 0, 0)
call SetUnitScale(.dummy, 0, 0, 0)
call UnitAddAbility(.dummy, Interface.LOCUST_ID)
call UnitRemoveAbility(.dummy, Interface.LOCUST_ID)
call SetTextTagVisibility(.text, false)
call SetTextTagPosUnit(.text, .dummy, 0)
return this
endmethod
static method createEx takes player p, real minx, real maxy, real z returns thistype
local thistype this = thistype.allocate()
set .customValue = 0
set .camera = 0
set .minx = minx
set .maxy = maxy
set .z = 100 + z
set .displayed = false
set .dummy = CreateUnit(Interface.PLAYER, Interface.DUMMY_TYPE, 0, 0, 0)
if (User.Local == p) then
set .text = CreateTextTag()
endif
call SetUnitScale(.dummy, 0, 0, 0)
call UnitAddAbility(.dummy, Interface.LOCUST_ID)
call UnitRemoveAbility(.dummy, Interface.LOCUST_ID)
call SetTextTagVisibility(.text, false)
call SetTextTagPosUnit(.text, .dummy, 0)
return this
endmethod
method destroy takes nothing returns nothing
call DestroyTextTag(.text)
if .displayed then
set .AllShow[.index] = .AllShow[.CountShow]
set .AllShow[.index].index = .index
set .CountShow = .CountShow - 1
endif
endmethod
method update takes nothing returns nothing
local VECTOR3 Pos = .camera.win2World(.minx, .maxy, .z)
call SetUnitX(.dummy, Pos.x)
call SetUnitY(.dummy, Pos.y)
call SetTextTagPosUnit(.text, .dummy, (Pos.z-GetTerrainZ(Pos.x,Pos.y))-14.8)
call Pos.destroy()
endmethod
method setPosition takes real minx, real maxy returns nothing
set .minx = minx
set .maxy = maxy
if .displayed then
call .update()
endif
endmethod
method show takes boolean show, Camera Cam returns nothing
if Cam != -1 then
set .camera = Cam
endif
call SetTextTagVisibility(.text, show)
if show != .displayed then
if show then
set .AllShow[.CountShow] = this
set .index = .CountShow
set .CountShow = .CountShow + 1
call .update()
else
set .CountShow = .CountShow - 1
set .AllShow[.index] = .AllShow[.CountShow]
set .AllShow[.index].index = .index
endif
endif
set .displayed = show
endmethod
static method updateAll takes nothing returns nothing
local integer i = .CountShow
loop
exitwhen i < 0
call .AllShow[i].update()
set i = i - 1
endloop
endmethod
endstruct
struct Interface extends array
implement InterfaceConfig
static method getRaceBorders takes race r returns integer
return RACE_BORDERS_START + (GetHandleId(r)-1)
endmethod
static method updateAll takes boolean but, boolean pic, boolean tex returns nothing
if but then
call UIButton.updateAll()
endif
if pic then
call UIPicture.updateAll()
endif
if tex then
call UIText.updateAll()
endif
endmethod
endstruct
function CreateWindow takes real x, real y, real size, real width, real height, race playerRace returns UIPicture
return UIPicture.createEx(x, y, /*z=*/2, size, Interface.WINDOW_DUMMY, width, height, Interface.getRaceBorders(playerRace))
endfunction
private function InterfaceClickR takes nothing returns nothing
local unit clicker = GetTriggerUnit()
local unit btnUnit = GetOrderTargetUnit()
set ClickingPlayer = GetOwningPlayer(clicker)
if (UIButton.click(btnUnit, false)) then
call PauseUnit(clicker, true)
call IssueImmediateOrderById(clicker, 851973)
call PauseUnit(clicker, false)
endif
set clicker = null
endfunction
private function InterfaceClickL takes nothing returns nothing
set ClickingPlayer = GetTriggerPlayer()
call UIButton.click(GetTriggerUnit(), true)
endfunction
private function InterfaceClickL_Timer takes nothing returns nothing
local integer i = 0
local integer c = UIButton.CountShow
local User user = User.first
loop
exitwhen user == User.NULL
set ClickingPlayer = user.handle
call UIButton.clickPeriodicSelect(user.handle, true)
set user = user.next
endloop
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local trigger t2 = CreateTrigger()
local integer i = 0
local User user = User.first
loop
exitwhen user == User.NULL
call TriggerRegisterPlayerUnitEvent(t, user.handle, EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, null)
static if (not Interface.LEFT_CLICK_TIMER) then
call TriggerRegisterPlayerUnitEvent(t2, user.handle, EVENT_PLAYER_UNIT_SELECTED, null)
endif
set user = user.next
endloop
call TriggerAddAction(t, function InterfaceClickR)
static if (Interface.LEFT_CLICK_TIMER) then
call TimerStart(CreateTimer(), Interface.LEFT_CLICK_TIMER_RATE, true, function InterfaceClickL_Timer)
else
call TriggerAddAction(t2, function InterfaceClickL)
endif
set UIButton.useDelay = Interface.LEFT_CLICK_TIMER
endfunction
endlibrary
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
* v1.2.1, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex assigns every unit an unique integer. It attempts to make that number within the
* maximum array limit so you can associate it with one.
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
///! external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/* ________________________________________________________________________
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
private module UnitDexConfig
// The raw code of the leave detection ability.
static constant integer DETECT_LEAVE_ABILITY = 'uDex'
// Allow debug messages (debug mode must also be on)
static constant boolean ALLOW_DEBUGGING = true
// Uncomment the lines below to define a filter for units being indexed
/*static method onFilter takes unit u returns boolean
return true
endmethod*/
endmodule
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines except for UnitDexRemove
*
* function GetUnitId takes unit whichUnit returns integer
* function GetUnitById takes integer index returns unit
*
* function UnitDexEnable takes boolean flag returns nothing
* function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
* function IsUnitIndexed takes unit u returns boolean
* function IsIndexingEnabled takes nothing returns boolean
*
* function GetIndexedUnit takes nothing returns unit
* function GetIndexedUnitId takes nothing returns integer
*
* function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
* function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
* function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
* function OnUnitIndex takes code func returns triggercondition
* function OnUnitDeidex takes code func returns triggercondition
* _________________________________________________________________________
* 4. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex.Enabled = false // toggle the indexer
* UnitDex.Initialized // returns true if the preload timer has finished
* UnitDex.Count // returns the amount of units indexed
* UnitDex.Unit[0] // access the UnitDex array directly
* UnitDex.Group // a unit group containing every indexed unit (for enumeration)
* UnitDex.LastIndex // returns the last indexed unit's id
* _________________________________________________________________________
* 5. Public Variables
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* These are to be used with the "eventtype" argument of the event API:
*
* constant integer EVENT_UNIT_INDEX = 0
* constant integer EVENT_UNIT_DEINDEX = 1
* _________________________________________________________________________
* 6. Examples
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Associate a unit with a variable
*
* set UnitFlag[GetUnitId(yourUnit)] = true
*
* 2. Allocate a struct instance using a units assigned ID
*
* local somestruct data = GetUnitId(yourUnit)
*
* 3. Detect when a unit leaves the map
*
* function Exit takes nothing returns nothing
* call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
* endfunction
*
* call OnUnitDeindex(function Exit)
* // or
* call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
* _________________________________________________________________________
* 7. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Enumerate all preplaced units
* 2. TriggerRegisterEnterRegion native to detect when a unit enters the map
* 3. Assign each unit that enters the map a unique integer
* 4. Give every unit an ability based off of defend. There is no native to accurately
* detect when a unit leaves the map but when a unit dies or is removed from the game
* they are issued the "undefend" order
* 5. Catch the "undefend" order and remove unit from the queue
* _________________________________________________________________________
* 8. Notes
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
* - The object merger line should be commented out after saving and restarting.
* - All public functions are inlined except UnitDexRemove.
*
* -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
// Event types
constant integer EVENT_UNIT_INDEX = 0
constant integer EVENT_UNIT_DEINDEX = 1
// System variables
private trigger array IndexTrig
private integer Index = 0
private real E=-1
private boolexpr FilterEnter
endglobals
function GetUnitId takes unit whichUnit returns integer
return GetUnitUserData(whichUnit)
endfunction
function GetUnitById takes integer index returns unit
return UnitDex.Unit[index]
endfunction
function GetIndexedUnit takes nothing returns unit
return UnitDex.Unit[UnitDex.LastIndex]
endfunction
function GetIndexedUnitId takes nothing returns integer
return UnitDex.LastIndex
endfunction
function IsUnitIndexed takes unit u returns boolean
return (GetUnitById(GetUnitId(u)) != null)
endfunction
function UnitDexEnable takes boolean flag returns nothing
set UnitDex.Enabled = flag
endfunction
function IsIndexingEnabled takes nothing returns boolean
return UnitDex.Enabled
endfunction
function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
return TriggerAddCondition(IndexTrig[eventtype], func)
endfunction
function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
call TriggerRemoveCondition(IndexTrig[eventtype], c)
endfunction
function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
endfunction
function OnUnitIndex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
endfunction
function OnUnitDeindex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
endfunction
function UnitDexRemove takes unit u, boolean runEvents returns boolean
return UnitDex.Remove(u, runEvents)
endfunction
/****************************************************************/
private keyword UnitDexCore
struct UnitDex extends array
static boolean Enabled = true
readonly static integer LastIndex
readonly static boolean Initialized=false
readonly static group Group=CreateGroup()
readonly static unit array Unit
readonly static integer Count = 0
readonly static integer array List
implement UnitDexConfig
private static integer Counter = 0
implement UnitDexCore
endstruct
/****************************************************************/
private module UnitDexCore
static method Remove takes unit u, boolean runEvents returns boolean
local integer i
if (IsUnitIndexed(u)) then
set i = GetUnitId(u)
set UnitDex.List[i] = Index
set Index = i
call GroupRemoveUnit(UnitDex.Group, u)
call SetUnitUserData(u, 0)
if (runEvents) then
set UnitDex.LastIndex = i
set E = EVENT_UNIT_DEINDEX
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
set E = -1
endif
set UnitDex.Unit[i] = null
set UnitDex.Count = UnitDex.Count - 1
return true
endif
return false
endmethod
private static method onGameStart takes nothing returns nothing
local integer i = 0
static if (not LIBRARY_GroupUtils) then // Check if GroupUtils exists so we can use it's enumeration group.
local group ENUM_GROUP = CreateGroup() // If not, create the group.
endif
// Index preplaced units
loop
call GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(i), FilterEnter)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
static if (not LIBRARY_GroupUtils) then
call DestroyGroup(ENUM_GROUP)
set ENUM_GROUP = null
endif
// run init triggers
set i = 1
loop
exitwhen i > Counter
set LastIndex = i
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
set E = EVENT_UNIT_INDEX
set E = -1
set i = i + 1
endloop
set LastIndex = Counter
set Initialized = true
set FilterEnter = null
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onEnter takes nothing returns boolean
local unit u = GetFilterUnit()
local integer i = GetUnitId(u)
local integer t = Index
if (i == 0 and thistype.Enabled) then
// If a filter was defined pass the unit through it.
static if (thistype.onFilter.exists) then
if (not thistype.onFilter(u)) then
set u = null
return false // check failed
endif
endif
// Handle debugging
static if (thistype.DEBUG_MODE and thistype.ALLOW_DEBUGGING) then
if (t == 0 and Counter+1 >= JASS_MAX_ARRAY_SIZE) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached!")
set u = null
return false
endif
endif
// Add to group of indexed units
call GroupAddUnit(thistype.Group, u)
// Give unit the leave detection ability
call UnitAddAbility(u, thistype.DETECT_LEAVE_ABILITY)
call UnitMakeAbilityPermanent(u, true, thistype.DETECT_LEAVE_ABILITY)
// Allocate index
if (Index != 0) then
set Index = List[t]
else
set Counter = Counter + 1
set t = Counter
endif
set List[t] = -1
set LastIndex = t
set thistype.Unit[t] = u
set Count = Count + 1
// Store the index
call SetUnitUserData(u, t)
if (thistype.Initialized) then
// Execute custom events registered with RegisterUnitIndexEvent
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_INDEX
// Reset so the event can occur again
set E = -1
endif
endif
set u = null
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit u
local integer i
// Check if order is undefend.
if (thistype.Enabled and GetIssuedOrderId() == 852056) then
set u = GetTriggerUnit()
// If unit was killed (not removed) then don't continue
if (GetUnitAbilityLevel(u, thistype.DETECT_LEAVE_ABILITY) != 0) then
set u = null
return false
endif
set i = GetUnitId(u)
// If unit has been indexed then deindex it
if (i > 0 and i <= Counter and u == GetUnitById(i)) then
// Recycle the index
set List[i] = Index
set Index = i
set LastIndex = i
// Remove to group of indexed units
call GroupRemoveUnit(thistype.Group, u)
// Execute custom events without any associated triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_DEINDEX
// Remove entry
call SetUnitUserData(u, 0)
set thistype.Unit[i] = null
// Decrement unit count
set Count = Count - 1
// Reset so the event can occur again
set E = -1
endif
set u = null
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local player p
local unit u
static if (not LIBRARY_WorldBounds) then // Check if WorldBounts exists, if not then define the necessary vars
local region reg = CreateRegion() // If WorldBounds wasn't found, create the region manually
local rect world = GetWorldBounds()
endif
set FilterEnter = Filter(function thistype.onEnter)
// Begin to index units when they enter the map
static if (LIBRARY_WorldBounds) then
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, FilterEnter)
else
call RegionAddRect(reg, world)
call TriggerRegisterEnterRegion(CreateTrigger(), reg, FilterEnter)
call RemoveRect(world)
set world = null
endif
call TriggerAddCondition(t, Filter(function thistype.onLeave))
set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
loop
set p = Player(i)
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
// Hide the detect ability from players
call SetPlayerAbilityAvailable(p, thistype.DETECT_LEAVE_ABILITY, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TimerStart(CreateTimer(), 0, false, function thistype.onGameStart)
endmethod
endmodule
endlibrary
library PlayerUtils
/**************************************************************
*
* v1.2.8 by TriggerHappy
*
* This library provides a struct which caches data about players
* as well as provides functionality for manipulating player colors.
*
* Constants
* ------------------
*
* force FORCE_PLAYING - Player group of everyone who is playing.
*
* Struct API
* -------------------
* struct User
*
* static method fromIndex takes integer i returns User
* static method fromLocal takes nothing returns User
* static method fromPlaying takes integer id returns User
*
* static method operator [] takes integer id returns User
* static method operator count takes nothing returns integer
*
* method operator name takes nothing returns string
* method operator name= takes string name returns nothing
* method operator color takes nothing returns playercolor
* method operator color= takes playercolor c returns nothing
* method operator defaultColor takes nothing returns playercolor
* method operator hex takes nothing returns string
* method operator nameColored takes nothing returns string
*
* method toPlayer takes nothing returns player
* method colorUnits takes playercolor c returns nothing
*
* readonly string originalName
* readonly boolean isPlaying
* readonly static player Local
* readonly static integer LocalId
* readonly static integer AmountPlaying
* readonly static playercolor array Color
* readonly static player array PlayingPlayer
*
**************************************************************/
globals
// automatically change unit colors when changing player color
private constant boolean AUTO_COLOR_UNITS = true
// use an array for name / color lookups (instead of function calls)
private constant boolean ARRAY_LOOKUP = false
// this only applies if ARRAY_LOOKUP is true
private constant boolean HOOK_SAFETY = false // disable for speed, but only use the struct to change name/color safely
constant force FORCE_PLAYING = CreateForce()
private string array Name
private string array Hex
private string array OriginalHex
private playercolor array CurrentColor
endglobals
private keyword PlayerUtilsInit
struct User extends array
static constant integer NULL = 15
readonly player handle
readonly integer id
readonly thistype next
readonly thistype prev
readonly string originalName
readonly boolean isPlaying
readonly static thistype first
readonly static thistype last
readonly static player Local
readonly static integer LocalId
readonly static integer AmountPlaying = 0
readonly static playercolor array Color
static if not (LIBRARY_GroupUtils) then
readonly static group ENUM_GROUP = CreateGroup()
endif
private static thistype array PlayingPlayer
private static integer array PlayingPlayerIndex
// similar to Player(#)
static method fromIndex takes integer i returns thistype
return thistype(i)
endmethod
// similar to GetLocalPlayer
static method fromLocal takes nothing returns thistype
return thistype(thistype.LocalId)
endmethod
// access active players array
static method fromPlaying takes integer index returns thistype
return PlayingPlayer[index]
endmethod
static method operator [] takes player p returns thistype
return thistype(GetPlayerId(p))
endmethod
method toPlayer takes nothing returns player
return this.handle
endmethod
method operator name takes nothing returns string
static if (ARRAY_LOOKUP) then
return Name[this]
else
return GetPlayerName(this.handle)
endif
endmethod
method operator name= takes string newName returns nothing
call SetPlayerName(this.handle, newName)
static if (ARRAY_LOOKUP) then
static if not (HOOK_SAFETY) then
set Name[this] = newName
endif
endif
endmethod
method operator color takes nothing returns playercolor
static if (ARRAY_LOOKUP) then
return CurrentColor[this]
else
return GetPlayerColor(this.handle)
endif
endmethod
method operator hex takes nothing returns string
return OriginalHex[GetHandleId(this.color)]
endmethod
method operator color= takes playercolor c returns nothing
call SetPlayerColor(this.handle, c)
static if (ARRAY_LOOKUP) then
set CurrentColor[this] = c
static if not (HOOK_SAFETY) then
static if (AUTO_COLOR_UNITS) then
call this.colorUnits(color)
endif
endif
endif
endmethod
method operator defaultColor takes nothing returns playercolor
return Color[this]
endmethod
method operator nameColored takes nothing returns string
return hex + this.name + "|r"
endmethod
method colorUnits takes playercolor c returns nothing
local unit u
call GroupEnumUnitsOfPlayer(ENUM_GROUP, this.handle, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call SetUnitColor(u, c)
call GroupRemoveUnit(ENUM_GROUP, u)
endloop
endmethod
static method onLeave takes nothing returns boolean
local thistype p = thistype[GetTriggerPlayer()]
local integer i = .PlayingPlayerIndex[p.id]
// clean up
call ForceRemovePlayer(FORCE_PLAYING, p.toPlayer())
// recycle index
set .AmountPlaying = .AmountPlaying - 1
set .PlayingPlayerIndex[i] = .PlayingPlayerIndex[.AmountPlaying]
set .PlayingPlayer[i] = .PlayingPlayer[.AmountPlaying]
if (.AmountPlaying == 1) then
set p.prev.next = User.NULL
set p.next.prev = User.NULL
else
set p.prev.next = p.next
set p.next.prev = p.prev
endif
set .last = .PlayingPlayer[.AmountPlaying]
set p.isPlaying = false
return false
endmethod
implement PlayerUtilsInit
endstruct
private module PlayerUtilsInit
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local thistype p
set thistype.Local = GetLocalPlayer()
set thistype.LocalId = GetPlayerId(thistype.Local)
set OriginalHex[0] = "|cffff0303"
set OriginalHex[1] = "|cff0042ff"
set OriginalHex[2] = "|cff1ce6b9"
set OriginalHex[3] = "|cff540081"
set OriginalHex[4] = "|cfffffc01"
set OriginalHex[5] = "|cfffe8a0e"
set OriginalHex[6] = "|cff20c000"
set OriginalHex[7] = "|cffe55bb0"
set OriginalHex[8] = "|cff959697"
set OriginalHex[9] = "|cff7ebff1"
set OriginalHex[10] = "|cff106246"
set OriginalHex[11] = "|cff4e2a04"
set thistype.first = User.NULL
loop
exitwhen i == 12
set p = User(i)
set p.handle = Player(i)
set p.id = i
set thistype.Color[i] = GetPlayerColor(p.handle)
set CurrentColor[i] = thistype.Color[i]
if (GetPlayerController(p.handle) == MAP_CONTROL_USER and GetPlayerSlotState(p.handle) == PLAYER_SLOT_STATE_PLAYING) then
set .PlayingPlayer[AmountPlaying] = p
set .PlayingPlayerIndex[i] = .AmountPlaying
set .last = i
if (.first == User.NULL) then
set .first = i
set User(i).next = User.NULL
set User(i).prev = User.NULL
else
set User(i).prev = PlayingPlayer[AmountPlaying-1].id
set PlayingPlayer[AmountPlaying-1].next = User(i)
set User(i).next = User.NULL
endif
set p.isPlaying = true
call TriggerRegisterPlayerEvent(t, p.handle, EVENT_PLAYER_LEAVE)
call ForceAddPlayer(FORCE_PLAYING, p.handle)
set Hex[p] = OriginalHex[GetHandleId(thistype.Color[i])]
set .AmountPlaying = .AmountPlaying + 1
endif
set Name[p] = GetPlayerName(p.handle)
set p.originalName=Name[p]
set i = i + 1
endloop
call TriggerAddCondition(t, Filter(function thistype.onLeave))
endmethod
endmodule
//===========================================================================
static if (ARRAY_LOOKUP) then
static if (HOOK_SAFETY) then
private function SetPlayerNameHook takes player whichPlayer, string name returns nothing
set Name[GetPlayerId(whichPlayer)] = name
endfunction
private function SetPlayerColorHook takes player whichPlayer, playercolor color returns nothing
local User p = User[whichPlayer]
set Hex[p] = OriginalHex[GetHandleId(color)]
set CurrentColor[p] = color
static if (AUTO_COLOR_UNITS) then
call p.colorUnits(color)
endif
endfunction
hook SetPlayerName SetPlayerNameHook
hook SetPlayerColor SetPlayerColorHook
endif
endif
endlibrary
library Camera requires UIMath, PlayerUtils
globals
// configuration
// location on the map where the terrain is the default level
constant real CHECK_DELTAZ_X = -1200
constant real CHECK_DELTAZ_Y = 950
constant real SCREEN_WIDTH = 0.544
constant real SCREEN_HEIGHT = 0.302
constant real SCREEN_ASPECT_RATIO = SCREEN_WIDTH/SCREEN_HEIGHT
constant integer CAMERA_DUMMY_TYPE = 'e001'
constant player CAMERA_DUMMY_PLAYER = Player(bj_PLAYER_NEUTRAL_EXTRA)
private real DeltaZ = 0
// endconfig
public unit array DummyUnit
public location DummyLoc = Location(0,0)
endglobals
function GetTerrainZ takes real x, real y returns real
call MoveLocation(DummyLoc, x, y)
return GetLocationZ(DummyLoc)
endfunction
function GetCameraDeltaZ takes nothing returns real
return DeltaZ
endfunction
private function Matrix4Perspective1 takes MATRIX4 Output, real fovy, real Aspect, real zn, real zf returns MATRIX4
return Output.SetValues(2*zn/fovy,0,0,0,0,2*zn/Aspect,0,0,0,0,zf/(zf-zn),1,0,0,zn*zf/(zn-zf),0)
endfunction
private function Matrix4Perspective2 takes MATRIX4 Output, real n, real f, real r, real l, real t, real b returns MATRIX4
return Output.SetValues(2*n/(r-l), 0, (r+l)/(r-l), 0, 0, 2*n/(t-b), (t+b)/(t-b), 0, 0, 0, -(f+n)/(f-n), -2*f*n/(f-n), 0, 0, -1, 0)
endfunction
private function Matrix4Look takes MATRIX4 Output, VECTOR3 PosCamera, VECTOR3 AxisX, VECTOR3 AxisY, VECTOR3 AxisZ returns MATRIX4
return Output.SetValues(AxisX.x,AxisY.x,AxisZ.x,0,AxisX.y,AxisY.y,AxisZ.y,0,AxisX.z,AxisY.z,AxisZ.z,0,-Vec3Dot(AxisX, PosCamera),-Vec3Dot(AxisY, PosCamera),-Vec3Dot(AxisZ, PosCamera),1)
endfunction
struct Camera
VECTOR3 eye
VECTOR3 at
unit target
real distance
real yaw
real pitch
real roll
VECTOR3 axisX
VECTOR3 axisY
VECTOR3 axisZ
private MATRIX4 view
private MATRIX4 projection
private boolean change
integer customValue
method win2World takes real X, real Y, real Range returns VECTOR3
local VECTOR3 Output = VECTOR3.create()
set Output.x = .eye.x+.axisZ.x*Range+X*.axisX.x*SCREEN_WIDTH*Range+Y*.axisY.x*SCREEN_HEIGHT*Range
set Output.y = .eye.y+.axisZ.y*Range+X*.axisX.y*SCREEN_WIDTH*Range+Y*.axisY.y*SCREEN_HEIGHT*Range
set Output.z = .eye.z+.axisZ.z*Range+X*.axisX.z*SCREEN_WIDTH*Range+Y*.axisY.z*SCREEN_HEIGHT*Range
return Output
endmethod
method world2Win takes real X, real Y, real Z returns VECTOR3
local VECTOR3 Pos = VECTOR3.New_1(X, Y, Z)
local boolean b
call Vec3Transform_2(Pos, Pos, .view)
set b = Pos.z < 0
call Vec3Transform_2(Pos, Pos, .projection)
if b then
set Pos.z = -Pos.z
endif
return Pos
endmethod
private method updateDistanceYawPitch takes nothing returns nothing
local real dx = .at.x-.eye.x
local real dy = .at.y-.eye.y
local real dz = .at.z-.eye.z
local real len2d
set .distance = SquareRoot(dx*dx+dy*dy+dz*dz)
set .yaw = Atan2(dy, dx)
set len2d = SquareRoot(dx*dx+dy*dy)
set .pitch = Atan2(dz, len2d)
endmethod
private method updateAxisMatrix takes nothing returns nothing
local MATRIX3 mat
call Vec3Normalize(.axisZ, Vec3Subtract(.axisZ, .at, .eye))
set mat = Matrix3RotationAxis(MATRIX3.create(), .axisZ, -.roll)
call Vec3Normalize(.axisX, Vec3Cross(.axisX, .axisZ, VECTOR3.oneZ))
call Vec3Transform_1(.axisY, Vec3Cross(.axisY, .axisX, .axisZ), mat)
call Vec3Transform_1(.axisX, .axisX, mat)
call Matrix4Look(.view, .eye, .axisX, .axisY, .axisZ)
call mat.destroy()
endmethod
method applyCameraForPlayer takes player p, boolean ignoreChange returns boolean
if GetLocalPlayer() == p then
call SetCameraField(CAMERA_FIELD_ROTATION, .yaw*bj_RADTODEG, 0)
call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, .pitch*bj_RADTODEG, 0)
call SetCameraField(CAMERA_FIELD_ROLL, .roll*bj_RADTODEG, 0)
call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, .distance, 0)
call SetCameraTargetController(DummyUnit[GetPlayerId(p)], .at.x, .at.y, false)
call SetCameraField(CAMERA_FIELD_ZOFFSET, .at.z-DeltaZ, 0)
endif
if .change or ignoreChange then
set .change = false
return true
endif
return false
endmethod
method setPosition takes real x, real y, real z returns nothing
local real dx = x-.at.x
local real dy = y-.at.y
local real dz = z-.at.z
set .eye.x = .eye.x+dx
set .eye.y = .eye.y+dy
set .eye.z = .eye.z+dz
set .at.x = x
set .at.y = y
set .at.z = z
set .change = true
endmethod
method setEyeAndAt takes real ex, real ey, real ez, real tx, real ty, real tz returns nothing
set .eye.x = ex
set .eye.y = ey
set .eye.z = ez
set .at.x = tx
set .at.y = ty
set .at.z = tz
call .updateDistanceYawPitch()
call .updateAxisMatrix()
set .change = true
endmethod
method setYawPitchRoll takes real yaw, real pitch, real roll, boolean EyeLock returns nothing
local real Z = .distance*Sin(pitch)
local real XY = .distance*Cos(pitch)
local real X = XY*Cos(yaw)
local real Y = XY*Sin(yaw)
set .yaw = yaw
set .pitch = pitch
set .roll = roll
if EyeLock then
set .at.x = .eye.x+X
set .at.y = .eye.y+Y
set .at.z = .eye.z+Z
else
set .eye.x = .at.x-X
set .eye.y = .at.y-Y
set .eye.z = .at.z-Z
endif
call .updateAxisMatrix()
set .change = true
endmethod
static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set .customValue = 0
set .change = true
set .eye = VECTOR3.New_1(0.0,-922.668,DeltaZ+1367.912)
set .at = VECTOR3.New_1(0, 0, DeltaZ)
set .distance = 0
set .yaw = 0
set .pitch = 0
set .roll = 0
set .axisX = VECTOR3.create()
set .axisY = VECTOR3.create()
set .axisZ = VECTOR3.create()
set .view = MATRIX4.create()
set .projection = Matrix4Perspective2(MATRIX4.create(), 0.5, 10000, -SCREEN_WIDTH/2, SCREEN_WIDTH/2, -SCREEN_HEIGHT/2, SCREEN_HEIGHT/2)
call .updateDistanceYawPitch()
call .updateAxisMatrix()
return this
endmethod
method destroy takes nothing returns nothing
call .eye.destroy()
call .at.destroy()
call .axisX.destroy()
call .axisY.destroy()
call .axisZ.destroy()
call .view.destroy()
call .projection.destroy()
call this.destroy()
endmethod
endstruct
private module CamInitModule
private static method onInit takes nothing returns nothing
local integer i = 0
local User user
loop
exitwhen i == User.AmountPlaying
set user = User.fromPlaying(i)
set DummyUnit[user.id] = CreateUnit(CAMERA_DUMMY_PLAYER, CAMERA_DUMMY_TYPE, 0, 0, 0)
call ShowUnit(DummyUnit[user.id], false)
call PauseUnit(DummyUnit[user.id], true)
set i = i + 1
endloop
// init delta z
call SetCameraPosition(CHECK_DELTAZ_X, CHECK_DELTAZ_Y)
set DeltaZ = GetCameraTargetPositionZ() - 6
endmethod
endmodule
private struct CamInit
implement CamInitModule
endstruct
endlibrary
library UIMath initializer Init
public function R2I_N takes real r returns integer
local integer i = R2I(r)
if (r < I2R(i)+0.5) then
return i
else
return i+1
endif
endfunction
struct VECTOR3
static VECTOR3 Zero
static VECTOR3 oneX
static VECTOR3 oneY
static VECTOR3 oneZ
real x
real y
real z
static method New_0 takes nothing returns VECTOR3
local VECTOR3 this = VECTOR3.create()
set .x = 0
set .y = 0
set .z = 0
return this
endmethod
static method New_1 takes real x, real y, real z returns VECTOR3
local VECTOR3 this = VECTOR3.create()
set .x = x
set .y = y
set .z = z
return this
endmethod
static method New_2 takes VECTOR3 v returns VECTOR3
local VECTOR3 this = VECTOR3.create()
set .x = v.x
set .y = v.y
set .z = v.z
return this
endmethod
method SetValues takes real x, real y, real z returns VECTOR3
set .x = x
set .y = y
set .z = z
return this
endmethod
method Length takes nothing returns real
return SquareRoot(.x*.x+.y*.y+.z*.z)
endmethod
method LengthSq takes nothing returns real
return .x*.x+.y*.y+.z*.z
endmethod
method ToString takes nothing returns string
return "Vector3 id "+I2S(this) + "\nx = "+R2S(.x)+" y = "+R2S(.y)+" z = "+R2S(.z)
endmethod
endstruct
function Vec3Add takes VECTOR3 Output, VECTOR3 v1, VECTOR3 v2 returns VECTOR3
set Output.x = v1.x + v2.x
set Output.y = v1.y + v2.y
set Output.z = v1.z + v2.z
return Output
endfunction
function Vec3Subtract takes VECTOR3 Output, VECTOR3 v1, VECTOR3 v2 returns VECTOR3
set Output.x = v1.x - v2.x
set Output.y = v1.y - v2.y
set Output.z = v1.z - v2.z
return Output
endfunction
function Vec3Scale takes VECTOR3 Output, VECTOR3 v, real r returns VECTOR3
set Output.x = v.x * r
set Output.y = v.y * r
set Output.z = v.z * r
return Output
endfunction
function Vec3Division takes VECTOR3 Output, VECTOR3 v, real r returns VECTOR3
set Output.x = v.x / r
set Output.y = v.y / r
set Output.z = v.z / r
return Output
endfunction
function Vec3Length takes VECTOR3 v returns real
return SquareRoot(v.x*v.x+v.y*v.y+v.z*v.z)
endfunction
function Vec3LengthSq takes VECTOR3 v returns real
return v.x*v.x+v.y*v.y+v.z*v.z
endfunction
function Vec3Normalize takes VECTOR3 Output, VECTOR3 v returns VECTOR3
local real len = SquareRoot(v.x*v.x+v.y*v.y+v.z*v.z)
set Output.x = v.x/len
set Output.y = v.y/len
set Output.z = v.z/len
return Output
endfunction
function Vec3Dot takes VECTOR3 v1, VECTOR3 v2 returns real
return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z
endfunction
function Vec3Cross takes VECTOR3 Output, VECTOR3 v1, VECTOR3 v2 returns VECTOR3
local real y = v1.z * v2.x - v1.x * v2.z
local real z = v1.x * v2.y - v1.y * v2.x
set Output.x = v1.y * v2.z - v1.z * v2.y
set Output.y = y
set Output.z = z
return Output
endfunction
function Vec3Transform_1 takes VECTOR3 Output, VECTOR3 v, MATRIX3 m returns VECTOR3
local real y = v.x*m.m12+v.y*m.m22+v.z*m.m32
local real z = v.x*m.m13+v.y*m.m23+v.z*m.m33
set Output.x = v.x*m.m11+v.y*m.m21+v.z*m.m31
set Output.y = y
set Output.z = z
return Output
endfunction
function Vec3Transform_2 takes VECTOR3 Output, VECTOR3 v, MATRIX4 m returns VECTOR3
local real y = v.x*m.m12+v.y*m.m22+v.z*m.m32+m.m42
local real z = v.x*m.m13+v.y*m.m23+v.z*m.m33+m.m43
local real w = v.x*m.m14+v.y*m.m24+v.z*m.m34+m.m44
set Output.x = (v.x*m.m11+v.y*m.m21+v.z*m.m31+m.m41)/w
set Output.y = y/w
set Output.z = z/w
return Output
endfunction
struct MATRIX3
static MATRIX3 Zero
static MATRIX3 E
real m11
real m12
real m13
real m21
real m22
real m23
real m31
real m32
real m33
static method New_0 takes nothing returns MATRIX3
local MATRIX3 this = MATRIX3.create()
set .m11 = 0
set .m12 = 0
set .m13 = 0
set .m21 = 0
set .m22 = 0
set .m23 = 0
set .m31 = 0
set .m32 = 0
set .m33 = 0
return this
endmethod
static method New_1 takes real r11, real r12, real r13, real r21, real r22, real r23, real r31, real r32, real r33 returns MATRIX3
local MATRIX3 this = MATRIX3.create()
set .m11 = r11
set .m12 = r12
set .m13 = r13
set .m21 = r21
set .m22 = r22
set .m23 = r23
set .m31 = r31
set .m32 = r32
set .m33 = r33
return this
endmethod
static method New_2 takes MATRIX3 m returns MATRIX3
local MATRIX3 this = MATRIX3.create()
set .m11 = m.m11
set .m12 = m.m12
set .m13 = m.m13
set .m21 = m.m21
set .m22 = m.m22
set .m23 = m.m23
set .m31 = m.m31
set .m32 = m.m32
set .m33 = m.m33
return this
endmethod
method SetValues takes real r11, real r12, real r13, real r21, real r22, real r23, real r31, real r32, real r33 returns MATRIX3
set .m11 = r11
set .m12 = r12
set .m13 = r13
set .m21 = r21
set .m22 = r22
set .m23 = r23
set .m31 = r31
set .m32 = r32
set .m33 = r33
return this
endmethod
method ToString takes nothing returns string
return "Matrux3 id "+I2S(this)+"\n"+R2S(.m11)+" "+R2S(.m12)+" "+R2S(.m13)+"\n"+R2S(.m21)+" "+R2S(.m22)+" "+R2S(.m23)+"\n"+R2S(.m31)+" "+R2S(.m32)+" "+R2S(.m33)
endmethod
endstruct
function Matrix3Multiply takes MATRIX3 Output, MATRIX3 M1, MATRIX3 M2 returns MATRIX3
return Output.SetValues(M1.m11*M2.m11+M1.m21*M2.m12+M1.m31*M2.m13,M1.m12*M2.m11+M1.m22*M2.m12+M1.m32*M2.m13,M1.m13*M2.m11+M1.m23*M2.m12+M1.m33*M2.m13,M1.m11*M2.m21+M1.m21*M2.m22+M1.m31*M2.m23,M1.m12*M2.m21+M1.m22*M2.m22+M1.m32*M2.m23,M1.m13*M2.m21+M1.m23*M2.m22+M1.m33*M2.m23,M1.m11*M2.m31+M1.m21*M2.m32+M1.m31*M2.m33,M1.m12*M2.m31+M1.m22*M2.m32+M1.m32*M2.m33,M1.m13*M2.m31+M1.m23*M2.m32+M1.m33*M2.m33)
endfunction
function Matrix3Scaling takes MATRIX3 Output, real x, real y, real z returns MATRIX3
return Output.SetValues(x,0,0,0,y,0,0,0,z)
endfunction
function Matrix3RotationX takes MATRIX3 Output, real a returns MATRIX3
return Output.SetValues(1,0,0,0,Cos(a),-Sin(a),0,Sin(a),Cos(a))
endfunction
function Matrix3RotationY takes MATRIX3 Output, real a returns MATRIX3
return Output.SetValues(Cos(a),0,Sin(a),0,1,0,-Sin(a),0,Cos(a))
endfunction
function Matrix3RotationZ takes MATRIX3 Output, real a returns MATRIX3
return Output.SetValues(Cos(a),-Sin(a),0,Sin(a),Cos(a),0,0,0,1)
endfunction
function Matrix3RotationAxis takes MATRIX3 Output, VECTOR3 v, real a returns MATRIX3
local real cosa = Cos(a)
local real sina = Sin(a)
return Output.SetValues(cosa+(1-cosa)*v.x*v.x,(1-cosa)*v.x*v.y-sina*v.z,(1-cosa)*v.x*v.z+sina*v.y,(1-cosa)*v.y*v.x+sina*v.z,cosa+(1-cosa)*v.y*v.y,(1-cosa)*v.y*v.z-sina*v.x,(1-cosa)*v.z*v.x-sina*v.y,(1-cosa)*v.z*v.y+sina*v.x,cosa+(1-cosa)*v.z*v.z)
endfunction
function Matrix3RotationYawPitchRoll takes MATRIX3 Output, real Yaw, real Pitch, real Roll returns MATRIX3
local real cosa = Cos(Yaw)
local real sina = Sin(Yaw)
local real cosb = Cos(Pitch)
local real sinb = Sin(Pitch)
local real cosy = Cos(Roll)
local real siny = Sin(Roll)
return Output.SetValues(cosa*cosb,cosa*sinb*siny-sina*cosy,cosa*sinb*cosy+sina*siny,sina*cosb,sina*sinb*siny+cosa*cosy,sina*sinb*cosy-cosa*siny,-sinb,cosb*siny,cosb*cosy)
endfunction
struct MATRIX4
static MATRIX4 Zero
static MATRIX4 E
real m11
real m12
real m13
real m14
real m21
real m22
real m23
real m24
real m31
real m32
real m33
real m34
real m41
real m42
real m43
real m44
static method New_0 takes nothing returns MATRIX4
local MATRIX4 this = MATRIX4.create()
set .m11 = 0
set .m12 = 0
set .m13 = 0
set .m14 = 0
set .m21 = 0
set .m22 = 0
set .m23 = 0
set .m24 = 0
set .m31 = 0
set .m32 = 0
set .m33 = 0
set .m34 = 0
set .m41 = 0
set .m42 = 0
set .m43 = 0
set .m44 = 0
return this
endmethod
static method New_1 takes real r11, real r12, real r13, real r14, real r21, real r22, real r23, real r24, real r31, real r32, real r33, real r34, real r41, real r42, real r43, real r44 returns MATRIX4
local MATRIX4 this = MATRIX4.create()
set .m11 = r11
set .m12 = r12
set .m13 = r13
set .m14 = r14
set .m21 = r21
set .m22 = r22
set .m23 = r23
set .m24 = r24
set .m31 = r31
set .m32 = r32
set .m33 = r33
set .m34 = r34
set .m41 = r41
set .m42 = r42
set .m43 = r43
set .m44 = r44
return this
endmethod
static method New_2 takes MATRIX4 m returns MATRIX4
local MATRIX4 this = MATRIX4.create()
set .m11 = m.m11
set .m12 = m.m12
set .m13 = m.m13
set .m14 = m.m14
set .m21 = m.m21
set .m22 = m.m22
set .m23 = m.m23
set .m24 = m.m24
set .m31 = m.m31
set .m32 = m.m32
set .m33 = m.m33
set .m34 = m.m34
set .m41 = m.m41
set .m42 = m.m42
set .m43 = m.m43
set .m44 = m.m44
return this
endmethod
static method New_3 takes MATRIX3 m returns MATRIX4
local MATRIX4 this = MATRIX4.create()
set .m11 = m.m11
set .m12 = m.m12
set .m13 = m.m13
set .m14 = 0
set .m21 = m.m21
set .m22 = m.m22
set .m23 = m.m23
set .m24 = 0
set .m31 = m.m31
set .m32 = m.m32
set .m33 = m.m33
set .m34 = 0
set .m41 = 0
set .m42 = 0
set .m43 = 0
set .m44 = 1
return this
endmethod
method SetValues takes real r11, real r12, real r13, real r14, real r21, real r22, real r23, real r24, real r31, real r32, real r33, real r34, real r41, real r42, real r43, real r44 returns MATRIX4
set .m11 = r11
set .m12 = r12
set .m13 = r13
set .m14 = r14
set .m21 = r21
set .m22 = r22
set .m23 = r23
set .m24 = r24
set .m31 = r31
set .m32 = r32
set .m33 = r33
set .m34 = r34
set .m41 = r41
set .m42 = r42
set .m43 = r43
set .m44 = r44
return this
endmethod
method ToString takes nothing returns string
return "Matrux4 id "+I2S(this)+"\n"+R2S(.m11)+" "+R2S(.m12)+" "+R2S(.m13)+" "+R2S(.m14)+"\n"+R2S(.m21)+" "+R2S(.m22)+" "+R2S(.m23)+" "+R2S(.m24)+"\n"+R2S(.m31)+" "+R2S(.m32)+" "+R2S(.m33)+" "+R2S(.m34)+"\n"+R2S(.m41)+" "+R2S(.m42)+" "+R2S(.m43)+" "+R2S(.m44)
endmethod
endstruct
function Matrix4Multiply takes MATRIX4 Output, MATRIX4 M1, MATRIX4 M2 returns MATRIX4
return Output.SetValues(M1.m11*M2.m11+M1.m21*M2.m12+M1.m31*M2.m13+M1.m41*M2.m14,M1.m12*M2.m11+M1.m22*M2.m12+M1.m32*M2.m13+M1.m42*M2.m14,M1.m13*M2.m11+M1.m23*M2.m12+M1.m33*M2.m13+M1.m43*M2.m14,M1.m14*M2.m11+M1.m24*M2.m12+M1.m34*M2.m13+M1.m44*M2.m14,M1.m11*M2.m21+M1.m21*M2.m22+M1.m31*M2.m23+M1.m41*M2.m24,M1.m12*M2.m21+M1.m22*M2.m22+M1.m32*M2.m23+M1.m42*M2.m24,M1.m13*M2.m21+M1.m23*M2.m22+M1.m33*M2.m23+M1.m43*M2.m24,M1.m14*M2.m21+M1.m24*M2.m22+M1.m34*M2.m23+M1.m44*M2.m24,M1.m11*M2.m31+M1.m21*M2.m32+M1.m31*M2.m33+M1.m41*M2.m34,M1.m12*M2.m31+M1.m22*M2.m32+M1.m32*M2.m33+M1.m42*M2.m34,M1.m13*M2.m31+M1.m23*M2.m32+M1.m33*M2.m33+M1.m43*M2.m34,M1.m14*M2.m31+M1.m24*M2.m32+M1.m34*M2.m33+M1.m44*M2.m34,M1.m11*M2.m41+M1.m21*M2.m42+M1.m31*M2.m43+M1.m41*M2.m44,M1.m12*M2.m41+M1.m22*M2.m42+M1.m32*M2.m43+M1.m42*M2.m44,M1.m13*M2.m41+M1.m23*M2.m42+M1.m33*M2.m43+M1.m43*M2.m44,M1.m14*M2.m41+M1.m24*M2.m42+M1.m34*M2.m43+M1.m44*M2.m44)
endfunction
private function Init takes nothing returns nothing
set VECTOR3.Zero = VECTOR3.New_0()
set VECTOR3.oneX = VECTOR3.New_1(1,0,0)
set VECTOR3.oneY = VECTOR3.New_1(0,1,0)
set VECTOR3.oneZ = VECTOR3.New_1(0,0,1)
set MATRIX3.Zero = MATRIX3.New_0()
set MATRIX3.E = MATRIX3.New_1(1,0,0,0,1,0,0,0,1)
set MATRIX4.Zero = MATRIX4.New_0()
set MATRIX4.E = MATRIX4.New_1(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)
endfunction
endlibrary
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
library Table
//***************************************************************
//* Table object 3.1
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht = InitHashtable()
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
library SimError initializer init
//**************************************************************************************************
//*
//* SimError
//*
//* Mimic an interface error message
//* call SimError(ForPlayer, msg)
//* ForPlayer : The player to show the error
//* msg : The error
//*
//* To implement this function, copy this trigger and paste it in your map.
//* Unless of course you are actually reading the library from wc3c's scripts section, then just
//* paste the contents into some custom text trigger in your map.
//*
//**************************************************************************************************
//==================================================================================================
globals
private sound error
endglobals
//====================================================================================================
function SimError takes player ForPlayer, string msg returns nothing
set msg="\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"+msg+"|r"
if (GetLocalPlayer() == ForPlayer) then
call ClearTextMessages()
call DisplayTimedTextToPlayer( ForPlayer, 0.52, 0.96, 2.00, msg )
call StartSound( error )
endif
endfunction
private function init takes nothing returns nothing
set error=CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
//call StartSound( error ) //apparently the bug in which you play a sound for the first time
//and it doesn't work is not there anymore in patch 1.22
endfunction
endlibrary
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
globals
// High enough so the unit is no longer visible, low enough so the
// game doesn't crash...
//
// I think you need 0.0 or soemthing negative prior to patch 1.22
//
private constant real EXTRA = 700.0
endglobals
//=========================================================================================
globals
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns nothing
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
local real x2=x
local real y2=y
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
if (x != x2) then
call SetUnitX(u,x)
endif
if (y != y2) then
call SetUnitY(u,y)
endif
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local region r=CreateRegion()
local rect rc
set minx=GetCameraBoundMinX() - EXTRA
set miny=GetCameraBoundMinY() - EXTRA
set maxx=GetCameraBoundMaxX() + EXTRA
set maxy=GetCameraBoundMaxY() + EXTRA
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddAction(t, function dis)
//this is not necessary but I'll do it anyway:
set t=null
set r=null
set rc=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library BonusMod requires optional AbilityPreload, optional xepreload
// modified by triggerhappy, module initializer
private keyword AbilityBonus
////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ BonusMod - v3.3.1
//@=============================================================================
//@ Credits:
//@-----------------------------------------------------------------------------
//@ Written by:
//@ Earth-Fury
//@ Based on the work of:
//@ weaaddar
//@-----------------------------------------------------------------------------
//@ If you use this system, please at least credit weaaddar. Without him, this
//@ system would not exist. I would be happy if you credited me as well.
//@=============================================================================
//@ Requirements:
//@-----------------------------------------------------------------------------
//@ This library is written in vJass and thus requires JASS Helper in order to
//@ function correctly. This library also uses the ObjectMerger created by
//@ PitzerMike. The ObjectMerger must be configured as an external tool for
//@ JASS Helper.
//@
//@ All of these things are present in the NewGen world editor.
//@
//@=============================================================================
//@ Introduction:
//@-----------------------------------------------------------------------------
//@ BonusMod is a system for applying reversible bonuses to certain stats, such
//@ as attack speed or mana regen, for specific units. Most of the bonuses
//@ provided by BonusMod show green or red numbers in the command card, exactly
//@ like the bonuses provided by items.
//@
//@ BonusMod has two kinds of bonuses:
//@ 1. Ability based bonuses
//@ 2. Code based bonuses
//@
//@ All of the bonuses in the configuration section for the basic BonusMod
//@ library are ability-based bonuses. Code-based bonuses are provided by
//@ additional libraries.
//@
//@ Ability based bonuses have a limit to how much of a bonus they can apply.
//@ The actual limit depends on the number of abilities that type of bonus uses.
//@ See the "Default bonuses" section of this readme for the default limits
//@ of the bonuses that come with BonusMod. For changing the limits of the
//@ default bonuses, or for adding new types of bonuses, see the below
//@ configuration section.
//@
//@ Code based bonuses may or may not have a limit to how much of a bonus they
//@ can apply. The limits for code based bonuses depend entirely on how the
//@ bonus is implemented. See their documentation for more information.
//@
//@=============================================================================
//@ Adding BonusMod to your map:
//@-----------------------------------------------------------------------------
//@ First, you must place the BonusMod library in a custom-text trigger in your
//@ map.
//@
//@ You must then save your map with ability generation enabled. After you save
//@ your map with ability generation enabled, you must close your map in the
//@ editor, and reopen it. You can then disable ability generation.
//@ See the configuration section for information on how to enable and disable
//@ ability generation.
//@
//@=============================================================================
//@ Default bonuses:
//@-----------------------------------------------------------------------------
//@
//@ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//@ | Bonus Type constants: | Minimum bonus: | Maximum bonus: |
//@ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//@ | BONUS_SIGHT_RANGE | -2048 | +2047 |
//@ | BONUS_ATTACK_SPEED | -512 | +511 |
//@ | BONUS_ARMOR | -1024 | +1023 |
//@ | BONUS_MANA_REGEN_PERCENT | -512% | +511% |
//@ | BONUS_LIFE_REGEN | -256 | +255 |
//@ | BONUS_DAMAGE | -1024 | +1023 |
//@ | BONUS_STRENGTH | -256 | +255 |
//@ | BONUS_AGILITY | -256 | +255 |
//@ | BONUS_INTELLIGENCE | -256 | +255 |
//@ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//@
//@ Notes:
//@ - The bonuses for stength, agility, and intelligence can only be
//@ applied to heroes. Attempting to add them to normal units will
//@ fail to work completely.
//@ - Using a negative BONUS_STRENGTH bonus can give a unit negative
//@ maximum life. Don't do that. It really messes stuff up.
//@ - Using a negative BONUS_INTELLIGENCE bonus can remove a hero's
//@ mana. This is not a big issue, as mana will return when the
//@ bonus is removed.
//@ - The maximum effective sight range for a unit is 1800.
//@ - There is a maximum attack speed. I have no idea what it is.
//@
//@ See the configuration section for information on how to change the range of
//@ bonuses, as well as how to add new ability-based bonuses, and remove unused
//@ ones.
//@
//@=============================================================================
//@ Public API / Function list:
//@-----------------------------------------------------------------------------
//@ Note that BonusMod will only output error messages if JASS Helper is set to
//@ compile in debug mode.
//@
//@ Bonus constants such as BONUS_DAMAGE have .min and .max properties which
//@ are the minimum and maximum bonus that type of bonus can apply. Note that
//@ for code based bonuses, these constants may not reflect the minimum or
//@ maximum bonus for a specific unit. Use the IsBonusValid() function to check
//@ if the given bonus value is okay for a given unit.
//@
//@ function SetUnitBonus
//@ takes unit u, Bonus bonusType, integer amount
//@ returns integer
//@
//@ This function sets the bonus of the type bonusType for the given unit to
//@ the given amount. The returned integer is the unit's actual current
//@ bonus, after it has been changed. If the given amount is above the
//@ maximum possible bonus for this type, then the maximum possible bonus
//@ is applied to the unit. The same is true if the given value is below
//@ the minimum possible bonus.
//@
//@ function GetUnitBonus
//@ takes unit u, Bonus bonusType
//@ returns integer
//@
//@ Returns the given unit's current bonus of bonusType. A value of 0 means
//@ that the given unit does not have a bonus of the given type.
//@
//@ function AddUnitBonus
//@ takes unit u, Bonus bonusType, integer amount
//@ returns integer
//@
//@ Increases the unit's bonus by the given amount. You can use a negitive
//@ amount to subtract from the unit's current bonus. Note that the same
//@ rules SetUnitBonus has apply for going over/under the maximum bonus.
//@ The returned value is the unit's actual new bonus.
//@
//@ function RemoveUnitBonus
//@ takes unit u, Bonus bonusType
//@ returns nothing
//@
//@ Sets the bonus of the type bonusType to 0 for the given unit. This
//@ function is faster then using SetUnitBonus(u, bonusType, 0).
//@
//@ function IsBonusValid
//@ takes unit u, Bonus abstractBonus, integer value
//@ returns boolean
//@
//@ Returns true if the given value is a valid bonus value for the given
//@ unit. This will also return false if the given bonus type is a hero-
//@ only bonus type, and the given unit is not a hero.
//@
//@=============================================================================
//@ Writing code-based bonuses:
//@-----------------------------------------------------------------------------
//@ This section of the readme tells you how to create your own bonus types
//@ that apply their bonuses using vJass code instead of abilities. You do not
//@ need to read or understand this to use BonusMod as-is.
//@
//@ Creating a new bonus type is simple. Extend the Bonus struct, implement the
//@ methods provided within it, and create a single instance of your struct
//@ within a variable named BONUS_YOUR_BONUS_TYPES_NAME of the type Bonus.
//@
//@ The methods you must implement are:
//@
//@ method setBonus takes unit u, integer amount returns integer
//@ This method sets the given unit's current bonus to amount, returning
//@ the actual bonus that was applied. If the given amount is higher then
//@ the maximum amount your bonus type can apply to a unit, you must apply
//@ the maximum possible bonus, and return that amount. The same holds true
//@ for the minimum bonus.
//@
//@ method getBonus takes unit u returns integer
//@ This method returns the current bonus the given unit has.
//@
//@ method removeBonus takes unit u returns nothing
//@ This method sets the current bonus of the given unit to 0.
//@
//@ method isValueInRange takes integer value returns boolean
//@ This method returns true if the given integer is a valid bonus amount
//@ for this bonus type, and false otherwise.
//@
//@ Note that it is your responsibility to do any clean up in the event a unit
//@ dies or is removed with an active bonus on it. There is no guarantee that
//@ removeBonus() will be called before a unit dies or is removed.
//@
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
////////////////////////////////////////////////////////////////////////////////
//==============================================================================
// Configuration:
//==============================================================================
//------------------------------------------------------------------------------
// If the following constant is set to true, the abilities used by this library
// will be preloaded at map initialization. This will slightly increase loading
// time, but will prevent a slight to medium lag spike the first time a bonus
// of a type is applied.
//
// Note that your map must contain either the xepreload library, or the
// AbilityPreload library for preloading to work.
//
// It is highly recommended that you do not set this to false.
//------------------------------------------------------------------------------
globals
private constant boolean PRELOAD_ABILITIES = true
public boolean Init
endglobals
//------------------------------------------------------------------------------
// The BonusMod_BeginBonuses macro takes a single boolean type parameter.
// If set to true, bonus abilities will be created (or recreated) on save.
// If set to false, abilities will not be generated.
//
// If you modify any of the bonus declaration macros, or add new ones, you must
// regenerate abilities.
//
// Note that if you remove a bonus, the abilities it had created will not be
// automatically removed. This is also true of reducing the number of abilities
// a bonus uses.
//
// After you generate abilities, you must close your map and reopen it in the
// editor. You can then disable ability generation until the next time you
// modify the bonus types.
//------------------------------------------------------------------------------
//! runtextmacro BonusMod_BeginBonuses("false")
//--------------------------------------------------------------------------
// Below are where bonus types are defined.
//
// The first parameter is the name of the bonus type. A constant will be
// generated for each bonus type, that will take the form: BONUS_NAME
//
// The second parameter is the maximum power of 2 the bonus type can add
// to a unit. For example, 8 abilities gives a range of -256 to +255.
//
// The third parameter is the base ability. The base ability must give the
// desired effect when the given field is changed.
//
// The fourth parameter is the rawcode prefix of the bonuses generated
// abilities. The prefix must be 3 characters long. Your map must not
// already contain bonuses which start with the given prefix.
//
// The fifth parameter is the object field to modify for each generated
// ability.
//
// The sixth parameter must be true of the bonus should only work on hero
// units, and false otherwise.
//
// The final parameter is the icon that will be displayed in the object
// editor. This has no effect on anything ingame.
//--------------------------------------------------------------------------
// | NAME |ABILITY|SOURCE |PREFIX|FIELD | HERO | ICON
// | | COUNT |ABILITY| | | ONLY |
//! runtextmacro BonusMod_DeclareBonus("ARMOR", "10", "AId1", "(A)", "Idef", "false", "BTNHumanArmorUpOne.blp")
//! runtextmacro BonusMod_DeclareBonus("DAMAGE", "10", "AItg", "(B)", "Iatt", "false", "BTNSteelMelee.blp")
//! runtextmacro BonusMod_DeclareBonus("SIGHT_RANGE", "11", "AIsi", "(C)", "Isib", "false", "BTNTelescope.blp")
//! runtextmacro BonusMod_DeclareBonus("LIFE_REGEN", "8", "Arel", "(E)", "Ihpr", "false", "BTNRingSkull.blp")
//! runtextmacro BonusMod_DeclareBonus("STRENGTH", "8", "AIa1", "(F)", "Istr", "true" , "BTNGoldRing.blp")
//! runtextmacro BonusMod_DeclareBonus("AGILITY", "8", "AIa1", "(G)", "Iagi", "true" , "BTNGoldRing.blp")
//! runtextmacro BonusMod_DeclareBonus("INTELLIGENCE", "8", "AIa1", "(H)", "Iint", "true" , "BTNGoldRing.blp")
// | NAME |ABILITY|SOURCE |PREFIX|FIELD |HERO | ICON
// | | COUNT |ABILITY| | | ONLY |
//! runtextmacro BonusMod_DeclarePercentBonus("ATTACK_SPEED", "9", "AIsx", "(I)", "Isx1", "false", "BTNGlove.blp")
//! runtextmacro BonusMod_DeclarePercentBonus("MANA_REGEN_PERCENT", "9", "AIrm", "(D)", "Imrp", "false", "BTNSobiMask.blp")
//! runtextmacro BonusMod_EndBonuses()
//==============================================================================
// End of configuration
//==============================================================================
//! textmacro BonusMod_BeginBonuses takes SHOULD_GENERATE_ABILITIES
private function Setup takes nothing returns nothing
set Init=true
// The following is a lua script for the ObjectMerger, used to generate abilities
//*
//! externalblock extension=lua ObjectMerger $FILENAME$
//! i if "$SHOULD_GENERATE_ABILITIES$" == "true" then
//! i function FormatName(name)
//! i name = string.lower(name)
//! i name = string.gsub(name, "_", " ")
//! i s = name
//! i name = ""
//! i for w in string.gmatch(s, "%a%w*") do
//! i name = name .. string.upper(string.sub(w, 1, 1)) .. string.sub(w, 2, -1)
//! i name = name .. " "
//! i end
//! i name = string.sub(name, 1, string.len(name) - 1)
//! i return name
//! i end
//! i function SetupAbility(name, suffix, icon, hero)
//! i makechange(current, "anam", "BonusMod - " .. FormatName(name))
//! i makechange(current, "ansf", "(" .. suffix .. ")")
//! i makechange(current, "aart", "ReplaceableTextures\\CommandButtons\\" .. icon)
//! i makechange(current, "aite", 0)
//! i if hero then
//! i makechange(current, "Iagi", 1, 0)
//! i makechange(current, "Iint", 1, 0)
//! i makechange(current, "Istr", 1, 0)
//! i end
//! i end
//! i function CreateAbility(sourceAbility, prefix, field, abilityCount, name, icon)
//! i powOf2 = abilityCount - 1
//! i lengthOfMax = string.len(tostring(2^abilityCount))
//! i for i = 0, powOf2 do
//! i padding = ""
//! i for k = 0, lengthOfMax - string.len(tostring(2^i)) - 1 do
//! i padding = padding .. "0"
//! i end
//! i createobject(sourceAbility, prefix .. string.sub(chars, i + 1, i + 1))
//! i SetupAbility(name, "+" .. padding .. tostring(2 ^ i), icon, true)
//! i makechange(current, field, 1, tostring(2^i))
//! i end
//! i createobject(sourceAbility, prefix .. "-")
//! i SetupAbility(name, "-" .. tostring(2 ^ abilityCount), icon, true)
//! i makechange(current, field, 1, tostring(-(2^abilityCount)))
//! i end
//! i function CreatePercentageAbility(sourceAbility, prefix, field, abilityCount, name, icon)
//! i powOf2 = abilityCount - 1
//! i lengthOfMax = string.len(tostring(2^abilityCount))
//! i for i = 0, powOf2 do
//! i padding = ""
//! i for k = 0, lengthOfMax - string.len(tostring(2^i)) - 1 do
//! i padding = padding .. "0"
//! i end
//! i createobject(sourceAbility, prefix .. string.sub(chars, i + 1, i + 1))
//! i SetupAbility(name, "+" .. padding .. tostring(2 ^ i) .. "%", icon, false)
//! i makechange(current, field, 1, tostring((2 ^ i) / 100))
//! i end
//! i createobject(sourceAbility, prefix .. "-")
//! i SetupAbility(name, "-" .. tostring(2 ^ abilityCount) .. "%", icon, false)
//! i makechange(current, field, 1, tostring(-((2 ^ abilityCount) / 100)))
//! i end
//! i setobjecttype("abilities")
//! i chars = "abcdefghijklmnopqrstuvwxyz"
//! i
// */
//! endtextmacro
//! textmacro BonusMod_DeclareBonus takes NAME, ABILITY_COUNT, SOURCE_ABILITY, RAWCODE_PREFIX, FIELD, HERO_ONLY, ICON
//! i CreateAbility("$SOURCE_ABILITY$", "$RAWCODE_PREFIX$", "$FIELD$", $ABILITY_COUNT$, "$NAME$", "$ICON$")
globals
Bonus BONUS_$NAME$
endglobals
set BONUS_$NAME$ = AbilityBonus.create('$RAWCODE_PREFIX$a', $ABILITY_COUNT$, '$RAWCODE_PREFIX$-', $HERO_ONLY$)
//! endtextmacro
//! textmacro BonusMod_DeclarePercentBonus takes NAME, ABILITY_COUNT, SOURCE_ABILITY, RAWCODE_PREFIX, FIELD, HERO_ONLY, ICON
//! i CreatePercentageAbility("$SOURCE_ABILITY$", "$RAWCODE_PREFIX$", "$FIELD$", $ABILITY_COUNT$, "$NAME$", "$ICON$")
globals
Bonus BONUS_$NAME$
endglobals
set BONUS_$NAME$ = AbilityBonus.create('$RAWCODE_PREFIX$a', $ABILITY_COUNT$, '$RAWCODE_PREFIX$-', $HERO_ONLY$)
//! endtextmacro
//! textmacro BonusMod_EndBonuses
//*
//! i end
//! endexternalblock
// */
endfunction
//! endtextmacro
// ===
// Precomputed integer powers of 2
// ===
globals
private integer array powersOf2
private integer powersOf2Count = 0
endglobals
// ===
// Utility functions
// ===
private function ErrorMsg takes string func, string s returns nothing
call BJDebugMsg("|cffFF0000BonusMod Error|r|cffFFFF00:|r |cff8080FF" + func + "|r|cffFFFF00:|r " + s)
endfunction
private function LoadAbility takes integer abilityId returns nothing
static if PRELOAD_ABILITIES then
static if LIBRARY_xepreload then
call XE_PreloadAbility(abilityId)
else
static if LIBRARY_AbilityPreload then
call AbilityPreload(abilityId)
endif
endif
endif
endfunction
// ===
// Bonus Types
// ===
private interface BonusInterface
integer minBonus = 0
integer maxBonus = 0
private method destroy takes nothing returns nothing defaults nothing
endinterface
private keyword isBonusObject
struct Bonus extends BonusInterface
boolean isBonusObject = false
public static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set this.isBonusObject = true
return this
endmethod
stub method setBonus takes unit u, integer amount returns integer
debug call ErrorMsg("Bonus.setBonus()", "I have no idea how or why you did this, but don't do it.")
return 0
endmethod
stub method getBonus takes unit u returns integer
debug call ErrorMsg("Bonus.getBonus()", "I have no idea how or why you did this, but don't do it.")
return 0
endmethod
stub method removeBonus takes unit u returns nothing
call this.setBonus(u, 0)
endmethod
stub method isValidBonus takes unit u, integer value returns boolean
return true
endmethod
method operator min takes nothing returns integer
return this.minBonus
endmethod
method operator max takes nothing returns integer
return this.maxBonus
endmethod
endstruct
private struct AbilityBonus extends Bonus
public integer count
public integer rawcode
public integer negativeRawcode
public integer minBonus = 0
public integer maxBonus = 0
public boolean heroesOnly
public static method create takes integer rawcode, integer count, integer negativeRawcode, boolean heroesOnly returns thistype
local thistype bonus = thistype.allocate()
local integer i
debug local boolean error = false
// Error messages
static if DEBUG_MODE then
if rawcode == 0 then
call ErrorMsg("AbilityBonus.create()", "Bonus constructed with a rawcode of 0?!")
call bonus.destroy()
return 0
endif
if count < 0 or count == 0 then
call ErrorMsg("AbilityBonus.create()", "Bonus constructed with an ability count <= 0?!")
call bonus.destroy()
return 0
endif
endif
// Grow powers of 2
if powersOf2Count < count then
set i = powersOf2Count
loop
exitwhen i > count
set powersOf2[i] = 2 * powersOf2[i - 1]
set i = i + 1
endloop
set powersOf2Count = count
endif
// Preload this bonus' abilities
static if PRELOAD_ABILITIES then
set i = 0
loop
exitwhen i == count
call LoadAbility(rawcode + i)
set i = i + 1
endloop
if negativeRawcode != 0 then
call LoadAbility(negativeRawcode)
endif
endif
// Set up this bonus object
set bonus.count = count
set bonus.negativeRawcode = negativeRawcode
set bonus.rawcode = rawcode
set bonus.heroesOnly = heroesOnly
// Calculate the minimum and maximum bonuses
if negativeRawcode != 0 then
set bonus.minBonus = -powersOf2[count]
else
set bonus.minBonus = 0
endif
set bonus.maxBonus = powersOf2[count] - 1
// Return the bonus object
return bonus
endmethod
// Interface methods:
method setBonus takes unit u, integer amount returns integer
return SetUnitBonus.evaluate(u, this, amount)
endmethod
method getBonus takes unit u returns integer
return GetUnitBonus.evaluate(u, this)
endmethod
method removeBonus takes unit u returns nothing
call RemoveUnitBonus.evaluate(u, this)
endmethod
public method isValidBonus takes unit u, integer value returns boolean
return (value >= this.minBonus) and (value <= this.maxBonus)
endmethod
endstruct
// ===
// Public API
// ===
function IsBonusValid takes unit u, Bonus abstractBonus, integer value returns boolean
local AbilityBonus bonus = AbilityBonus(abstractBonus)
static if DEBUG_MODE then
if not abstractBonus.isBonusObject then
call ErrorMsg("IsBonusValid()", "Invalid bonus type given")
endif
endif
if abstractBonus.min > value or abstractBonus.max < value then
return false
endif
if abstractBonus.getType() != AbilityBonus.typeid then
return abstractBonus.isValidBonus(u, value)
endif
if bonus.heroesOnly and not IsUnitType(u, UNIT_TYPE_HERO) then
return false
endif
return (value >= bonus.minBonus) and (value <= bonus.maxBonus)
endfunction
function RemoveUnitBonus takes unit u, Bonus abstractBonus returns nothing
local integer i = 0
local AbilityBonus bonus = AbilityBonus(abstractBonus)
static if DEBUG_MODE then
if not abstractBonus.isBonusObject then
call ErrorMsg("RemoveUnitBonus()", "Invalid bonus type given")
endif
endif
if abstractBonus.getType() != AbilityBonus.typeid then
call abstractBonus.removeBonus(u)
return
endif
if bonus.heroesOnly and not IsUnitType(u, UNIT_TYPE_HERO) then
debug call ErrorMsg("RemoveUnitBonus()", "Trying to remove a hero-only bonus from a non-hero unit")
return
endif
call UnitRemoveAbility(u, bonus.negativeRawcode)
loop
exitwhen i == bonus.count
call UnitRemoveAbility(u, bonus.rawcode + i)
set i = i + 1
endloop
endfunction
function SetUnitBonus takes unit u, Bonus abstractBonus, integer amount returns integer
local integer i
local integer output = 0
local AbilityBonus bonus = AbilityBonus(abstractBonus)
local boolean applyMinBonus = false
static if DEBUG_MODE then
if not abstractBonus.isBonusObject then
call ErrorMsg("SetUnitBonus()", "Invalid bonus type given")
endif
endif
if amount == 0 then
call RemoveUnitBonus(u, bonus)
return 0
endif
if abstractBonus.getType() != AbilityBonus.typeid then
return abstractBonus.setBonus(u, amount)
endif
if bonus.heroesOnly and not IsUnitType(u, UNIT_TYPE_HERO) then
debug call ErrorMsg("SetUnitBonus()", "Trying to set a hero-only bonus on a non-hero unit")
return 0
endif
if amount < bonus.minBonus then
debug call ErrorMsg("SetUnitBonus()", "Attempting to set a bonus to below its min value")
set amount = bonus.minBonus
elseif amount > bonus.maxBonus then
debug call ErrorMsg("SetUnitBonus()", "Attempting to set a bonus to above its max value")
set amount = bonus.maxBonus
endif
if amount < 0 then
set amount = -(bonus.minBonus - amount)
set applyMinBonus = true
endif
call UnitRemoveAbility(u, bonus.negativeRawcode)
set i = bonus.count - 1
loop
exitwhen i < 0
if amount >= powersOf2[i] then
call UnitAddAbility(u, bonus.rawcode + i)
call UnitMakeAbilityPermanent(u, true, bonus.rawcode + i)
static if DEBUG_MODE then
if GetUnitAbilityLevel(u, bonus.rawcode + i) <= 0 then
call ErrorMsg("SetUnitBonus()", "Failed to give the 2^" + I2S(i) + " ability to the unit!")
endif
endif
set amount = amount - powersOf2[i]
set output = output + powersOf2[i]
else
call UnitRemoveAbility(u, bonus.rawcode + i)
static if DEBUG_MODE then
if GetUnitAbilityLevel(u, bonus.rawcode + i) > 0 then
call ErrorMsg("SetUnitBonus()", "Unit still has the 2^" + I2S(i) + " ability after it was removed!")
endif
endif
endif
set i = i - 1
endloop
if applyMinBonus then
call UnitAddAbility(u, bonus.negativeRawcode)
call UnitMakeAbilityPermanent(u, true, bonus.negativeRawcode)
else
call UnitRemoveAbility(u, bonus.negativeRawcode)
endif
return output
endfunction
function GetUnitBonus takes unit u, Bonus abstractBonus returns integer
local integer i = 0
local integer amount = 0
local AbilityBonus bonus = AbilityBonus(abstractBonus)
static if DEBUG_MODE then
if not abstractBonus.isBonusObject then
call ErrorMsg("GetUnitBonus()", "Invalid bonus type given")
endif
endif
if abstractBonus.getType() != AbilityBonus.typeid then
return abstractBonus.getBonus(u)
endif
if bonus.heroesOnly and not IsUnitType(u, UNIT_TYPE_HERO) then
debug call ErrorMsg("GetUnitBonus()", "Trying to get a hero-only bonus from a non-hero unit")
return 0
endif
if GetUnitAbilityLevel(u, bonus.negativeRawcode) > 0 then
set amount = bonus.minBonus
endif
loop
exitwhen i == bonus.count
if GetUnitAbilityLevel(u, bonus.rawcode + i) > 0 then
set amount = amount + powersOf2[i]
endif
set i = i + 1
endloop
return amount
endfunction
function AddUnitBonus takes unit u, Bonus bonus, integer amount returns integer
return SetUnitBonus(u, bonus, GetUnitBonus(u, bonus) + amount)
endfunction
// ===
// Initialization
// ===
private module BonusModInitModule
private static method onInit takes nothing returns nothing
local integer i
// Set up powers of 2
set powersOf2[0] = 1
set powersOf2Count = 1
static if DEBUG_MODE and PRELOAD_ABILITIES and not LIBRARY_xepreload and not LIBRARY_AbilityPreload then
call ErrorMsg("Initialization", "PRELOAD_ABILITIES is set to true, but neither usable preloading library is detected")
endif
// Setup bonuses
call Setup()
endmethod
endmodule
struct BonusModStruct extends array
implement BonusModInitModule
static method addBonus takes unit u, Bonus bonus, integer amount returns integer
return SetUnitBonus(u, bonus, GetUnitBonus(u, bonus) + amount)
endmethod
endstruct
endlibrary
//TESH.scrollpos=172
//TESH.alwaysfold=0
library RegenBonuses initializer OnInit requires BonusMod, AutoIndex
////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ RegenBonuses for BonusMod
//@=============================================================================
//@ Credits:
//@-----------------------------------------------------------------------------
//@ Written by:
//@ Earth-Fury
//@=============================================================================
//@ Requirements:
//@-----------------------------------------------------------------------------
//@ This library requires the BonusMod library, and the AutoIndex library.
//@
//@=============================================================================
//@ Readme:
//@-----------------------------------------------------------------------------
//@ This library provides two new bonus types:
//@
//@ - BONUS_MANA_REGEN
//@ Regenerates an absolute amount of mana every second
//@
//@ - BONUS_LIFE_REGEN_PERCENT
//@ Regenerates a percent of a unit's maximum life every second
//@
//@ Any value is a valid bonus. There are no limits.
//@
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
////////////////////////////////////////////////////////////////////////////////
//==============================================================================
// Configuration:
//==============================================================================
//------------------------------------------------------------------------------
// The following constant determines how often the life and mana bonuses are
// applied to units. Note that this will not effect the values of the bonuses.
// The values of the bonuses will always be mana per second/percentage of life
// per second. This constant simply determines how often the values are updated.
//------------------------------------------------------------------------------
globals
private real REGEN_PERIOD = 0.32
endglobals
//==============================================================================
// End of configuration
//==============================================================================
globals
Bonus BONUS_MANA_REGEN
Bonus BONUS_LIFE_REGEN_PERCENT
endglobals
private function ErrorMsg takes string func, string s returns nothing
call BJDebugMsg("|cffFF0000RegenBonuses Error|r|cffFFFF00:|r |cff8080FF" + func + "|r|cffFFFF00:|r " + s)
endfunction
globals
private timer regenTimer = CreateTimer()
endglobals
private keyword BonusValues
private function DoRegen takes nothing returns nothing
local integer i
local BonusValues values
// Loop over all active bonuses
set i = 0
loop
exitwhen i == BonusValues.count
set values = BonusValues.all[i]
if GetWidgetLife(values.owner) > 0.0 then
if values.manaRegen > 0 then
// Do mana regen:
call SetUnitState(values.owner, UNIT_STATE_MANA, GetUnitState(values.owner, UNIT_STATE_MANA) + (values.manaRegen * REGEN_PERIOD))
endif
if values.lifeRegen > 0 then
// Do life regen:
call SetUnitState(values.owner, UNIT_STATE_LIFE, GetUnitState(values.owner, UNIT_STATE_LIFE) + (GetUnitState(values.owner, UNIT_STATE_MAX_LIFE) * (values.lifeRegen / 100.0)) * REGEN_PERIOD)
endif
endif
set i = i + 1
endloop
endfunction
private struct BonusValues
public integer manaRegen = 0
public integer lifeRegen = 0
public unit owner
private static thistype array owners
public static thistype array all
public static integer count = 0
public integer index
private static method create takes unit u returns thistype
local thistype this = thistype.allocate()
set this.owner = u
set thistype.owners[GetUnitId(u)] = this
if thistype.count == 0 then
call TimerStart(regenTimer, REGEN_PERIOD, true, function DoRegen)
endif
set thistype.all[thistype.count] = this
set this.index = thistype.count
set thistype.count = thistype.count + 1
return this
endmethod
private method onDestroy takes nothing returns nothing
set thistype.owners[GetUnitId(this.owner)] = thistype(0)
set thistype.all[this.index] = thistype.all[thistype.count]
set thistype.count = thistype.count - 1
if thistype.count == 0 then
call PauseTimer(regenTimer)
endif
set this.owner = null
endmethod
public static method getInstance takes unit u returns thistype
if thistype.owners[GetUnitId(u)] == 0 then
return thistype.create(u)
else
return thistype.owners[GetUnitId(u)]
endif
endmethod
public static method operator[] takes unit u returns thistype
return thistype.owners[GetUnitId(u)]
endmethod
public static method operator[]= takes unit u, thistype i returns nothing
set thistype.owners[GetUnitId(u)] = i
endmethod
endstruct
//! textmacro RegenBonuses_RegenBonusDefine takes NAME
private struct RegenBonus_$NAME$ extends Bonus
integer minBonus = -2147483648
integer maxBonus = 2147483647
method setBonus takes unit u, integer amount returns integer
local BonusValues values
if not IsUnitIndexed(u) then
debug call ErrorMsg("RegenBonus_$NAME$.setBonus()", "Unit that is not indexed by AutoIndex given")
return 0
endif
set values = BonusValues.getInstance(u)
set values.$NAME$Regen = amount
return amount
endmethod
method getBonus takes unit u returns integer
local BonusValues values
if not IsUnitIndexed(u) then
debug call ErrorMsg("RegenBonus_$NAME$.getBonus()", "Unit that is not indexed by AutoIndex given")
return 0
endif
set values = BonusValues[u]
if values == 0 then
return 0
endif
return values.$NAME$Regen
endmethod
method removeBonus takes unit u returns nothing
call this.setBonus(u, 0)
endmethod
method isValidBonus takes unit u, integer value returns boolean
return IsUnitIndexed(u)
endmethod
endstruct
//! endtextmacro
//! runtextmacro RegenBonuses_RegenBonusDefine("life")
//! runtextmacro RegenBonuses_RegenBonusDefine("mana")
private function OnLeaveMap takes unit u returns nothing
if BonusValues[u] != 0 then
call BonusValues[u].destroy()
endif
endfunction
private function OnInit takes nothing returns nothing
call OnUnitDeindexed(OnLeaveMap)
set BONUS_MANA_REGEN = RegenBonus_mana.create()
set BONUS_LIFE_REGEN_PERCENT = RegenBonus_life.create()
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library UnitMaxStateBonuses requires BonusMod, UnitMaxState, AutoIndex
////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ UnitMaxStateBonuses for BonusMod
//@=============================================================================
//@ Credits:
//@-----------------------------------------------------------------------------
//@ Written by:
//@ Earth-Fury
//@=============================================================================
//@ Requirements:
//@-----------------------------------------------------------------------------
//@ This library requires the BonusMod library, the UnitMaxState library by
//@ Earth-Fury, and the AutoIndex library by grim001.
//@
//@=============================================================================
//@ Readme:
//@-----------------------------------------------------------------------------
//@ This library provides two new bonus types:
//@
//@ - BONUS_LIFE
//@ - BONUS_MANA
//@
//@ These bonuses, of course, raise a unit's maximum life and mana.
//@
//@ Note that if you simply wish to permanently increase or decrease the maximum
//@ life or mana of a unit, you are likely best off using UnitMaxState directly.
//@
//@ The minimum life and mana bonuses are 1 more than -(unit's max state).
//@ That is to say, these bonuses can not fully remove a unit's life or mana.
//@
//@ There is no maximum bonus.
//@
//@ There is nothing to configure.
//@
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
////////////////////////////////////////////////////////////////////////////////
globals
Bonus BONUS_LIFE
Bonus BONUS_MANA
endglobals
private function ErrorMsg takes string func, string s returns nothing
call BJDebugMsg("|cffFF0000UnitMaxStateBonus Error|r|cffFFFF00:|r |cff8080FF" + func + "|r|cffFFFF00:|r " + s)
endfunction
private struct BonusValues
public integer mana = 0
public integer life = 0
public unit owner
private static thistype array owners
private static method create takes unit u returns thistype
local thistype this = thistype.allocate()
set this.owner = u
set thistype.owners[GetUnitId(u)] = this
return this
endmethod
private method onDestroy takes nothing returns nothing
set thistype.owners[GetUnitId(this.owner)] = thistype(0)
set this.owner = null
endmethod
public static method getInstance takes unit u returns thistype
if thistype.owners[GetUnitId(u)] == 0 then
return thistype.create(u)
else
return thistype.owners[GetUnitId(u)]
endif
endmethod
public static method operator[] takes unit u returns thistype
return thistype.owners[GetUnitId(u)]
endmethod
public static method operator[]= takes unit u, thistype i returns nothing
set thistype.owners[GetUnitId(u)] = i
endmethod
endstruct
//! textmacro UnitMaxStateBonus_DefineBonus takes NAME, STATE, MIN
private struct MaxStateBonus_$NAME$ extends Bonus
integer minBonus = -2147483648
integer maxBonus = 2147483647
method setBonus takes unit u, integer amount returns integer
local BonusValues values
local integer actual
local integer new
local real factor
if not IsUnitIndexed(u) then
debug call ErrorMsg("MaxStateBonus_$NAME$.setBonus()", "Unit that is not indexed by AutoIndex given")
return 0
endif
set values = BonusValues.getInstance(u)
set actual = R2I(GetUnitState(u, UNIT_STATE_MAX_$STATE$)) - values.$NAME$
set new = actual + amount
set factor = GetUnitState(u, UNIT_STATE_$STATE$) / GetUnitState(u, UNIT_STATE_MAX_$STATE$)
if new < $MIN$ then
if actual < $MIN$ then
set values.$NAME$ = 0
else
set values.$NAME$ = -actual + $MIN$
endif
call SetUnitState(u, UNIT_STATE_$STATE$, $MIN$)
call SetUnitMaxState(u, UNIT_STATE_MAX_$STATE$, $MIN$)
return values.$NAME$
else
set values.$NAME$ = amount
call SetUnitMaxState(u, UNIT_STATE_MAX_$STATE$, new)
call SetUnitState(u, UNIT_STATE_$STATE$, new * factor)
return values.$NAME$
endif
endmethod
method getBonus takes unit u returns integer
local BonusValues values
local integer actual
local integer new
local real factor
if not IsUnitIndexed(u) then
debug call ErrorMsg("MaxStateBonus_$NAME$.getBonus()", "Unit that is not indexed by AutoIndex given")
return 0
endif
set values = BonusValues[u]
if values == 0 then
return 0
endif
set values.$NAME$ = R2I(GetUnitState(u, UNIT_STATE_MAX_$STATE$)) - (R2I(GetUnitState(u, UNIT_STATE_MAX_$STATE$)) - values.$NAME$)
return values.$NAME$
endmethod
method removeBonus takes unit u returns nothing
call this.setBonus(u, 0)
endmethod
method isValidBonus takes unit u, integer value returns boolean
local integer currentBonus
if not IsUnitIndexed(u) then
return false
endif
set currentBonus = this.getBonus(u)
return R2I(GetUnitState(u, UNIT_STATE_MAX_$STATE$)) - currentBonus + value >= $MIN$
endmethod
endstruct
//! endtextmacro
//! runtextmacro UnitMaxStateBonus_DefineBonus("life", "LIFE", "1")
//! runtextmacro UnitMaxStateBonus_DefineBonus("mana", "MANA", "1")
private function OnLeaveMap takes unit u returns nothing
if BonusValues[u] != 0 then
call BonusValues[u].destroy()
endif
endfunction
private module StateInit
private static method onInit takes nothing returns nothing
set BONUS_LIFE = MaxStateBonus_life.create()
set BONUS_MANA = MaxStateBonus_mana.create()
call OnUnitDeindexed(OnLeaveMap)
endmethod
endmodule
private struct Data
implement StateInit
endstruct
endlibrary
//TESH.scrollpos=9
//TESH.alwaysfold=0
library MovementBonus initializer OnInit requires BonusMod, AutoIndex
////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ MovementBonus for BonusMod
//@=============================================================================
//@ Credits:
//@-----------------------------------------------------------------------------
//@ Written by:
//@ Earth-Fury
//@=============================================================================
//@ Requirements:
//@-----------------------------------------------------------------------------
//@ This library requires the BonusMod library, and the AutoIndex library.
//@
//@=============================================================================
//@ Readme:
//@-----------------------------------------------------------------------------
//@ This library provides one new bonus type:
//@
//@ - BONUS_MOVEMENT_SPEED
//@ Modifies a unit's movement speed.
//@
//@ A unit's movement speed can never go below or above the values configured
//@ in the gameplay constants for a map.
//@
//@ You should not modify a unit's movement speed any way other than via
//@ BonusMod. Doing so will cause issues.
//@
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
////////////////////////////////////////////////////////////////////////////////
//==============================================================================
// Configuration:
//==============================================================================
//------------------------------------------------------------------------------
// The following constants must be equal to the equivalent gameplay constants.
// By default, these are set to the default vlues for the gameplay constants.
//------------------------------------------------------------------------------
globals
constant integer MAX_UNIT_MOVEMENT = 400
constant integer MIN_UNIT_MOVEMENT = 150
constant integer MAX_BUILDING_MOVEMENT = 400
constant integer MIN_BUILDING_MOVEMENT = 25
endglobals
//==============================================================================
// End of configuration
//==============================================================================
globals
Bonus BONUS_MOVEMENT_SPEED
endglobals
private function ErrorMsg takes string func, string s returns nothing
call BJDebugMsg("|cffFF0000MovementBonus Error|r|cffFFFF00:|r |cff8080FF" + func + "|r|cffFFFF00:|r " + s)
endfunction
private struct BonusValue
public integer speed = 0
public unit owner
private static thistype array owners
public static thistype array all
public static integer count = 0
public integer index
private static method create takes unit u returns thistype
local thistype this = thistype.allocate()
set this.owner = u
set thistype.owners[GetUnitId(u)] = this
set thistype.all[thistype.count] = this
set this.index = thistype.count
set thistype.count = thistype.count + 1
return this
endmethod
private method onDestroy takes nothing returns nothing
set thistype.owners[GetUnitId(this.owner)] = thistype(0)
set thistype.all[this.index] = thistype.all[thistype.count]
set thistype.count = thistype.count - 1
set this.owner = null
endmethod
public static method getInstance takes unit u returns thistype
if thistype.owners[GetUnitId(u)] == 0 then
return thistype.create(u)
else
return thistype.owners[GetUnitId(u)]
endif
endmethod
public static method operator[] takes unit u returns thistype
return thistype.owners[GetUnitId(u)]
endmethod
public static method operator[]= takes unit u, thistype i returns nothing
set thistype.owners[GetUnitId(u)] = i
endmethod
endstruct
private struct MovementBonus extends Bonus
integer minBonus
integer maxBonus
static method create takes integer min, integer max returns thistype
local thistype this = allocate()
set this.minBonus = min
set this.maxBonus = max
return this
endmethod
method setBonus takes unit u, integer amount returns integer
local BonusValue value
local integer min
local integer max
local integer actualSpeed
if not IsUnitIndexed(u) then
debug call ErrorMsg("MovementBonus.setBonus()", "Unit that is not indexed by AutoIndex given")
return 0
endif
set value = BonusValue.getInstance(u)
if IsUnitType(u, UNIT_TYPE_STRUCTURE) then
set min = MIN_BUILDING_MOVEMENT
set max = MAX_BUILDING_MOVEMENT
else
set min = MIN_UNIT_MOVEMENT
set max = MAX_UNIT_MOVEMENT
endif
set actualSpeed = R2I(GetUnitMoveSpeed(u)) - value.speed
if actualSpeed + amount < min then
debug call ErrorMsg("MovementBonus.setBonus()", "Attempting to set a unit's speed below it's minimum")
set amount = -actualSpeed + min
elseif actualSpeed + amount > max then
debug call ErrorMsg("MovementBonus.setBonus()", "Ateempting to set a unit's speed above it's maximum")
set amount = max - actualSpeed
endif
call SetUnitMoveSpeed(u, actualSpeed + amount)
set value.speed = amount
return amount
endmethod
method getBonus takes unit u returns integer
local BonusValue value
if not IsUnitIndexed(u) then
debug call ErrorMsg("MovementBonus.getBonus()", "Unit that is not indexed by AutoIndex given")
return 0
endif
set value = BonusValue[u]
if value == 0 then
return 0
endif
return value.speed
endmethod
method removeBonus takes unit u returns nothing
call this.setBonus(u, 0)
endmethod
method isValidBonus takes unit u, integer value returns boolean
local integer min
local integer max
local integer currentBonus
local integer actualSpeed
if not IsUnitIndexed(u) then
return false
endif
if IsUnitType(u, UNIT_TYPE_STRUCTURE) then
set min = MIN_BUILDING_MOVEMENT
set max = MAX_BUILDING_MOVEMENT
else
set min = MIN_UNIT_MOVEMENT
set max = MAX_UNIT_MOVEMENT
endif
set actualSpeed = R2I(GetUnitMoveSpeed(u)) - this.getBonus(u)
if actualSpeed + value < min then
return false
elseif actualSpeed + value > max then
return false
else
return true
endif
endmethod
endstruct
private function OnLeaveMap takes unit u returns nothing
if BonusValue[u] != 0 then
call BonusValue[u].destroy()
endif
endfunction
private function OnInit takes nothing returns nothing
local integer min
local integer max
call OnUnitDeindexed(OnLeaveMap)
if MAX_UNIT_MOVEMENT > MAX_BUILDING_MOVEMENT then
set max = MAX_UNIT_MOVEMENT
else
set max = MAX_BUILDING_MOVEMENT
endif
if MIN_UNIT_MOVEMENT < MIN_BUILDING_MOVEMENT then
set min = MIN_UNIT_MOVEMENT
else
set min = MIN_BUILDING_MOVEMENT
endif
set BONUS_MOVEMENT_SPEED = MovementBonus.create(min, max)
endfunction
endlibrary
//TESH.scrollpos=9
//TESH.alwaysfold=0
library UnitMaxState requires optional AbilityPreload, optional xepreload
//==============================================================================
// UnitMaxState v2.1
//==============================================================================
// Credits:
//------------------------------------------------------------------------------
// Written By:
// Earth-Fury
//
// Original System By:
// Blade.dk
//
// Intermittent Version By:
// Deaod
//
// With Thanks To:
// - weaaddar for BonusMod and thus inspiration
// - PitzerMike for the ObjectMerger
// - Vexorian for vJass and Jass Helper
// - PipeDream for Grimoire
// - SFilip for TESH
// - MindWorX for maintaining NewGen
//------------------------------------------------------------------------------
// If you use this library in your map, please at least give credit to Blade.dk.
// Without him, this library would not exist.
//==============================================================================
// Introduction:
//------------------------------------------------------------------------------
// UnitMaxState is a library which allows you to modify a unit's maximum life,
// or maximum mana. To achieve this, the library abuses a bug with the AIlf and
// AImz abilities, which is too complex to explain here.
//
// I do believe it was indeed Blade.dk who initially found this bug. If not,
// it's still his system I stole and rewrote. Further, let me thank Deaod for
// writing up his version of this system, which inspired both the reality of
// this rewrite, and the method abilities are handled within it.
//
//==============================================================================
// Requirements:
//------------------------------------------------------------------------------
// UnitMaxState is written in vJass and requires the NewGen editor, or
// Jass Helper with PitzerMike's Object Merger configured for it.
//
// UnitMaxState requires the latest version of Jass Helper.
//
// Preloading of abilities requires either AbilityPreload or xepreload. Neither
// are required for the library to function; however, having one or the other
// will remove the slight delay the first time a unit's max state is changed.
//
//==============================================================================
// Using UnitMaxState:
//------------------------------------------------------------------------------
// UnitMaxState comes with two useful functions:
//
// nothing SetUnitMaxState(unit <target>, unitstate <state>, real <value>)
// Changes <target>'s unitstate <state> to be equal to <value>.
//
// nothing AddUnitMaxState(unit <target>, unitstate <state>, real <value>)
// Adds <value> to <target>'s <state> unitstate. Note that you can use
// negative values with this function.
//
// Both of these functions accept unitstate's other than UNIT_STATE_MAX_LIFE and
// UNIT_STATE_MAX_MANA. There is a small performance penalty in using these over
// direct usage of SetUnitState.
//
// You must not use life/mana boosting upgrades in combination with this system.
//
// Attempting to set a unit's maximum life below 1, or mana below 0 will do
// nothing. A debug message will be output if the script is compiled in
// debug mode.
//==============================================================================
//==============================================================================
// Configuration:
//------------------------------------------------------------------------------
// The below textmacro call is an all-in-one configuration line.
//------------------------------------------------------------------------------
// The first parameter is a boolean.
//
// If true, the abilities used by this system will be created on save. This adds
// a slight delay to saving your map. You only ever have to create the abilities
// the first time this library is added to your map, or if you modify any of
// the other configuration options.
//
// Note that to make the ability creation permanent, you must save with ability
// creation enabled, close your map, and reopen it in the editor. You can then
// disable ability creation, as the abilities will be permanently in your map.
//------------------------------------------------------------------------------
// The second parameter is an integer.
//
// This is the number of abilities this system will use for adding/removing
// life/mana. Note that this system uses four sets of abilities, so the actual
// number of abilities generated and used will be the value you pass here,
// multiplied by 4.
//
// The higher this number, the faster large bonuses will be added. This number
// should never have to go above 13. Between 3 and 5 will work fine for most
// maps.
//------------------------------------------------------------------------------
// The fourth and fifth parameters are 3 character prefixes for rawcodes.
//
// The first one is for the life-modifying abilities, while the second is for
// the mana-modifying abilities.
//
// Please, make sure your map has no abilities whose rawcodes begin with either
// of these prefixes before saving! Otherwise, those abilities will be
// overwritten. You can change these to any 3 character combination, if your
// map does already contain abilities whose rawcodes begin with these prefixes.
//------------------------------------------------------------------------------
//! runtextmacro UnitMaxState_Configuration("false", "3", "ZxL", "ZxM")
//------------------------------------------------------------------------------
// End of configuration
//------------------------------------------------------------------------------
//! textmacro UnitMaxState_Configuration takes LOAD_ABILITIES, ABILITY_COUNT, LIFE_PREFIX, MANA_PREFIX
//*
//! externalblock extension=lua ObjectMerger $FILENAME$
//! i function CreateAbilities(baseAbility, rawcodePrefix, field, name, icon)
//! i k = 0
//! i for sign = -1, 1, 2 do
//! i signStr = "+"
//! i if sign < 0 then
//! i signStr = "-"
//! i end
//! i j = 0
//! i for i = 0, (abilityCount - 1) * 3, 3 do
//! i j = j + 1
//! i createobject(baseAbility, rawcodePrefix .. string.sub(Chars, k + 1, k + 1))
//! i makechange(current, "anam", "UnitMaxState - " .. name)
//! i makechange(current, "ansf", "(" .. signStr .. tostring(j) .. ")")
//! i makechange(current, "aart", "ReplaceableTextures\\CommandButtons\\" .. icon)
//! i makechange(current, "aite", 0)
//! i makechange(current, "alev", 4)
//! i makechange(current, field, 1, 0)
//! i makechange(current, field, 2, 2^(i + 0) * sign)
//! i makechange(current, field, 3, 2^(i + 1) * sign)
//! i makechange(current, field, 4, 2^(i + 2) * sign)
//! i k = k + 1
//! i end
//! i end
//! i end
//! i if $LOAD_ABILITIES$ then
//! i setobjecttype("abilities")
//! i abilityCount = $ABILITY_COUNT$
//! i Chars = "abcdefghijklmnopqrstuvwxyz"
//! i CreateAbilities("AIlf", "$LIFE_PREFIX$", "Ilif", "Life", "BTNHealthStone.blp")
//! i CreateAbilities("AImz", "$MANA_PREFIX$", "Iman", "Mana", "BTNManaStone.blp")
//! i end
//! endexternalblock
// */
globals
private constant integer RAWCODE_LIFE = '$LIFE_PREFIX$a'
private constant integer RAWCODE_MANA = '$MANA_PREFIX$a'
public constant integer ABILITY_COUNT = $ABILITY_COUNT$
endglobals
//! endtextmacro
globals
private constant boolean PRELOAD_ABILITIES = false
private integer array POWERS_OF_2
endglobals
private function ErrorMsg takes string s returns nothing
debug call BJDebugMsg("SetUnitMaxState: " + s)
endfunction
function SetUnitMaxState takes unit target, unitstate state, real targetValue returns nothing
local integer difference
local integer rawcode
local integer abilityId
local integer abilityLevel
local integer currentAbility
if state == UNIT_STATE_MAX_LIFE then
set rawcode = RAWCODE_LIFE
if targetValue < 1 then
call ErrorMsg("You can not set a unit's max life to below 1")
return
endif
elseif state == UNIT_STATE_MAX_MANA then
set rawcode = RAWCODE_MANA
if targetValue < 0 then
call ErrorMsg("You can not set a unit's max mana to below 0")
return
endif
else
call SetUnitState(target, state, targetValue)
return
endif
set difference = R2I(targetValue) - R2I(GetUnitState(target, state))
if difference < 0 then
set difference = -difference
set rawcode = rawcode + ABILITY_COUNT
endif
set abilityId = ABILITY_COUNT - 1
set abilityLevel = 4
set currentAbility = rawcode + abilityId
loop
exitwhen difference == 0
if difference >= POWERS_OF_2[abilityId * 3 + (abilityLevel - 2)] then
call UnitAddAbility(target, currentAbility)
call SetUnitAbilityLevel(target, currentAbility, abilityLevel)
call UnitRemoveAbility(target, currentAbility)
set difference = difference - POWERS_OF_2[abilityId * 3 + (abilityLevel - 2)]
else
set abilityLevel = abilityLevel - 1
if abilityLevel <= 1 then
set abilityId = abilityId - 1
set abilityLevel = 4
set currentAbility = rawcode + abilityId
endif
endif
endloop
endfunction
function AddUnitMaxState takes unit target, unitstate state, real additionalValue returns nothing
call SetUnitMaxState(target, state, GetUnitState(target, state) + additionalValue)
endfunction
//! textmacro UnitMaxState_Preload takes RAWCODE
set i = 0
loop
exitwhen i == ABILITY_COUNT * 2 - 1
static if LIBRARY_AbilityPreload then
call AbilityPreload($RAWCODE$ + i)
elseif LIBRARY_xepreload then
call XE_PreloadAbility($RAWCODE$ + i)
endif
set i = i + 1
endloop
//! endtextmacro
private module UnitStatInit
private static method onInit takes nothing returns nothing
local integer i
local integer k
set i = 1
set POWERS_OF_2[0] = 1
loop
exitwhen i == ABILITY_COUNT * 2 * 2 * 3 + 1
set POWERS_OF_2[i] = POWERS_OF_2[i - 1] * 2
set i = i + 1
endloop
static if DEBUG_MODE and PRELOAD_ABILITIES and not LIBRARY_AbilityPreload and not LIBRARY_xepreload then
call ErrorMsg("Ability preloading was enabled, but neither of the supported preload libraries are present")
elseif PRELOAD_ABILITIES then
//! runtextmacro UnitMaxState_Preload("RAWCODE_LIFE")
//! runtextmacro UnitMaxState_Preload("RAWCODE_MANA")
endif
endmethod
endmodule
private struct Data
implement UnitStatInit
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library AutoIndex requires UnitDex
function interface IndexFunc takes unit u returns nothing
struct AutoIndex
private static IndexFunc array indexfuncs
private static integer indexfuncs_n = -1
private static IndexFunc array deindexfuncs
private static integer deindexfuncs_n = -1
private static IndexFunc indexfunc
static method getIndex takes unit u returns integer
return GetUnitId(u)
endmethod
static method isUnitIndexed takes unit u returns boolean
return u != null and UnitDex.Unit[GetUnitId(u)] == u
endmethod
static method getIndexDebug takes unit u returns integer
if u == null then
return 0
elseif GetUnitTypeId(u) == 0 then
call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
elseif UnitDex.Unit[GetUnitId(u)] != u and GetIssuedOrderId() != 852056 then
call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
endif
return getIndex(u)
endmethod
private static method onUnitIndexed_sub takes nothing returns nothing
call indexfunc.evaluate(GetEnumUnit())
endmethod
static method onUnitIndexed takes IndexFunc func returns nothing
set indexfuncs_n = indexfuncs_n + 1
set indexfuncs[indexfuncs_n] = func
if UnitDex.Initialized then //During initialization, evaluate the indexfunc for every preplaced unit.
set indexfunc = func
call ForGroup(UnitDex.Group, function AutoIndex.onUnitIndexed_sub)
endif
endmethod
static method onUnitDeindexed takes IndexFunc func returns nothing
set deindexfuncs_n = deindexfuncs_n + 1
set deindexfuncs[deindexfuncs_n] = func
endmethod
endstruct
function OnUnitIndexed takes IndexFunc func returns nothing
call AutoIndex.onUnitIndexed(func)
endfunction
function OnUnitDeindexed takes IndexFunc func returns nothing
call AutoIndex.onUnitDeindexed(func)
endfunction
endlibrary
//TESH.scrollpos=458
//TESH.alwaysfold=0
library AutoIndex
//===========================================================================
// Information:
//==============
//
// AutoIndex is a very simple script to utilize. Just call GetUnitId(unit) to
// get get the unique value assigned to a particular unit. AutoIndex differs from
// other unit indexing libraries because it automatically assigns an ID to each
// unit as it enters the map, and automatically frees that ID as the unit leaves
// the map. This gives you several advantages as the user:
//
// 1.) The GetUnitId function inlines directly to a GetUnitUserData call (or a
// LoadInteger call if UseUnitUserData is disabled.)
// 2.) You don't need to manually free IDs as units leave the map.
// 3.) Detecting removing units to free their indexes is O(1), and less costly
// performance-wise than a timer scanning the map for removed units.
//
// If you turn on debug mode, AutoIndex will become slower, but it will show
// you a variety of helpful error messages. It can detect the following problems:
// -Passing a null unit to GetUnitId
// -Passing a removed or decayed unit to GetUnitId
// -Code outside of AutoIndex has overwritten a unit's UserData value.
// -GetUnitId was used on a filtered unit (a unit you don't want indexed).
//
// AutoIndex also provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game, and
// handle the creation or destruction of attached data or other things.
//
//===========================================================================
// How to install AutoIndex:
//===========================
//
// 1.) Copy and paste this script into your map.
// 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
// ability for you. Close and re-open the map. After that, disable the macro
// to prevent the save delay.
//
//===========================================================================
// How to use AutoIndex:
//=======================
//
// So you can get a unique integer for each unit, but how do you use that to
// attach data to a unit? GetUnitId will always return a number in the range of
// 1-8190. This means it can be used as an array index, as demonstrated below:
//
// globals
// integer array IntegerData
// real array RealData
// SomeStruct array SomeStructData
// englobals
//
// function Example takes nothing returns nothing
// local unit u = CreateUnit(...)
// local integer id = GetUnitId(u)
// //You now have a unique index for the unit, so you can
// //attach or retrieve data about the unit using arrays.
// set IntegerData[id] = 5
// set RealData[id] = 25.0
// set SomeStructData[id] = SomeStruct.create()
// //If you have access to the same unit in another function, you can
// //retrieve the data by using GetUnitId() and reading the arrays.
// endfunction
//
// The UnitFilter function in the config section is provided so that you can
// make AutoIndex completely ignore any unit-types that don't want to be indexed.
// You may want to ignore dummy casters or system-private units, especially ones
// that use UnitUserData internally. You don't need to worry about xe dummy units,
// as those are automatically filtered.
//
//===========================================================================
// How to use OnUnitIndexed / OnUnitDeindexed:
//=============================================
//
// AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
// and the OnUnitDeindexed event when a unit leaves the map. Functions used
// as events must take a unit and return nothing. An example is given below:
//
// function UnitEntersMap takes unit u returns nothing
// call BJDebugMsg(GetUnitName(u)+" was indexed with the ID "+I2S(GetUnitId(u)))
// endfunction
//
// function UnitLeavesMap takes unit u returns nothing
// call BJDebugMsg(GetUnitName(u)+" was deindexed with the ID "+I2S(GetUnitId(u)))
// endfunction
//
// function Init takes nothing returns nothing
// call OnUnitIndexed(UnitEntersMap)
// call OnUnitDeindexed(UnitLeavesMap)
// endfunction
//
// As you can see, it works perfectly fine to call GetUnitId() on a unit
// during either of these events.
//
// If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from needing
// to manually enumerate preplaced units (or units created by initialization
// code that ran before OnUnitIndexed was called).
//
// OnUnitDeindexed runs while a unit still exists, which means you can
// still do things such as destroy special effects attached to the unit.
// The unit will cease to exist immediately after the event is over.
//
//===========================================================================
// AutoIndex API:
//================
//
// GetUnitId(unit) -> integer
// This function returns a unique ID in the range of 1-8190 for the
// specified unit. Use it to attach data to the unit. This function
// inlines directly to GetUnitUserData or LoadInteger if debug mode
// is disabled. If debug mode is enabled, it can display error mess-
// ages when passed a null, decayed or filtered unit.
//
// IsUnitIndexed(unit) -> boolean
// This function returns a boolean indicating whether the specified
// unit is indexed or not. A unit would not be indexed if you ignored
// it using the UnitFilter function, or if it is a xe dummy unit.
//
// OnUnitIndexed(IndexFunc)
// This function accepts an IndexFunc, which must take a unit and
// return nothing. The IndexFunc will be fired instantly whenever
// a unit enters the map. You may use GetUnitId on the unit. When
// you call this function during map initialization, every existing
// unit will be considered as entering the map.
//
// OnUnitDeindexed(IndexFunc)
// Same as above, but runs whenever a unit is leaving the map. When
// this event runs, the unit still exists, but it will cease to exist
// as soon as the event ends. You may use GetUnitId on the unit.
//
//===========================================================================
// Configuration:
//================
//! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
//Save your map with this Object Merger call enabled, then close and reopen your
//map. Disable it by removing the exclamation to remove the delay while saving.
globals
private constant integer LeaveDetectAbilityID = 'lvdt'
//This rawcode must match the parameter after "Adef" in the
//ObjectMergermacro above. You can change both if you want.
private constant boolean UseUnitUserData = true
//If this is set to true, UnitUserData will be used. You should only set
//this to false if something else in your map already uses UnitUserData.
//A hashtable will be used instead, but it is about 60% slower.
endglobals
public function UnitFilter takes nothing returns boolean
return true
endfunction
//Any units you filter out in this function will not be indexed.
//Use GetFilterUnit() to refer to the filtered unit. You do not
//need to filter out xe dummy units; they are already filtered.
//===========================================================================
// User functions:
//=================
function GetUnitId takes unit u returns integer
static if DEBUG_MODE then
return AutoIndex.getIndexDebug(u)
else
return AutoIndex.getIndex(u)
endif
endfunction
function IsUnitIndexed takes unit u returns boolean
return AutoIndex.isUnitIndexed(u)
endfunction
function interface IndexFunc takes unit u returns nothing
function OnUnitIndexed takes IndexFunc func returns nothing
call AutoIndex.onUnitIndexed(func)
endfunction
function OnUnitDeindexed takes IndexFunc func returns nothing
call AutoIndex.onUnitDeindexed(func)
endfunction
//===========================================================================
hook RemoveUnit AutoIndex.hook_RemoveUnit
hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
struct AutoIndex
private static trigger enter = CreateTrigger()
private static trigger status = CreateTrigger()
private static trigger creepdeath = CreateTrigger()
private static group preplaced = CreateGroup()
private static timer allowdecay = CreateTimer()
private static hashtable ht
private static boolean array dead
private static boolean array summoned
private static boolean array animated
private static boolean array nodecay
private static boolean array removing
private static IndexFunc array indexfuncs
private static integer indexfuncs_n = -1
private static IndexFunc array deindexfuncs
private static integer deindexfuncs_n = -1
private static IndexFunc indexfunc
private static unit array allowdecayunit
private static integer allowdecay_n = -1
private static boolean duringinit = true
private static boolean array altered
private static unit array idunit
//===========================================================================
static method getIndex takes unit u returns integer
static if UseUnitUserData then
return GetUnitUserData(u)
else
return LoadInteger(ht, 0, GetHandleId(u))
endif
endmethod
//Resolves to an inlinable one-liner after the static if.
static method getIndexDebug takes unit u returns integer
local integer index = getIndex(u)
if u == null then
call BJDebugMsg("AutoIndex error: Null unit passed to GetUnitId.")
elseif GetUnitTypeId(u) == 0 then
call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
elseif idunit[index] != u then
call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
endif
return index
endmethod
//If debug mode is enabled, use the getIndex method that shows errors.
static method setIndex takes unit u, integer index returns nothing
static if UseUnitUserData then
call SetUnitUserData(u, index)
else
call SaveInteger(ht, 0, GetHandleId(u), index)
endif
endmethod
//Resolves to an inlinable one-liner after the static if.
//===========================================================================
static method isUnitAnimateDead takes unit u returns boolean
return animated[getIndex(u)]
endmethod
//Don't use this; use IsUnitAnimateDead from StatusEvents instead.
static method isUnitIndexed takes unit u returns boolean
return u != null and idunit[getIndex(u)] == u
endmethod
//===========================================================================
private static method onUnitIndexed_sub takes nothing returns nothing
call indexfunc.evaluate(GetEnumUnit())
endmethod
//During initialization, evaluate the indexfunc for every preplaced unit.
static method onUnitIndexed takes IndexFunc func returns nothing
set indexfuncs_n = indexfuncs_n + 1
set indexfuncs[indexfuncs_n] = func
if duringinit then
set indexfunc = func
call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
endif
endmethod
static method onUnitDeindexed takes IndexFunc func returns nothing
set deindexfuncs_n = deindexfuncs_n + 1
set deindexfuncs[deindexfuncs_n] = func
endmethod
//===========================================================================
private static method hook_RemoveUnit takes unit whichUnit returns nothing
set removing[getIndex(whichUnit)] = true
endmethod
private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
set removing[getIndex(whichUnit)] = true
endmethod
//Intercepts whenever RemoveUnit or ReplaceUnitBJ is called and sets a flag.
private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
static if UseUnitUserData then
if IsUnitIndexed(whichUnit) then
if getIndex(whichUnit) == data then
call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
else
call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
if idunit[data] != null then
call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
endif
set altered[data] = true
endif
endif
endif
endmethod
//In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
//Displays an error message if outside code tries to alter a unit's index.
//===========================================================================
private static method allowDecay takes nothing returns nothing
local integer n = allowdecay_n
loop
exitwhen n < 0
set nodecay[getIndex(allowdecayunit[n])] = false
set allowdecayunit[n] = null
set n = n - 1
endloop
set allowdecay_n = -1
endmethod
//Iterate through all the units in the stack and allow them to decay again.
private static method detectStatus takes nothing returns boolean
local unit u = GetTriggerUnit()
local integer index = getIndex(u)
local integer n
if idunit[index] == u then //Ignore non-indexed units.
if not IsUnitType(u, UNIT_TYPE_DEAD) then
if dead[index] then //The unit was dead, but now it's alive.
set dead[index] = false //The unit has been resurrected.
//! runtextmacro optional RunStatusEvent("Resurrect")
//If StatusEvents is in the map, run the resurrection events.
if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
set summoned[index] = true //If the unit gained the summoned flag,
set animated[index] = true //it's been raised with Animate Dead.
//! runtextmacro optional RunStatusEvent("AnimateDead")
//If StatusEvents is in the map, run the Animate Dead events.
endif
endif
else
if not removing[index] and not dead[index] and not animated[index] then
set dead[index] = true //The unit was alive, but now it's dead.
set nodecay[index] = true //A dead unit can't decay for at least 0. seconds.
set allowdecay_n = allowdecay_n + 1 //Add the unit to a stack. After the timer
set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
//! runtextmacro optional RunStatusEvent("Death")
//If StatusEvents is in the map, run the Death events.
//! runtextmacro optional TransportUnload()
//If TransportEvents is in the map, remove the dead unit from whatever transport it's in.
elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
//If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
//If .animated was true and the unit is dead, the unit died and exploded.
//If .removing was true, the unit is being removed or replaced.
//! runtextmacro optional TransportUnload()
//If TransportEvents is in the map, remove the leaving unit from whatever transport it's in.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
//! runtextmacro optional TransportClean()
//If TransportEvents is in the map, and the leaving unit is a
//transport, clean the transport- related data from the unit.
call AutoIndex(index).destroy() //Free the index by destroying the AutoIndex struct.
set idunit[index] = null //Null this unit reference to prevent a leak.
endif
endif
endif
set u = null
return false
endmethod
private static method isUndefendOrder takes nothing returns boolean
//! runtextmacro optional TransportUnloadCheck()
//If TransportEvents is in the map, check whether a unit is unloading.
return GetIssuedOrderId() == 852056
endmethod
//===========================================================================
private static method unitEntersMap takes unit u returns nothing
local integer index
local integer n = 0
if getIndex(u) != 0 then //If a unit already has an ID, don't assign a new one.
return //This only happens if a unit leaves the entire map area.
endif
set index = create()
call setIndex(u, index) //Assign an index to the entering unit.
call UnitAddAbility(u, LeaveDetectAbilityID) //Add the leave detect ability to the entering unit.
call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD) //Reset all of the flags for the entering
set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //unit. These flags are necessary to detect
set animated[index] = false //when the unit leaves the map.
set nodecay[index] = false
set removing[index] = false
debug set altered[index] = false //In debug mode, this flag tracks wheter a unit's index was altered.
set idunit[index] = u //Attach the unit that is supposed to have this index to the index.
loop //Run the OnUnitIndexed events.
exitwhen n > indexfuncs_n
call indexfuncs[n].evaluate(u)
set n = n + 1
endloop
endmethod
private static method initPreplacedUnit takes nothing returns nothing
static if LIBRARY_xebasic then
if GetUnitTypeId(GetEnumUnit()) == XE_DUMMY_UNITID then
return //Don't index xe dummy units, and don't add
endif //them to the group of preplaced units.
endif
call GroupAddUnit(preplaced, GetEnumUnit()) //Assemble a group of all the preplaced units.
call unitEntersMap(GetEnumUnit()) //Initialize each preplaced unit.
return
endmethod
private static method initEnteringUnit takes nothing returns boolean
static if LIBRARY_xebasic then
if GetUnitTypeId(GetFilterUnit()) == XE_DUMMY_UNITID then
return false //Don't index xe dummy units, and don't add
endif //them to the group of preplaced units.
endif
if duringinit then
call GroupAddUnit(preplaced, GetFilterUnit())
//Add units that are created during initialization to the preplaced units group.
//This ensures that all units are noticed by OnUnitIndexed during initialization.
endif
call unitEntersMap(GetFilterUnit()) //Initialize each unit that enters the map.
return false
endmethod
//===========================================================================
private static method afterInit takes nothing returns nothing
set duringinit = false //Initialization is over; set a flag.
call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
call GroupClear(preplaced) //The preplaced units group is
call DestroyGroup(preplaced) //no longer needed, so clean it.
set preplaced = null
endmethod
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local group g = CreateGroup()
local integer i = 15
static if not UseUnitUserData then
set ht = InitHashtable() //Only create a hashtable if it will be used.
endif
loop
exitwhen i < 0
call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
//Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
call TriggerRegisterPlayerUnitEvent(status, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, function UnitFilter)
//Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event to notice Undefend orders. Ignore filtered units.
call GroupEnumUnitsOfPlayer(g, Player(i), Condition(function UnitFilter))
call ForGroup(g, function AutoIndex.initPreplacedUnit)
//Enum every non-filtered unit on the map during initialization and assign it a unique
//index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
set i = i - 1
endloop
call TriggerAddCondition(status, And(function AutoIndex.isUndefendOrder, function AutoIndex.detectStatus))
//The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
//And() is used here to avoid using a trigger action, which starts a new thread and is slower.
call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, function UnitFilter)
call TriggerAddCondition(creepdeath, function AutoIndex.detectStatus)
//The detectStatus method must also fire when a neutral hostile creep dies, in case it was
//sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
call RegionAddRect(maparea, bounds) //GetWorldBounds() contains the entire map area, including the shaded boundry areas.
call TriggerRegisterEnterRegion(enter, maparea, And(function UnitFilter, function AutoIndex.initEnteringUnit))
//Only the filter function of an EnterRegion trigger runs instantly when a unit is created.
//Using And() lets both the UnitFilter and indexing function run when a unit enters the map.
call TimerStart(CreateTimer(), 0., false, function AutoIndex.afterInit)
//After any time elapses, perform after-initialization actions.
call GroupClear(g)
call DestroyGroup(g)
call RemoveRect(bounds)
set g = null
set bounds = null
endmethod
endstruct
endlibrary
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'e001'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xepreload requires xebasic, optional TimerUtils
//************************************************************************
// xepreload 0.7 // modified by triggerhappy, module initialiazer
// ---------
// Ah, the joy of preloading abilities, it is such a necessary evil...
// Notice you are not supposed to use this system in places outside map init
//
// This one does the preloading and tries to minimize the hit on loading time
// for example, it only needs one single native call per ability preloaded.
//
//************************************************************************
//===========================================================================================================
globals
private unit dum=null
private constant boolean ALLOW_DEBUGGING=true
endglobals
private keyword DebugIdInteger2IdString
//inline friendly (when debug mode is off..)
function XE_PreloadAbility takes integer abilid returns nothing
call UnitAddAbility(dum, abilid)
static if DEBUG_MODE then
static if ALLOW_DEBUGGING then
if GetUnitAbilityLevel(dum, abilid) == 0 then
call BJDebugMsg("XE_PreloadAbility: Ability "+ GetObjectName(abilid) + " " + DebugIdInteger2IdString.evaluate(abilid)+" does not exist.")
endif
endif
endif
endfunction
// ................................................................................
//================================================================================
// Convert a integer id value into a 4-letter id code.
// * Taken from cheats.j so I don't have to code it again.
// * Used only on debug so making a whole library for it seemed silly
// * Private so people don't begin using xepreload just to call this function....
// * It will not work correctly if you paste this code in the custom script section
// due to the infamous % bug. Then again, if you do that then you probably
// deserve it....
//
private function DebugIdInteger2IdString takes integer value returns string
local string charMap = ".................................!.#$%&'()*+,-./0123456789:;<=>.@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~................................................................................................................................."
local string result = ""
local integer remainingValue = value
local integer charValue
local integer byteno
set byteno = 0
loop
set charValue = ModuloInteger(remainingValue, 256)
set remainingValue = remainingValue / 256
set result = SubString(charMap, charValue, charValue + 1) + result
set byteno = byteno + 1
exitwhen byteno == 4
endloop
return result
endfunction
//--------------------------------
private function kill takes nothing returns nothing
call RemoveUnit(dum)
set dum=null
static if (LIBRARY_TimerUtils ) then
call ReleaseTimer( GetExpiredTimer() )
else
call DestroyTimer(GetExpiredTimer())
endif
endfunction
private module PreloadInit
private static method onInit takes nothing returns nothing
local timer t
set dum = CreateUnit( Player(15), XE_DUMMY_UNITID, 0,0,0)
if( dum == null) then
debug call BJDebugMsg("xePreload : XE_DUMMY_UNITID ("+DebugIdInteger2IdString.evaluate(XE_DUMMY_UNITID)+") not added correctly to the map.")
endif
static if (LIBRARY_TimerUtils) then
set t=NewTimer()
else
set t=CreateTimer()
endif
call TimerStart(t,0.0,false,function kill)
set t=null
endmethod
endmodule
private struct Init extends array
implement PreloadInit
endstruct
endlibrary
//TESH.scrollpos=54
//TESH.alwaysfold=0
library AbilityPreload
//===========================================================================
// Information:
//==============
//
// Preloading removes the noticeable delay the first time an ability
// is loaded in a game. If an ability was not already on a pre-placed unit
// or a unit that was created during initialization, preloading is needed
// to prevent a delay.
//
//===========================================================================
// AbilityPreload API:
//=====================
//
// AbilityPreload(abilityid) :
// Call this before any time has elapsed to preload a specific
// ability. If debug mode is enabled, you will see an error message
// if you call this after initialization, or if you try to preload
// an ability that does not exist. Will inline to a UnitAddAbility
// call if debug mode is disabled.
//
// AbilityRangePreload(start, end) :
// Same as AbilityPreload, but preloads a range of abilities.
// It will iterates between the two rawcode values and preload
// every ability along the way. It will not show an error message
// for non-existent abilities.
//
//===========================================================================
// Configuration:
//================
globals
private constant integer PreloadUnitRawcode = 'zsmc'
//This is the rawcode for "Sammy!". It is never used and has no model,
//which makes an ideal preloading unit. Change it if you want to.
endglobals
//===========================================================================
globals
private unit PreloadUnit
endglobals
function AbilityPreload takes integer abilityid returns nothing
static if DEBUG_MODE then
if GetUnitTypeId(PreloadUnit) == 0 then
call BJDebugMsg("AbilityPreload error: Can't preload an ability after initialization")
return
endif
endif
call UnitAddAbility(PreloadUnit, abilityid)
static if DEBUG_MODE then
if GetUnitAbilityLevel(PreloadUnit, abilityid) == 0 then
call BJDebugMsg("AbilityPreload error: Attempted to preload a non-existent ability")
endif
endif
endfunction
function AbilityRangePreload takes integer start, integer end returns nothing
local integer i = 1
static if DEBUG_MODE then
if GetUnitTypeId(PreloadUnit) == 0 then
call BJDebugMsg("AbilityPreload error: Can't preload an ability after initialization")
return
endif
endif
if start > end then
set i = -1
endif
loop
exitwhen start > end
call UnitAddAbility(PreloadUnit, start)
set start = start + i
endloop
endfunction
//===========================================================================
private module PreloadInit
private static method onInit takes nothing returns nothing
set PreloadUnit = CreateUnit(Player(15), PreloadUnitRawcode, 0., 0., 0.)
call UnitApplyTimedLife(PreloadUnit, 0, .001)
call ShowUnit(PreloadUnit, false)
call UnitAddAbility(PreloadUnit, 'Aloc')
endmethod
endmodule
private struct Init extends array
implement PreloadInit
endstruct
endlibrary