- Joined
- Nov 30, 2007
- Messages
- 1,202
Caution: This is still work in progress, so the provided map is only a demo.
This is a dynamic menu system using the ability to change tooltip for different spell levels to change whats being displayed to the player. The objective here is to create a generic dialog/button system using spells any suggestions on how to improve it or make it GUI friendly is greatly appreciated.
Features
When you don't like the ability interface, that's pretty much it.
This is a dynamic menu system using the ability to change tooltip for different spell levels to change whats being displayed to the player. The objective here is to create a generic dialog/button system using spells any suggestions on how to improve it or make it GUI friendly is greatly appreciated.
Features
- Supports 24 players and is MPI
- Menus can have more options then you could possibly ever need.
- Each menu option can have custom defined icons for when they are enabled and disabled.
- Options can change their state between enabled and disabled.
- Note 2: You cannot exclude some options for certain players at this point (You would have to create one menu for each player if you want to accomplish this).
- When you don't want to use dialogue window but want to achieve something similar with less restrictions.
- Use cases: Interaction options with objects, simple equipment system, 11+ ability-inventory system, recepie system, NPC dialogue system, gate system, etc.
When you don't like the ability interface, that's pretty much it.
JASS:
public SpellOption selectedSpellOption
function GetSelectedSpellOption takes nothing returns SpellOption
struct SpellOption
public string label
public string text
public trigger trg
static method create takes trigger trg, string label, string text returns thistype
method destroy takes nothing returns nothing
struct SpellMenu
public string title
static method create takes string title returns thistype
method destroy takes nothing returns nothing
method open takes player p returns nothing
method close takes player p returns nothing
method refresh takes nothing returns nothing
method addO takes trigger t, string label, string text returns thistype
method removeO takes string label returns boolean
method getO takes integer index returns SpellOption
method findO takes string label returns SpellOption
method clear takes nothing returns nothing
JASS:
library SpellOptionsMenu uses Table
globals
private constant integer MAX_PLAYER_SLOT = 24
private constant integer AB_1 = 'A000'
private constant integer AB_2 = 'A001'
private constant integer AB_3 = 'A002'
private constant integer AB_4 = 'A003'
private constant integer AB_5 = 'A004'
private constant integer AB_6 = 'A005'
private constant integer AB_7 = 'A006'
private constant integer AB_8 = 'A007'
private constant integer AB_9 = 'A008'
private constant integer AB_10 = 'A009'
private constant integer AB_11 = 'A00A'
private constant integer AB_12 = 'A00B'
private constant integer AB_NEXT = 'A00C'
private constant integer D_1 = 'A00D'
private constant integer D_2 = 'A00E'
private constant integer D_3 = 'A00F'
private constant integer D_4 = 'A00G'
private constant integer D_5 = 'A00H'
private constant integer D_6 = 'A00I'
private constant integer D_7 = 'A00J'
private constant integer D_8 = 'A00K'
private constant integer D_9 = 'A00L'
private constant integer D_10 = 'A00M'
private constant integer D_11 = 'A00N'
private constant integer D_12 = 'A00O'
private constant integer OPTION_DUMMY = 'n000'
private constant string DEFAULT_DISBTN = "ReplaceableTextures\\CommandButtonsDisabled\\DISBTNHeal.blp"
private constant string DEFAULT_BTN = "ReplaceableTextures\\CommandButtons\\BTNHeal.blp"
private constant real OPTION_X = 0 // Should be a X coordinate where no player will have any vision over
private constant real OPTION_Y = 0 // Should be a Y coordinate where no player will have any vision over
private constant integer OPTIONS_PER_WINDOW = 11
endglobals
function GetSelectedSpellMenu takes nothing returns SpellMenu
return SpellMenu(udg_SBM_menuKey)
endfunction
function GetSelectedSpellIndex takes nothing returns integer
return udg_SBM_optionIndex
endfunction
function GetSelectedSpellOption takes nothing returns SpellOption
return SpellOption(udg_SBM_optionKey)
endfunction
struct SpellOption
public string label
public string text
public trigger trg
public boolean hidden
public boolean enabled
public string btn
public string disbtn
static method create takes trigger trg, string label, string text, string btn, string disbtn returns thistype
local thistype this = .allocate()
set this.btn = btn
set this.disbtn = disbtn
set this.trg = trg
set this.enabled = true
set this.label = label
set this.text = text
return this
endmethod
method destroy takes nothing returns nothing
set .trg = null
set .label = null
set .text = null
set .btn = null
set .disbtn = null
call .deallocate()
endmethod
method operator icon takes nothing returns string
if .enabled then
if .btn != null then
return .btn
endif
return DEFAULT_BTN
endif
if .disbtn != null then
return .disbtn
endif
return DEFAULT_DISBTN
endmethod
endstruct
struct SpellMenu
private static unit array playerMenuUnit[MAX_PLAYER_SLOT]
private static SpellMenu array playerOpenMenu[MAX_PLAYER_SLOT]
private static integer array playerWindow[MAX_PLAYER_SLOT]
private static integer array spell[13]
private static integer array passive[12]
private static string array hotkey[13]
private static Table spellTable
private string name
private Table options
private integer length
static method create takes string title returns thistype
local thistype this = .allocate()
set this.title = title
set this.length = 0
set this.options = Table.create()
return this
endmethod
method destroy takes nothing returns nothing
call .clear()
// Should deselect it selected
call .options.destroy()
call .deallocate()
endmethod
method operator title= takes string title returns nothing
set .name = title
call .refresh()
endmethod
method operator title takes nothing returns string
return .name
endmethod
method open takes player p returns nothing
local integer pid = GetPlayerId(p)
set thistype.playerOpenMenu[pid] = this
set thistype.playerWindow[pid] = 0
call .showWindow(p, pid, 0)
endmethod
method close takes player p returns nothing
local integer pid = GetPlayerId(p)
set thistype.playerOpenMenu[pid] = 0
call ShowUnit(thistype.playerMenuUnit[pid], false)
endmethod
method refresh takes nothing returns nothing
local integer i = 0
local player p
loop
exitwhen i == MAX_PLAYER_SLOT
if thistype.playerOpenMenu[i] == this then
call .open(Player(i))
endif
set i = i + 1
endloop
endmethod
method enableO takes integer index returns nothing
local SpellOption option = .options[index]
set option.enabled = true
endmethod
method disableO takes integer index returns nothing
local SpellOption option = .options[index]
set option.enabled = false
endmethod
method setBtnO takes integer index, string iconpath returns nothing
local SpellOption option = .options[index]
set option.btn = iconpath
endmethod
method setDisBtnO takes integer index, string iconpath returns nothing
local SpellOption option = .options[index]
set option.disbtn = iconpath
endmethod
method showO takes integer index returns nothing
local SpellOption option = .options[index]
set option.hidden = false
endmethod
method hideO takes integer index returns nothing
local SpellOption option = .options[index]
set option.hidden = true
endmethod
private method getNumWindows takes nothing returns integer
local integer maxWindow = .length/OPTIONS_PER_WINDOW
if ModuloInteger(.length, OPTIONS_PER_WINDOW) == 0 and .length != 0 then
set maxWindow = maxWindow - 1
endif
return maxWindow
endmethod
private method showWindow takes player p, integer pid, integer window returns nothing
local integer i
local integer j = 0
local SpellOption op
local integer maxWindow = .getNumWindows()
local integer curSpell
set thistype.playerWindow[pid] = window
if thistype.playerWindow[pid] > maxWindow then
set thistype.playerWindow[pid] = 0
endif
set i = thistype.playerWindow[pid]*OPTIONS_PER_WINDOW
set j = 0
loop
exitwhen j == 12
if i < .length then
set op = .options[i]
if op.enabled then
call SetPlayerAbilityAvailable(p, thistype.spell[j], true)
call SetPlayerAbilityAvailable(p, thistype.passive[j], false)
set curSpell = thistype.spell[j]
call BlzSetAbilityTooltip(curSpell, op.label + " " + thistype.hotkey[j], pid + 1)
else
call SetPlayerAbilityAvailable(p, thistype.spell[j], false)
call SetPlayerAbilityAvailable(p, thistype.passive[j], true)
set curSpell = thistype.passive[j]
endif
call BlzSetAbilityExtendedTooltip(curSpell, op.text, pid + 1)
call BlzSetAbilityTooltip(curSpell, op.label + " " + thistype.hotkey[j], pid + 1)
if GetLocalPlayer() == p then
call BlzSetAbilityIcon(curSpell, op.icon)
endif
else
call SetPlayerAbilityAvailable(p, thistype.spell[j], false)
call SetPlayerAbilityAvailable(p, thistype.passive[j], false)
endif
set i = i + 1
set j = j + 1
endloop
if .length > 12 then // Replaces last with next button if multiple windows
call SetPlayerAbilityAvailable(p, thistype.spell[11], false)
call SetPlayerAbilityAvailable(p, thistype.spell[12], true)
else
call SetPlayerAbilityAvailable(p, thistype.spell[12], false)
endif
call BlzSetUnitName(thistype.playerMenuUnit[pid], .name + " " + I2S(thistype.playerWindow[pid] + 1) + "/" + I2S(maxWindow + 1))
call ShowUnit(thistype.playerMenuUnit[pid], true)
if GetLocalPlayer() == p then
call ClearSelection()
call SelectUnit(thistype.playerMenuUnit[pid], true)
endif
endmethod
private method removeAt takes integer index returns boolean
local integer i = index
if index < 0 or index >= .length then
return false
endif
set .length = .length - 1
loop
exitwhen i == .length
set .options[i] = .options[i + 1]
set i = i + 1
endloop
call .options[.length].destroy()
call .options.remove(.length)
return true
endmethod
method indexOf takes string label returns integer
local integer i = 0
local SpellOption option
loop
exitwhen i == .length
set option = .options[i]
if option.label == label then
return i
endif
set i = i + 1
endloop
return -1
endmethod
// Rename to add
method pushO takes SpellOption option returns thistype
set .options[.length] = option
set .length = .length + 1
return this
endmethod
method insertO takes integer index, SpellOption option returns boolean
local integer i
if index < 0 or index > .length then
return false
endif
set i = .length
loop
exitwhen i == index
set .options[i] = options[i - 1]
set i = i - 1
endloop
set .length = .length + 1
return true
endmethod
//method deleteO takes integer index returns nothing
//endmehtod
// Change argument to SpellOption
method removeO takes string label returns boolean
local integer index = .indexOf(label)
local boolean result
if index == -1 then
return false
endif
set result = .removeAt(index)
call .refresh()
return result
endmethod
method getO takes integer index returns SpellOption
return .options[index]
endmethod
method findO takes string label returns SpellOption
local integer index = .indexOf(label)
if index == -1 then
return 0
endif
return .options[index]
endmethod
method clear takes nothing returns nothing
local integer i = .size - 1
loop
exitwhen i < 0
call .removeAt(i)
set i = i - 1
endloop
call .refresh()
endmethod
method size takes nothing returns integer
return .length
endmethod
private static method onOptionSelected takes nothing returns nothing
local player p = GetTriggerPlayer()
local integer pid = GetPlayerId(p)
local thistype menu = thistype.playerOpenMenu[pid]
local integer spell = GetSpellAbilityId()
local integer optIndex = thistype.playerWindow[pid]*OPTIONS_PER_WINDOW + thistype.spellTable[spell] - 1
local SpellOption opt = menu.getO(optIndex)
if spell == AB_NEXT then
call menu.showWindow(p, pid, thistype.playerWindow[pid] + 1)
else
set udg_SBM_optionKey = opt
set udg_SBM_menuKey = menu
set udg_SBM_optionIndex = optIndex
call TriggerExecute(opt.trg)
set udg_SBM_event = 0.
set udg_SBM_event = 1.
endif
set p = null
endmethod
private static method addAbilityWithLevel takes unit u, integer spell, integer lvl returns nothing
call UnitAddAbility(u, spell)
call SetUnitAbilityLevel(u, spell, lvl)
endmethod
private static method onInit takes nothing returns nothing
local player p
local integer h
local integer i = 0
local trigger t = CreateTrigger()
set SpellMenu.spell[0] = AB_1
set SpellMenu.spell[1] = AB_2
set SpellMenu.spell[2] = AB_3
set SpellMenu.spell[3] = AB_4
set SpellMenu.spell[4] = AB_5
set SpellMenu.spell[5] = AB_6
set SpellMenu.spell[6] = AB_7
set SpellMenu.spell[7] = AB_8
set SpellMenu.spell[8] = AB_9
set SpellMenu.spell[9] = AB_10
set SpellMenu.spell[10] = AB_11
set SpellMenu.spell[11] = AB_12
set SpellMenu.spell[12] = AB_NEXT
set SpellMenu.passive[0] = D_1
set SpellMenu.passive[1] = D_2
set SpellMenu.passive[2] = D_3
set SpellMenu.passive[3] = D_4
set SpellMenu.passive[4] = D_5
set SpellMenu.passive[5] = D_6
set SpellMenu.passive[6] = D_7
set SpellMenu.passive[7] = D_8
set SpellMenu.passive[8] = D_9
set SpellMenu.passive[9] = D_10
set SpellMenu.passive[10] = D_11
set SpellMenu.passive[11] = D_12
loop
set p = Player(i)
if (GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and /*
*/ GetPlayerController(p) == MAP_CONTROL_USER) then
set thistype.playerMenuUnit[i] = CreateUnit(p, OPTION_DUMMY, OPTION_X, OPTION_Y, 0)
call TriggerRegisterUnitEvent(t, thistype.playerMenuUnit[i], EVENT_UNIT_SPELL_FINISH )
call ShowUnit(thistype.playerMenuUnit[i], false)
set h = 0
loop
exitwhen h == 13
call addAbilityWithLevel(thistype.playerMenuUnit[i], SpellMenu.spell[h], i + 1) // enabled options
call addAbilityWithLevel(thistype.playerMenuUnit[i], SpellMenu.spell[h], i + 1) // Add next spell
if h < 12 then
call addAbilityWithLevel(thistype.playerMenuUnit[i], SpellMenu.passive[h], i + 1) // disabled options
endif
set h = h + 1
endloop
endif
set i = i + 1
exitwhen i == MAX_PLAYER_SLOT
endloop
call TriggerAddAction(t, function thistype.onOptionSelected)
set thistype.spellTable = Table.create()
set h = 0
loop
exitwhen h == 13
set thistype.spellTable[ SpellMenu.spell[h]] = h + 1
set thistype.hotkey[h] = BlzGetAbilityTooltip(SpellMenu.spell[h], 1)
set h = h + 1
endloop
endmethod
endstruct
endlibrary
JASS:
scope DoorMenu initializer Init
/* This is example usage of the library to manipulate a gate with a shared CallBack trigger */
globals
SpellMenu doorMenu
trigger trgCallBack
endglobals
private function Main takes nothing returns nothing
if GetTriggerUnit() == gg_unit_ncop_0002 then
call doorMenu.open(GetTriggerPlayer())
endif
endfunction
private function CallBack takes nothing returns nothing
local SpellOption option = GetSelectedSpellOption()
local destructable d = gg_dest_ATg3_0000
if option.label == "Open" then
call ModifyGateBJ(bj_GATEOPERATION_OPEN, d)
call doorMenu.addO(trgCallBack, "Close", "Closes the Door.")
call doorMenu.removeO("Open")
call doorMenu.removeO("Lock")
call BJDebugMsg("Door opened!")
elseif option.label == "Close" then
call ModifyGateBJ(bj_GATEOPERATION_CLOSE, d)
call doorMenu.addO(trgCallBack, "Lock", "Locks the door.")
call doorMenu.addO(trgCallBack, "Open", "Opens the door.")
call doorMenu.removeO("Close")
call BJDebugMsg("Door closed!")
elseif option.label == "Lock" then
set option.label = "Unlock"
set option.text = "Unlocks the door."
call doorMenu.removeO("Open")
// If we didn't add "Open" here we would need to manually refresh the selection
// since the option label and text has changed.
// call doorMenu.refresh()
call BJDebugMsg("Door locked!")
elseif option.label == "Unlock" then
set option.label = "Lock"
set option.text = "Locks the door."
call BJDebugMsg("Door unlocked!")
call doorMenu.addO(trgCallBack, "Open", "Opens the door.")
endif
endfunction
private function Init takes nothing returns nothing
local trigger tMain = CreateTrigger()
set trgCallBack = CreateTrigger()
call TriggerAddAction(trgCallBack, function CallBack)
call TriggerRegisterPlayerSelectionEventBJ(tMain, Player(0), true)
call TriggerAddAction(tMain, function Main)
set doorMenu = SpellMenu.create("Door Menu")
call doorMenu.addO(trgCallBack, "Lock", "Locks the door.")
call doorMenu.addO(trgCallBack, "Open", "Opens the door.")
endfunction
endscope
JASS:
scope LargeTest initializer Init
globals
private SpellMenu menu
private trigger trgCallBack
private integer MAX = 24
endglobals
private function Main takes nothing returns nothing
if GetTriggerUnit() == gg_unit_ncop_0003 then
call menu.open(GetTriggerPlayer())
endif
endfunction
private function CallBack takes nothing returns nothing
local SpellOption option = GetSelectedSpellOption()
local integer i = 0
loop
exitwhen i == MAX
if (option.label == "Option " + I2S(i + 1)) then
call BJDebugMsg("The Selection Option was found [" + I2S(i) + "]")
call SetUnitOwner(gg_unit_njks_0001, Player(i), true )
call SetUnitColor( gg_unit_ncop_0003, GetPlayerColor(Player(i)) )
endif
set i = i + 1
endloop
endfunction
private function Init takes nothing returns nothing
local integer i = 0
local trigger tMain = CreateTrigger()
set trgCallBack = CreateTrigger()
call TriggerAddAction(trgCallBack, function CallBack)
call TriggerRegisterPlayerSelectionEventBJ(tMain, Player(0), true)
call TriggerRegisterPlayerSelectionEventBJ(tMain, Player(1), true)
call TriggerRegisterPlayerSelectionEventBJ(tMain, Player(22), true)
call TriggerRegisterPlayerSelectionEventBJ(tMain, Player(23), true)
call TriggerAddAction(tMain, function Main)
set menu = SpellMenu.create("Large Menu")
loop
exitwhen i == MAX
call menu.addO(trgCallBack, "Option " + I2S(i + 1), "Select this option...")
set i = i + 1
endloop
endfunction
endscope
- Add event detection for OnMenuClosed that can be used by the user.
- Add event detection for OnMenuOpened that can be used by the user.
- Remove refresh from being an internal method call.
- Add disabled icons for each spell so that an option can be seen but not touched, alternatively just disable the callback (but still having them selectable).
- Change from using a trigger execute as call back to real-event variable change
- Make it possible to show/hide options without having to remove them from the menu.
- (Make it possible to show/hide and enable/disable option for specific players)
- Added
method enableO takes integer index returns nothing
- Added
method disableO takes integer index returns nothing
- Added
method setBtnO takes integer index, string iconpath returns nothing
- Added
method setDisBtnO takes integer index, string iconpath returns nothing
- Added:
method indexOf takes string label returns integer
- Changed:
frommethod addO takes trigger t, string label, string text, string icon returns boolean
tomethod addO takes SpellOption option returns boolean
- Attemting to open an empty menu will no longer work.
Attachments
Last edited: