24 Commits

Author SHA1 Message Date
Eduardo Bart
d4370e7c5d Fix issue #127 2012-10-24 18:51:57 -02:00
Eduardo Bart
8bb115d6d4 Fix issue #134 2012-10-24 18:03:15 -02:00
Eduardo Bart
c7890e7a49 Fix issue #124 2012-10-23 18:16:14 -02:00
Eduardo Bart
a6424f3022 Fix issue #118 2012-10-23 17:32:04 -02:00
Eduardo Bart
7114946278 Fix issue #112 2012-10-23 17:15:59 -02:00
Eduardo Bart
5f72488eba Fix issue #121 2012-10-23 17:09:39 -02:00
Eduardo Bart
6acdb0fd64 Fix issue #132 2012-10-23 16:00:12 -02:00
Samuel
7f864003d8 Converted cooldownbar to miniwindow 2012-10-13 21:33:04 +02:00
Samuel
053d29a64b Finishing Minimap Icons
Added rightclick menues:
- on map mark: 'Delete Mark'
- on minimap: 'Create Mark'

Dialog:
http://i.imgur.com/BY33k.png
2012-10-12 01:42:10 +02:00
Samuel
3990ee76e7 Minimap Icons
Now server can call:
doPlayerAddMapMark(cid, pos, icon, description)
and it will be parsed.

TODO:
Adding map icons by rightclick on minimap menu.
Removing icons.

Needs to be tested when switching between different versions.
2012-10-11 17:36:00 +02:00
Samuel
f48fb4343f Moveable Tabbars, Stretch/Shrink Fix
Added key 'moveable' to tabbars
(tabbars are ordered with margins now, not with anchors to the previous
widget)

If stretching is forbidden by options the mapPanel will now update when
the window is resized
2012-10-10 22:20:32 +02:00
Samuel
478e796dbd Option: Don't Stretch/Shrink Game Window
Sets gameMapPanel to size 480 x 352
Prevents resizing.
2012-10-10 01:25:50 +02:00
Samuel
95e46dbbaf Fix: Multiline npc messages
- Multiline npc messages with curly braces will now display correctly
- Curly braces are no longer shown in screen
- Removed some tabs  in spellsystem :S My bad ..

TODO: Hightlight text in screen area not only in console?
2012-10-09 20:34:39 +02:00
Samuel
c0a3b083f6 Fix loading, style, tooltip 2012-10-09 03:43:52 +02:00
Eduardo Bart
ba407072a5 Merge https://github.com/TheSumm/otclient 2012-10-08 22:00:53 -03:00
Samuel
286a0fea58 Update to cooldown panel
- Removed cooldowns from game interface
- Using UIProgressRect
2012-10-09 02:46:23 +02:00
TheSumm
adc89f132f Merge pull request #116 from TheSumm/spellSystem
Spell Cooldowns for 8.70+
2012-10-08 12:50:10 -07:00
Samuel
01993c133d Merge branch 'master' of https://github.com/TheSumm/otclient 2012-10-08 21:45:03 +02:00
otfallen
5227a65d74 The smallest change of 2012 2012-10-08 21:41:26 +02:00
Samuel
af6a32263c Spell Cooldowns for 8.70+
- Moved SpelllistSettings to spells.lua
- Added cooldownbar for 8.70+
2012-10-08 21:32:25 +02:00
TheSumm
efdfb0e946 Merge pull request #115 from TheSumm/spellSystem
Custom spell support & tweaks
2012-10-07 21:59:45 -07:00
Samuel
b4642f9038 Custom spell support & tweaks
- Description available
- Easy setup for custom spells
- Sample custom spells
2012-10-08 06:51:25 +02:00
otfallen
f26b359ae5 The smallest change of 2012 2012-10-08 01:04:14 +00:00
Eduardo Bart
ec8a9eddf2 Next version will be 0.5.5 2012-10-07 15:37:57 -03:00
47 changed files with 1228 additions and 328 deletions

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 2.6)
project(otclient) project(otclient)
set(VERSION "0.5.4") set(VERSION "0.5.5")
set(FRAMEWORK_SOUND ON) set(FRAMEWORK_SOUND ON)
set(FRAMEWORK_GRAPHICS ON) set(FRAMEWORK_GRAPHICS ON)

View File

@@ -7,9 +7,6 @@ g_logger.setLogFile(g_resources.getWorkDir() .. g_app.getCompactName() .. ".log"
-- print first terminal message -- print first terminal message
g_logger.info(g_app.getName() .. ' ' .. g_app.getVersion() .. ' rev ' .. g_app.getBuildRevision() .. ' (' .. g_app.getBuildCommit() .. ') built on ' .. g_app.getBuildDate() .. ' for arch ' .. g_app.getBuildArch()) g_logger.info(g_app.getName() .. ' ' .. g_app.getVersion() .. ' rev ' .. g_app.getBuildRevision() .. ' (' .. g_app.getBuildCommit() .. ') built on ' .. g_app.getBuildDate() .. ' for arch ' .. g_app.getBuildArch())
--add base folder to search path
g_resources.addSearchPath(g_resources.getWorkDir())
-- add modules directory to the search path -- add modules directory to the search path
if not g_resources.addSearchPath(g_resources.getWorkDir() .. "modules", true) then if not g_resources.addSearchPath(g_resources.getWorkDir() .. "modules", true) then
g_logger.fatal("Unable to add modules directory to the search path.") g_logger.fatal("Unable to add modules directory to the search path.")

View File

@@ -42,13 +42,17 @@ Panel
id: fullscreen id: fullscreen
!text: tr('Fullscreen') !text: tr('Fullscreen')
OptionCheckBox
id: dontStretchShrink
!text: tr('Don\'t stretch/shrink Game Window')
Label Label
id: backgroundFrameRateLabel id: backgroundFrameRateLabel
!text: tr('Game framerate limit: %s', 'max') !text: tr('Game framerate limit: %s', 'max')
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: prev.bottom anchors.top: prev.bottom
margin-top: 6 margin-top: 16
@onSetup: | @onSetup: |
local value = Options.getOption('backgroundFrameRate') local value = Options.getOption('backgroundFrameRate')
local text = value local text = value

View File

@@ -5,6 +5,7 @@ local defaultOptions = {
showFps = true, showFps = true,
showPing = true, showPing = true,
fullscreen = false, fullscreen = false,
dontStretchShrink = false,
classicControl = false, classicControl = false,
walkBooster = false, walkBooster = false,
smartWalk = false, smartWalk = false,
@@ -144,6 +145,10 @@ function Options.setOption(key, value)
end) end)
elseif key == 'fullscreen' then elseif key == 'fullscreen' then
g_window.setFullscreen(value) g_window.setFullscreen(value)
elseif key == 'dontStretchShrink' then
addEvent(function()
modules.game_interface.updateStretchShrink()
end)
elseif key == 'enableMusic' then elseif key == 'enableMusic' then
g_sounds.enableMusic(value) g_sounds.enableMusic(value)
elseif key == 'showLeftPanel' then elseif key == 'showLeftPanel' then

View File

@@ -17,7 +17,7 @@ OptionCheckBox < CheckBox
MainWindow MainWindow
id: optionsWindow id: optionsWindow
!text: tr('Options') !text: tr('Options')
size: 350 280 size: 350 290
@onEnter: Options.hide() @onEnter: Options.hide()
@onEscape: Options.hide() @onEscape: Options.hide()

View File

@@ -11,13 +11,8 @@ TabBarButton < UIButton
color: #aaaaaa color: #aaaaaa
anchors.top: parent.top anchors.top: parent.top
padding: 5 padding: 5
anchors.left: parent.left
$first:
anchors.left: parent.left
$!first:
anchors.left: prev.right
margin-left: 5
$hover !checked: $hover !checked:
image-clip: 0 20 20 20 image-clip: 0 20 20 20

View File

@@ -9,7 +9,9 @@ function UISplitter.create()
end end
function UISplitter:onHoverChange(hovered) function UISplitter:onHoverChange(hovered)
if hovered then -- Check if margin can be changed
local margin = (self.vertical and self:getMarginBottom() or self:getMarginRight())
if hovered and (self:canUpdateMargin(margin + 1) ~= margin or self:canUpdateMargin(margin - 1) ~= margin) then
if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end
if self:getWidth() > self:getHeight() then if self:getWidth() > self:getHeight() then
g_mouse.setVerticalCursor() g_mouse.setVerticalCursor()

View File

@@ -6,6 +6,60 @@ local function onTabClick(tab)
tab.tabBar:selectTab(tab) tab.tabBar:selectTab(tab)
end end
local function updateMargins(tabBar)
if #tabBar.tabs == 0 then return end
local currentMargin = 0
for i = 1, #tabBar.tabs do
if i == 1 then
tabBar.tabs[i]:setMarginLeft(0)
else
tabBar.tabs[i]:setMarginLeft(5 * (i - 1) + currentMargin)
end
currentMargin = currentMargin + tabBar.tabs[i]:getWidth()
end
end
local function onTabMousePress(tab, mousePos, mouseButton)
if mouseButton == MouseLeftButton and tab.tabBar.tabsMoveable then
tab.tabBar.selected = tab
end
end
local function onTabMouseRelease(tab, mousePos, mouseButton)
local tabs = tab.tabBar.tabs
if tab.tabBar.selected then
local lastMargin = -5
for i = 1, #tabs do
local nextMargin = tabs[i + 1] and (tabs[i + 1] == tab and (tabs[i]:getMarginLeft() + tabs[i]:getWidth() + 5) or tabs[i + 1]:getMarginLeft()) or tab.tabBar:getWidth()
if tab:getMarginLeft() >= lastMargin and tab:getMarginLeft() < nextMargin then
if tabs[i] ~= tab then
local newIndex = table.find(tab.tabBar.tabs, tab.tabBar.tabs[i])
table.remove(tab.tabBar.tabs, table.find(tab.tabBar.tabs, tab))
table.insert(tab.tabBar.tabs, newIndex, tab)
updateMargins(tab.tabBar)
break
else
updateMargins(tab.tabBar)
break
end
end
lastMargin = tab.tabBar.tabs[i]:getMarginLeft() == 0 and -5 or tab.tabBar.tabs[i]:getMarginLeft()
end
end
tab.tabBar.selected = nil
end
local function onTabMouseMove(tab, mousePos, mouseMoved)
if tab == tab.tabBar.selected then
local newMargin = tab:getMarginLeft() + mouseMoved.x
if newMargin >= -5 and newMargin < tab.tabBar:getWidth() - tab:getWidth() then
tab:setMarginLeft(newMargin)
end
end
end
local function tabBlink(tab) local function tabBlink(tab)
if not tab.blinking then return end if not tab.blinking then return end
tab:setOn(not tab:isOn()) tab:setOn(not tab:isOn())
@@ -17,6 +71,8 @@ function UITabBar.create()
local tabbar = UITabBar.internalCreate() local tabbar = UITabBar.internalCreate()
tabbar:setFocusable(false) tabbar:setFocusable(false)
tabbar.tabs = {} tabbar.tabs = {}
tabbar.selected = nil -- dragged tab
tabsMoveable = false
return tabbar return tabbar
end end
@@ -41,16 +97,52 @@ function UITabBar:addTab(text, panel)
tab:setText(text) tab:setText(text)
tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight()) tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight())
tab.onClick = onTabClick tab.onClick = onTabClick
tab.onMousePress = onTabMousePress
tab.onMouseRelease = onTabMouseRelease
tab.onMouseMove = onTabMouseMove
tab.onDestroy = function() tab.tabPanel:destroy() end tab.onDestroy = function() tab.tabPanel:destroy() end
table.insert(self.tabs, tab) table.insert(self.tabs, tab)
if #self.tabs == 1 then if #self.tabs == 1 then
self:selectTab(tab) self:selectTab(tab)
tab:setMarginLeft(0)
else
local newMargin = 5 * (#self.tabs - 1)
for i = 1, #self.tabs - 1 do
newMargin = newMargin + self.tabs[i]:getWidth()
end
tab:setMarginLeft(newMargin)
end end
return tab return tab
end end
-- Additional function to move the tab by lua
function UITabBar:moveTab(tab, units)
local index = table.find(self.tabs, tab)
if index == nil then return end
local focus = false
if self.currentTab == tab then
self:selectPrevTab()
focus = true
end
table.remove(self.tabs, index)
local newIndex = math.min(#self.tabs+1, math.max(index + units, 1))
table.insert(self.tabs, newIndex, tab)
if focus then self:selectTab(tab) end
updateMargins(self)
return newIndex
end
function UITabBar:onStyleApply(styleName, styleNode)
if styleNode['moveable'] then
self.tabsMoveable = styleNode['moveable']
end
end
function UITabBar:removeTab(tab) function UITabBar:removeTab(tab)
local index = table.find(self.tabs, tab) local index = table.find(self.tabs, tab)
if index == nil then return end if index == nil then return end

Binary file not shown.

View File

@@ -272,8 +272,8 @@ function addText(text, speaktype, tabName, creatureName)
end end
-- Contains letter width for font "verdana-11px-antialised" as console is based on it -- Contains letter width for font "verdana-11px-antialised" as console is based on it
local letterWidth = { local letterWidth = { -- New line (10) and Space (32) have width 1 because they are printed and not replaced with spacer
[32] = 4, [33] = 3, [34] = 6, [35] = 8, [36] = 7, [37] = 13, [38] = 9, [39] = 3, [40] = 5, [41] = 5, [42] = 6, [43] = 8, [44] = 4, [45] = 5, [46] = 3, [47] = 8, [10] = 1, [32] = 1, [33] = 3, [34] = 6, [35] = 8, [36] = 7, [37] = 13, [38] = 9, [39] = 3, [40] = 5, [41] = 5, [42] = 6, [43] = 8, [44] = 4, [45] = 5, [46] = 3, [47] = 8,
[48] = 7, [49] = 6, [50] = 7, [51] = 7, [52] = 7, [53] = 7, [54] = 7, [55] = 7, [56] = 7, [57] = 7, [58] = 3, [59] = 4, [60] = 8, [61] = 8, [62] = 8, [63] = 6, [48] = 7, [49] = 6, [50] = 7, [51] = 7, [52] = 7, [53] = 7, [54] = 7, [55] = 7, [56] = 7, [57] = 7, [58] = 3, [59] = 4, [60] = 8, [61] = 8, [62] = 8, [63] = 6,
[64] = 10, [65] = 9, [66] = 7, [67] = 7, [68] = 8, [69] = 7, [70] = 7, [71] = 8, [72] = 8, [73] = 5, [74] = 5, [75] = 7, [76] = 7, [77] = 9, [78] = 8, [79] = 8, [64] = 10, [65] = 9, [66] = 7, [67] = 7, [68] = 8, [69] = 7, [70] = 7, [71] = 8, [72] = 8, [73] = 5, [74] = 5, [75] = 7, [76] = 7, [77] = 9, [78] = 8, [79] = 8,
[80] = 7, [81] = 8, [82] = 8, [83] = 7, [84] = 8, [85] = 8, [86] = 8, [87] = 12, [88] = 8, [89] = 8, [90] = 7, [91] = 5, [92] = 8, [93] = 5, [94] = 9, [95] = 8, [80] = 7, [81] = 8, [82] = 8, [83] = 7, [84] = 8, [85] = 8, [86] = 8, [87] = 12, [88] = 8, [89] = 8, [90] = 7, [91] = 5, [92] = 8, [93] = 5, [94] = 9, [95] = 8,
@@ -324,7 +324,7 @@ function addTabText(text, speaktype, tab, creatureName)
local player = g_game.getLocalPlayer() local player = g_game.getLocalPlayer()
if speaktype.npcChat and player:getName() ~= creatureName then -- Check if it is the npc who is talking if speaktype.npcChat and (player:getName() ~= creatureName or player:getName() == 'Account Manager') then -- Check if it is the npc who is talking
local highlightData = getHighlightedText(text) local highlightData = getHighlightedText(text)
if #highlightData == 0 then if #highlightData == 0 then
labelHighlight:setText("") labelHighlight:setText("")
@@ -341,16 +341,30 @@ function addTabText(text, speaktype, tab, creatureName)
label:setText(text) label:setText(text)
-- Calculate the positions of the highlighted text and fill with string.char(127) [Width: 1] -- Calculate the positions of the highlighted text and fill with string.char(127) [Width: 1]
local drawText = label:getDrawText()
local tmpText = "" local tmpText = ""
for i = 1, #highlightData / 3 do for i = 1, #highlightData / 3 do
local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] } local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] }
local lastBlockEnd = (highlightData[(i-2)*3+2] or 1) local lastBlockEnd = (highlightData[(i-2)*3+2] or 1)
for letter = lastBlockEnd, dataBlock._start-1 do for letter = lastBlockEnd, dataBlock._start-1 do
tmpText = tmpText .. string.rep(string.char(127), letterWidth[string.byte(text:sub(letter, letter))]) local tmpChar = string.byte(drawText:sub(letter, letter))
local fillChar = (tmpChar == 10 or tmpChar == 32) and string.char(tmpChar) or string.char(127)
tmpText = tmpText .. string.rep(fillChar, letterWidth[tmpChar])
end end
tmpText = tmpText .. dataBlock.words tmpText = tmpText .. dataBlock.words
end end
-- Fill the highlight label to the same size as default label
local finalBlockEnd = (highlightData[(#highlightData/3-1)*3+2] or 1)
for letter = finalBlockEnd, drawText:len() do
local tmpChar = string.byte(drawText:sub(letter, letter))
local fillChar = (tmpChar == 10 or tmpChar == 32) and string.char(tmpChar) or string.char(127)
tmpText = tmpText .. string.rep(fillChar, letterWidth[tmpChar])
end
labelHighlight:setText(tmpText) labelHighlight:setText(tmpText)
end end
else else
@@ -530,14 +544,31 @@ function applyMessagePrefixies(name, level, message)
end end
function onTalk(name, level, mode, message, channelId, creaturePos) function onTalk(name, level, mode, message, channelId, creaturePos)
if mode == MessageModes.GamemasterBroadcast then
modules.game_textmessage.displayBroadcastMessage(name .. ': ' .. message)
return
end
if ignoreNpcMessages and mode == MessageModes.NpcFrom then return end if ignoreNpcMessages and mode == MessageModes.NpcFrom then return end
if (mode == MessageModes.Say or mode == MessageModes.Whisper or mode == MessageModes.Yell or if (mode == MessageModes.Say or mode == MessageModes.Whisper or mode == MessageModes.Yell or
mode == MessageModes.Spell or mode == MessageModes.MonsterSay or mode == MessageModes.MonsterYell or mode == MessageModes.Spell or mode == MessageModes.MonsterSay or mode == MessageModes.MonsterYell or
mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud) and mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud) and
creaturePos then creaturePos then
-- Remove curly braces from screen message
local staticMessage = message
if mode == MessageModes.NpcFrom then
local highlightData = getHighlightedText(staticMessage)
if #highlightData > 0 then
for i = 1, #highlightData / 3 do
local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] }
staticMessage = staticMessage:gsub("{"..dataBlock.words.."}", dataBlock.words)
end
end
end
local staticText = StaticText.create() local staticText = StaticText.create()
staticText:addMessage(name, mode, message) staticText:addMessage(name, mode, staticMessage)
g_map.addThing(staticText, creaturePos, -1) g_map.addThing(staticText, creaturePos, -1)
end end

View File

@@ -78,6 +78,7 @@ Panel
anchors.top: prev.top anchors.top: prev.top
anchors.right: next.left anchors.right: next.left
margin-left: 5 margin-left: 5
moveable: true
TabButton TabButton
id: nextChannelButton id: nextChannelButton

Binary file not shown.

View File

@@ -0,0 +1,117 @@
cooldownWindow = nil
cooldownButton = nil
contentsPanel = nil
spellCooldownPanel = nil
function init()
connect(g_game, { onGameStart = show,
onGameEnd = hide,
onSpellGroupCooldown = onSpellGroupCooldown,
onSpellCooldown = onSpellCooldown })
cooldownButton = TopMenu.addRightGameToggleButton('cooldownButton', tr('Cooldowns'), 'cooldown.png', toggle)
cooldownButton:setOn(true)
cooldownButton:hide()
cooldownWindow = g_ui.loadUI('cooldown.otui', modules.game_interface.getRightPanel())
cooldownWindow:disableResize()
cooldownWindow:setup()
contentsPanel = cooldownWindow:getChildById('contentsPanel')
spellCooldownPanel = contentsPanel:getChildById('spellCooldownPanel')
if g_game.isOnline() then
show()
end
end
function terminate()
disconnect(g_game, { onGameStart = show,
onGameEnd = hide,
onSpellGroupCooldown = onSpellGroupCooldown,
onSpellCooldown = onSpellCooldown })
cooldownButton:destroy()
cooldownWindow:destroy()
end
function onMiniWindowClose()
cooldownButton:setOn(false)
end
function toggle()
if cooldownButton:isOn() then
cooldownWindow:close()
cooldownButton:setOn(false)
else
cooldownWindow:open()
cooldownButton:setOn(true)
end
end
function show()
if g_game.getFeature(GameSpellList) then
cooldownWindow:show()
cooldownButton:show()
end
end
function hide()
cooldownWindow:hide()
cooldownButton:hide()
end
function updateProgressRect(progressRect, interval, init)
if init then
progressRect:setPercent(0)
else
progressRect:setPercent(progressRect:getPercent() + 4)
end
if progressRect:getPercent() < 100 then
removeEvent(progressRect.event)
progressRect.event = scheduleEvent(function() updateProgressRect(progressRect, interval) end, interval)
end
end
function onSpellCooldown(iconId, duration)
local spellName = SpelllistSettings[modules.game_spelllist.getSpelllistProfile()].spellIcons[iconId]
if not spellName then return end
local otcIconId = tonumber(SpellInfo[modules.game_spelllist.getSpelllistProfile()][spellName].icon)
if not otcIconId and SpellIcons[SpellInfo[modules.game_spelllist.getSpelllistProfile()][spellName].icon] then
otcIconId = SpellIcons[SpellInfo[modules.game_spelllist.getSpelllistProfile()][spellName].icon][1]
end
if not otcIconId then return end
local icon = spellCooldownPanel:getChildById(spellName)
if not icon then
icon = g_ui.createWidget('SpellIcon', spellCooldownPanel)
icon:setId(spellName)
icon:setImageSource('/game_cooldown/icons/' .. SpelllistSettings[modules.game_spelllist.getSpelllistProfile()].iconFile)
icon:setImageClip(modules.game_spelllist.getIconImageClip(otcIconId))
icon.event = scheduleEvent(function() icon:destroy() end, duration)
local progressRect = g_ui.createWidget('SpellProgressRect', icon)
updateProgressRect(progressRect, duration/25, true)
progressRect:setTooltip(spellName)
end
end
function onSpellGroupCooldown(groupId, duration)
if not SpellGroups[groupId] then return end
local icon = contentsPanel:getChildById('groupIcon' .. SpellGroups[groupId])
local progressRect = contentsPanel:getChildById('progressRect' .. SpellGroups[groupId])
if icon then
icon:setOn(true)
removeEvent(icon.event)
icon.event = scheduleEvent(function() icon:setOn(false) end, duration)
end
if progressRect then
removeEvent(progressRect.event)
updateProgressRect(progressRect, duration/25, true)
end
end

View File

@@ -0,0 +1,9 @@
Module
name: game_cooldown
description: Spellcooldowns
author: OTClient team
website: www.otclient.info
sandboxed: true
scripts: [ cooldown.lua ]
@onLoad: init()
@onUnload: terminate()

View File

@@ -0,0 +1,102 @@
SpellGroupIcon < UIWidget
size: 22 22
image-size: 22 22
image-source: /game_cooldown/icons/cooldownIcons.png
focusable: false
margin-top: 3
SpellIcon < UIWidget
size: 22 22
image-size: 22 22
margin-left: 2
anchors.top: prev.top
anchors.left: prev.right
focusable: false
$first:
margin-top: 3
anchors.top: parent.top
anchors.left: parent.left
SpellProgressRect < UIProgressRect
background: #585858AA
percent: 100
focusable: false
MiniWindow
id: cooldownWindow
!text: tr('Spell Cooldowns')
height: 85
icon: cooldown.png
@onClose: modules.game_cooldown.onMiniWindowClose()
&save: true
MiniWindowContents
SpellGroupIcon
id: groupIconAttack
image-clip: 0 0 20 20
anchors.top: parent.top
anchors.left: parent.left
margin-left: 3
$on:
image-clip: 0 20 20 20
SpellProgressRect
id: progressRectAttack
anchors.fill: groupIconAttack
!tooltip: tr('Attack')
SpellGroupIcon
id: groupIconHealing
image-clip: 20 0 20 20
anchors.top: parent.top
anchors.left: groupIconAttack.right
margin-left: 3
$on:
image-clip: 20 20 20 20
SpellProgressRect
id: progressRectHealing
anchors.fill: groupIconHealing
!tooltip: tr('Healing')
SpellGroupIcon
id: groupIconSupport
image-clip: 40 0 20 20
anchors.top: parent.top
anchors.left: groupIconHealing.right
margin-left: 3
$on:
image-clip: 40 20 20 20
SpellProgressRect
id: progressRectSupport
anchors.fill: groupIconSupport
!tooltip: tr('Support')
SpellGroupIcon
id: groupIconSpecial
image-clip: 60 0 20 20
anchors.top: parent.top
anchors.left: groupIconSupport.right
margin-left: 3
$on:
image-clip: 60 20 20 20
SpellProgressRect
id: progressRectSpecial
anchors.fill: groupIconSpecial
!tooltip: tr('Special')
Panel
id: spellCooldownPanel
margin-top: 5
anchors.top: groupIconSpecial.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 30
padding: 1
margin-left: 2
margin-right: 2
border: 1 #444444

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -10,6 +10,7 @@ mouseGrabberWidget = nil
countWindow = nil countWindow = nil
logoutWindow = nil logoutWindow = nil
exitWindow = nil exitWindow = nil
bottomSplitter = nil
function init() function init()
g_ui.importStyle('styles/countwindow.otui') g_ui.importStyle('styles/countwindow.otui')
@@ -21,10 +22,12 @@ function init()
gameRootPanel = g_ui.displayUI('gameinterface.otui') gameRootPanel = g_ui.displayUI('gameinterface.otui')
gameRootPanel:hide() gameRootPanel:hide()
gameRootPanel:lower() gameRootPanel:lower()
gameRootPanel.onGeometryChange = updateStretchShrink
mouseGrabberWidget = gameRootPanel:getChildById('mouseGrabber') mouseGrabberWidget = gameRootPanel:getChildById('mouseGrabber')
mouseGrabberWidget.onMouseRelease = onMouseGrabberRelease mouseGrabberWidget.onMouseRelease = onMouseGrabberRelease
bottomSplitter = gameRootPanel:getChildById('bottomSplitter')
gameMapPanel = gameRootPanel:getChildById('gameMapPanel') gameMapPanel = gameRootPanel:getChildById('gameMapPanel')
gameRightPanel = gameRootPanel:getChildById('gameRightPanel') gameRightPanel = gameRootPanel:getChildById('gameRightPanel')
gameLeftPanel = gameRootPanel:getChildById('gameLeftPanel') gameLeftPanel = gameRootPanel:getChildById('gameLeftPanel')
@@ -90,6 +93,7 @@ function show()
gameRootPanel:show() gameRootPanel:show()
gameRootPanel:focus() gameRootPanel:focus()
gameMapPanel:followCreature(g_game.getLocalPlayer()) gameMapPanel:followCreature(g_game.getLocalPlayer())
updateStretchShrink()
end end
function hide() function hide()
@@ -187,6 +191,16 @@ function smartWalk(defaultDir)
end end
end end
function updateStretchShrink()
if Options.getOption('dontStretchShrink') then
gameMapPanel:setKeepAspectRatio(true)
gameMapPanel:setVisibleDimension({ width = 15, height = 11 })
-- Set gameMapPanel size to height = 11 * 32
bottomSplitter:setMarginBottom(bottomSplitter:getMarginBottom() + (gameMapPanel:getHeight() - 32 * 11) - 10)
end
end
function toggleAspectRatio() function toggleAspectRatio()
if gameMapPanel:isKeepAspectRatioEnabled() then if gameMapPanel:isKeepAspectRatioEnabled() then
gameMapPanel:setKeepAspectRatio(false) gameMapPanel:setKeepAspectRatio(false)

View File

@@ -1,3 +1,4 @@
GameSidePanel < UIMiniWindowContainer GameSidePanel < UIMiniWindowContainer
image-source: /images/sidepanel.png image-source: /images/sidepanel.png
image-border: 4 image-border: 4
@@ -62,7 +63,7 @@ UIWidget
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
relative-margin: bottom relative-margin: bottom
margin-bottom: 172 margin-bottom: 172
@canUpdateMargin: function(self, newMargin) return math.max(math.min(newMargin, self:getParent():getHeight() - 300), 100) end @canUpdateMargin: function(self, newMargin) if Options.getOption('dontStretchShrink') then return self:getMarginBottom() end return math.max(math.min(newMargin, self:getParent():getHeight() - 300), 100) end
@onGeometryChange: function(self) self:setMarginBottom(math.min(math.max(self:getParent():getHeight() - 300, 100), self:getMarginBottom())) end @onGeometryChange: function(self) self:setMarginBottom(math.min(math.max(self:getParent():getHeight() - 300, 100), self:getMarginBottom())) end
UIWidget UIWidget

View File

@@ -29,5 +29,6 @@ Module
- game_playermount - game_playermount
- game_market - game_market
- game_spelllist - game_spelllist
- game_cooldown
@onLoad: init() @onLoad: init()
@onUnload: terminate() @onUnload: terminate()

View File

@@ -0,0 +1,190 @@
FlagButton < CheckBox
size: 15 15
margin-left: 2
image-source: images/flagcheckbox.png
image-size: 15 15
image-border: 3
icon-source: images/mapflags.png
icon-size: 11 11
icon-clip: 0 0 11 11
icon-offset: 2 4
text:
$!checked:
image-clip: 26 0 26 26
$hover !checked:
image-clip: 78 0 26 26
$checked:
image-clip: 0 0 26 26
$hover checked:
image-clip: 52 0 26 26
FlagWindow < MainWindow
id: flagWindow
!text: tr('Create Map Mark')
size: 196 170
Label
id: position
!text: tr('Position:')
text-auto-resize: true
anchors.top: parent.top
anchors.left: parent.left
margin-top: 2
Label
!text: tr('Description:')
anchors.left: parent.left
anchors.top: prev.bottom
margin-top: 7
TextEdit
id: description
margin-top: 3
anchors.left: parent.left
anchors.top: prev.bottom
width: 158
FlagButton
id: flag1
anchors.left: parent.left
anchors.top: prev.bottom
margin-top: 6
margin-left: 0
FlagButton
id: flag2
icon-clip: 11 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag3
icon-clip: 22 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag4
icon-clip: 33 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag5
icon-clip: 44 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag6
icon-clip: 55 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag7
icon-clip: 66 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag8
icon-clip: 77 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag9
icon-clip: 88 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag10
icon-clip: 99 0 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag11
icon-clip: 0 11 11 11
anchors.left: parent.left
anchors.top: prev.bottom
margin-top: 6
margin-left: 0
FlagButton
id: flag12
icon-clip: 11 11 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag13
icon-clip: 22 11 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag14
icon-clip: 33 11 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag15
icon-clip: 44 11 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag16
icon-clip: 55 11 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag17
icon-clip: 66 11 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag18
icon-clip: 77 11 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag19
icon-clip: 88 11 11 11
anchors.left: prev.right
anchors.top: prev.top
FlagButton
id: flag20
icon-clip: 99 11 11 11
anchors.left: prev.right
anchors.top: prev.top
Button
id: okButton
!text: tr('Ok')
anchors.top: prev.bottom
anchors.left: parent.left
margin-top: 10
width: 60
Button
id: cancelButton
!text: tr('Cancel')
anchors.top: prev.top
anchors.left: prev.right
margin-left: 15
width: 60

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -7,17 +7,24 @@ minimapWidget = nil
minimapButton = nil minimapButton = nil
minimapWindow = nil minimapWindow = nil
flagsPanel = nil
flagWindow = nil
nextFlagId = 0
--[[ --[[
Known Issue (TODO): Known Issue (TODO):
If you move the minimap compass directions and If you move the minimap compass directions and
you change floor it will not update the minimap. you change floor it will not update the minimap.
]] ]]
function init() function init()
g_ui.importStyle('flagwindow.otui')
connect(g_game, { connect(g_game, {
onGameStart = online, onGameStart = online,
onGameEnd = offline, onGameEnd = offline,
onAutomapFlag = addMapFlag
}) })
connect(LocalPlayer, { onPositionChange = center }) connect(LocalPlayer, { onPositionChange = center,
onPositionChange = updateMapFlags })
g_keyboard.bindKeyDown('Ctrl+M', toggle) g_keyboard.bindKeyDown('Ctrl+M', toggle)
@@ -30,7 +37,9 @@ function init()
minimapWidget = minimapWindow:recursiveGetChildById('minimap') minimapWidget = minimapWindow:recursiveGetChildById('minimap')
g_mouse.bindAutoPress(minimapWidget, compassClick, nil, MouseRightButton) g_mouse.bindAutoPress(minimapWidget, compassClick, nil, MouseRightButton)
g_mouse.bindAutoPress(minimapWidget, compassClick, nil, MouseLeftButton) --g_mouse.bindAutoPress(minimapWidget, compassClick, nil, MouseLeftButton)
minimapWidget.onMousePress = createThingMenu
minimapWidget:setAutoViewMode(false) minimapWidget:setAutoViewMode(false)
minimapWidget:setViewMode(1) -- mid view minimapWidget:setViewMode(1) -- mid view
minimapWidget:setDrawMinimapColors(true) minimapWidget:setDrawMinimapColors(true)
@@ -38,18 +47,28 @@ function init()
minimapWidget:setKeepAspectRatio(false) minimapWidget:setKeepAspectRatio(false)
minimapWidget.onMouseRelease = onMinimapMouseRelease minimapWidget.onMouseRelease = onMinimapMouseRelease
minimapWidget.onMouseWheel = onMinimapMouseWheel minimapWidget.onMouseWheel = onMinimapMouseWheel
flagsPanel = minimapWindow:recursiveGetChildById('flagsPanel')
reset() reset()
minimapWindow:setup() minimapWindow:setup()
loadMapFlags()
if g_game.isOnline() then
addEvent(function() updateMapFlags() end)
end
end end
function terminate() function terminate()
disconnect(g_game, { disconnect(g_game, {
onGameStart = online, onGameStart = online,
onGameEnd = offline, onGameEnd = offline,
onAutomapFlag = addMapFlag
}) })
disconnect(LocalPlayer, { onPositionChange = center }) disconnect(LocalPlayer, { onPositionChange = center,
onPositionChange = updateMapFlags })
destroyFlagWindow()
saveMapFlags()
if g_game.isOnline() then if g_game.isOnline() then
saveMap() saveMap()
end end
@@ -60,9 +79,188 @@ function terminate()
minimapWindow:destroy() minimapWindow:destroy()
end end
function destroyFlagWindow()
if flagWindow then
flagWindow:destroy()
flagWindow = nil
end
end
function createThingMenu(widget, menuPosition, button)
if not g_game.isOnline() then return end
if button ~= MouseRightButton then return end
local menu = g_ui.createWidget('PopupMenu')
if widget == minimapWidget then
menu:addOption(tr('Create mark'), function()
local position = minimapWidget:getPosition(menuPosition)
if position then
showFlagDialog(position)
end
end)
else
menu:addOption(tr('Delete mark'), function()
widget:destroy()
end)
end
menu:display(menuPosition)
end
function showFlagDialog(position)
if flagWindow then return end
if not position then return end
flagWindow = g_ui.createWidget('FlagWindow', rootWidget)
local positionLabel = flagWindow:getChildById('position')
local description = flagWindow:getChildById('description')
local okButton = flagWindow:getChildById('okButton')
local cancelButton = flagWindow:getChildById('cancelButton')
positionLabel:setText(tr('Position: %i %i %i', position.x, position.y, position.z))
flagRadioGroup = UIRadioGroup.create()
local flagCheckbox = {}
for i = 1, 20 do
local checkbox = flagWindow:getChildById('flag' .. i)
table.insert(flagCheckbox, checkbox)
checkbox.icon = i
flagRadioGroup:addWidget(checkbox)
end
flagRadioGroup:selectWidget(flagCheckbox[1])
cancelButton.onClick = function()
flagRadioGroup:destroy()
destroyFlagWindow()
end
okButton.onClick = function()
addMapFlag(position, flagRadioGroup:getSelectedWidget().icon, description:getText())
flagRadioGroup:destroy()
destroyFlagWindow()
end
end
function loadMapFlags()
mapFlags = {}
local flagSettings = g_settings.getNode('MapFlags')
if flagSettings then
for i = 1, #flagSettings do
local flag = flagSettings[i]
addMapFlag(flag.position, flag.icon, flag.description, flag.id, flag.version)
if i == #flagSettings then
nextFlagId = flag.id + 1
end
end
end
end
function saveMapFlags()
local flagSettings = {}
for i = 1, flagsPanel:getChildCount() do
local child = flagsPanel:getChildByIndex(i)
table.insert(flagSettings, { position = child.position,
icon = child.icon,
description = child.description,
id = child.id,
version = child.version })
end
g_settings.setNode('MapFlags', flagSettings)
end
function getFlagIconClip(id)
return (((id)%10)*11) .. ' ' .. ((math.ceil(id/10+0.1)-1)*11) .. ' 11 11'
end
function addMapFlag(pos, icon, message, flagId, version)
if not(icon >= 1 and icon <= 20) or not pos then
return
end
version = version or g_game.getClientVersion()
-- Check if flag is set for that position
for i = 1, flagsPanel:getChildCount() do
local flag = flagsPanel:getChildByIndex(i)
if flag.position.x == pos.x and flag.position.y == pos.y and flag.position.z == pos.z
and version == flag.version then
return
end
end
if not flagId then
flagId = nextFlagId
nextFlagId = nextFlagId + 1
end
local flagWidget = g_ui.createWidget('FlagWidget', flagsPanel)
flagWidget:setIconClip(getFlagIconClip(icon - 1))
flagWidget:setId('flag' .. flagId)
flagWidget.position = pos
flagWidget.icon = icon
flagWidget.description = message
if message and message:len() > 0 then
flagWidget:setTooltip(tr(message))
end
flagWidget.id = flagId
flagWidget.version = version
updateMapFlag(flagId)
flagWidget.onMousePress = createThingMenu
end
function getMapArea()
return minimapWidget:getPosition( { x = 1 + minimapWidget:getX(), y = 1 + minimapWidget:getY() } ),
minimapWidget:getPosition( { x = -2 + minimapWidget:getWidth() + minimapWidget:getX(), y = -2 + minimapWidget:getHeight() + minimapWidget:getY() } )
end
function isFlagVisible(flag, firstPosition, lastPosition)
return flag.version == g_game.getClientVersion() and (minimapWidget:getZoom() >= 30 and minimapWidget:getZoom() <= 150) and flag.position.x >= firstPosition.x and flag.position.x <= lastPosition.x and flag.position.y >= firstPosition.y and flag.position.y <= lastPosition.y and flag.position.z == firstPosition.z
end
function updateMapFlag(id)
local firstPosition, lastPosition = getMapArea()
if not firstPosition or not lastPosition then
return
end
local flag = flagsPanel:getChildById('flag' .. id)
if isFlagVisible(flag, firstPosition, lastPosition) then
flag:setVisible(true)
flag:setMarginLeft( -5 + (minimapWidget:getWidth() / (lastPosition.x - firstPosition.x)) * (flag.position.x - firstPosition.x))
flag:setMarginTop( -5 + (minimapWidget:getHeight() / (lastPosition.y - firstPosition.y)) * (flag.position.y - firstPosition.y))
else
flag:setVisible(false)
end
end
function updateMapFlags()
local firstPosition, lastPosition = getMapArea()
if not firstPosition or not lastPosition then
return
end
for i = 1, flagsPanel:getChildCount() do
local flag = flagsPanel:getChildByIndex(i)
if isFlagVisible(flag, firstPosition, lastPosition) then
flag:setVisible(true)
flag:setMarginLeft( -5 + (minimapWidget:getWidth() / (lastPosition.x - firstPosition.x)) * (flag.position.x - firstPosition.x))
flag:setMarginTop( -5 + (minimapWidget:getHeight() / (lastPosition.y - firstPosition.y)) * (flag.position.y - firstPosition.y))
else
flag:setVisible(false)
end
end
end
function online() function online()
reset() reset()
loadMap() loadMap()
updateMapFlags()
end end
function offline() function offline()
@@ -109,6 +307,8 @@ function center()
local player = g_game.getLocalPlayer() local player = g_game.getLocalPlayer()
if not player then return end if not player then return end
minimapWidget:followCreature(player) minimapWidget:followCreature(player)
updateMapFlags()
end end
function compassClick(self, mousePos, mouseButton, elapsed) function compassClick(self, mousePos, mouseButton, elapsed)
@@ -133,6 +333,8 @@ function compassClick(self, mousePos, mouseButton, elapsed)
local cameraPos = minimapWidget:getCameraPosition() local cameraPos = minimapWidget:getCameraPosition()
local pos = {x = cameraPos.x + movex, y = cameraPos.y + movey, z = cameraPos.z} local pos = {x = cameraPos.x + movex, y = cameraPos.y + movey, z = cameraPos.z}
minimapWidget:setCameraPosition(pos) minimapWidget:setCameraPosition(pos)
updateMapFlags()
end end
function onButtonClick(id) function onButtonClick(id)
@@ -153,6 +355,8 @@ function onButtonClick(id)
minimapWidget:setCameraPosition(pos) minimapWidget:setCameraPosition(pos)
end end
end end
updateMapFlags()
end end
function onMinimapMouseRelease(self, mousePosition, mouseButton) function onMinimapMouseRelease(self, mousePosition, mouseButton)
@@ -179,6 +383,7 @@ function onMinimapMouseWheel(self, mousePos, direction)
else else
self:zoomOut() self:zoomOut()
end end
updateMapFlags()
end end
function onMiniWindowClose() function onMiniWindowClose()

View File

@@ -8,6 +8,13 @@ MapControl < Button
$hover !pressed: $hover !pressed:
icon-clip: 0 16 16 16 icon-clip: 0 16 16 16
FlagWidget < UIWidget
size: 11 11
icon-clip: 0 0 11 11
icon-source: /game_minimap/images/mapflags.png
anchors.left: parent.left
anchors.top: parent.top
FloorUpControl < MapControl FloorUpControl < MapControl
icon-source: /game_minimap/images/floor_up.png icon-source: /game_minimap/images/floor_up.png
@@ -26,6 +33,7 @@ MiniWindow
height: 150 height: 150
icon: minimap.png icon: minimap.png
@onClose: modules.game_minimap.onMiniWindowClose() @onClose: modules.game_minimap.onMiniWindowClose()
@onGeometryChange: updateMapFlags()
&save: true &save: true
Label Label
@@ -43,6 +51,11 @@ MiniWindow
id: minimap id: minimap
anchors.fill: parent anchors.fill: parent
Panel
id: flagsPanel
anchors.fill: minimap
phantom: true
FloorUpControl FloorUpControl
id: floorUp id: floorUp
anchors.right: parent.right anchors.right: parent.right

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,34 +1,37 @@
spelllistWindow = nil local SpelllistProfile = 'Default'
spelllistButton = nil
spellList = nil
nameValueLabel = nil
formulaValueLabel = nil
vocationValueLabel = nil
groupValueLabel = nil
typeValueLabel = nil
cooldownValueLabel = nil
levelValueLabel = nil
manaValueLabel = nil
premiumValueLabel = nil
vocationBoxAny = nil spelllistWindow = nil
vocationBoxSorcerer = nil spelllistButton = nil
vocationBoxDruid = nil spellList = nil
vocationBoxPaladin = nil nameValueLabel = nil
vocationBoxKnight = nil formulaValueLabel = nil
vocationValueLabel = nil
groupValueLabel = nil
typeValueLabel = nil
cooldownValueLabel = nil
levelValueLabel = nil
manaValueLabel = nil
premiumValueLabel = nil
descriptionValueLabel = nil
groupBoxAny = nil vocationBoxAny = nil
groupBoxAttack = nil vocationBoxSorcerer = nil
groupBoxHealing = nil vocationBoxDruid = nil
groupBoxSupport = nil vocationBoxPaladin = nil
vocationBoxKnight = nil
premiumBoxAny = nil groupBoxAny = nil
premiumBoxNo = nil groupBoxAttack = nil
premiumBoxYes = nil groupBoxHealing = nil
groupBoxSupport = nil
vocationRadioGroup = nil premiumBoxAny = nil
groupRadioGroup = nil premiumBoxNo = nil
premiumRadioGroup = nil premiumBoxYes = nil
vocationRadioGroup = nil
groupRadioGroup = nil
premiumRadioGroup = nil
-- consts -- consts
FILTER_PREMIUM_ANY = 0 FILTER_PREMIUM_ANY = 0
@@ -56,13 +59,27 @@ local filters = {
groupId = FILTER_GROUP_ANY groupId = FILTER_GROUP_ANY
} }
local spellDisplayOrder = {'Animate Dead', 'Annihilation', 'Avalanche', 'Berserk', 'Blood Rage', 'Brutal Strike', 'Cancel Invisibility', 'Challenge', 'Chameleon', 'Charge', 'Conjure Arrow', 'Conjure Bolt', 'Conjure Explosive Arrow', 'Conjure Piercing Bolt', 'Conjure Poisoned Arrow', 'Conjure Power Bolt', 'Conjure Sniper Arrow', 'Convince Creature', 'Creature Illusion', 'Cure Bleeding', 'Cure Burning', 'Cure Curse', 'Cure Electrification', 'Cure Poison', 'Cure Poison Rune', 'Curser', 'Death Strike', 'Desintegrate', 'Destroy Field', 'Divine Caldera', 'Divine Healing', 'Divine Missile', 'Electrify', 'Enchant Party', 'Enchant Spear', 'Enchant Staff', 'Energy Beam', 'Energy Field', 'Energy Strike', 'Energy Wall', 'Energy Wave', 'Energybomb', 'Envenom', 'Eternal Winter', 'Ethereal Spear', 'Explosion', 'Fierce Berserk', 'Find Person', 'Fire Field', 'Fire Wall', 'Fire Wave', 'Fireball', 'Firebomb', 'Flame Strike', 'Food', 'Front Sweep', 'Great Energy Beam', 'Great Fireball', 'Great Light', 'Groundshaker', 'Haste', 'Heal Friend', 'Heal Party', 'Heavy Magic Missile', 'Hells Core', 'Holy Flash', 'Holy Missile', 'Ice Strike', 'Ice Wave', 'Icicle', 'Ignite', 'Inflict Wound', 'Intense Healing', 'Intense Healing Rune', 'Intense Recovery', 'Intense Wound Cleansing', 'Invisibility', 'Levitate', 'Light', 'Light Healing', 'Light Magic Missile', 'Lightning', 'Magic Rope', 'Magic Shield', 'Magic Wall', 'Mass Healing', 'Paralyze', 'Physical Strike', 'Poison Bomb', 'Poison Field', 'Poison Wall', 'Protect Party', 'Protector', 'Rage of the Skies', 'Recovery', 'Salvation', 'Sharpshooter', 'Soulfire', 'Stalagmite', 'Stone Shower', 'Strong Energy Strike', 'Strong Ethereal Spear', 'Strong Flame Strike', 'Strong Haste', 'Strong Ice Strike', 'Strong Ice Wave', 'Strong Terra Strike', 'Sudden Death', 'Summon Creature', 'Swift Foot', 'Terra Strike', 'Terra Wave', 'Thunderstorm', 'Train Party', 'Ultimate Energy Strike', 'Ultimate Flame Strike', 'Ultimate Healing', 'Ultimate Healing Rune', 'Ultimate Ice Strike', 'Ultimate Light', 'Ultimate Terra Strike', 'Whirlwind Throw', 'Wild Growth', 'Wound Cleansing', 'Wrath of Nature'} function getSpelllistProfile()
return SpelllistProfile
function getIconImageClip(id)
return (((id-1)%12)*32) .. ' ' .. ((math.ceil(id/12)-1)*32) .. ' 32 32'
end end
function setupOptions() function setSpelllistProfile(name)
if SpelllistProfile == name then return end
if SpelllistSettings[name] and SpellInfo[name] then
local oldProfile = SpelllistProfile
SpelllistProfile = name
changeSpelllistProfile(oldProfile)
else
perror('Spelllist profile \'' .. name .. '\' could not be set.')
end
end
function getIconImageClip(id)
return (((id-1)%12)*SpelllistSettings[SpelllistProfile].iconSize.width) .. ' ' .. ((math.ceil(id/12)-1)*SpelllistSettings[SpelllistProfile].iconSize.height) .. ' ' .. SpelllistSettings[SpelllistProfile].iconSize.width .. ' ' .. SpelllistSettings[SpelllistProfile].iconSize.height
end
function setOptions()
if g_game.getClientVersion() >= 950 then -- Vocation is only send in newer clients if g_game.getClientVersion() >= 950 then -- Vocation is only send in newer clients
spelllistWindow:getChildById('buttonFilterVocation'):setVisible(true) spelllistWindow:getChildById('buttonFilterVocation'):setVisible(true)
else else
@@ -71,8 +88,8 @@ function setupOptions()
end end
function init() function init()
connect(g_game, { onGameStart = setupOptions, connect(g_game, { onGameStart = setOptions,
onGameEnd = resetWindow }) onGameEnd = resetWindow })
spelllistWindow = g_ui.displayUI('spelllist.otui', modules.game_interface.getRightPanel()) spelllistWindow = g_ui.displayUI('spelllist.otui', modules.game_interface.getRightPanel())
spelllistWindow:hide() spelllistWindow:hide()
@@ -89,6 +106,7 @@ function init()
levelValueLabel = spelllistWindow:getChildById('labelLevelValue') levelValueLabel = spelllistWindow:getChildById('labelLevelValue')
manaValueLabel = spelllistWindow:getChildById('labelManaValue') manaValueLabel = spelllistWindow:getChildById('labelManaValue')
premiumValueLabel = spelllistWindow:getChildById('labelPremiumValue') premiumValueLabel = spelllistWindow:getChildById('labelPremiumValue')
descriptionValueLabel = spelllistWindow:getChildById('labelDescriptionValue')
vocationBoxAny = spelllistWindow:getChildById('vocationBoxAny') vocationBoxAny = spelllistWindow:getChildById('vocationBoxAny')
vocationBoxSorcerer = spelllistWindow:getChildById('vocationBoxSorcerer') vocationBoxSorcerer = spelllistWindow:getChildById('vocationBoxSorcerer')
@@ -136,34 +154,16 @@ function init()
g_keyboard.bindKeyPress('Down', function() spellList:focusNextChild(KeyboardFocusReason) end, spelllistWindow) g_keyboard.bindKeyPress('Down', function() spellList:focusNextChild(KeyboardFocusReason) end, spelllistWindow)
g_keyboard.bindKeyPress('Up', function() spellList:focusPreviousChild(KeyboardFocusReason) end, spelllistWindow) g_keyboard.bindKeyPress('Up', function() spellList:focusPreviousChild(KeyboardFocusReason) end, spelllistWindow)
for i = 1, #spellDisplayOrder do initialiseSpelllist()
local spell = spellDisplayOrder[i] setOptions()
local info = SpellInfo[spell] resizeWindow()
local tmpLabel = g_ui.createWidget('SpellListLabel', spellList)
tmpLabel:setId(spell)
tmpLabel:setText(spell .. '\n\'' .. info.words .. '\'')
tmpLabel:setPhantom(false)
if not(SpellIcons[info.icon]) then
perror('Spell icon \'' .. info.icon .. '\' not found.')
else
tmpLabel:setImageClip(getIconImageClip(SpellIcons[info.icon][1]))
end
tmpLabel.onClick = updateSpellInformation
end
connect(spellList, { onChildFocusChange = function(self, focusedChild)
if focusedChild == nil then return end
updateSpellInformation(focusedChild)
end })
setupOptions()
end end
function terminate() function terminate()
disconnect(g_game, { onGameStart = setupOptions, disconnect(g_game, { onGameStart = setOptions,
onGameEnd = resetWindow }) onGameEnd = resetWindow,
onSpellGroupCooldown = modules.game_interface.setGroupCooldown,
onSpellCooldown = onSpellCooldown })
disconnect(spellList, { onChildFocusChange = function(self, focusedChild) disconnect(spellList, { onChildFocusChange = function(self, focusedChild)
if focusedChild == nil then return end if focusedChild == nil then return end
@@ -171,48 +171,98 @@ function terminate()
end }) end })
spelllistButton:destroy() spelllistButton:destroy()
spelllistButton = nil spelllistButton = nil
spelllistWindow:destroy() spelllistWindow:destroy()
spelllistWindow = nil spelllistWindow = nil
vocationRadioGroup:destroy() vocationRadioGroup:destroy()
vocationRadioGroup = nil vocationRadioGroup = nil
groupRadioGroup:destroy() groupRadioGroup:destroy()
groupRadioGroup = nil groupRadioGroup = nil
premiumRadioGroup:destroy() premiumRadioGroup:destroy()
premiumRadioGroup = nil premiumRadioGroup = nil
spellList = nil spellList = nil
nameValueLabel = nil nameValueLabel = nil
formulaValueLabel = nil formulaValueLabel = nil
vocationValueLabel = nil vocationValueLabel = nil
groupValueLabel = nil groupValueLabel = nil
typeValueLabel = nil typeValueLabel = nil
cooldownValueLabel = nil cooldownValueLabel = nil
levelValueLabel = nil levelValueLabel = nil
manaValueLabel = nil manaValueLabel = nil
premiumValueLabel = nil premiumValueLabel = nil
descriptionValueLabel = nil
vocationBoxAny = nil vocationBoxAny = nil
vocationBoxSorcerer = nil vocationBoxSorcerer = nil
vocationBoxDruid = nil vocationBoxDruid = nil
vocationBoxPaladin = nil vocationBoxPaladin = nil
vocationBoxKnight = nil vocationBoxKnight = nil
groupBoxAny = nil groupBoxAny = nil
groupBoxAttack = nil groupBoxAttack = nil
groupBoxHealing = nil groupBoxHealing = nil
groupBoxSupport = nil groupBoxSupport = nil
premiumBoxAny = nil premiumBoxAny = nil
premiumBoxNo = nil premiumBoxNo = nil
premiumBoxYes = nil premiumBoxYes = nil
end
function initialiseSpelllist()
for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do
local spell = SpelllistSettings[SpelllistProfile].spellOrder[i]
local info = SpellInfo[SpelllistProfile][spell]
local tmpLabel = g_ui.createWidget('SpellListLabel', spellList)
tmpLabel:setId(spell)
tmpLabel:setText(spell .. '\n\'' .. info.words .. '\'')
tmpLabel:setPhantom(false)
local iconId = tonumber(info.icon)
if not iconId and SpellIcons[info.icon] then
iconId = SpellIcons[info.icon][1]
end
if not(iconId) then
perror('Spell icon \'' .. info.icon .. '\' not found.')
end
tmpLabel:setHeight(SpelllistSettings[SpelllistProfile].iconSize.height + 4)
tmpLabel:setTextOffset(topoint((SpelllistSettings[SpelllistProfile].iconSize.width + 10) .. ' ' .. (SpelllistSettings[SpelllistProfile].iconSize.height - 32)/2 + 3))
tmpLabel:setImageSource('/game_spelllist/icons/' .. SpelllistSettings[SpelllistProfile].iconFile)
tmpLabel:setImageClip(getIconImageClip(iconId))
tmpLabel:setImageSize(tosize(SpelllistSettings[SpelllistProfile].iconSize.width .. ' ' .. SpelllistSettings[SpelllistProfile].iconSize.height))
tmpLabel.onClick = updateSpellInformation
end
connect(spellList, { onChildFocusChange = function(self, focusedChild)
if focusedChild == nil then return end
updateSpellInformation(focusedChild)
end })
end
function changeSpelllistProfile(oldProfile)
-- Delete old labels
for i = 1, #SpelllistSettings[oldProfile].spellOrder do
local spell = SpelllistSettings[oldProfile].spellOrder[i]
local tmpLabel = spellList:getChildById(spell)
tmpLabel:destroy()
end
-- Create new spelllist and ajust window
initialiseSpelllist()
setOptions()
resizeWindow()
resetWindow()
end end
function updateSpelllist() function updateSpelllist()
for i = 1, #spellDisplayOrder do for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do
local spell = spellDisplayOrder[i] local spell = SpelllistSettings[SpelllistProfile].spellOrder[i]
local info = SpellInfo[spell] local info = SpellInfo[SpelllistProfile][spell]
local tmpLabel = spellList:getChildById(spell) local tmpLabel = spellList:getChildById(spell)
local localPlayer = g_game.getLocalPlayer() local localPlayer = g_game.getLocalPlayer()
@@ -227,18 +277,19 @@ end
function updateSpellInformation(widget) function updateSpellInformation(widget)
local spell = widget:getId() local spell = widget:getId()
local name = '' local name = ''
local formula = '' local formula = ''
local vocation = '' local vocation = ''
local group = '' local group = ''
local type = '' local type = ''
local cooldown = '' local cooldown = ''
local level = '' local level = ''
local mana = '' local mana = ''
local premium = '' local premium = ''
local description = ''
if SpellInfo[spell] then if SpellInfo[SpelllistProfile][spell] then
local info = SpellInfo[spell] local info = SpellInfo[SpelllistProfile][spell]
name = spell name = spell
formula = info.words formula = info.words
@@ -262,6 +313,7 @@ function updateSpellInformation(widget)
level = info.level level = info.level
mana = info.mana .. ' / ' .. info.soul mana = info.mana .. ' / ' .. info.soul
premium = (info.premium and 'yes' or 'no') premium = (info.premium and 'yes' or 'no')
description = info.description or '-'
end end
nameValueLabel:setText(name) nameValueLabel:setText(name)
@@ -273,6 +325,7 @@ function updateSpellInformation(widget)
levelValueLabel:setText(level) levelValueLabel:setText(level)
manaValueLabel:setText(mana) manaValueLabel:setText(mana)
premiumValueLabel:setText(premium) premiumValueLabel:setText(premium)
descriptionValueLabel:setText(description)
end end
function toggle() function toggle()
@@ -333,6 +386,11 @@ function toggleFilter(widget, selectedWidget)
updateSpelllist() updateSpelllist()
end end
function resizeWindow()
spelllistWindow:setWidth(SpelllistSettings['Default'].spellWindowWidth + SpelllistSettings[SpelllistProfile].iconSize.width - 32)
spellList:setWidth(SpelllistSettings['Default'].spellListWidth + SpelllistSettings[SpelllistProfile].iconSize.width - 32)
end
function resetWindow() function resetWindow()
spelllistWindow:hide() spelllistWindow:hide()
spelllistButton:setOn(false) spelllistButton:setOn(false)

View File

@@ -29,7 +29,7 @@ SpellInfoValueLabel < Label
MainWindow MainWindow
id: spelllistWindow id: spelllistWindow
!text: tr('Spell List') !text: tr('Spell List')
size: 500 400 size: 550 400
@onEscape: toggle() @onEscape: toggle()
TextList TextList
@@ -142,6 +142,12 @@ MainWindow
anchors.top: labelMana.bottom anchors.top: labelMana.bottom
text: Premium: text: Premium:
SpellInfoLabel
id: labelDescription
anchors.left: spellList.right
anchors.top: labelPremium.bottom
text: Description:
SpellInfoValueLabel SpellInfoValueLabel
id: labelNameValue id: labelNameValue
anchors.left: labelName.right anchors.left: labelName.right
@@ -187,6 +193,11 @@ MainWindow
anchors.left: labelPremium.right anchors.left: labelPremium.right
anchors.top: labelManaValue.bottom anchors.top: labelManaValue.bottom
SpellInfoValueLabel
id: labelDescriptionValue
anchors.left: labelDescription.right
anchors.top: labelPremiumValue.bottom
Label Label
id: labelVocationFilter id: labelVocationFilter
anchors.top: labelPremium.bottom anchors.top: labelPremium.bottom
@@ -194,7 +205,7 @@ MainWindow
width: 70 width: 70
font: verdana-11px-monochrome font: verdana-11px-monochrome
text: Vocation text: Vocation
margin-top: 25 margin-top: 30
margin-left: 20 margin-left: 20
CheckBox CheckBox
@@ -245,7 +256,7 @@ MainWindow
width: 70 width: 70
font: verdana-11px-monochrome font: verdana-11px-monochrome
text: Group text: Group
margin-top: 25 margin-top: 30
margin-left: 20 margin-left: 20
CheckBox CheckBox
@@ -288,7 +299,7 @@ MainWindow
width: 70 width: 70
font: verdana-11px-monochrome font: verdana-11px-monochrome
text: Premium text: Premium
margin-top: 25 margin-top: 30
margin-left: 20 margin-left: 20
CheckBox CheckBox

View File

@@ -107,6 +107,10 @@ function displayGameMessage(text)
displayMessage(MessageModes.Game, text) displayMessage(MessageModes.Game, text)
end end
function displayBroadcastMessage(text)
displayMessage(MessageModes.Warning, text)
end
function clearMessages() function clearMessages()
for _i,child in pairs(messagesPanel:recursiveGetChildren()) do for _i,child in pairs(messagesPanel:recursiveGetChildren()) do
if child:getId():match('Label') then if child:getId():match('Label') then

View File

@@ -1,133 +1,161 @@
-- ['Spell Name'] = {words = '', exhaustion = spellCooldown, premium = true/false, type = 'Instant'/'Conjure', icon = iconName, mana = manaCost, level = levelRequirement, soul = soulCost, group = {[groupId] = groupCooldown}, vocation = {vocationIds}} SpelllistSettings = {
SpellInfo = { ['Default'] = {
['Death Strike'] = {words = 'exori mort', exhaustion = 2000, premium = true, type = 'Instant', icon = 'deathstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, iconFile = 'icons.png',
['Flame Strike'] = {words = 'exori flam', exhaustion = 2000, premium = true, type = 'Instant', icon = 'flamestrike', mana = 20, level = 14, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}, iconSize = {width = 32, height = 32},
['Strong Flame Strike'] = {words = 'exori gran flam', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongflamestrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, spellListWidth = 210,
['Ultimate Flame Strike'] = {words = 'exori max flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateflamestrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, spellWindowWidth = 550,
['Energy Strike'] = {words = 'exori vis', exhaustion = 2000, premium = true, type = 'Instant', icon = 'energystrike', mana = 20, level = 12, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}, spellIcons = {[39] = 'Strong Haste', [131] = 'Charge', [91] = 'Poison Bomb', [22] = 'Energy Beam', [142] = 'Envenom', [48] = 'Conjure Poisoned Arrow', [49] = 'Conjure Explosive Arrow', [114] = 'Icicle', [130] = 'Holy Missile', [56] = 'Wrath of Nature', [112] = 'Ice Strike', [87] = 'Death Strike', [10] = 'Light', [24] = 'Hells Core', [75] = 'Ultimate Light', [151] = 'Strong Energy Strike', [42] = 'Food', [117] = 'Thunderstorm', [156] = 'Ultimate Ice Strike', [18] = 'Explosion', [116] = 'Stone Shower', [59] = 'Front Sweep', [11] = 'Great Light', [155] = 'Ultimate Energy Strike', [115] = 'Avalanche', [118] = 'Eternal Winter', [82] = 'Mass Healing', [139] = 'Curser', [54] = 'Paralyze', [15] = 'Fireball', [157] = 'Ultimate Terra Strike', [83] = 'Animate Dead', [9] = 'Summon Creature', [13] = 'Energy Wave', [5] = 'Ultimate Healing Rune', [88] = 'Energy Strike', [86] = 'Magic Wall', [19] = 'Fire Wave', [149] = 'Lightning', [14] = 'Chameleon', [29] = 'Cure Poison', [78] = 'Desintegrate', [105] = 'Fierce Berserk', [108] = 'Conjure Sniper Arrow', [30] = 'Destroy Field', [12] = 'Convince Creature', [4] = 'Intense Healing Rune', [31] = 'Cure Poison Rune', [152] = 'Strong Ice Strike', [21] = 'Sudden Death', [27] = 'Energy Field', [80] = 'Berserk', [55] = 'Energybomb', [28] = 'Fire Wall', [43] = 'Strong Ice Wave', [50] = 'Soulfire', [57] = 'Strong Ethereal Spear', [26] = 'Poison Field', [61] = 'Brutal Strike', [17] = 'Firebomb', [45] = 'Invisibility', [20] = 'Find Person', [146] = 'Cure Electrification', [7] = 'Light Magic Missile', [16] = 'Great Fireball', [92] = 'Enchant Staff', [148] = 'Physical Strike', [132] = 'Protector', [111] = 'Ethereal Spear', [143] = 'Holy Flash', [77] = 'Stalagmite', [33] = 'Energy Wall', [107] = 'Whirlwind Throw', [38] = 'Creature Illusion', [158] = 'Intense Wound Cleansing', [124] = 'Divine Caldera', [84] = 'Heal Friend', [8] = 'Heavy Magic Missile', [25] = 'Fire Field', [125] = 'Divine Healing', [140] = 'Electrify', [95] = 'Conjure Power Bolt', [36] = 'Salvation', [134] = 'Swift Foot', [109] = 'Conjure Piercing Bolt', [79] = 'Conjure Bolt', [141] = 'Inflict Wound', [153] = 'Strong Terra Strike', [1] = 'Light Healing', [51] = 'Conjure Arrow', [123] = 'Wound Cleansing', [129] = 'Enchant Party', [128] = 'Heal Party', [127] = 'Protect Party', [126] = 'Train Party', [23] = 'Great Energy Beam', [2] = 'Intense Healing', [133] = 'Blood Rage', [160] = 'Intense Recovery', [94] = 'Wild Growth', [89] = 'Flame Strike', [147] = 'Cure Curse', [93] = 'Challenge', [90] = 'Cancel Invisibility', [110] = 'Enchant Spear', [6] = 'Haste', [44] = 'Magic Shield', [81] = 'Levitate', [145] = 'Cure Burning', [76] = 'Magic Rope', [3] = 'Ultimate Healing', [159] = 'Recovery', [122] = 'Divine Missile', [120] = 'Terra Wave', [144] = 'Cure Bleeding', [150] = 'Strong Flame Strike', [113] = 'Terra Strike', [62] = 'Annihilation', [121] = 'Ice Wave', [135] = 'Sharpshooter', [138] = 'Ignite', [32] = 'Poison Wall', [119] = 'Rage of the Skies', [154] = 'Ultimate Flame Strike', [106] = 'Groundshaker'},
['Strong Energy Strike'] = {words = 'exori gran vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongenergystrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, spellOrder = {'Animate Dead', 'Annihilation', 'Avalanche', 'Berserk', 'Blood Rage', 'Brutal Strike', 'Cancel Invisibility', 'Challenge', 'Chameleon', 'Charge', 'Conjure Arrow', 'Conjure Bolt', 'Conjure Explosive Arrow', 'Conjure Piercing Bolt', 'Conjure Poisoned Arrow', 'Conjure Power Bolt', 'Conjure Sniper Arrow', 'Convince Creature', 'Creature Illusion', 'Cure Bleeding', 'Cure Burning', 'Cure Curse', 'Cure Electrification', 'Cure Poison', 'Cure Poison Rune', 'Curser', 'Death Strike', 'Desintegrate', 'Destroy Field', 'Divine Caldera', 'Divine Healing', 'Divine Missile', 'Electrify', 'Enchant Party', 'Enchant Spear', 'Enchant Staff', 'Energy Beam', 'Energy Field', 'Energy Strike', 'Energy Wall', 'Energy Wave', 'Energybomb', 'Envenom', 'Eternal Winter', 'Ethereal Spear', 'Explosion', 'Fierce Berserk', 'Find Person', 'Fire Field', 'Fire Wall', 'Fire Wave', 'Fireball', 'Firebomb', 'Flame Strike', 'Food', 'Front Sweep', 'Great Energy Beam', 'Great Fireball', 'Great Light', 'Groundshaker', 'Haste', 'Heal Friend', 'Heal Party', 'Heavy Magic Missile', 'Hells Core', 'Holy Flash', 'Holy Missile', 'Ice Strike', 'Ice Wave', 'Icicle', 'Ignite', 'Inflict Wound', 'Intense Healing', 'Intense Healing Rune', 'Intense Recovery', 'Intense Wound Cleansing', 'Invisibility', 'Levitate', 'Light', 'Light Healing', 'Light Magic Missile', 'Lightning', 'Magic Rope', 'Magic Shield', 'Magic Wall', 'Mass Healing', 'Paralyze', 'Physical Strike', 'Poison Bomb', 'Poison Field', 'Poison Wall', 'Protect Party', 'Protector', 'Rage of the Skies', 'Recovery', 'Salvation', 'Sharpshooter', 'Soulfire', 'Stalagmite', 'Stone Shower', 'Strong Energy Strike', 'Strong Ethereal Spear', 'Strong Flame Strike', 'Strong Haste', 'Strong Ice Strike', 'Strong Ice Wave', 'Strong Terra Strike', 'Sudden Death', 'Summon Creature', 'Swift Foot', 'Terra Strike', 'Terra Wave', 'Thunderstorm', 'Train Party', 'Ultimate Energy Strike', 'Ultimate Flame Strike', 'Ultimate Healing', 'Ultimate Healing Rune', 'Ultimate Ice Strike', 'Ultimate Light', 'Ultimate Terra Strike', 'Whirlwind Throw', 'Wild Growth', 'Wound Cleansing', 'Wrath of Nature'}
['Ultimate Energy Strike'] = {words = 'exori max vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateenergystrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, },
['Whirlwind Throw'] = {words = 'exori hur', exhaustion = 6000, premium = true, type = 'Instant', icon = 'whirlwindthrow', mana = 40, level = 28, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Fire Wave'] = {words = 'exevo flam hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'firewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, ['Sample'] = {
['Ethereal Spear'] = {words = 'exori con', exhaustion = 2000, premium = true, type = 'Instant', icon = 'eterealspear', mana = 25, level = 23, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, iconFile = 'sample.png',
['Strong Ethereal Spear'] = {words = 'exori gran con', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongetherealspear', mana = 55, level = 90, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, iconSize = {width = 64, height = 64},
['Energy Beam'] = {words = 'exevo vis lux', exhaustion = 4000, premium = false, type = 'Instant', icon = 'energybeam', mana = 40, level = 23, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, spellIcons = {[1] = 'Wind Walk', [2] = 'Fire Breath', [3] = 'Moonglaives', [5] = 'Firefly', [4] = 'Critical Strike'},
['Great Energy Beam'] = {words = 'exevo gran vis lux', exhaustion = 6000, premium = false, type = 'Instant', icon = 'greatenergybeam', mana = 110, level = 29, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, spellOrder = {'Critical Strike', 'Firefly', 'Fire Breath', 'Moonglaives', 'Wind Walk'}
['Groundshaker'] = {words = 'exori mas', exhaustion = 8000, premium = true, type = 'Instant', icon = 'groundshaker', mana = 160, level = 33, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, }
['Berserk'] = {words = 'exori', exhaustion = 4000, premium = true, type = 'Instant', icon = 'berserk', mana = 115, level = 35, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Annihilation'] = {words = 'exori gran ico', exhaustion = 30000, premium = true, type = 'Instant', icon = 'annihilation', mana = 300, level = 110,soul = 0, group = {[1] = 4000}, vocations = {4, 8}},
['Brutal Strike'] = {words = 'exori ico', exhaustion = 6000, premium = true, type = 'Instant', icon = 'brutalstrike', mana = 30, level = 16, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Front Sweep'] = {words = 'exori min', exhaustion = 6000, premium = true, type = 'Instant', icon = 'frontsweep', mana = 200, level = 70, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Inflict Wound'] = {words = 'utori kor', exhaustion = 30000, premium = true, type = 'Instant', icon = 'inflictwound', mana = 30, level = 40, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Ignite'] = {words = 'utori flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ignite', mana = 30, level = 26, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Lightning'] = {words = 'exori amp vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'lightning', mana = 60, level = 55, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}},
['Curser'] = {words = 'utori mort', exhaustion = 50000, premium = true, type = 'Instant', icon = 'curse', mana = 30, level = 75, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Electrify'] = {words = 'utori vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'electrify', mana = 30, level = 34, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Energy Wave'] = {words = 'exevo vis hur', exhaustion = 8000, premium = false, type = 'Instant', icon = 'energywave', mana = 170, level = 38, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Rage of the Skies'] = {words = 'exevo gran mas vis', exhaustion = 40000, premium = true, type = 'Instant', icon = 'rageoftheskies', mana = 600, level = 55, soul = 0, group = {[1] = 4000}, vocations = {1, 5}},
['Fierce Berserk'] = {words = 'exori gran', exhaustion = 6000, premium = true, type = 'Instant', icon = 'fierceberserk', mana = 340, level = 90, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Hells Core'] = {words = 'exevo gran mas flam', exhaustion = 40000, premium = true, type = 'Instant', icon = 'hellscore', mana = 1100, level = 60, soul = 0, group = {[1] = 4000}, vocations = {1, 5}},
['Holy Flash'] = {words = 'utori san', exhaustion = 40000, premium = true, type = 'Instant', icon = 'holyflash', mana = 30, level = 70, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Divine Missile'] = {words = 'exori san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'divinemissile', mana = 20, level = 40, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Divine Caldera'] = {words = 'exevo mas san', exhaustion = 4000, premium = true, type = 'Instant', icon = 'divinecaldera', mana = 160, level = 50, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Physical Strike'] = {words = 'exori moe ico', exhaustion = 2000, premium = true, type = 'Instant', icon = 'physicalstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Eternal Winter'] = {words = 'exevo gran mas frigo',exhaustion = 40000, premium = true, type = 'Instant', icon = 'eternalwinter', mana = 1050, level = 60, soul = 0, group = {[1] = 4000}, vocations = {2, 6}},
['Ice Strike'] = {words = 'exori frigo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'icestrike', mana = 20, level = 15, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}},
['Strong Ice Strike'] = {words = 'exori gran frigo', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicestrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}},
['Ultimate Ice Strike'] = {words = 'exori max frigo', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateicestrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {2, 6}},
['Ice Wave'] = {words = 'exevo frigo hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'icewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Strong Ice Wave'] = {words = 'exevo gran frigo hur',exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicewave', mana = 170, level = 40, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Envenom'] = {words = 'utori pox', exhaustion = 40000, premium = true, type = 'Instant', icon = 'envenom', mana = 30, level = 50, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Terra Strike'] = {words = 'exori tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'terrastrike', mana = 20, level = 13, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}},
['Strong Terra Strike'] = {words = 'exori gran tera', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongterrastrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}},
['Ultimate Terra Strike'] = {words = 'exori max tera', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateterrastrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {2, 6}},
['Terra Wave'] = {words = 'exevo tera hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'terrawave', mana = 210, level = 38, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Wrath of Nature'] = {words = 'exevo gran mas tera', exhaustion = 40000, premium = true, type = 'Instant', icon = 'wrathofnature', mana = 700, level = 55, soul = 0, group = {[1] = 4000}, vocations = {2, 6}},
['Light Healing'] = {words = 'exura', exhaustion = 1000, premium = false, type = 'Instant', icon = 'lighthealing', mana = 20, level = 9, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}},
['Wound Cleansing'] = {words = 'exura ico', exhaustion = 1000, premium = false, type = 'Instant', icon = 'woundcleansing', mana = 40, level = 10, soul = 0, group = {[2] = 1000}, vocations = {4, 8}},
['Intense Wound Cleansing'] = {words = 'exura gran ico', exhaustion = 600000,premium = true, type = 'Instant', icon = 'intensewoundcleansing', mana = 200, level = 80, soul = 0, group = {[2] = 1000}, vocations = {4, 8}},
['Cure Bleeding'] = {words = 'exana kor', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curebleeding', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {4, 8}},
['Cure Electrification'] = {words = 'exana vis', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curseelectrification', mana = 30, level = 22, soul = 0, group = {[2] = 1000}, vocations = {2, 6}},
['Cure Poison'] = {words = 'exana pox', exhaustion = 6000, premium = false, type = 'Instant', icon = 'curepoison', mana = 30, level = 10, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Cure Burning'] = {words = 'exana flam', exhaustion = 6000, premium = true, type = 'Instant', icon = 'cureburning', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {2, 6}},
['Cure Curse'] = {words = 'exana mort', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curecurse', mana = 40, level = 80, soul = 0, group = {[2] = 1000}, vocations = {3, 7}},
['Recovery'] = {words = 'utura', exhaustion = 60000, premium = true, type = 'Instant', icon = 'recovery', mana = 75, level = 50, soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}},
['Intense Recovery'] = {words = 'utura gran', exhaustion = 60000, premium = true, type = 'Instant', icon = 'intenserecovery', mana = 165, level = 100,soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}},
['Salvation'] = {words = 'exura gran san', exhaustion = 1000, premium = true, type = 'Instant', icon = 'salvation', mana = 210, level = 60, soul = 0, group = {[2] = 1000}, vocations = {3, 7}},
['Intense Healing'] = {words = 'exura gran', exhaustion = 1000, premium = false, type = 'Instant', icon = 'intensehealing', mana = 70, level = 20, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}},
['Heal Friend'] = {words = 'exura sio', exhaustion = 1000, premium = true, type = 'Instant', icon = 'healfriend', mana = 140, level = 18, soul = 0, group = {[2] = 1000}, vocations = {2, 6}},
['Ultimate Healing'] = {words = 'exura vita', exhaustion = 1000, premium = false, type = 'Instant', icon = 'ultimatehealing', mana = 160, level = 30, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 5, 6}},
['Mass Healing'] = {words = 'exura gran mas res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'masshealing', mana = 150, level = 36, soul = 0, group = {[2] = 1000}, vocations = {2, 6}},
['Divine Healing'] = {words = 'exura san', exhaustion = 1000, premium = false, type = 'Instant', icon = 'divinehealing', mana = 160, level = 35, soul = 0, group = {[2] = 1000}, vocations = {3, 7}},
['Light'] = {words = 'utevo lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'light', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Find Person'] = {words = 'exiva', exhaustion = 2000, premium = false, type = 'Instant', icon = 'findperson', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Magic Rope'] = {words = 'exani tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'magicrope', mana = 20, level = 9, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Levitate'] = {words = 'exani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'levitate', mana = 50, level = 12, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Great Light'] = {words = 'utevo gran lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'greatlight', mana = 60, level = 13, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Magic Shield'] = {words = 'utamo vita', exhaustion = 2000, premium = false, type = 'Instant', icon = 'magicshield', mana = 50, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Haste'] = {words = 'utani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'haste', mana = 60, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Charge'] = {words = 'utani tempo hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'charge', mana = 100, level = 25, soul = 0, group = {[3] = 2000}, vocations = {4, 8}},
['Swift Foot'] = {words = 'utamo tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'swiftfoot', mana = 400, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {3, 7}},
['Challenge'] = {words = 'exeta res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'challenge', mana = 30, level = 20, soul = 0, group = {[3] = 2000}, vocations = {8}},
['Strong Haste'] = {words = 'utani gran hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'stronghaste', mana = 100, level = 20, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Creature Illusion'] = {words = 'utevo res ina', exhaustion = 2000, premium = false, type = 'Instant', icon = 'creatureillusion', mana = 100, level = 23, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Ultimate Light'] = {words = 'utevo vis lux', exhaustion = 2000, premium = true, type = 'Instant', icon = 'ultimatelight', mana = 140, level = 26, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Cancel Invisibility'] = {words = 'exana ina', exhaustion = 2000, premium = true, type = 'Instant', icon = 'cancelinvisibility', mana = 200, level = 26, soul = 0, group = {[3] = 2000}, vocations = {3, 7}},
['Invisibility'] = {words = 'utana vid', exhaustion = 2000, premium = false, type = 'Instant', icon = 'invisible', mana = 440, level = 35, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Sharpshooter'] = {words = 'utito tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'sharpshooter', mana = 450, level = 60, soul = 0, group = {[2] = 10000, [3] = 10000}, vocations = {3, 7}},
['Protector'] = {words = 'utamo tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protector', mana = 200, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {4, 8}},
['Blood Rage'] = {words = 'utito tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'bloodrage', mana = 290, level = 60, soul = 0, group = {[3] = 2000}, vocations = {4, 8}},
['Train Party'] = {words = 'utito mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'trainparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {8}},
['Protect Party'] = {words = 'utamo mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protectparty', mana = 'Var.', evel = 32, soul = 0, group = {[3] = 2000}, vocations = {7}},
['Heal Party'] = {words = 'utura mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'healparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {6}},
['Enchant Party'] = {words = 'utori mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'enchantparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {5}},
['Summon Creature'] = {words = 'utevo res', exhaustion = 2000, premium = false, type = 'Instant', icon = 'summoncreature', mana = 'Var.', level = 25, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Conjure Arrow'] = {words = 'exevo con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurearrow', mana = 100, level = 13, soul = 1, group = {[3] = 2000}, vocations = {3, 7}},
['Food'] = {words = 'exevo pan', exhaustion = 2000, premium = false, type = 'Instant', icon = 'food', mana = 120, level = 14, soul = 1, group = {[3] = 2000}, vocations = {2, 6}},
['Conjure Poisoned Arrow'] = {words = 'exevo con pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonedarrow', mana = 130, level = 16, soul = 2, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Bolt'] = {words = 'exevo con mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurebolt', mana = 140, level = 17, soul = 2, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Sniper Arrow'] = {words = 'exevo con hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'sniperarrow', mana = 160, level = 24, soul = 3, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Explosive Arrow'] = {words = 'exevo con flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosivearrow', mana = 290, level = 25, soul = 3, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Piercing Bolt'] = {words = 'exevo con grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'piercingbolt', mana = 180, level = 33, soul = 3, group = {[3] = 2000}, vocations = {3, 7}},
['Enchant Staff'] = {words = 'exeta vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantstaff', mana = 80, level = 41, soul = 0, group = {[3] = 2000}, vocations = {5}},
['Enchant Spear'] = {words = 'exeta con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantspear', mana = 350, level = 45, soul = 3, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Power Bolt'] = {words = 'exevo con vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'powerbolt', mana = 800, level = 59, soul = 3, group = {[3] = 2000}, vocations = {7}},
['Poison Field'] = {words = 'adevo grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonfield', mana = 200, level = 14, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Light Magic Missile'] = {words = 'adori min vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'lightmagicmissile', mana = 120, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Fire Field'] = {words = 'adevo grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firefield', mana = 240, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Fireball'] = {words = 'adori flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'fireball', mana = 460, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 5}},
['Energy Field'] = {words = 'adevo grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energyfield', mana = 320, level = 18, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Stalagmite'] = {words = 'adori tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stalagmite', mana = 400, level = 24, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}},
['Great Fireball'] = {words = 'adori mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'greatfireball', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {1, 5}},
['Heavy Magic Missile'] = {words = 'adori vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'heavymagicmissile', mana = 350, level = 25, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}},
['Poison Bomb'] = {words = 'adevo mas pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonbomb', mana = 520, level = 25, soul = 2, group = {[3] = 2000}, vocations = {2, 6}},
['Firebomb'] = {words = 'adevo mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firebomb', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Soulfire'] = {words = 'adevo res flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'soulfire', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Poison Wall'] = {words = 'adevo mas grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonwall', mana = 640, level = 29, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Explosion'] = {words = 'adevo mas hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosion', mana = 570, level = 31, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Fire Wall'] = {words = 'adevo mas grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firewall', mana = 780, level = 33, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Energybomb'] = {words = 'adevo mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energybomb', mana = 880, level = 37, soul = 5, group = {[3] = 2000}, vocations = {1, 5}},
['Energy Wall'] = {words = 'adevo mas grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energywall', mana = 1000, level = 41, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Sudden Death'] = {words = 'adori gran mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'suddendeath', mana = 985, level = 45, soul = 5, group = {[3] = 2000}, vocations = {1, 5}},
['Cure Poison Rune'] = {words = 'adana pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'antidote', mana = 200, level = 15, soul = 1, group = {[3] = 2000}, vocations = {2, 6}},
['Intense Healing Rune'] = {words = 'adura gran', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'intensehealingrune', mana = 240, level = 15, soul = 2, group = {[3] = 2000}, vocations = {2, 6}},
['Ultimate Healing Rune'] = {words = 'adura vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'ultimatehealingrune', mana = 400, level = 24, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Convince Creature'] = {words = 'adeta sio', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'convincecreature', mana = 200, level = 16, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Animate Dead'] = {words = 'adana mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'animatedead', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Chameleon'] = {words = 'adevo ina', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'chameleon', mana = 600, level = 27, soul = 2, group = {[3] = 2000}, vocations = {2, 6}},
['Destroy Field'] = {words = 'adito grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'destroyfield', mana = 120, level = 17, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}},
['Desintegrate'] = {words = 'adito tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'desintegrate', mana = 200, level = 21, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}},
['Magic Wall'] = {words = 'adevo grav tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'magicwall', mana = 750, level = 32, soul = 5, group = {[3] = 2000}, vocations = {1, 5}},
['Wild Growth'] = {words = 'adevo grav vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'wildgrowth', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {2, 6}},
['Paralyze'] = {words = 'adana ani', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'paralyze', mana = 1400, level = 54, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Icicle'] = {words = 'adori frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'icicle', mana = 460, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Avalanche'] = {words = 'adori mas frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'avalanche', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Stone Shower'] = {words = 'adori mas tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stoneshower', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Thunderstorm'] = {words = 'adori mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'thunderstorm', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {1, 5}},
['Holy Missile'] = {words = 'adori san', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'holymissile', mana = 350, level = 27, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}
} }
-- ['const_name'] = {client_id, TFS_id} -- ['Spell Name'] = {words = '', exhaustion = spellCooldown, premium = true/false, type = 'Instant'/'Conjure', icon = iconName, mana = manaCost, level = levelRequirement, soul = soulCost, group = {[groupId] = groupCooldown}, vocation = {vocationIds}}
SpellInfo = {
['Default'] = {
['Death Strike'] = {words = 'exori mort', exhaustion = 2000, premium = true, type = 'Instant', icon = 'deathstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Flame Strike'] = {words = 'exori flam', exhaustion = 2000, premium = true, type = 'Instant', icon = 'flamestrike', mana = 20, level = 14, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}},
['Strong Flame Strike'] = {words = 'exori gran flam', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongflamestrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}},
['Ultimate Flame Strike'] = {words = 'exori max flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateflamestrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {1, 5}},
['Energy Strike'] = {words = 'exori vis', exhaustion = 2000, premium = true, type = 'Instant', icon = 'energystrike', mana = 20, level = 12, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}},
['Strong Energy Strike'] = {words = 'exori gran vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongenergystrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}},
['Ultimate Energy Strike'] = {words = 'exori max vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateenergystrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {1, 5}},
['Whirlwind Throw'] = {words = 'exori hur', exhaustion = 6000, premium = true, type = 'Instant', icon = 'whirlwindthrow', mana = 40, level = 28, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Fire Wave'] = {words = 'exevo flam hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'firewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Ethereal Spear'] = {words = 'exori con', exhaustion = 2000, premium = true, type = 'Instant', icon = 'eterealspear', mana = 25, level = 23, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Strong Ethereal Spear'] = {words = 'exori gran con', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongetherealspear', mana = 55, level = 90, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Energy Beam'] = {words = 'exevo vis lux', exhaustion = 4000, premium = false, type = 'Instant', icon = 'energybeam', mana = 40, level = 23, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Great Energy Beam'] = {words = 'exevo gran vis lux', exhaustion = 6000, premium = false, type = 'Instant', icon = 'greatenergybeam', mana = 110, level = 29, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Groundshaker'] = {words = 'exori mas', exhaustion = 8000, premium = true, type = 'Instant', icon = 'groundshaker', mana = 160, level = 33, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Berserk'] = {words = 'exori', exhaustion = 4000, premium = true, type = 'Instant', icon = 'berserk', mana = 115, level = 35, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Annihilation'] = {words = 'exori gran ico', exhaustion = 30000, premium = true, type = 'Instant', icon = 'annihilation', mana = 300, level = 110,soul = 0, group = {[1] = 4000}, vocations = {4, 8}},
['Brutal Strike'] = {words = 'exori ico', exhaustion = 6000, premium = true, type = 'Instant', icon = 'brutalstrike', mana = 30, level = 16, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Front Sweep'] = {words = 'exori min', exhaustion = 6000, premium = true, type = 'Instant', icon = 'frontsweep', mana = 200, level = 70, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Inflict Wound'] = {words = 'utori kor', exhaustion = 30000, premium = true, type = 'Instant', icon = 'inflictwound', mana = 30, level = 40, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Ignite'] = {words = 'utori flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ignite', mana = 30, level = 26, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Lightning'] = {words = 'exori amp vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'lightning', mana = 60, level = 55, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}},
['Curser'] = {words = 'utori mort', exhaustion = 50000, premium = true, type = 'Instant', icon = 'curse', mana = 30, level = 75, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Electrify'] = {words = 'utori vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'electrify', mana = 30, level = 34, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Energy Wave'] = {words = 'exevo vis hur', exhaustion = 8000, premium = false, type = 'Instant', icon = 'energywave', mana = 170, level = 38, soul = 0, group = {[1] = 2000}, vocations = {1, 5}},
['Rage of the Skies'] = {words = 'exevo gran mas vis', exhaustion = 40000, premium = true, type = 'Instant', icon = 'rageoftheskies', mana = 600, level = 55, soul = 0, group = {[1] = 4000}, vocations = {1, 5}},
['Fierce Berserk'] = {words = 'exori gran', exhaustion = 6000, premium = true, type = 'Instant', icon = 'fierceberserk', mana = 340, level = 90, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Hells Core'] = {words = 'exevo gran mas flam', exhaustion = 40000, premium = true, type = 'Instant', icon = 'hellscore', mana = 1100, level = 60, soul = 0, group = {[1] = 4000}, vocations = {1, 5}},
['Holy Flash'] = {words = 'utori san', exhaustion = 40000, premium = true, type = 'Instant', icon = 'holyflash', mana = 30, level = 70, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Divine Missile'] = {words = 'exori san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'divinemissile', mana = 20, level = 40, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Divine Caldera'] = {words = 'exevo mas san', exhaustion = 4000, premium = true, type = 'Instant', icon = 'divinecaldera', mana = 160, level = 50, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Physical Strike'] = {words = 'exori moe ico', exhaustion = 2000, premium = true, type = 'Instant', icon = 'physicalstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Eternal Winter'] = {words = 'exevo gran mas frigo',exhaustion = 40000, premium = true, type = 'Instant', icon = 'eternalwinter', mana = 1050, level = 60, soul = 0, group = {[1] = 4000}, vocations = {2, 6}},
['Ice Strike'] = {words = 'exori frigo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'icestrike', mana = 20, level = 15, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}},
['Strong Ice Strike'] = {words = 'exori gran frigo', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicestrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}},
['Ultimate Ice Strike'] = {words = 'exori max frigo', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateicestrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {2, 6}},
['Ice Wave'] = {words = 'exevo frigo hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'icewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Strong Ice Wave'] = {words = 'exevo gran frigo hur',exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicewave', mana = 170, level = 40, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Envenom'] = {words = 'utori pox', exhaustion = 40000, premium = true, type = 'Instant', icon = 'envenom', mana = 30, level = 50, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Terra Strike'] = {words = 'exori tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'terrastrike', mana = 20, level = 13, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}},
['Strong Terra Strike'] = {words = 'exori gran tera', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongterrastrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}},
['Ultimate Terra Strike'] = {words = 'exori max tera', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateterrastrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {2, 6}},
['Terra Wave'] = {words = 'exevo tera hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'terrawave', mana = 210, level = 38, soul = 0, group = {[1] = 2000}, vocations = {2, 6}},
['Wrath of Nature'] = {words = 'exevo gran mas tera', exhaustion = 40000, premium = true, type = 'Instant', icon = 'wrathofnature', mana = 700, level = 55, soul = 0, group = {[1] = 4000}, vocations = {2, 6}},
['Light Healing'] = {words = 'exura', exhaustion = 1000, premium = false, type = 'Instant', icon = 'lighthealing', mana = 20, level = 9, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}},
['Wound Cleansing'] = {words = 'exura ico', exhaustion = 1000, premium = false, type = 'Instant', icon = 'woundcleansing', mana = 40, level = 10, soul = 0, group = {[2] = 1000}, vocations = {4, 8}},
['Intense Wound Cleansing'] = {words = 'exura gran ico', exhaustion = 600000,premium = true, type = 'Instant', icon = 'intensewoundcleansing', mana = 200, level = 80, soul = 0, group = {[2] = 1000}, vocations = {4, 8}},
['Cure Bleeding'] = {words = 'exana kor', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curebleeding', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {4, 8}},
['Cure Electrification'] = {words = 'exana vis', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curseelectrification', mana = 30, level = 22, soul = 0, group = {[2] = 1000}, vocations = {2, 6}},
['Cure Poison'] = {words = 'exana pox', exhaustion = 6000, premium = false, type = 'Instant', icon = 'curepoison', mana = 30, level = 10, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Cure Burning'] = {words = 'exana flam', exhaustion = 6000, premium = true, type = 'Instant', icon = 'cureburning', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {2, 6}},
['Cure Curse'] = {words = 'exana mort', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curecurse', mana = 40, level = 80, soul = 0, group = {[2] = 1000}, vocations = {3, 7}},
['Recovery'] = {words = 'utura', exhaustion = 60000, premium = true, type = 'Instant', icon = 'recovery', mana = 75, level = 50, soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}},
['Intense Recovery'] = {words = 'utura gran', exhaustion = 60000, premium = true, type = 'Instant', icon = 'intenserecovery', mana = 165, level = 100,soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}},
['Salvation'] = {words = 'exura gran san', exhaustion = 1000, premium = true, type = 'Instant', icon = 'salvation', mana = 210, level = 60, soul = 0, group = {[2] = 1000}, vocations = {3, 7}},
['Intense Healing'] = {words = 'exura gran', exhaustion = 1000, premium = false, type = 'Instant', icon = 'intensehealing', mana = 70, level = 20, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}},
['Heal Friend'] = {words = 'exura sio', exhaustion = 1000, premium = true, type = 'Instant', icon = 'healfriend', mana = 140, level = 18, soul = 0, group = {[2] = 1000}, vocations = {2, 6}},
['Ultimate Healing'] = {words = 'exura vita', exhaustion = 1000, premium = false, type = 'Instant', icon = 'ultimatehealing', mana = 160, level = 30, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 5, 6}},
['Mass Healing'] = {words = 'exura gran mas res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'masshealing', mana = 150, level = 36, soul = 0, group = {[2] = 1000}, vocations = {2, 6}},
['Divine Healing'] = {words = 'exura san', exhaustion = 1000, premium = false, type = 'Instant', icon = 'divinehealing', mana = 160, level = 35, soul = 0, group = {[2] = 1000}, vocations = {3, 7}},
['Light'] = {words = 'utevo lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'light', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Find Person'] = {words = 'exiva', exhaustion = 2000, premium = false, type = 'Instant', icon = 'findperson', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Magic Rope'] = {words = 'exani tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'magicrope', mana = 20, level = 9, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Levitate'] = {words = 'exani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'levitate', mana = 50, level = 12, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Great Light'] = {words = 'utevo gran lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'greatlight', mana = 60, level = 13, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Magic Shield'] = {words = 'utamo vita', exhaustion = 2000, premium = false, type = 'Instant', icon = 'magicshield', mana = 50, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Haste'] = {words = 'utani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'haste', mana = 60, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}},
['Charge'] = {words = 'utani tempo hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'charge', mana = 100, level = 25, soul = 0, group = {[3] = 2000}, vocations = {4, 8}},
['Swift Foot'] = {words = 'utamo tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'swiftfoot', mana = 400, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {3, 7}},
['Challenge'] = {words = 'exeta res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'challenge', mana = 30, level = 20, soul = 0, group = {[3] = 2000}, vocations = {8}},
['Strong Haste'] = {words = 'utani gran hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'stronghaste', mana = 100, level = 20, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Creature Illusion'] = {words = 'utevo res ina', exhaustion = 2000, premium = false, type = 'Instant', icon = 'creatureillusion', mana = 100, level = 23, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Ultimate Light'] = {words = 'utevo vis lux', exhaustion = 2000, premium = true, type = 'Instant', icon = 'ultimatelight', mana = 140, level = 26, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Cancel Invisibility'] = {words = 'exana ina', exhaustion = 2000, premium = true, type = 'Instant', icon = 'cancelinvisibility', mana = 200, level = 26, soul = 0, group = {[3] = 2000}, vocations = {3, 7}},
['Invisibility'] = {words = 'utana vid', exhaustion = 2000, premium = false, type = 'Instant', icon = 'invisible', mana = 440, level = 35, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Sharpshooter'] = {words = 'utito tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'sharpshooter', mana = 450, level = 60, soul = 0, group = {[2] = 10000, [3] = 10000}, vocations = {3, 7}},
['Protector'] = {words = 'utamo tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protector', mana = 200, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {4, 8}},
['Blood Rage'] = {words = 'utito tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'bloodrage', mana = 290, level = 60, soul = 0, group = {[3] = 2000}, vocations = {4, 8}},
['Train Party'] = {words = 'utito mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'trainparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {8}},
['Protect Party'] = {words = 'utamo mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protectparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {7}},
['Heal Party'] = {words = 'utura mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'healparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {6}},
['Enchant Party'] = {words = 'utori mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'enchantparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {5}},
['Summon Creature'] = {words = 'utevo res', exhaustion = 2000, premium = false, type = 'Instant', icon = 'summoncreature', mana = 'Var.', level = 25, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Conjure Arrow'] = {words = 'exevo con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurearrow', mana = 100, level = 13, soul = 1, group = {[3] = 2000}, vocations = {3, 7}},
['Food'] = {words = 'exevo pan', exhaustion = 2000, premium = false, type = 'Instant', icon = 'food', mana = 120, level = 14, soul = 1, group = {[3] = 2000}, vocations = {2, 6}},
['Conjure Poisoned Arrow'] = {words = 'exevo con pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonedarrow', mana = 130, level = 16, soul = 2, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Bolt'] = {words = 'exevo con mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurebolt', mana = 140, level = 17, soul = 2, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Sniper Arrow'] = {words = 'exevo con hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'sniperarrow', mana = 160, level = 24, soul = 3, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Explosive Arrow'] = {words = 'exevo con flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosivearrow', mana = 290, level = 25, soul = 3, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Piercing Bolt'] = {words = 'exevo con grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'piercingbolt', mana = 180, level = 33, soul = 3, group = {[3] = 2000}, vocations = {3, 7}},
['Enchant Staff'] = {words = 'exeta vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantstaff', mana = 80, level = 41, soul = 0, group = {[3] = 2000}, vocations = {5}},
['Enchant Spear'] = {words = 'exeta con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantspear', mana = 350, level = 45, soul = 3, group = {[3] = 2000}, vocations = {3, 7}},
['Conjure Power Bolt'] = {words = 'exevo con vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'powerbolt', mana = 800, level = 59, soul = 3, group = {[3] = 2000}, vocations = {7}},
['Poison Field'] = {words = 'adevo grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonfield', mana = 200, level = 14, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Light Magic Missile'] = {words = 'adori min vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'lightmagicmissile', mana = 120, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Fire Field'] = {words = 'adevo grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firefield', mana = 240, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Fireball'] = {words = 'adori flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'fireball', mana = 460, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 5}},
['Energy Field'] = {words = 'adevo grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energyfield', mana = 320, level = 18, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Stalagmite'] = {words = 'adori tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stalagmite', mana = 400, level = 24, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}},
['Great Fireball'] = {words = 'adori mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'greatfireball', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {1, 5}},
['Heavy Magic Missile'] = {words = 'adori vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'heavymagicmissile', mana = 350, level = 25, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}},
['Poison Bomb'] = {words = 'adevo mas pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonbomb', mana = 520, level = 25, soul = 2, group = {[3] = 2000}, vocations = {2, 6}},
['Firebomb'] = {words = 'adevo mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firebomb', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Soulfire'] = {words = 'adevo res flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'soulfire', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Poison Wall'] = {words = 'adevo mas grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonwall', mana = 640, level = 29, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Explosion'] = {words = 'adevo mas hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosion', mana = 570, level = 31, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Fire Wall'] = {words = 'adevo mas grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firewall', mana = 780, level = 33, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Energybomb'] = {words = 'adevo mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energybomb', mana = 880, level = 37, soul = 5, group = {[3] = 2000}, vocations = {1, 5}},
['Energy Wall'] = {words = 'adevo mas grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energywall', mana = 1000, level = 41, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Sudden Death'] = {words = 'adori gran mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'suddendeath', mana = 985, level = 45, soul = 5, group = {[3] = 2000}, vocations = {1, 5}},
['Cure Poison Rune'] = {words = 'adana pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'antidote', mana = 200, level = 15, soul = 1, group = {[3] = 2000}, vocations = {2, 6}},
['Intense Healing Rune'] = {words = 'adura gran', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'intensehealingrune', mana = 240, level = 15, soul = 2, group = {[3] = 2000}, vocations = {2, 6}},
['Ultimate Healing Rune'] = {words = 'adura vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'ultimatehealingrune', mana = 400, level = 24, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Convince Creature'] = {words = 'adeta sio', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'convincecreature', mana = 200, level = 16, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Animate Dead'] = {words = 'adana mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'animatedead', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}},
['Chameleon'] = {words = 'adevo ina', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'chameleon', mana = 600, level = 27, soul = 2, group = {[3] = 2000}, vocations = {2, 6}},
['Destroy Field'] = {words = 'adito grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'destroyfield', mana = 120, level = 17, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}},
['Desintegrate'] = {words = 'adito tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'desintegrate', mana = 200, level = 21, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}},
['Magic Wall'] = {words = 'adevo grav tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'magicwall', mana = 750, level = 32, soul = 5, group = {[3] = 2000}, vocations = {1, 5}},
['Wild Growth'] = {words = 'adevo grav vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'wildgrowth', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {2, 6}},
['Paralyze'] = {words = 'adana ani', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'paralyze', mana = 1400, level = 54, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Icicle'] = {words = 'adori frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'icicle', mana = 460, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Avalanche'] = {words = 'adori mas frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'avalanche', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Stone Shower'] = {words = 'adori mas tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stoneshower', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}},
['Thunderstorm'] = {words = 'adori mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'thunderstorm', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {1, 5}},
['Holy Missile'] = {words = 'adori san', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'holymissile', mana = 350, level = 27, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}
},
['Sample'] = {
['Wind Walk'] = {words = 'windwalk', description = 'Run at enormous speed.', exhaustion = 2000, premium = false, type = 'Instant', icon = 1, mana = 50, level = 10, soul = 0, group = {[3] = 2000}, vocations = {1, 2}},
['Fire Breath'] = {words = 'firebreath', description = 'A strong firewave.', exhaustion = 2000, premium = false, type = 'Instant', icon = 2, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {4, 8}},
['Moonglaives'] = {words = 'moonglaives', description = 'Throw moonglaives around you.', exhaustion = 2000, premium = false, type = 'Instant', icon = 3, mana = 90, level = 55, soul = 0, group = {[1] = 2000}, vocations = {3, 7}},
['Critical Strike'] = {words = 'criticalstrike', description = 'Land a critical strike.', exhaustion = 2000, premium = false, type = 'Instant', icon = 4, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {3, 4, 7, 8}},
['Firefly'] = {words = 'firefly', description = 'Summon a angry firefly', exhaustion = 2000, premium = false, type = 'Instant', icon = 5, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}
}
}
-- ['const_name'] = {client_id, TFS_id}
-- Conversion from TFS icon id to the id used by client (icons.png order) -- Conversion from TFS icon id to the id used by client (icons.png order)
SpellIcons = { SpellIcons = {
['intenserecovery'] = {16, 160}, ['intenserecovery'] = {16, 160},
@@ -284,5 +312,5 @@ SpellGroups = {
[1] = 'Attack', [1] = 'Attack',
[2] = 'Healing', [2] = 'Healing',
[3] = 'Support', [3] = 'Support',
[4] = 'Powerstrikes' [4] = 'Special'
} }

View File

@@ -98,7 +98,7 @@ void Logger::fireOldMessages()
void Logger::setLogFile(const std::string& file) void Logger::setLogFile(const std::string& file)
{ {
m_outFile.open(file.c_str(), std::ios::out | std::ios::app); m_outFile.open(stdext::utf8_to_latin1(file.c_str()).c_str(), std::ios::out | std::ios::app);
if(!m_outFile.is_open() || !m_outFile.good()) { if(!m_outFile.is_open() || !m_outFile.good()) {
g_logger.error(stdext::format("Unable to save log to '%s'", file)); g_logger.error(stdext::format("Unable to save log to '%s'", file));
return; return;

View File

@@ -34,6 +34,7 @@ ResourceManager g_resources;
void ResourceManager::init(const char *argv0) void ResourceManager::init(const char *argv0)
{ {
PHYSFS_init(argv0); PHYSFS_init(argv0);
PHYSFS_permitSymbolicLinks(1);
} }
void ResourceManager::terminate() void ResourceManager::terminate()
@@ -44,21 +45,23 @@ void ResourceManager::terminate()
void ResourceManager::discoverWorkDir(const std::string& appName, const std::string& existentFile) void ResourceManager::discoverWorkDir(const std::string& appName, const std::string& existentFile)
{ {
// search for modules directory // search for modules directory
std::string sep = PHYSFS_getDirSeparator(); std::string possiblePaths[] = { g_resources.getCurrentDir(),
std::string possiblePaths[] = { boost::filesystem::current_path().generic_string() + sep, g_resources.getBaseDir() + "../",
g_resources.getBaseDir() + ".." + sep, g_resources.getBaseDir() + "../share/" + appName + "/",
g_resources.getBaseDir() + ".." + sep + "share" + sep + appName + sep, g_resources.getBaseDir() + appName + "/" };
g_resources.getBaseDir() + appName + sep };
bool found = false; bool found = false;
for(const std::string& dir : possiblePaths) { for(const std::string& dir : possiblePaths) {
// try to directory to modules path to see if it exists if(!PHYSFS_addToSearchPath(dir.c_str(), 0))
std::ifstream fin(dir + existentFile); continue;
if(fin) {
if(PHYSFS_exists(existentFile.c_str())) {
g_logger.debug(stdext::format("Found work dir at '%s'", dir.c_str())); g_logger.debug(stdext::format("Found work dir at '%s'", dir.c_str()));
m_workDir = dir; m_workDir = dir;
found = true; found = true;
break; break;
} }
PHYSFS_removeFromSearchPath(dir.c_str());
} }
if(!found) if(!found)
@@ -75,13 +78,18 @@ bool ResourceManager::setupUserWriteDir(const std::string& appWriteDirName)
dirName = appWriteDirName; dirName = appWriteDirName;
#endif #endif
std::string writeDir = userDir + dirName; std::string writeDir = userDir + dirName;
if(!PHYSFS_setWriteDir(writeDir.c_str())) {
if(!PHYSFS_setWriteDir(userDir.c_str()) || !PHYSFS_mkdir(dirName.c_str())) {
g_logger.error(stdext::format("Unable to create write directory '%s': %s", writeDir, PHYSFS_getLastError()));
return false;
}
}
return setWriteDir(writeDir); return setWriteDir(writeDir);
} }
bool ResourceManager::setWriteDir(const std::string& writeDir, bool create) bool ResourceManager::setWriteDir(const std::string& writeDir, bool create)
{ {
boost::filesystem::create_directory(writeDir);
if(!PHYSFS_setWriteDir(writeDir.c_str())) { if(!PHYSFS_setWriteDir(writeDir.c_str())) {
g_logger.error(stdext::format("Unable to set write directory '%s': %s", writeDir, PHYSFS_getLastError())); g_logger.error(stdext::format("Unable to set write directory '%s': %s", writeDir, PHYSFS_getLastError()));
return false; return false;
@@ -113,7 +121,7 @@ bool ResourceManager::addSearchPath(const std::string& path, bool pushFront)
} }
if(!found) { if(!found) {
g_logger.error(stdext::format("Could not add '%s' to directory search path. Reason %s", path, PHYSFS_getLastError())); //g_logger.error(stdext::format("Could not add '%s' to directory search path. Reason %s", path, PHYSFS_getLastError()));
return false; return false;
} }
} }
@@ -121,7 +129,6 @@ bool ResourceManager::addSearchPath(const std::string& path, bool pushFront)
m_searchPaths.push_front(savePath); m_searchPaths.push_front(savePath);
else else
m_searchPaths.push_back(savePath); m_searchPaths.push_back(savePath);
m_hasSearchPath = true;
return true; return true;
} }
@@ -161,31 +168,21 @@ bool ResourceManager::directoryExists(const std::string& directoryName)
void ResourceManager::loadFile(const std::string& fileName, std::iostream& out) void ResourceManager::loadFile(const std::string& fileName, std::iostream& out)
{ {
out.clear(std::ios::goodbit); out.clear(std::ios::goodbit);
if(m_hasSearchPath) { std::string fullPath = resolvePath(fileName);
std::string fullPath = resolvePath(fileName); PHYSFS_file* file = PHYSFS_openRead(fullPath.c_str());
PHYSFS_file* file = PHYSFS_openRead(fullPath.c_str()); if(!file) {
if(!file) { out.clear(std::ios::failbit);
out.clear(std::ios::failbit); stdext::throw_exception(stdext::format("unable to load file '%s': %s", fullPath.c_str(), PHYSFS_getLastError()));
stdext::throw_exception(stdext::format("unable to load file '%s': %s", fullPath.c_str(), PHYSFS_getLastError()));
} else {
int fileSize = PHYSFS_fileLength(file);
if(fileSize > 0) {
std::vector<char> buffer(fileSize);
PHYSFS_read(file, (void*)&buffer[0], 1, fileSize);
out.write(&buffer[0], fileSize);
} else
out.clear(std::ios::eofbit);
PHYSFS_close(file);
out.seekg(0, std::ios::beg);
}
} else { } else {
std::ifstream fin(fileName); int fileSize = PHYSFS_fileLength(file);
if(!fin) { if(fileSize > 0) {
out.clear(std::ios::failbit); std::vector<char> buffer(fileSize);
stdext::throw_exception(stdext::format("unable to load file '%s': %s", fileName.c_str(), PHYSFS_getLastError())); PHYSFS_read(file, (void*)&buffer[0], 1, fileSize);
} else { out.write(&buffer[0], fileSize);
out << fin.rdbuf(); } else
} out.clear(std::ios::eofbit);
PHYSFS_close(file);
out.seekg(0, std::ios::beg);
} }
} }
@@ -300,8 +297,17 @@ std::string ResourceManager::getRealDir(const std::string& path)
return dir; return dir;
} }
std::string ResourceManager::getBaseDir() std::string ResourceManager::getCurrentDir()
{ {
return PHYSFS_getBaseDir(); char buffer[2048];
PHYSFS_utf8FromLatin1((boost::filesystem::current_path().generic_string() + "/").c_str(), buffer, 2048);
return buffer;
}
std::string ResourceManager::getBaseDir()
{
char buffer[2048];
PHYSFS_utf8FromLatin1(PHYSFS_getBaseDir(), buffer, 2048);
return buffer;
} }

View File

@@ -64,6 +64,7 @@ public:
std::string resolvePath(const std::string& path); std::string resolvePath(const std::string& path);
std::string getRealDir(const std::string& path); std::string getRealDir(const std::string& path);
std::string getCurrentDir();
std::string getBaseDir(); std::string getBaseDir();
std::string getWriteDir() { return m_writeDir; } std::string getWriteDir() { return m_writeDir; }
std::string getWorkDir() { return m_workDir; } std::string getWorkDir() { return m_workDir; }
@@ -72,7 +73,6 @@ public:
private: private:
std::string m_workDir; std::string m_workDir;
std::string m_writeDir; std::string m_writeDir;
stdext::boolean<false> m_hasSearchPath;
std::deque<std::string> m_searchPaths; std::deque<std::string> m_searchPaths;
}; };

View File

@@ -103,6 +103,7 @@ void PlatformWindow::processKeyUp(Fw::Key keyCode)
if(m_onInputEvent) { if(m_onInputEvent) {
m_inputEvent.reset(Fw::KeyUpInputEvent); m_inputEvent.reset(Fw::KeyUpInputEvent);
m_inputEvent.keyCode = keyCode;
m_onInputEvent(m_inputEvent); m_onInputEvent(m_inputEvent);
} }
} }

View File

@@ -847,7 +847,7 @@ void WIN32Window::setFullscreen(bool fullscreen)
} else { } else {
SetWindowLong(m_window, GWL_STYLE, (dwStyle & ~(WS_POPUP | WS_EX_TOPMOST)) | WS_OVERLAPPEDWINDOW); SetWindowLong(m_window, GWL_STYLE, (dwStyle & ~(WS_POPUP | WS_EX_TOPMOST)) | WS_OVERLAPPEDWINDOW);
SetWindowPlacement(m_window, &wpPrev); SetWindowPlacement(m_window, &wpPrev);
SetWindowPos(m_window, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); SetWindowPos(m_window, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
} }
} }
@@ -946,7 +946,7 @@ std::string WIN32Window::getClipboardText()
if(hglb) { if(hglb) {
LPTSTR lptstr = (LPTSTR)GlobalLock(hglb); LPTSTR lptstr = (LPTSTR)GlobalLock(hglb);
if(lptstr) { if(lptstr) {
text = stdext::utf8_to_latin1((uchar*)lptstr); text = stdext::utf8_to_latin1(lptstr);
GlobalUnlock(hglb); GlobalUnlock(hglb);
} }
} }

View File

@@ -1056,7 +1056,7 @@ std::string X11Window::getClipboardText()
&bytesLeft, &bytesLeft,
&data); &data);
if(len > 0) { if(len > 0) {
clipboardText = stdext::utf8_to_latin1(data); clipboardText = stdext::utf8_to_latin1((char*)data);
} }
} }

View File

@@ -67,9 +67,9 @@ uint64_t hex_to_dec(const std::string& str)
return num; return num;
} }
std::string utf8_to_latin1(uchar *utf8) std::string utf8_to_latin1(const std::string& src)
{ {
auto utf8CharToLatin1 = [](uchar *utf8, int *read) -> char { auto utf8CharToLatin1 = [](const uchar *utf8, int *read) -> char {
char c = '?'; char c = '?';
uchar opt1 = utf8[0]; uchar opt1 = utf8[0];
*read = 1; *read = 1;
@@ -89,10 +89,10 @@ std::string utf8_to_latin1(uchar *utf8)
}; };
std::string out; std::string out;
int len = strlen((char*)utf8); int len = src.length();
for(int i=0; i<len;) { for(int i=0; i<len;) {
int read = 0; int read = 0;
uchar *utf8char = &utf8[i]; uchar *utf8char = (uchar*)&src[i];
out += utf8CharToLatin1(utf8char, &read); out += utf8CharToLatin1(utf8char, &read);
i += read; i += read;
} }

View File

@@ -42,7 +42,7 @@ std::string date_time_string();
std::string dec_to_hex(uint64_t num); std::string dec_to_hex(uint64_t num);
uint64_t hex_to_dec(const std::string& str); uint64_t hex_to_dec(const std::string& str);
std::string utf8_to_latin1(uchar *utf8); std::string utf8_to_latin1(const std::string& src);
void tolower(std::string& str); void tolower(std::string& str);
void toupper(std::string& str); void toupper(std::string& str);
void trim(std::string& str); void trim(std::string& str);

View File

@@ -40,7 +40,7 @@ int main(int argc, const char* argv[])
// find script init.lua and run it // find script init.lua and run it
g_resources.discoverWorkDir(g_app.getCompactName(), "init.lua"); g_resources.discoverWorkDir(g_app.getCompactName(), "init.lua");
if(!g_lua.safeRunScript(g_resources.getWorkDir() + "init.lua")) if(!g_lua.safeRunScript("init.lua"))
g_logger.fatal("Unable to run script init.lua!"); g_logger.fatal("Unable to run script init.lua!");
// the run application main loop // the run application main loop

View File

@@ -359,6 +359,30 @@ namespace Otc
PATHFIND_ALLOW_NONPATHABLE = 4, PATHFIND_ALLOW_NONPATHABLE = 4,
PATHFIND_ALLOW_NONWALKABLE = 8 PATHFIND_ALLOW_NONWALKABLE = 8
}; };
enum AutomapFlags
{
MAPMARK_TICK = 0,
MAPMARK_QUESTION,
MAPMARK_EXCLAMATION,
MAPMARK_STAR,
MAPMARK_CROSS,
MAPMARK_TEMPLE,
MAPMARK_KISS,
MAPMARK_SHOVEL,
MAPMARK_SWORD,
MAPMARK_FLAG,
MAPMARK_LOCK,
MAPMARK_BAG,
MAPMARK_SKULL,
MAPMARK_DOLLAR,
MAPMARK_REDNORTH,
MAPMARK_REDSOUTH,
MAPMARK_REDEAST,
MAPMARK_REDWEST,
MAPMARK_GREENNORTH,
MAPMARK_GREENSOUTH
};
} }
#endif #endif

View File

@@ -710,7 +710,7 @@ void Game::useWith(const ItemPtr& item, const ThingPtr& toThing)
if(!pos.isValid()) // virtual item if(!pos.isValid()) // virtual item
pos = Position(0xFFFF, 0, 0); // means that is a item in inventory pos = Position(0xFFFF, 0, 0); // means that is a item in inventory
if(toThing->isCreature()) if(toThing->isCreature() && g_game.getClientVersion() >= 860)
m_protocolGame->sendUseOnCreature(pos, item->getId(), item->getStackpos(), toThing->getId()); m_protocolGame->sendUseOnCreature(pos, item->getId(), item->getStackpos(), toThing->getId());
else else
m_protocolGame->sendUseItemWith(pos, item->getId(), item->getStackpos(), toThing->getPosition(), toThing->getId(), toThing->getStackpos()); m_protocolGame->sendUseItemWith(pos, item->getId(), item->getStackpos(), toThing->getPosition(), toThing->getId(), toThing->getStackpos());

View File

@@ -28,11 +28,6 @@
void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName) void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName)
{ {
if(accountName.empty() || accountPassword.empty()) {
callLuaField("onError", "You must enter an account name and password.");
return;
}
m_accountName = accountName; m_accountName = accountName;
m_accountPassword = accountPassword; m_accountPassword = accountPassword;
m_characterName = characterName; m_characterName = characterName;

View File

@@ -1288,10 +1288,11 @@ void ProtocolGame::parseTutorialHint(const InputMessagePtr& msg)
void ProtocolGame::parseAutomapFlag(const InputMessagePtr& msg) void ProtocolGame::parseAutomapFlag(const InputMessagePtr& msg)
{ {
// ignored Position pos = getPosition(msg); // position
getPosition(msg); // position int icon = msg->getU8(); // icon
msg->getU8(); // icon std::string description = msg->getString(); // message
msg->getString(); // message
g_game.processAutomapFlag(pos, icon, description);
} }
void ProtocolGame::parseQuestLog(const InputMessagePtr& msg) void ProtocolGame::parseQuestLog(const InputMessagePtr& msg)

View File

@@ -102,16 +102,11 @@ ImagePtr SpriteManager::getSpriteImage(int id)
int read = 0; int read = 0;
// decompress pixels // decompress pixels
while(read < pixelDataSize) { while(read < pixelDataSize && writePos < SPRITE_DATA_SIZE) {
uint16 transparentPixels = m_spritesFile->getU16(); uint16 transparentPixels = m_spritesFile->getU16();
uint16 coloredPixels = m_spritesFile->getU16(); uint16 coloredPixels = m_spritesFile->getU16();
if(writePos + transparentPixels*4 + coloredPixels*3 >= SPRITE_DATA_SIZE) { for(int i = 0; i < transparentPixels && writePos < SPRITE_DATA_SIZE; i++) {
g_logger.warning(stdext::format("corrupt sprite id %d", id));
return nullptr;
}
for(int i = 0; i < transparentPixels; i++) {
pixels[writePos + 0] = 0x00; pixels[writePos + 0] = 0x00;
pixels[writePos + 1] = 0x00; pixels[writePos + 1] = 0x00;
pixels[writePos + 2] = 0x00; pixels[writePos + 2] = 0x00;
@@ -119,12 +114,11 @@ ImagePtr SpriteManager::getSpriteImage(int id)
writePos += 4; writePos += 4;
} }
for(int i = 0; i < coloredPixels; i++) { for(int i = 0; i < coloredPixels && writePos < SPRITE_DATA_SIZE; i++) {
pixels[writePos + 0] = m_spritesFile->getU8(); pixels[writePos + 0] = m_spritesFile->getU8();
pixels[writePos + 1] = m_spritesFile->getU8(); pixels[writePos + 1] = m_spritesFile->getU8();
pixels[writePos + 2] = m_spritesFile->getU8(); pixels[writePos + 2] = m_spritesFile->getU8();
pixels[writePos + 3] = 0xFF; pixels[writePos + 3] = 0xFF;
writePos += 4; writePos += 4;
} }

View File

@@ -191,10 +191,9 @@ void ThingTypeManager::parseItemType(uint16 id, TiXmlElement* elem)
itemType = ItemTypePtr(new ItemType); itemType = ItemTypePtr(new ItemType);
itemType->setServerId(serverId); itemType->setServerId(serverId);
addItemType(itemType); addItemType(itemType);
} } else
itemType = getItemType(serverId);
itemType = getItemType(serverId);
assert(itemType && "Internal error");
itemType->setName(elem->Attribute("name")); itemType->setName(elem->Attribute("name"));
for(TiXmlElement* attrib = elem->FirstChildElement(); attrib; attrib = attrib->NextSiblingElement()) { for(TiXmlElement* attrib = elem->FirstChildElement(); attrib; attrib = attrib->NextSiblingElement()) {
std::string key = attrib->Attribute("key"); std::string key = attrib->Attribute("key");