35 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
Samuel
8536c61c01 Spell List: Small tweaks
Tweaks and formatting.
2012-10-07 04:17:45 +02:00
Samuel
a83be17bfe Revert "Spell List: Small tweaks"
This reverts commit 261dd40b96.
2012-10-07 04:12:49 +02:00
Samuel
261dd40b96 Spell List: Small tweaks
Tweaks and formatting in spells.lua
2012-10-07 04:11:11 +02:00
Samuel
eae002ea71 Spell List module
Spell List similar to Flash client

http://i.imgur.com/Tyxs2.png
2012-10-07 03:24:06 +02:00
Samuel
f0e9cf070e Important fix to uiscrollarea
I messed up the code and forgot to commit this.
->Check if function is set before calling it here.
2012-10-06 13:02:46 +00:00
Eduardo Bart
5756f20026 Fix orange text bug for protocol >861 2012-10-05 19:18:16 -03:00
Eduardo Bart
fa8b77f0c8 Fix issue #109 2012-10-05 16:14:05 -03:00
otfallen
ced5c035b9 optimize otb/xml a bit.
m_itemTypes was being resized too much than expected so is allocin
new itemtype each time it was not being found (which is fake).

otb loader is slow, not sure if it's because of the binary reader
or just the file format is stupid
2012-10-05 20:31:05 +00:00
Eduardo Bart
526885f70d Fix issue #14 2012-10-05 15:17:10 -03:00
Samuel
6c2539bbd4 Added support for curly braces in NPC chat
-Added overlay to the default consoleBuffer for highlighting
-Char 127 now is used as spacer (Width 1)
-Supports default font "verdana-11px-antialised"

http://i.imgur.com/8drWH.png
2012-10-05 17:50:54 +02:00
Eduardo Bart
737001264d Update version to 0.5.4 2012-10-04 20:33:10 -03:00
62 changed files with 2114 additions and 112 deletions

View File

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

View File

@@ -31,7 +31,8 @@ In short, if you need to compile OTClient, follow these tutorials:
### Need help?
Try to ask questions in [otland](http://otland.net/forum.php) or talk with us at #otclient irc.freenode.net
Try to ask questions in [otland](http://otland.net/f494/), now we have a board for the project there,
or talk with us at #otclient irc.freenode.net
### Bugs

View File

@@ -7,9 +7,6 @@ g_logger.setLogFile(g_resources.getWorkDir() .. g_app.getCompactName() .. ".log"
-- 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())
--add base folder to search path
g_resources.addSearchPath(g_resources.getWorkDir())
-- add modules directory to the search path
if not g_resources.addSearchPath(g_resources.getWorkDir() .. "modules", true) then
g_logger.fatal("Unable to add modules directory to the search path.")

View File

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

View File

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

View File

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

View File

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

View File

@@ -71,6 +71,7 @@ function UIScrollArea:setVerticalScrollBar(scrollbar)
local virtualOffset = self:getVirtualOffset()
virtualOffset.y = value
self:setVirtualOffset(virtualOffset)
if self.onScrollbarChange then self:onScrollbarChange(value) end
end
self:updateScrollBars()
end
@@ -81,6 +82,7 @@ function UIScrollArea:setHorizontalScrollBar(scrollbar)
local virtualOffset = self:getVirtualOffset()
virtualOffset.x = value
self:setVirtualOffset(virtualOffset)
if self.onScrollbarChange then self:onScrollbarChange(value) end
end
self:updateScrollBars()
end

View File

@@ -9,7 +9,9 @@ function UISplitter.create()
end
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 self:getWidth() > self:getHeight() then
g_mouse.setVerticalCursor()

View File

@@ -6,6 +6,60 @@ local function onTabClick(tab)
tab.tabBar:selectTab(tab)
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)
if not tab.blinking then return end
tab:setOn(not tab:isOn())
@@ -17,6 +71,8 @@ function UITabBar.create()
local tabbar = UITabBar.internalCreate()
tabbar:setFocusable(false)
tabbar.tabs = {}
tabbar.selected = nil -- dragged tab
tabsMoveable = false
return tabbar
end
@@ -41,16 +97,52 @@ function UITabBar:addTab(text, panel)
tab:setText(text)
tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight())
tab.onClick = onTabClick
tab.onMousePress = onTabMousePress
tab.onMouseRelease = onTabMouseRelease
tab.onMouseMove = onTabMouseMove
tab.onDestroy = function() tab.tabPanel:destroy() end
table.insert(self.tabs, tab)
if #self.tabs == 1 then
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
return tab
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)
local index = table.find(self.tabs, tab)
if index == nil then return end

View File

@@ -41,6 +41,7 @@ function init()
connect(Creature, {
onSkullChange = updateCreatureSkull,
onEmblemChange = updateCreatureEmblem,
onOutfitChange = onCreatureOutfitChange,
onHealthPercentChange = onCreatureHealthPercentChange,
onPositionChange = onCreaturePositionChange,
onAppear = onCreatureAppear,
@@ -67,6 +68,7 @@ function terminate()
disconnect(Creature, {
onSkullChange = updateCreatureSkull,
onEmblemChange = updateCreatureEmblem,
onOutfitChange = onCreatureOutfitChange,
onHealthPercentChange = onCreatureHealthPercentChange,
onPositionChange = onCreaturePositionChange,
onAppear = onCreatureAppear,
@@ -126,6 +128,8 @@ function doCreatureFitFilters(creature)
return false
end
if not creature:canBeSeen() then return false end
local hidePlayers = hidePlayersButton:isChecked()
local hideNPCs = hideNPCsButton:isChecked()
local hideMonsters = hideMonstersButton:isChecked()
@@ -168,9 +172,17 @@ function onCreaturePositionChange(creature, newPos, oldPos)
end
end
function onCreatureOutfitChange(creature, outfit, oldOutfit)
if not creature:canBeSeen() then
removeCreature(creature)
elseif doCreatureFitFilters(creature) then
removeCreature(creature)
addCreature(creature)
end
end
function onCreatureAppear(creature)
local player = g_game.getLocalPlayer()
if creature ~= player and creature:getPosition().z == player:getPosition().z and doCreatureFitFilters(creature) then
if doCreatureFitFilters(creature) then
addCreature(creature)
end
end

Binary file not shown.

View File

@@ -271,6 +271,38 @@ function addText(text, speaktype, tabName, creatureName)
end
end
-- Contains letter width for font "verdana-11px-antialised" as console is based on it
local letterWidth = { -- New line (10) and Space (32) have width 1 because they are printed and not replaced with spacer
[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,
[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,
[96] = 5, [97] = 7, [98] = 7, [99] = 6, [100] = 7, [101] = 7, [102] = 5, [103] = 7, [104] = 7, [105] = 3, [106] = 4, [107] = 7, [108] = 3, [109] = 11, [110] = 7,
[111] = 7, [112] = 7, [113] = 7, [114] = 6, [115] = 6, [116] = 5, [117] = 7, [118] = 8, [119] = 10, [120] = 8, [121] = 8, [122] = 6, [123] = 7, [124] = 4, [125] = 7, [126] = 8,
[127] = 1, [128] = 7, [129] = 6, [130] = 3, [131] = 7, [132] = 6, [133] = 11, [134] = 7, [135] = 7, [136] = 7, [137] = 13, [138] = 7, [139] = 4, [140] = 11, [141] = 6, [142] = 6,
[143] = 6, [144] = 6, [145] = 4, [146] = 3, [147] = 7, [148] = 6, [149] = 6, [150] = 7, [151] = 10, [152] = 7, [153] = 10, [154] = 6, [155] = 5, [156] = 11, [157] = 6, [158] = 6,
[159] = 8, [160] = 4, [161] = 3, [162] = 7, [163] = 7, [164] = 7, [165] = 8, [166] = 4, [167] = 7, [168] = 6, [169] = 10, [170] = 6, [171] = 8, [172] = 8, [173] = 16, [174] = 10,
[175] = 8, [176] = 5, [177] = 8, [178] = 5, [179] = 5, [180] = 6, [181] = 7, [182] = 7, [183] = 3, [184] = 5, [185] = 6, [186] = 6, [187] = 8, [188] = 12, [189] = 12, [190] = 12,
[191] = 6, [192] = 9, [193] = 9, [194] = 9, [195] = 9, [196] = 9, [197] = 9, [198] = 11, [199] = 7, [200] = 7, [201] = 7, [202] = 7, [203] = 7, [204] = 5, [205] = 5, [206] = 6,
[207] = 5, [208] = 8, [209] = 8, [210] = 8, [211] = 8, [212] = 8, [213] = 8, [214] = 8, [215] = 8, [216] = 8, [217] = 8, [218] = 8, [219] = 8, [220] = 8, [221] = 8, [222] = 7,
[223] = 7, [224] = 7, [225] = 7, [226] = 7, [227] = 7, [228] = 7, [229] = 7, [230] = 11, [231] = 6, [232] = 7, [233] = 7, [234] = 7, [235] = 7, [236] = 3, [237] = 4, [238] = 4,
[239] = 4, [240] = 7, [241] = 7, [242] = 7, [243] = 7, [244] = 7, [245] = 7, [246] = 7, [247] = 9, [248] = 7, [249] = 7, [250] = 7, [251] = 7, [252] = 7, [253] = 8, [254] = 7, [255] = 8
}
-- Return information about start, end in the string and the highlighted words
function getHighlightedText(text)
local tmpData = {}
repeat
local tmp = {string.find(text, "{([^}]+)}", tmpData[#tmpData-1])}
for _, v in pairs(tmp) do
table.insert(tmpData, v)
end
until not(string.find(text, "{([^}]+)}", tmpData[#tmpData-1]))
return tmpData
end
function addTabText(text, speaktype, tab, creatureName)
if Options.getOption('showTimestampsInConsole') then
text = os.date('%H:%M') .. ' ' .. text
@@ -284,11 +316,70 @@ function addTabText(text, speaktype, tab, creatureName)
label:setColor(speaktype.color)
consoleTabBar:blinkTab(tab)
-- Overlay for consoleBuffer which shows highlighted words only
local consoleBufferHighlight = panel:getChildById('consoleBufferHighlight')
local labelHighlight = g_ui.createWidget('ConsoleLabel', consoleBufferHighlight)
labelHighlight:setId('consoleLabel' .. panel:getChildCount())
labelHighlight:setColor("#1f9ffe")
local player = g_game.getLocalPlayer()
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)
if #highlightData == 0 then
labelHighlight:setText("")
else
-- Remove the curly braces
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] }
text = text:gsub("{"..dataBlock.words.."}", dataBlock.words)
-- Recalculate positions as braces are removed
highlightData[(i-1)*3+1] = dataBlock._start - ((i-1) * 2)
highlightData[(i-1)*3+2] = dataBlock._end - (1 + (i-1) * 2)
end
label:setText(text)
-- Calculate the positions of the highlighted text and fill with string.char(127) [Width: 1]
local drawText = label:getDrawText()
local tmpText = ""
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 lastBlockEnd = (highlightData[(i-2)*3+2] or 1)
for letter = lastBlockEnd, dataBlock._start-1 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
tmpText = tmpText .. dataBlock.words
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)
end
else
labelHighlight:setText("")
end
label.onMouseRelease = function (self, mousePos, mouseButton) popupMenu(mousePos, mouseButton, creatureName, text) end
if consoleBuffer:getChildCount() > MAX_LINES then
consoleBuffer:getFirstChild():destroy()
end
if consoleBufferHighlight:getChildCount() > MAX_LINES then
consoleBufferHighlight:getFirstChild():destroy()
end
end
function popupMenu(mousePos, mouseButton, creatureName, text)
@@ -453,14 +544,31 @@ function applyMessagePrefixies(name, level, message)
end
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 (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.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud) and
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()
staticText:addMessage(name, mode, message)
staticText:addMessage(name, mode, staticMessage)
g_map.addThing(staticText, creaturePos, -1)
end
@@ -577,7 +685,7 @@ function onGameStart()
if savedChannels then
for channelName, channelId in pairs(savedChannels) do
channelId = tonumber(channelId)
if channelId ~= 0 then
if channelId ~= 0 and channelId < 100 then
if not table.find(channels, channelId) then
g_game.joinChannel(channelId)
end

View File

@@ -24,6 +24,31 @@ ConsoleTabBarPanel < TabBarRoundedPanel
inverted-scroll: true
padding: 1
ScrollablePanel
id: consoleBufferHighlight
anchors.fill: parent
margin-right: 12
vertical-scrollbar: consoleScrollBarHighlight
layout:
type: verticalBox
align-bottom: true
border-width: 1
border-color: #202327
inverted-scroll: true
padding: 1
@onScrollbarChange: |
local consoleScrollBar = self:getParent():getChildById('consoleScrollBar')
local consoleScrollBarHighlight = self:getParent():getChildById('consoleScrollBarHighlight')
consoleScrollBar:setValue(consoleScrollBarHighlight:getValue())
VerticalScrollBar
id: consoleScrollBarHighlight
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
step: 14
pixels-scroll: true
VerticalScrollBar
id: consoleScrollBar
anchors.top: parent.top
@@ -53,6 +78,7 @@ Panel
anchors.top: prev.top
anchors.right: next.left
margin-left: 5
moveable: true
TabButton
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
logoutWindow = nil
exitWindow = nil
bottomSplitter = nil
function init()
g_ui.importStyle('styles/countwindow.otui')
@@ -21,10 +22,12 @@ function init()
gameRootPanel = g_ui.displayUI('gameinterface.otui')
gameRootPanel:hide()
gameRootPanel:lower()
gameRootPanel.onGeometryChange = updateStretchShrink
mouseGrabberWidget = gameRootPanel:getChildById('mouseGrabber')
mouseGrabberWidget.onMouseRelease = onMouseGrabberRelease
bottomSplitter = gameRootPanel:getChildById('bottomSplitter')
gameMapPanel = gameRootPanel:getChildById('gameMapPanel')
gameRightPanel = gameRootPanel:getChildById('gameRightPanel')
gameLeftPanel = gameRootPanel:getChildById('gameLeftPanel')
@@ -70,6 +73,7 @@ function bindKeys()
g_keyboard.bindKeyDown('Ctrl+W', function() g_map.cleanTexts() modules.game_textmessage.clearMessages() end, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+;', toggleDash, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+.', toggleAspectRatio, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+N', function() gameMapPanel:setDrawTexts(not gameMapPanel:isDrawingTexts()) end, gameRootPanel)
end
function terminate()
@@ -89,6 +93,7 @@ function show()
gameRootPanel:show()
gameRootPanel:focus()
gameMapPanel:followCreature(g_game.getLocalPlayer())
updateStretchShrink()
end
function hide()
@@ -186,6 +191,16 @@ function smartWalk(defaultDir)
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()
if gameMapPanel:isKeepAspectRatioEnabled() then
gameMapPanel:setKeepAspectRatio(false)
@@ -543,4 +558,4 @@ function onLeftPanelVisibilityChange(leftPanel, visible)
children[i]:setParent(gameRightPanel)
end
end
end
end

View File

@@ -1,3 +1,4 @@
GameSidePanel < UIMiniWindowContainer
image-source: /images/sidepanel.png
image-border: 4
@@ -62,10 +63,10 @@ UIWidget
anchors.bottom: parent.bottom
relative-margin: bottom
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
UIWidget
id: mouseGrabber
focusable: false
visible: false
visible: false

View File

@@ -28,5 +28,7 @@ Module
- game_playerdeath
- game_playermount
- game_market
- game_spelllist
- game_cooldown
@onLoad: init()
@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
minimapWindow = nil
flagsPanel = nil
flagWindow = nil
nextFlagId = 0
--[[
Known Issue (TODO):
If you move the minimap compass directions and
you change floor it will not update the minimap.
]]
function init()
g_ui.importStyle('flagwindow.otui')
connect(g_game, {
onGameStart = online,
onGameEnd = offline,
onAutomapFlag = addMapFlag
})
connect(LocalPlayer, { onPositionChange = center })
connect(LocalPlayer, { onPositionChange = center,
onPositionChange = updateMapFlags })
g_keyboard.bindKeyDown('Ctrl+M', toggle)
@@ -30,7 +37,9 @@ function init()
minimapWidget = minimapWindow:recursiveGetChildById('minimap')
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:setViewMode(1) -- mid view
minimapWidget:setDrawMinimapColors(true)
@@ -38,18 +47,28 @@ function init()
minimapWidget:setKeepAspectRatio(false)
minimapWidget.onMouseRelease = onMinimapMouseRelease
minimapWidget.onMouseWheel = onMinimapMouseWheel
flagsPanel = minimapWindow:recursiveGetChildById('flagsPanel')
reset()
minimapWindow:setup()
loadMapFlags()
if g_game.isOnline() then
addEvent(function() updateMapFlags() end)
end
end
function terminate()
disconnect(g_game, {
onGameStart = online,
onGameEnd = offline,
onAutomapFlag = addMapFlag
})
disconnect(LocalPlayer, { onPositionChange = center })
disconnect(LocalPlayer, { onPositionChange = center,
onPositionChange = updateMapFlags })
destroyFlagWindow()
saveMapFlags()
if g_game.isOnline() then
saveMap()
end
@@ -60,9 +79,188 @@ function terminate()
minimapWindow:destroy()
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()
reset()
loadMap()
updateMapFlags()
end
function offline()
@@ -109,6 +307,8 @@ function center()
local player = g_game.getLocalPlayer()
if not player then return end
minimapWidget:followCreature(player)
updateMapFlags()
end
function compassClick(self, mousePos, mouseButton, elapsed)
@@ -133,6 +333,8 @@ function compassClick(self, mousePos, mouseButton, elapsed)
local cameraPos = minimapWidget:getCameraPosition()
local pos = {x = cameraPos.x + movex, y = cameraPos.y + movey, z = cameraPos.z}
minimapWidget:setCameraPosition(pos)
updateMapFlags()
end
function onButtonClick(id)
@@ -153,6 +355,8 @@ function onButtonClick(id)
minimapWidget:setCameraPosition(pos)
end
end
updateMapFlags()
end
function onMinimapMouseRelease(self, mousePosition, mouseButton)
@@ -179,6 +383,7 @@ function onMinimapMouseWheel(self, mousePos, direction)
else
self:zoomOut()
end
updateMapFlags()
end
function onMiniWindowClose()

View File

@@ -7,6 +7,13 @@ MapControl < Button
$hover !pressed:
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
icon-source: /game_minimap/images/floor_up.png
@@ -26,6 +33,7 @@ MiniWindow
height: 150
icon: minimap.png
@onClose: modules.game_minimap.onMiniWindowClose()
@onGeometryChange: updateMapFlags()
&save: true
Label
@@ -43,6 +51,11 @@ MiniWindow
id: minimap
anchors.fill: parent
Panel
id: flagsPanel
anchors.fill: minimap
phantom: true
FloorUpControl
id: floorUp
anchors.right: parent.right

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,413 @@
local SpelllistProfile = 'Default'
spelllistWindow = nil
spelllistButton = nil
spellList = nil
nameValueLabel = nil
formulaValueLabel = nil
vocationValueLabel = nil
groupValueLabel = nil
typeValueLabel = nil
cooldownValueLabel = nil
levelValueLabel = nil
manaValueLabel = nil
premiumValueLabel = nil
descriptionValueLabel = nil
vocationBoxAny = nil
vocationBoxSorcerer = nil
vocationBoxDruid = nil
vocationBoxPaladin = nil
vocationBoxKnight = nil
groupBoxAny = nil
groupBoxAttack = nil
groupBoxHealing = nil
groupBoxSupport = nil
premiumBoxAny = nil
premiumBoxNo = nil
premiumBoxYes = nil
vocationRadioGroup = nil
groupRadioGroup = nil
premiumRadioGroup = nil
-- consts
FILTER_PREMIUM_ANY = 0
FILTER_PREMIUM_NO = 1
FILTER_PREMIUM_YES = 2
FILTER_VOCATION_ANY = 0
FILTER_VOCATION_SORCERER = 1
FILTER_VOCATION_DRUID = 2
FILTER_VOCATION_PALADIN = 3
FILTER_VOCATION_KNIGHT = 4
FILTER_GROUP_ANY = 0
FILTER_GROUP_ATTACK = 1
FILTER_GROUP_HEALING = 2
FILTER_GROUP_SUPPORT = 3
-- Filter Settings
local filters = {
level = false,
vocation = false,
vocationId = FILTER_VOCATION_ANY,
premium = FILTER_PREMIUM_ANY,
groupId = FILTER_GROUP_ANY
}
function getSpelllistProfile()
return SpelllistProfile
end
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
spelllistWindow:getChildById('buttonFilterVocation'):setVisible(true)
else
spelllistWindow:getChildById('buttonFilterVocation'):setVisible(false)
end
end
function init()
connect(g_game, { onGameStart = setOptions,
onGameEnd = resetWindow })
spelllistWindow = g_ui.displayUI('spelllist.otui', modules.game_interface.getRightPanel())
spelllistWindow:hide()
spelllistButton = TopMenu.addRightGameToggleButton('spelllistButton', tr('Spell List'), 'spelllist.png', toggle)
spelllistButton:setOn(false)
nameValueLabel = spelllistWindow:getChildById('labelNameValue')
formulaValueLabel = spelllistWindow:getChildById('labelFormulaValue')
vocationValueLabel = spelllistWindow:getChildById('labelVocationValue')
groupValueLabel = spelllistWindow:getChildById('labelGroupValue')
typeValueLabel = spelllistWindow:getChildById('labelTypeValue')
cooldownValueLabel = spelllistWindow:getChildById('labelCooldownValue')
levelValueLabel = spelllistWindow:getChildById('labelLevelValue')
manaValueLabel = spelllistWindow:getChildById('labelManaValue')
premiumValueLabel = spelllistWindow:getChildById('labelPremiumValue')
descriptionValueLabel = spelllistWindow:getChildById('labelDescriptionValue')
vocationBoxAny = spelllistWindow:getChildById('vocationBoxAny')
vocationBoxSorcerer = spelllistWindow:getChildById('vocationBoxSorcerer')
vocationBoxDruid = spelllistWindow:getChildById('vocationBoxDruid')
vocationBoxPaladin = spelllistWindow:getChildById('vocationBoxPaladin')
vocationBoxKnight = spelllistWindow:getChildById('vocationBoxKnight')
groupBoxAny = spelllistWindow:getChildById('groupBoxAny')
groupBoxAttack = spelllistWindow:getChildById('groupBoxAttack')
groupBoxHealing = spelllistWindow:getChildById('groupBoxHealing')
groupBoxSupport = spelllistWindow:getChildById('groupBoxSupport')
premiumBoxAny = spelllistWindow:getChildById('premiumBoxAny')
premiumBoxYes = spelllistWindow:getChildById('premiumBoxYes')
premiumBoxNo = spelllistWindow:getChildById('premiumBoxNo')
vocationRadioGroup = UIRadioGroup.create()
vocationRadioGroup:addWidget(vocationBoxAny)
vocationRadioGroup:addWidget(vocationBoxSorcerer)
vocationRadioGroup:addWidget(vocationBoxDruid)
vocationRadioGroup:addWidget(vocationBoxPaladin)
vocationRadioGroup:addWidget(vocationBoxKnight)
groupRadioGroup = UIRadioGroup.create()
groupRadioGroup:addWidget(groupBoxAny)
groupRadioGroup:addWidget(groupBoxAttack)
groupRadioGroup:addWidget(groupBoxHealing)
groupRadioGroup:addWidget(groupBoxSupport)
premiumRadioGroup = UIRadioGroup.create()
premiumRadioGroup:addWidget(premiumBoxAny)
premiumRadioGroup:addWidget(premiumBoxYes)
premiumRadioGroup:addWidget(premiumBoxNo)
premiumRadioGroup:selectWidget(premiumBoxAny)
vocationRadioGroup:selectWidget(vocationBoxAny)
groupRadioGroup:selectWidget(groupBoxAny)
vocationRadioGroup.onSelectionChange = toggleFilter
groupRadioGroup.onSelectionChange = toggleFilter
premiumRadioGroup.onSelectionChange = toggleFilter
spellList = spelllistWindow:getChildById('spellList')
g_keyboard.bindKeyPress('Down', function() spellList:focusNextChild(KeyboardFocusReason) end, spelllistWindow)
g_keyboard.bindKeyPress('Up', function() spellList:focusPreviousChild(KeyboardFocusReason) end, spelllistWindow)
initialiseSpelllist()
setOptions()
resizeWindow()
end
function terminate()
disconnect(g_game, { onGameStart = setOptions,
onGameEnd = resetWindow,
onSpellGroupCooldown = modules.game_interface.setGroupCooldown,
onSpellCooldown = onSpellCooldown })
disconnect(spellList, { onChildFocusChange = function(self, focusedChild)
if focusedChild == nil then return end
updateSpellInformation(focusedChild)
end })
spelllistButton:destroy()
spelllistButton = nil
spelllistWindow:destroy()
spelllistWindow = nil
vocationRadioGroup:destroy()
vocationRadioGroup = nil
groupRadioGroup:destroy()
groupRadioGroup = nil
premiumRadioGroup:destroy()
premiumRadioGroup = nil
spellList = nil
nameValueLabel = nil
formulaValueLabel = nil
vocationValueLabel = nil
groupValueLabel = nil
typeValueLabel = nil
cooldownValueLabel = nil
levelValueLabel = nil
manaValueLabel = nil
premiumValueLabel = nil
descriptionValueLabel = nil
vocationBoxAny = nil
vocationBoxSorcerer = nil
vocationBoxDruid = nil
vocationBoxPaladin = nil
vocationBoxKnight = nil
groupBoxAny = nil
groupBoxAttack = nil
groupBoxHealing = nil
groupBoxSupport = nil
premiumBoxAny = nil
premiumBoxNo = 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
function updateSpelllist()
for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do
local spell = SpelllistSettings[SpelllistProfile].spellOrder[i]
local info = SpellInfo[SpelllistProfile][spell]
local tmpLabel = spellList:getChildById(spell)
local localPlayer = g_game.getLocalPlayer()
if (not(filters.level) or info.level <= localPlayer:getLevel()) and (not(filters.vocation) or table.find(info.vocations, localPlayer:getVocation())) and (filters.vocationId == FILTER_VOCATION_ANY or table.find(info.vocations, filters.vocationId) or table.find(info.vocations, filters.vocationId+4)) and (filters.groupId == FILTER_GROUP_ANY or info.group[filters.groupId]) and (filters.premium == FILTER_PREMIUM_ANY or (info.premium and filters.premium == FILTER_PREMIUM_YES) or (not(info.premium) and filters.premium == FILTER_PREMIUM_NO)) then
tmpLabel:setVisible(true)
else
tmpLabel:setVisible(false)
end
end
end
function updateSpellInformation(widget)
local spell = widget:getId()
local name = ''
local formula = ''
local vocation = ''
local group = ''
local type = ''
local cooldown = ''
local level = ''
local mana = ''
local premium = ''
local description = ''
if SpellInfo[SpelllistProfile][spell] then
local info = SpellInfo[SpelllistProfile][spell]
name = spell
formula = info.words
for i = 1, #info.vocations do
local vocationId = info.vocations[i]
if vocationId <= 4 or not(table.find(info.vocations, (vocationId-4))) then
vocation = vocation .. (vocation:len() == 0 and '' or ', ') .. VocationNames[vocationId]
end
end
cooldown = (info.exhaustion / 1000) .. 's'
for groupId, groupName in ipairs(SpellGroups) do
if info.group[groupId] then
group = group .. (group:len() == 0 and '' or ' / ') .. groupName
cooldown = cooldown .. ' / ' .. (info.group[groupId] / 1000) .. 's'
end
end
type = info.type
level = info.level
mana = info.mana .. ' / ' .. info.soul
premium = (info.premium and 'yes' or 'no')
description = info.description or '-'
end
nameValueLabel:setText(name)
formulaValueLabel:setText(formula)
vocationValueLabel:setText(vocation)
groupValueLabel:setText(group)
typeValueLabel:setText(type)
cooldownValueLabel:setText(cooldown)
levelValueLabel:setText(level)
manaValueLabel:setText(mana)
premiumValueLabel:setText(premium)
descriptionValueLabel:setText(description)
end
function toggle()
if spelllistButton:isOn() then
spelllistWindow:hide()
spelllistButton:setOn(false)
else
spelllistWindow:show()
spelllistButton:setOn(true)
end
end
function toggleFilter(widget, selectedWidget)
if widget == vocationRadioGroup then
local boxId = selectedWidget:getId()
if boxId == 'vocationBoxAny' then
filters.vocationId = FILTER_VOCATION_ANY
elseif boxId == 'vocationBoxSorcerer' then
filters.vocationId = FILTER_VOCATION_SORCERER
elseif boxId == 'vocationBoxDruid' then
filters.vocationId = FILTER_VOCATION_DRUID
elseif boxId == 'vocationBoxPaladin' then
filters.vocationId = FILTER_VOCATION_PALADIN
elseif boxId == 'vocationBoxKnight' then
filters.vocationId = FILTER_VOCATION_KNIGHT
end
elseif widget == groupRadioGroup then
local boxId = selectedWidget:getId()
if boxId == 'groupBoxAny' then
filters.groupId = FILTER_GROUP_ANY
elseif boxId == 'groupBoxAttack' then
filters.groupId = FILTER_GROUP_ATTACK
elseif boxId == 'groupBoxHealing' then
filters.groupId = FILTER_GROUP_HEALING
elseif boxId == 'groupBoxSupport' then
filters.groupId = FILTER_GROUP_SUPPORT
end
elseif widget == premiumRadioGroup then
local boxId = selectedWidget:getId()
if boxId == 'premiumBoxAny' then
filters.premium = FILTER_PREMIUM_ANY
elseif boxId == 'premiumBoxNo' then
filters.premium = FILTER_PREMIUM_NO
elseif boxId == 'premiumBoxYes' then
filters.premium = FILTER_PREMIUM_YES
end
else
local id = widget:getId()
if id == 'buttonFilterLevel' then
filters.level = not(filters.level)
widget:setOn(filters.level)
elseif id == 'buttonFilterVocation' then
filters.vocation = not(filters.vocation)
widget:setOn(filters.vocation)
end
end
updateSpelllist()
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()
spelllistWindow:hide()
spelllistButton:setOn(false)
-- Resetting filters
filters.level = false
filters.vocation = false
local buttonFilterLevel = spelllistWindow:getChildById('buttonFilterLevel')
buttonFilterLevel:setOn(filters.level)
local buttonFilterVocation = spelllistWindow:getChildById('buttonFilterVocation')
buttonFilterVocation:setOn(filters.vocation)
vocationRadioGroup:selectWidget(vocationBoxAny)
groupRadioGroup:selectWidget(groupBoxAny)
premiumRadioGroup:selectWidget(premiumBoxAny)
updateSpelllist()
end

View File

@@ -0,0 +1,9 @@
Module
name: game_spelllist
description: View available spells
author: Summ, Edubart
website: www.otclient.info
sandboxed: true
scripts: [ spelllist.lua ]
@onLoad: init()
@onUnload: terminate()

View File

@@ -0,0 +1,328 @@
SpellListLabel < Label
font: verdana-11px-monochrome
background-color: alpha
text-offset: 42 3
focusable: true
height: 36
image-clip: 0 0 32 32
image-size: 32 32
image-offset: 2 2
image-source: /game_spelllist/icons/icons.png
$focus:
background-color: #ffffff22
color: #ffffff
SpellInfoLabel < Label
width: 70
font: verdana-11px-monochrome
text-align: right
margin-left: 10
margin-top: 5
SpellInfoValueLabel < Label
text-align: left
width: 190
margin-left: 10
margin-top: 5
MainWindow
id: spelllistWindow
!text: tr('Spell List')
size: 550 400
@onEscape: toggle()
TextList
id: spellList
vertical-scrollbar: spellsScrollBar
anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: next.top
margin-bottom: 10
padding: 1
width: 210
focusable: false
Button
id: buttonCancel
!text: tr('Close')
width: 64
anchors.right: parent.right
anchors.bottom: parent.bottom
@onClick: toggle()
VerticalScrollBar
id: spellsScrollBar
anchors.top: spellList.top
anchors.bottom: spellList.bottom
anchors.right: spellList.right
step: 50
pixels-scroll: true
SpellInfoLabel
id: labelName
anchors.left: spellList.right
anchors.top: spellList.top
text: Name:
Button
id: buttonFilterLevel
!text: tr('Level')
!tooltip: tr('Hide spells for higher exp. levels')
width: 64
anchors.left: spellList.left
anchors.top: spellList.bottom
@onClick: toggleFilter(self)
margin-left: 10
margin-top: 5
color: #FF0000D0
$on:
color: green
Button
id: buttonFilterVocation
!text: tr('Vocation')
!tooltip: tr('Hide spells for other vocations')
width: 64
anchors.left: prev.right
anchors.top: spellList.bottom
@onClick: toggleFilter(self)
margin-left: 10
margin-top: 5
color: #FF0000D0
$on:
color: green
SpellInfoLabel
id: labelFormula
anchors.left: spellList.right
anchors.top: labelName.bottom
text: Formula:
SpellInfoLabel
id: labelVocation
anchors.left: spellList.right
anchors.top: labelFormula.bottom
text: Vocation:
SpellInfoLabel
id: labelGroup
anchors.left: spellList.right
anchors.top: labelVocation.bottom
text: Group:
SpellInfoLabel
id: labelType
anchors.left: spellList.right
anchors.top: labelGroup.bottom
text: Type:
SpellInfoLabel
id: labelCooldown
anchors.left: spellList.right
anchors.top: labelType.bottom
text: Cooldown:
SpellInfoLabel
id: labelLevel
anchors.left: spellList.right
anchors.top: labelCooldown.bottom
text: Level:
SpellInfoLabel
id: labelMana
anchors.left: spellList.right
anchors.top: labelLevel.bottom
text: Mana / Soul:
SpellInfoLabel
id: labelPremium
anchors.left: spellList.right
anchors.top: labelMana.bottom
text: Premium:
SpellInfoLabel
id: labelDescription
anchors.left: spellList.right
anchors.top: labelPremium.bottom
text: Description:
SpellInfoValueLabel
id: labelNameValue
anchors.left: labelName.right
anchors.top: spellList.top
SpellInfoValueLabel
id: labelFormulaValue
anchors.left: labelFormula.right
anchors.top: labelNameValue.bottom
SpellInfoValueLabel
id: labelVocationValue
anchors.left: labelVocation.right
anchors.top: labelFormulaValue.bottom
SpellInfoValueLabel
id: labelGroupValue
anchors.left: labelGroup.right
anchors.top: labelVocationValue.bottom
SpellInfoValueLabel
id: labelTypeValue
anchors.left: labelType.right
anchors.top: labelGroupValue.bottom
SpellInfoValueLabel
id: labelCooldownValue
anchors.left: labelCooldown.right
anchors.top: labelTypeValue.bottom
SpellInfoValueLabel
id: labelLevelValue
anchors.left: labelLevel.right
anchors.top: labelCooldownValue.bottom
SpellInfoValueLabel
id: labelManaValue
anchors.left: labelMana.right
anchors.top: labelLevelValue.bottom
SpellInfoValueLabel
id: labelPremiumValue
anchors.left: labelPremium.right
anchors.top: labelManaValue.bottom
SpellInfoValueLabel
id: labelDescriptionValue
anchors.left: labelDescription.right
anchors.top: labelPremiumValue.bottom
Label
id: labelVocationFilter
anchors.top: labelPremium.bottom
anchors.left: spellList.right
width: 70
font: verdana-11px-monochrome
text: Vocation
margin-top: 30
margin-left: 20
CheckBox
id: vocationBoxAny
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
margin-left: 3
text: Any
width: 50
CheckBox
id: vocationBoxSorcerer
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: Sorcerer
width: 50
CheckBox
id: vocationBoxDruid
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: Druid
width: 50
CheckBox
id: vocationBoxPaladin
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: Paladin
width: 50
CheckBox
id: vocationBoxKnight
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: Knight
width: 50
Label
id: labelGroupFilter
anchors.top: labelPremium.bottom
anchors.left: labelVocationFilter.right
width: 70
font: verdana-11px-monochrome
text: Group
margin-top: 30
margin-left: 20
CheckBox
id: groupBoxAny
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
margin-left: 3
text: Any
width: 50
CheckBox
id: groupBoxAttack
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: Attack
width: 50
CheckBox
id: groupBoxHealing
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: Healing
width: 50
CheckBox
id: groupBoxSupport
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: Support
width: 50
Label
id: labelPremiumFilter
anchors.top: labelPremium.bottom
anchors.left: labelGroupFilter.right
width: 70
font: verdana-11px-monochrome
text: Premium
margin-top: 30
margin-left: 20
CheckBox
id: premiumBoxAny
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
margin-left: 3
text: Any
width: 50
CheckBox
id: premiumBoxNo
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: No
width: 50
CheckBox
id: premiumBoxYes
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 3
text: Yes
width: 50

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

View File

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

View File

@@ -19,5 +19,6 @@ Module
dofile 'player'
dofile 'market'
dofile 'thing'
dofile 'spells'
dofiles 'ui'

316
modules/gamelib/spells.lua Normal file
View File

@@ -0,0 +1,316 @@
SpelllistSettings = {
['Default'] = {
iconFile = 'icons.png',
iconSize = {width = 32, height = 32},
spellListWidth = 210,
spellWindowWidth = 550,
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'},
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'}
},
['Sample'] = {
iconFile = 'sample.png',
iconSize = {width = 64, height = 64},
spellIcons = {[1] = 'Wind Walk', [2] = 'Fire Breath', [3] = 'Moonglaives', [5] = 'Firefly', [4] = 'Critical Strike'},
spellOrder = {'Critical Strike', 'Firefly', 'Fire Breath', 'Moonglaives', 'Wind Walk'}
}
}
-- ['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)
SpellIcons = {
['intenserecovery'] = {16, 160},
['recovery'] = {15, 159},
['intensewoundcleansing'] = {4, 158},
['ultimateterrastrike'] = {37, 157},
['ultimateicestrike'] = {34, 156},
['ultimateenergystrike'] = {31, 155},
['ultimateflamestrike'] = {28, 154},
['strongterrastrike'] = {36, 153},
['strongicestrike'] = {33, 152},
['strongenergystrike'] = {30, 151},
['strongflamestrike'] = {27, 150},
['lightning'] = {51, 149},
['physicalstrike'] = {17, 148},
['curecurse'] = {11, 147},
['curseelectrification'] = {14, 146},
['cureburning'] = {13, 145},
['curebleeding'] = {12, 144},
['holyflash'] = {53, 143},
['envenom'] = {58, 142},
['inflictwound'] = {57, 141},
['electrify'] = {56, 140},
['curse'] = {54, 139},
['ignite'] = {55, 138},
-- [[ 136 / 137 Unknown ]]
['sharpshooter'] = {121, 135},
['swiftfoot'] = {119, 134},
['bloodrage'] = {96, 133},
['protector'] = {122, 132},
['charge'] = {98, 131},
['holymissile'] = {76, 130},
['enchantparty'] = {113, 129},
['healparty'] = {126, 128},
['protectparty'] = {123, 127},
['trainparty'] = {120, 126},
['divinehealing'] = {2, 125},
['divinecaldera'] = {40, 124},
['woundcleansing'] = {3, 123},
['divinemissile'] = {39, 122},
['icewave'] = {45, 121},
['terrawave'] = {47, 120},
['rageoftheskies'] = {52, 119},
['eternalwinter'] = {50, 118},
['thunderstorm'] = {63, 117},
['stoneshower'] = {65, 116},
['avalanche'] = {92, 115},
['icicle'] = {75, 114},
['terrastrike'] = {35, 113},
['icestrike'] = {32, 112},
['eterealspear'] = {18, 111},
['enchantspear'] = {104, 110},
['piercingbolt'] = {110, 109},
['sniperarrow'] = {112, 108},
['whirlwindthrow'] = {19, 107},
['groundshaker'] = {25, 106},
['fierceberserk'] = {22, 105},
-- [[ 96 - 104 Unknown ]]
['powerbolt'] = {108, 95},
['wildgrowth'] = {61, 94},
['challenge'] = {97, 93},
['enchantstaff'] = {103, 92},
['poisonbomb'] = {70, 91},
['cancelinvisibility'] = {95, 90},
['flamestrike'] = {26, 89},
['energystrike'] = {29, 88},
['deathstrike'] = {38, 87},
['magicwall'] = {72, 86},
['healfriend'] = {8, 84},
['animatedead'] = {93, 83},
['masshealing'] = {9, 82},
['levitate'] = {125, 81},
['berserk'] = {21, 80},
['conjurebolt'] = {107, 79},
['desintegrate'] = {88, 78},
['stalagmite'] = {66, 77},
['magicrope'] = {105, 76},
['ultimatelight'] = {115, 75},
-- [[ 71 - 64 TFS House Commands ]]
-- [[ 63 - 70 Unknown ]]
['annihilation'] = {24, 62},
['brutalstrike'] = {23, 61},
-- [[ 60 Unknown ]]
['frontsweep'] = {20, 59},
-- [[ 58 Unknown ]]
['strongetherealspear'] = {59, 57},
['wrathofnature'] = {48, 56},
['energybomb'] = {86, 55},
['paralyze'] = {71, 54},
-- [[ 53 Unknown ]]
-- [[ 52 TFS Retrieve Friend ]]
['conjurearrow'] = {106, 51},
['soulfire'] = {67, 50},
['explosivearrow'] = {109, 49},
['poisonedarrow'] = {111, 48},
-- [[ 46 / 47 Unknown ]]
['invisible'] = {94, 45},
['magicshield'] = {124, 44},
['strongicewave'] = {46, 43},
['food'] = {99, 42},
-- [[ 40 / 41 Unknown ]]
['stronghaste'] = {102, 39},
['creatureillusion'] = {100, 38},
-- [[ 37 TFS Move ]]
['salvation'] = {60, 36},
-- [[ 34 / 35 Unknown ]]
['energywall'] = {84, 33},
['poisonwall'] = {68, 32},
['antidote'] = {10, 31},
['destroyfield'] = {87, 30},
['curepoison'] = {10, 29},
['firewall'] = {80, 28},
['energyfield'] = {85, 27},
['poisonfield'] = {69, 26},
['firefield'] = {81, 25},
['hellscore'] = {49, 24},
['greatenergybeam'] = {42, 23},
['energybeam'] = {41, 22},
['suddendeath'] = {64, 21},
['findperson'] = {114, 20},
['firewave'] = {44, 19},
['explosion'] = {83, 18},
['firebomb'] = {82, 17},
['greatfireball'] = {78, 16},
['fireball'] = {79, 15},
['chameleon'] = {91, 14},
['energywave'] = {43, 13},
['convincecreature'] = {90, 12},
['greatlight'] = {116, 11},
['light'] = {117, 10},
['summoncreature'] = {118, 9},
['heavymagicmissile'] = {77, 8},
['lightmagicmissile'] = {73, 7},
['haste'] = {101, 6},
['ultimatehealingrune'] = {62, 5},
['intensehealingrune'] = {74, 4},
['ultimatehealing'] = {1, 3},
['intensehealing'] = {7, 2},
['lighthealing'] = {6, 1}
}
VocationNames = {
[1] = 'Sorcerer',
[2] = 'Druid',
[3] = 'Paladin',
[4] = 'Knight',
[5] = 'Master Sorcerer',
[6] = 'Elder Druid',
[7] = 'Royal Paladin',
[8] = 'Elite Knight'
}
SpellGroups = {
[1] = 'Attack',
[2] = 'Healing',
[3] = 'Support',
[4] = 'Special'
}

View File

@@ -98,7 +98,7 @@ void Logger::fireOldMessages()
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()) {
g_logger.error(stdext::format("Unable to save log to '%s'", file));
return;

View File

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

View File

@@ -52,6 +52,9 @@ void BitmapFont::load(const OTMLNodePtr& fontNode)
m_glyphsSize[32].setWidth(spaceWidth);
m_glyphsSize[160].setWidth(spaceWidth);
// use 127 as spacer [Width: 1]
m_glyphsSize[127].setWidth(1);
// new line actually has a size that will be useful in multiline algorithm
m_glyphsSize[(uchar)'\n'] = Size(1, m_glyphHeight);

View File

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

View File

@@ -847,7 +847,7 @@ void WIN32Window::setFullscreen(bool fullscreen)
} else {
SetWindowLong(m_window, GWL_STYLE, (dwStyle & ~(WS_POPUP | WS_EX_TOPMOST)) | WS_OVERLAPPEDWINDOW);
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) {
LPTSTR lptstr = (LPTSTR)GlobalLock(hglb);
if(lptstr) {
text = stdext::utf8_to_latin1((uchar*)lptstr);
text = stdext::utf8_to_latin1(lptstr);
GlobalUnlock(hglb);
}
}

View File

@@ -1056,7 +1056,7 @@ std::string X11Window::getClipboardText()
&bytesLeft,
&data);
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;
}
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 = '?';
uchar opt1 = utf8[0];
*read = 1;
@@ -89,10 +89,10 @@ std::string utf8_to_latin1(uchar *utf8)
};
std::string out;
int len = strlen((char*)utf8);
int len = src.length();
for(int i=0; i<len;) {
int read = 0;
uchar *utf8char = &utf8[i];
uchar *utf8char = (uchar*)&src[i];
out += utf8CharToLatin1(utf8char, &read);
i += read;
}

View File

@@ -42,7 +42,7 @@ std::string date_time_string();
std::string dec_to_hex(uint64_t num);
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 toupper(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
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!");
// the run application main loop

View File

@@ -359,6 +359,30 @@ namespace Otc
PATHFIND_ALLOW_NONPATHABLE = 4,
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

View File

@@ -60,6 +60,9 @@ Creature::Creature() : Thing()
void Creature::draw(const Point& dest, float scaleFactor, bool animate)
{
if(!canBeSeen())
return;
Point animationOffset = animate ? m_walkOffset : Point(0,0);
if(m_showTimedSquare && animate) {
@@ -523,6 +526,7 @@ void Creature::setDirection(Otc::Direction direction)
void Creature::setOutfit(const Outfit& outfit)
{
Outfit oldOutfit = outfit;
if(outfit.getCategory() != ThingCategoryCreature) {
if(!g_things.isValidDatId(outfit.getAuxId(), outfit.getCategory()))
return;
@@ -534,6 +538,8 @@ void Creature::setOutfit(const Outfit& outfit)
m_outfit = outfit;
}
m_walkAnimationPhase = 0; // might happen when player is walking and outfit is changed.
callLuaField("onOutfitChange", m_outfit, oldOutfit);
}
void Creature::setSpeed(uint16 speed)

View File

@@ -102,6 +102,8 @@ public:
bool isWalking() { return m_walking; }
bool isRemoved() { return m_removed; }
bool isInvisible() { return m_outfit.getCategory() == ThingCategoryEffect && m_outfit.getAuxId() == 13; }
bool canBeSeen() { return !isInvisible() || isPlayer(); }
bool isCreature() { return true; }

View File

@@ -710,7 +710,7 @@ void Game::useWith(const ItemPtr& item, const ThingPtr& toThing)
if(!pos.isValid()) // virtual item
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());
else
m_protocolGame->sendUseItemWith(pos, item->getId(), item->getStackpos(), toThing->getPosition(), toThing->getId(), toThing->getStackpos());

View File

@@ -368,6 +368,8 @@ void OTClient::registerLuaFunctions()
g_lua.bindClassMemberFunction<Creature>("showStaticSquare", &Creature::showStaticSquare);
g_lua.bindClassMemberFunction<Creature>("hideStaticSquare", &Creature::hideStaticSquare);
g_lua.bindClassMemberFunction<Creature>("isWalking", &Creature::isWalking);
g_lua.bindClassMemberFunction<Creature>("isInvisible", &Creature::isInvisible);
g_lua.bindClassMemberFunction<Creature>("canBeSeen", &Creature::canBeSeen);
g_lua.registerClass<ItemType>();
g_lua.bindClassMemberFunction<ItemType>("getServerId", &ItemType::getServerId);

View File

@@ -28,6 +28,8 @@ int push_luavalue(const Outfit& outfit)
g_lua.newTable();
g_lua.pushInteger(outfit.getId());
g_lua.setField("type");
g_lua.pushInteger(outfit.getAuxId());
g_lua.setField("auxType");
g_lua.pushInteger(outfit.getAddons());
g_lua.setField("addons");
g_lua.pushInteger(outfit.getHead());
@@ -50,6 +52,8 @@ bool luavalue_cast(int index, Outfit& outfit)
if(g_lua.isTable(index)) {
g_lua.getField("type", index);
outfit.setId(g_lua.popInteger());
g_lua.getField("auxType", index);
outfit.setAuxId(g_lua.popInteger());
g_lua.getField("addons", index);
outfit.setAddons(g_lua.popInteger());
g_lua.getField("head", index);

View File

@@ -175,6 +175,9 @@ void MapView::draw(const Rect& rect)
// avoid drawing texts on map in far zoom outs
if(m_viewMode == NEAR_VIEW && m_drawTexts) {
for(const CreaturePtr& creature : m_cachedFloorVisibleCreatures) {
if(!creature->canBeSeen())
continue;
Point creatureOffset = Point(16 - creature->getDisplacementX(), -3 - creature->getDisplacementY());
Position pos = creature->getPosition();
Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset;
@@ -378,7 +381,7 @@ void MapView::updateVisibleTilesCache(int start)
m_spiral.clear();
}
if(start == 0 && m_drawTexts && m_viewMode <= NEAR_VIEW)
if(start == 0 && m_viewMode <= NEAR_VIEW)
m_cachedFloorVisibleCreatures = g_map.getSpectators(cameraPosition, false);
}

View File

@@ -40,13 +40,13 @@ void buildMessageModesMap(int version) {
messageModesMap[Otc::MessageNpcFrom] = 5;
messageModesMap[Otc::MessagePrivateFrom] = 6;
messageModesMap[Otc::MessagePrivateTo] = 6;
messageModesMap[Otc::MessageChannelHighlight] = 7;
messageModesMap[Otc::MessageChannel] = 8;
messageModesMap[Otc::MessageChannel] = 7;
messageModesMap[Otc::MessageChannelManagement] = 8;
messageModesMap[Otc::MessageGamemasterBroadcast] = 9;
messageModesMap[Otc::MessageGamemasterChannel] = 10;
messageModesMap[Otc::MessageGamemasterPrivateFrom] = 11;
messageModesMap[Otc::MessageGamemasterPrivateTo] = 11;
messageModesMap[Otc::MessageChannelManagement] = 12;
messageModesMap[Otc::MessageChannelHighlight] = 12;
messageModesMap[Otc::MessageMonsterSay] = 13;
messageModesMap[Otc::MessageMonsterYell] = 14;
messageModesMap[Otc::MessageWarning] = 15;

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)
{
if(accountName.empty() || accountPassword.empty()) {
callLuaField("onError", "You must enter an account name and password.");
return;
}
m_accountName = accountName;
m_accountPassword = accountPassword;
m_characterName = characterName;

View File

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

View File

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

View File

@@ -109,15 +109,15 @@ void ThingTypeManager::loadOtb(const std::string& file)
if(signature != 0)
stdext::throw_exception("invalid otb file");
root->getU32(); // flags
root->skip(4);
m_otbMajorVersion = root->getU32();
m_otbMinorVersion = root->getU32();
root->getU32(); // build number
root->skip(4);
root->skip(128); // description
m_reverseItemTypes.clear();
m_itemTypes.resize(root->getChildren().size(), m_nullItemType);
m_itemTypes.resize(root->getChildren().size() + 1, m_nullItemType);
for(const BinaryTreePtr& node : root->getChildren()) {
ItemTypePtr itemType(new ItemType);
@@ -185,22 +185,14 @@ void ThingTypeManager::parseItemType(uint16 id, TiXmlElement* elem)
{
uint16 serverId = id;
ItemTypePtr itemType = nullptr;
if(serverId > 20000 && id < 20100) {
if(serverId > 20000 && serverId < 20100) {
serverId -= 20000;
itemType = ItemTypePtr(new ItemType);
itemType->setServerId(serverId);
addItemType(itemType);
}
if(!itemType) {
} else
itemType = getItemType(serverId);
if(itemType == m_nullItemType) {
itemType = ItemTypePtr(new ItemType);
itemType->setServerId(id);
addItemType(itemType);
}
}
itemType->setName(elem->Attribute("name"));
for(TiXmlElement* attrib = elem->FirstChildElement(); attrib; attrib = attrib->NextSiblingElement()) {
@@ -238,8 +230,8 @@ void ThingTypeManager::parseItemType(uint16 id, TiXmlElement* elem)
void ThingTypeManager::addItemType(const ItemTypePtr& itemType)
{
uint16 id = itemType->getServerId();
if(m_itemTypes.size() <= id)
m_itemTypes.resize((id * 3) / 2 + 1, m_nullItemType);
if(id > m_itemTypes.size())
m_itemTypes.resize(id + 1, m_nullItemType);
m_itemTypes[id] = itemType;
}

View File

@@ -451,7 +451,7 @@ bool Tile::isWalkable()
if(thing->isCreature()) {
CreaturePtr creature = thing->static_self_cast<Creature>();
if(!creature->isPassable())
if(!creature->isPassable() && creature->canBeSeen())
return false;
}
}