Client Upload Part 2

RD Core / Commit #5

Commit Client Upload Part 2

Alex Alex committed 19/10/2026 00:06 main Patch
1,244 files +271,215 -0
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/55.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/Claws.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/Critical Claws.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/Dark Steps.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/dash.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/Double Claws.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/Kanden shi.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/Ren.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/Rhythm Echo.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/killua/speed.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/kurapika/Holy Chain.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/restore.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/spell_icon.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spelllist/spelllist.lua +390-0 View file
@@ -0,0 +1,390 @@
1+local SpelllistProfile = 'Default'
2+
3+spelllistWindow = nil
4+spelllistButton = nil
5+spellList = nil
6+nameValueLabel = nil
7+formulaValueLabel = nil
8+vocationValueLabel = nil
9+groupValueLabel = nil
10+typeValueLabel = nil
11+cooldownValueLabel = nil
12+levelValueLabel = nil
13+manaValueLabel = nil
14+premiumValueLabel = nil
15+descriptionValueLabel = nil
16+
17+vocationBoxAny = nil
18+vocationBoxSorcerer = nil
19+vocationBoxDruid = nil
20+vocationBoxPaladin = nil
21+vocationBoxKnight = nil
22+
23+groupBoxAny = nil
24+groupBoxAttack = nil
25+groupBoxHealing = nil
26+groupBoxSupport = nil
27+
28+premiumBoxAny = nil
29+premiumBoxNo = nil
30+premiumBoxYes = nil
31+
32+vocationRadioGroup = nil
33+groupRadioGroup = nil
34+premiumRadioGroup = nil
35+
36+-- consts
37+FILTER_PREMIUM_ANY = 0
38+FILTER_PREMIUM_NO = 1
39+FILTER_PREMIUM_YES = 2
40+
41+FILTER_VOCATION_ANY = 0
42+FILTER_VOCATION_SORCERER = 1
43+FILTER_VOCATION_DRUID = 2
44+FILTER_VOCATION_PALADIN = 3
45+FILTER_VOCATION_KNIGHT = 4
46+
47+FILTER_GROUP_ANY = 0
48+FILTER_GROUP_ATTACK = 1
49+FILTER_GROUP_HEALING = 2
50+FILTER_GROUP_SUPPORT = 3
51+
52+-- Filter Settings
53+local filters = {
54+ level = false,
55+ vocation = false,
56+
57+ vocationId = FILTER_VOCATION_ANY,
58+ premium = FILTER_PREMIUM_ANY,
59+ groupId = FILTER_GROUP_ANY
60+}
61+
62+function getSpelllistProfile()
63+ return SpelllistProfile
64+end
65+
66+function setSpelllistProfile(name)
67+ if SpelllistProfile == name then return end
68+
69+ if SpelllistSettings[name] and SpellInfo[name] then
70+ local oldProfile = SpelllistProfile
71+ SpelllistProfile = name
72+ changeSpelllistProfile(oldProfile)
73+ else
74+ perror('Spelllist profile \'' .. name .. '\' could not be set.')
75+ end
76+end
77+
78+function online()
79+ if g_game.getFeature(GameSpellList) then
80+ --spelllistButton:show()
81+ else
82+ --spelllistButton:hide()
83+ end
84+
85+ -- Vocation is only send in newer clients
86+ if g_game.getClientVersion() >= 950 then
87+ spelllistWindow:getChildById('buttonFilterVocation'):setVisible(true)
88+ else
89+ spelllistWindow:getChildById('buttonFilterVocation'):setVisible(false)
90+ end
91+end
92+
93+function offline()
94+ resetWindow()
95+end
96+
97+function init()
98+ connect(g_game, { onGameStart = online,
99+ onGameEnd = offline })
100+
101+ spelllistWindow = g_ui.displayUI('spelllist', modules.game_interface.getRightPanel())
102+ spelllistWindow:hide()
103+
104+ --spelllistButton = modules.client_topmenu.addRightGameToggleButton('spelllistButton', tr('Spell List'), '/images/topbuttons/spelllist', toggle, false, 4)
105+ --spelllistButton:setOn(false)
106+
107+ nameValueLabel = spelllistWindow:getChildById('labelNameValue')
108+ formulaValueLabel = spelllistWindow:getChildById('labelFormulaValue')
109+ vocationValueLabel = spelllistWindow:getChildById('labelVocationValue')
110+ groupValueLabel = spelllistWindow:getChildById('labelGroupValue')
111+ typeValueLabel = spelllistWindow:getChildById('labelTypeValue')
112+ cooldownValueLabel = spelllistWindow:getChildById('labelCooldownValue')
113+ levelValueLabel = spelllistWindow:getChildById('labelLevelValue')
114+ manaValueLabel = spelllistWindow:getChildById('labelManaValue')
115+ premiumValueLabel = spelllistWindow:getChildById('labelPremiumValue')
116+ descriptionValueLabel = spelllistWindow:getChildById('labelDescriptionValue')
117+
118+ vocationBoxAny = spelllistWindow:getChildById('vocationBoxAny')
119+ vocationBoxSorcerer = spelllistWindow:getChildById('vocationBoxSorcerer')
120+ vocationBoxDruid = spelllistWindow:getChildById('vocationBoxDruid')
121+ vocationBoxPaladin = spelllistWindow:getChildById('vocationBoxPaladin')
122+ vocationBoxKnight = spelllistWindow:getChildById('vocationBoxKnight')
123+
124+ groupBoxAny = spelllistWindow:getChildById('groupBoxAny')
125+ groupBoxAttack = spelllistWindow:getChildById('groupBoxAttack')
126+ groupBoxHealing = spelllistWindow:getChildById('groupBoxHealing')
127+ groupBoxSupport = spelllistWindow:getChildById('groupBoxSupport')
128+
129+ premiumBoxAny = spelllistWindow:getChildById('premiumBoxAny')
130+ premiumBoxYes = spelllistWindow:getChildById('premiumBoxYes')
131+ premiumBoxNo = spelllistWindow:getChildById('premiumBoxNo')
132+
133+ vocationRadioGroup = UIRadioGroup.create()
134+ vocationRadioGroup:addWidget(vocationBoxAny)
135+ vocationRadioGroup:addWidget(vocationBoxSorcerer)
136+ vocationRadioGroup:addWidget(vocationBoxDruid)
137+ vocationRadioGroup:addWidget(vocationBoxPaladin)
138+ vocationRadioGroup:addWidget(vocationBoxKnight)
139+
140+ groupRadioGroup = UIRadioGroup.create()
141+ groupRadioGroup:addWidget(groupBoxAny)
142+ groupRadioGroup:addWidget(groupBoxAttack)
143+ groupRadioGroup:addWidget(groupBoxHealing)
144+ groupRadioGroup:addWidget(groupBoxSupport)
145+
146+ premiumRadioGroup = UIRadioGroup.create()
147+ premiumRadioGroup:addWidget(premiumBoxAny)
148+ premiumRadioGroup:addWidget(premiumBoxYes)
149+ premiumRadioGroup:addWidget(premiumBoxNo)
150+
151+ premiumRadioGroup:selectWidget(premiumBoxAny)
152+ vocationRadioGroup:selectWidget(vocationBoxAny)
153+ groupRadioGroup:selectWidget(groupBoxAny)
154+
155+ vocationRadioGroup.onSelectionChange = toggleFilter
156+ groupRadioGroup.onSelectionChange = toggleFilter
157+ premiumRadioGroup.onSelectionChange = toggleFilter
158+
159+ spellList = spelllistWindow:getChildById('spellList')
160+
161+ g_keyboard.bindKeyPress('Down', function() spellList:focusNextChild(KeyboardFocusReason) end, spelllistWindow)
162+ g_keyboard.bindKeyPress('Up', function() spellList:focusPreviousChild(KeyboardFocusReason) end, spelllistWindow)
163+
164+ initializeSpelllist()
165+ resizeWindow()
166+
167+ if g_game.isOnline() then
168+ online()
169+ end
170+end
171+
172+function terminate()
173+ disconnect(g_game, { onGameStart = online,
174+ onGameEnd = offline })
175+
176+ disconnect(spellList, { onChildFocusChange = function(self, focusedChild)
177+ if focusedChild == nil then return end
178+ updateSpellInformation(focusedChild)
179+ end })
180+
181+ spelllistWindow:destroy()
182+ --spelllistButton:destroy()
183+
184+ vocationRadioGroup:destroy()
185+ groupRadioGroup:destroy()
186+ premiumRadioGroup:destroy()
187+end
188+
189+function initializeSpelllist()
190+ for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do
191+ local spell = SpelllistSettings[SpelllistProfile].spellOrder[i]
192+ local info = SpellInfo[SpelllistProfile][spell]
193+
194+ local tmpLabel = g_ui.createWidget('SpellListLabel', spellList)
195+ tmpLabel:setId(spell)
196+ tmpLabel:setText(spell .. '\n\'' .. info.words .. '\'')
197+ tmpLabel:setPhantom(false)
198+
199+ local iconId = tonumber(info.icon)
200+ if not iconId and SpellIcons[info.icon] then
201+ iconId = SpellIcons[info.icon][1]
202+ end
203+
204+ if not(iconId) then
205+ perror('Spell icon \'' .. info.icon .. '\' not found.')
206+ end
207+
208+ tmpLabel:setHeight(SpelllistSettings[SpelllistProfile].iconSize.height + 4)
209+ tmpLabel:setTextOffset(topoint((SpelllistSettings[SpelllistProfile].iconSize.width + 10) .. ' ' .. (SpelllistSettings[SpelllistProfile].iconSize.height - 32)/2 + 3))
210+ tmpLabel:setImageSource(SpelllistSettings[SpelllistProfile].iconFile)
211+ tmpLabel:setImageClip(Spells.getImageClip(iconId, SpelllistProfile))
212+ tmpLabel:setImageSize(tosize(SpelllistSettings[SpelllistProfile].iconSize.width .. ' ' .. SpelllistSettings[SpelllistProfile].iconSize.height))
213+ tmpLabel.onClick = updateSpellInformation
214+ end
215+
216+ connect(spellList, { onChildFocusChange = function(self, focusedChild)
217+ if focusedChild == nil then return end
218+ updateSpellInformation(focusedChild)
219+ end })
220+end
221+
222+function changeSpelllistProfile(oldProfile)
223+ -- Delete old labels
224+ for i = 1, #SpelllistSettings[oldProfile].spellOrder do
225+ local spell = SpelllistSettings[oldProfile].spellOrder[i]
226+ local tmpLabel = spellList:getChildById(spell)
227+
228+ tmpLabel:destroy()
229+ end
230+
231+ -- Create new spelllist and ajust window
232+ initializeSpelllist()
233+ resizeWindow()
234+ resetWindow()
235+end
236+
237+function updateSpelllist()
238+ for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do
239+ local spell = SpelllistSettings[SpelllistProfile].spellOrder[i]
240+ local info = SpellInfo[SpelllistProfile][spell]
241+ local tmpLabel = spellList:getChildById(spell)
242+
243+ local localPlayer = g_game.getLocalPlayer()
244+ 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
245+ tmpLabel:setVisible(true)
246+ else
247+ tmpLabel:setVisible(false)
248+ end
249+ end
250+end
251+
252+function updateSpellInformation(widget)
253+ local spell = widget:getId()
254+
255+ local name = ''
256+ local formula = ''
257+ local vocation = ''
258+ local group = ''
259+ local type = ''
260+ local cooldown = ''
261+ local level = ''
262+ local mana = ''
263+ local premium = ''
264+ local description = ''
265+
266+ if SpellInfo[SpelllistProfile][spell] then
267+ local info = SpellInfo[SpelllistProfile][spell]
268+
269+ name = spell
270+ formula = info.words
271+
272+ for i = 1, #info.vocations do
273+ local vocationId = info.vocations[i]
274+ if vocationId <= 4 or not(table.find(info.vocations, (vocationId-4))) then
275+ vocation = vocation .. (vocation:len() == 0 and '' or ', ') .. VocationNames[vocationId]
276+ end
277+ end
278+
279+ cooldown = (info.exhaustion / 1000) .. 's'
280+ for groupId, groupName in ipairs(SpellGroups) do
281+ if info.group[groupId] then
282+ group = group .. (group:len() == 0 and '' or ' / ') .. groupName
283+ cooldown = cooldown .. ' / ' .. (info.group[groupId] / 1000) .. 's'
284+ end
285+ end
286+
287+ type = info.type
288+ level = info.level
289+ mana = info.mana .. ' / ' .. info.soul
290+ premium = (info.premium and 'yes' or 'no')
291+ description = info.description or '-'
292+ end
293+
294+ nameValueLabel:setText(name)
295+ formulaValueLabel:setText(formula)
296+ vocationValueLabel:setText(vocation)
297+ groupValueLabel:setText(group)
298+ typeValueLabel:setText(type)
299+ cooldownValueLabel:setText(cooldown)
300+ levelValueLabel:setText(level)
301+ manaValueLabel:setText(mana)
302+ premiumValueLabel:setText(premium)
303+ descriptionValueLabel:setText(description)
304+end
305+
306+function toggle()
307+ --[[if spelllistButton:isOn() then
308+ spelllistButton:setOn(false)
309+ spelllistWindow:hide()
310+ else
311+ spelllistButton:setOn(true)
312+ spelllistWindow:show()
313+ spelllistWindow:raise()
314+ spelllistWindow:focus()
315+ end]]
316+end
317+
318+function toggleFilter(widget, selectedWidget)
319+ if widget == vocationRadioGroup then
320+ local boxId = selectedWidget:getId()
321+ if boxId == 'vocationBoxAny' then
322+ filters.vocationId = FILTER_VOCATION_ANY
323+ elseif boxId == 'vocationBoxSorcerer' then
324+ filters.vocationId = FILTER_VOCATION_SORCERER
325+ elseif boxId == 'vocationBoxDruid' then
326+ filters.vocationId = FILTER_VOCATION_DRUID
327+ elseif boxId == 'vocationBoxPaladin' then
328+ filters.vocationId = FILTER_VOCATION_PALADIN
329+ elseif boxId == 'vocationBoxKnight' then
330+ filters.vocationId = FILTER_VOCATION_KNIGHT
331+ end
332+ elseif widget == groupRadioGroup then
333+ local boxId = selectedWidget:getId()
334+ if boxId == 'groupBoxAny' then
335+ filters.groupId = FILTER_GROUP_ANY
336+ elseif boxId == 'groupBoxAttack' then
337+ filters.groupId = FILTER_GROUP_ATTACK
338+ elseif boxId == 'groupBoxHealing' then
339+ filters.groupId = FILTER_GROUP_HEALING
340+ elseif boxId == 'groupBoxSupport' then
341+ filters.groupId = FILTER_GROUP_SUPPORT
342+ end
343+ elseif widget == premiumRadioGroup then
344+ local boxId = selectedWidget:getId()
345+ if boxId == 'premiumBoxAny' then
346+ filters.premium = FILTER_PREMIUM_ANY
347+ elseif boxId == 'premiumBoxNo' then
348+ filters.premium = FILTER_PREMIUM_NO
349+ elseif boxId == 'premiumBoxYes' then
350+ filters.premium = FILTER_PREMIUM_YES
351+ end
352+ else
353+ local id = widget:getId()
354+ if id == 'buttonFilterLevel' then
355+ filters.level = not(filters.level)
356+ widget:setOn(filters.level)
357+ elseif id == 'buttonFilterVocation' then
358+ filters.vocation = not(filters.vocation)
359+ widget:setOn(filters.vocation)
360+ end
361+ end
362+
363+ updateSpelllist()
364+end
365+
366+function resizeWindow()
367+ spelllistWindow:setWidth(SpelllistSettings['Default'].spellWindowWidth + SpelllistSettings[SpelllistProfile].iconSize.width - 32)
368+ spellList:setWidth(SpelllistSettings['Default'].spellListWidth + SpelllistSettings[SpelllistProfile].iconSize.width - 32)
369+end
370+
371+function resetWindow()
372+ spelllistWindow:hide()
373+ --spelllistButton:setOn(false)
374+
375+ -- Resetting filters
376+ filters.level = false
377+ filters.vocation = false
378+
379+ local buttonFilterLevel = spelllistWindow:getChildById('buttonFilterLevel')
380+ buttonFilterLevel:setOn(filters.level)
381+
382+ local buttonFilterVocation = spelllistWindow:getChildById('buttonFilterVocation')
383+ buttonFilterVocation:setOn(filters.vocation)
384+
385+ vocationRadioGroup:selectWidget(vocationBoxAny)
386+ groupRadioGroup:selectWidget(groupBoxAny)
387+ premiumRadioGroup:selectWidget(premiumBoxAny)
388+
389+ updateSpelllist()
390+end
A RD_Client/modules/game_spelllist/spelllist.otmod +9-0 View file
@@ -0,0 +1,9 @@
1+Module
2+ name: game_spelllist
3+ description: View available spells
4+ author: Summ, Edubart
5+ website: https://github.com/edubart/otclient
6+ sandboxed: true
7+ scripts: [ spelllist ]
8+ @onLoad: init()
9+ @onUnload: terminate()
A RD_Client/modules/game_spelllist/spelllist.otui +326-0 View file
@@ -0,0 +1,326 @@
1+SpellListLabel < Label
2+ font: verdana-11px-monochrome
3+ background-color: alpha
4+ text-offset: 42 3
5+ focusable: true
6+ height: 36
7+ image-clip: 0 0 32 32
8+ image-size: 32 32
9+ image-offset: 2 2
10+ image-source: /images/game/spells/defaultspells
11+
12+ $focus:
13+ background-color: #ffffff22
14+ color: #ffffff
15+
16+SpellInfoLabel < Label
17+ width: 70
18+ font: verdana-11px-monochrome
19+ text-align: right
20+ margin-left: 10
21+ margin-top: 5
22+
23+SpellInfoValueLabel < Label
24+ text-align: left
25+ width: 190
26+ margin-left: 10
27+ margin-top: 5
28+
29+FilterButton < Button
30+ width: 64
31+ anchors.left: prev.right
32+ anchors.top: spellList.bottom
33+ @onClick: toggleFilter(self)
34+ margin: 5 0 0 6
35+ color: #630000
36+ $on:
37+ color: green
38+
39+MainWindow
40+ id: spelllistWindow
41+ !text: tr('Spell List')
42+ size: 550 400
43+ @onEscape: toggle()
44+
45+ TextList
46+ id: spellList
47+ vertical-scrollbar: spellsScrollBar
48+ anchors.top: parent.top
49+ anchors.left: parent.left
50+ anchors.bottom: next.top
51+ margin-bottom: 10
52+ padding: 1
53+ width: 210
54+ focusable: false
55+
56+ Button
57+ id: buttonCancel
58+ !text: tr('Close')
59+ width: 64
60+ anchors.right: parent.right
61+ anchors.bottom: parent.bottom
62+ @onClick: toggle()
63+
64+ VerticalScrollBar
65+ id: spellsScrollBar
66+ anchors.top: spellList.top
67+ anchors.bottom: spellList.bottom
68+ anchors.right: spellList.right
69+ step: 50
70+ pixels-scroll: true
71+
72+ SpellInfoLabel
73+ id: labelName
74+ anchors.left: spellList.right
75+ anchors.top: spellList.top
76+ !text: tr('Name') .. ':'
77+
78+ Label
79+ anchors.left: parent.left
80+ anchors.top: spellList.bottom
81+ !text: tr('Filters') .. ':'
82+ margin-top: 8
83+
84+ FilterButton
85+ id: buttonFilterLevel
86+ !text: tr('Level')
87+ !tooltip: tr('Hide spells for higher exp. levels')
88+
89+ FilterButton
90+ id: buttonFilterVocation
91+ !text: tr('Vocation')
92+ !tooltip: tr('Hide spells for other vocations')
93+
94+ SpellInfoLabel
95+ id: labelFormula
96+ anchors.left: spellList.right
97+ anchors.top: labelName.bottom
98+ !text: tr('Formula') .. ':'
99+
100+
101+ SpellInfoLabel
102+ id: labelVocation
103+ anchors.left: spellList.right
104+ anchors.top: labelFormula.bottom
105+ !text: tr('Vocation') .. ':'
106+
107+ SpellInfoLabel
108+ id: labelGroup
109+ anchors.left: spellList.right
110+ anchors.top: labelVocation.bottom
111+ !text: tr('Group') .. ':'
112+
113+ SpellInfoLabel
114+ id: labelType
115+ anchors.left: spellList.right
116+ anchors.top: labelGroup.bottom
117+ !text: tr('Type') .. ':'
118+
119+ SpellInfoLabel
120+ id: labelCooldown
121+ anchors.left: spellList.right
122+ anchors.top: labelType.bottom
123+ !text: tr('Cooldown') .. ':'
124+
125+ SpellInfoLabel
126+ id: labelLevel
127+ anchors.left: spellList.right
128+ anchors.top: labelCooldown.bottom
129+ !text: tr('Level') .. ':'
130+
131+ SpellInfoLabel
132+ id: labelMana
133+ anchors.left: spellList.right
134+ anchors.top: labelLevel.bottom
135+ !text: tr('Mana') .. ' / ' .. tr('Soul') .. ':'
136+
137+ SpellInfoLabel
138+ id: labelPremium
139+ anchors.left: spellList.right
140+ anchors.top: labelMana.bottom
141+ !text: tr('Premium') .. ':'
142+
143+ SpellInfoLabel
144+ id: labelDescription
145+ anchors.left: spellList.right
146+ anchors.top: labelPremium.bottom
147+ !text: tr('Description') .. ':'
148+
149+ SpellInfoValueLabel
150+ id: labelNameValue
151+ anchors.left: labelName.right
152+ anchors.top: spellList.top
153+
154+ SpellInfoValueLabel
155+ id: labelFormulaValue
156+ anchors.left: labelFormula.right
157+ anchors.top: labelNameValue.bottom
158+
159+ SpellInfoValueLabel
160+ id: labelVocationValue
161+ anchors.left: labelVocation.right
162+ anchors.top: labelFormulaValue.bottom
163+
164+ SpellInfoValueLabel
165+ id: labelGroupValue
166+ anchors.left: labelGroup.right
167+ anchors.top: labelVocationValue.bottom
168+
169+ SpellInfoValueLabel
170+ id: labelTypeValue
171+ anchors.left: labelType.right
172+ anchors.top: labelGroupValue.bottom
173+
174+ SpellInfoValueLabel
175+ id: labelCooldownValue
176+ anchors.left: labelCooldown.right
177+ anchors.top: labelTypeValue.bottom
178+
179+ SpellInfoValueLabel
180+ id: labelLevelValue
181+ anchors.left: labelLevel.right
182+ anchors.top: labelCooldownValue.bottom
183+
184+ SpellInfoValueLabel
185+ id: labelManaValue
186+ anchors.left: labelMana.right
187+ anchors.top: labelLevelValue.bottom
188+
189+ SpellInfoValueLabel
190+ id: labelPremiumValue
191+ anchors.left: labelPremium.right
192+ anchors.top: labelManaValue.bottom
193+
194+ SpellInfoValueLabel
195+ id: labelDescriptionValue
196+ anchors.left: labelDescription.right
197+ anchors.top: labelPremiumValue.bottom
198+
199+ Label
200+ id: labelVocationFilter
201+ anchors.top: labelPremium.bottom
202+ anchors.left: spellList.right
203+ width: 70
204+ font: verdana-11px-monochrome
205+ !text: tr('Vocation')
206+ margin-top: 30
207+ margin-left: 20
208+
209+ CheckBox
210+ id: vocationBoxAny
211+ anchors.left: prev.left
212+ anchors.top: prev.bottom
213+ margin-top: 3
214+ margin-left: 3
215+ !text: tr('Any')
216+ width: 75
217+
218+ CheckBox
219+ id: vocationBoxSorcerer
220+ anchors.left: prev.left
221+ anchors.top: prev.bottom
222+ margin-top: 3
223+ !text: tr('Sorcerer')
224+ width: 75
225+
226+ CheckBox
227+ id: vocationBoxDruid
228+ anchors.left: prev.left
229+ anchors.top: prev.bottom
230+ margin-top: 3
231+ !text: tr('Druid')
232+ width: 75
233+
234+ CheckBox
235+ id: vocationBoxPaladin
236+ anchors.left: prev.left
237+ anchors.top: prev.bottom
238+ margin-top: 3
239+ !text: tr('Paladin')
240+ width: 75
241+
242+ CheckBox
243+ id: vocationBoxKnight
244+ anchors.left: prev.left
245+ anchors.top: prev.bottom
246+ margin-top: 3
247+ !text: tr('Knight')
248+ width: 75
249+
250+ Label
251+ id: labelGroupFilter
252+ anchors.top: labelPremium.bottom
253+ anchors.left: labelVocationFilter.right
254+ width: 70
255+ font: verdana-11px-monochrome
256+ !text: tr('Group')
257+ margin-top: 30
258+ margin-left: 20
259+
260+ CheckBox
261+ id: groupBoxAny
262+ anchors.left: prev.left
263+ anchors.top: prev.bottom
264+ margin-top: 3
265+ margin-left: 3
266+ !text: tr('Any')
267+ width: 75
268+
269+ CheckBox
270+ id: groupBoxAttack
271+ anchors.left: prev.left
272+ anchors.top: prev.bottom
273+ margin-top: 3
274+ !text: tr('Attack')
275+ width: 75
276+
277+ CheckBox
278+ id: groupBoxHealing
279+ anchors.left: prev.left
280+ anchors.top: prev.bottom
281+ margin-top: 3
282+ !text: tr('Healing')
283+ width: 75
284+
285+ CheckBox
286+ id: groupBoxSupport
287+ anchors.left: prev.left
288+ anchors.top: prev.bottom
289+ margin-top: 3
290+ !text: tr('Support')
291+ width: 75
292+
293+ Label
294+ id: labelPremiumFilter
295+ anchors.top: labelPremium.bottom
296+ anchors.left: labelGroupFilter.right
297+ width: 70
298+ font: verdana-11px-monochrome
299+ !text: tr('Premium')
300+ margin-top: 30
301+ margin-left: 20
302+
303+ CheckBox
304+ id: premiumBoxAny
305+ anchors.left: prev.left
306+ anchors.top: prev.bottom
307+ margin-top: 3
308+ margin-left: 3
309+ !text: tr('Any')
310+ width: 75
311+
312+ CheckBox
313+ id: premiumBoxNo
314+ anchors.left: prev.left
315+ anchors.top: prev.bottom
316+ margin-top: 3
317+ !text: tr('No')
318+ width: 75
319+
320+ CheckBox
321+ id: premiumBoxYes
322+ anchors.left: prev.left
323+ anchors.top: prev.bottom
324+ margin-top: 3
325+ !text: tr('Yes')
326+ width: 75
A RD_Client/modules/game_stats/stats.lua +58-0 View file
@@ -0,0 +1,58 @@
1+ui = nil
2+updateEvent = nil
3+
4+function init()
5+ ui = g_ui.loadUI('stats', modules.game_interface.getMapPanel())
6+
7+ if not modules.client_options.getOption("showPing") then
8+ ui.fps:hide()
9+ end
10+ if not modules.client_options.getOption("showFps") then
11+ ui.ping:hide()
12+ end
13+
14+ updateEvent = scheduleEvent(update, 200)
15+end
16+
17+function terminate()
18+ removeEvent(updateEvent)
19+end
20+
21+function update()
22+ updateEvent = scheduleEvent(update, 500)
23+ if ui:isHidden() then return end
24+
25+ text = 'FPS: ' .. g_app.getFps()
26+ ui.fps:setText(text)
27+
28+ local ping = g_game.getPing()
29+ if g_proxy and g_proxy.getPing() > 0 then
30+ ping = g_proxy.getPing()
31+ end
32+
33+ local text = 'Ping: '
34+ local color
35+ if ping < 0 then
36+ text = text .. "??"
37+ color = 'yellow'
38+ else
39+ text = text .. ping .. ' ms'
40+ if ping >= 500 then
41+ color = 'red'
42+ elseif ping >= 250 then
43+ color = 'yellow'
44+ else
45+ color = 'green'
46+ end
47+ end
48+ ui.ping:setText(text)
49+ ui.ping:setColor(color)
50+end
51+
52+function show()
53+ ui:setVisible(true)
54+end
55+
56+function hide()
57+ ui:setVisible(false)
58+end
A RD_Client/modules/game_stats/stats.otmod +9-0 View file
@@ -0,0 +1,9 @@
1+Module
2+ name: game_stats
3+ description: Display ping and fps
4+ author: otclient.ovh
5+ website: http://otclient.ovh
6+ sandboxed: true
7+ scripts: [ stats ]
8+ @onLoad: init()
9+ @onUnload: terminate()
A RD_Client/modules/game_stats/stats.otui +17-0 View file
@@ -0,0 +1,17 @@
1+UIWidget
2+ id: game_stats
3+ anchors.top: parent.top
4+ anchors.left: parent.left
5+ margin-left: 3
6+ size: 100 100
7+ visible: false
8+ layout:
9+ type: verticalBox
10+
11+ Label
12+ id: fps
13+ font: verdana-11px-rounded
14+
15+ Label
16+ id: ping
17+ font: verdana-11px-rounded
A RD_Client/modules/game_textmessage/icons/orange.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_textmessage/icons/purple.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_textmessage/icons/red.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_textmessage/icons/white.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_textmessage/textmessage.lua +249-0 View file
@@ -0,0 +1,249 @@
1+if not Color then
2+ local mapping = {
3+ red = "#FF0000",
4+ orange = "#FFA500",
5+ blue = "#0000FF",
6+ white = "#FFFFFF",
7+ green = "#00FF00",
8+ yellow = "#FFFF00",
9+ lightblue = "#ADD8E6",
10+ purple = "#800080",
11+ }
12+ Color = {
13+ fromString = function(s)
14+ s = s:match("^%s*(.-)%s*$") or s -- trim extra spaces
15+ return mapping[string.lower(s)] or "#FFFFFF"
16+ end
17+ }
18+end
19+
20+MessageSettings = {
21+ none = {},
22+ consoleRed = { color = TextColors.red, consoleTab='Default' },
23+ consoleOrange = { color = TextColors.orange, consoleTab='Default' },
24+ consoleBlue = { color = TextColors.blue, consoleTab='Default' },
25+ centerRed = { color = TextColors.red, consoleTab='Server Log', screenTarget='lowCenterLabel' },
26+ centerGreen = { color = TextColors.green, consoleTab='Server Log', screenTarget='highCenterLabel', consoleOption='showInfoMessagesInConsole' },
27+ centerWhite = { color = TextColors.white, consoleTab='Server Log', screenTarget='middleCenterLabel', consoleOption='showEventMessagesInConsole' },
28+ centerOrange = { color = TextColors.orange, consoleTab='Server Log', screenTarget='lowCenterLabel' },
29+ centerBlue = { color = TextColors.blue, consoleTab='Server Log', screenTarget='highCenterLabel' },
30+ centerYellow = { color = TextColors.yellow, consoleTab='Server Log', screenTarget='middleCenterLabel' },
31+ centerLightblue = { color = TextColors.lightblue, consoleTab='Server Log', screenTarget='lowCenterLabel' },
32+ centerPurple = { color = TextColors.purple, consoleTab='Server Log', screenTarget='highCenterLabel' },
33+ bottomWhite = { color = TextColors.white, consoleTab='Server Log', screenTarget='statusLabel', consoleOption='showEventMessagesInConsole' },
34+ status = { color = TextColors.white, consoleTab='Server Log', screenTarget='statusLabel', consoleOption='showStatusMessagesInConsole' },
35+ statusSmall = { color = TextColors.white, screenTarget='statusLabel' },
36+ private = { color = TextColors.lightblue, screenTarget='privateLabel' },
37+ privateAlert = { screenTarget='privateAlertWindow' }
38+}
39+
40+MessageTypes = {
41+ [MessageModes.MonsterSay] = MessageSettings.consoleOrange,
42+ [MessageModes.MonsterYell] = MessageSettings.consoleOrange,
43+ [MessageModes.BarkLow] = MessageSettings.consoleOrange,
44+ [MessageModes.BarkLoud] = MessageSettings.consoleOrange,
45+ [MessageModes.Failure] = MessageSettings.statusSmall,
46+ [MessageModes.Login] = MessageSettings.bottomWhite,
47+ [MessageModes.Game] = MessageSettings.centerWhite,
48+ [MessageModes.Status] = MessageSettings.status,
49+ [MessageModes.Warning] = MessageSettings.centerRed,
50+ [MessageModes.Look] = MessageSettings.centerGreen,
51+ [MessageModes.Loot] = MessageSettings.centerGreen,
52+ [MessageModes.Red] = MessageSettings.consoleRed,
53+ [MessageModes.Blue] = MessageSettings.consoleBlue,
54+ [MessageModes.PrivateFrom] = MessageSettings.consoleBlue,
55+
56+ [MessageModes.GamemasterBroadcast] = MessageSettings.consoleRed,
57+
58+ [MessageModes.DamageDealed] = MessageSettings.status,
59+ [MessageModes.DamageReceived] = MessageSettings.status,
60+ [MessageModes.Heal] = MessageSettings.status,
61+ [MessageModes.Exp] = MessageSettings.status,
62+
63+ [MessageModes.DamageOthers] = MessageSettings.none,
64+ [MessageModes.HealOthers] = MessageSettings.none,
65+ [MessageModes.ExpOthers] = MessageSettings.none,
66+
67+ [MessageModes.TradeNpc] = MessageSettings.centerWhite,
68+ [MessageModes.Guild] = MessageSettings.centerWhite,
69+ [MessageModes.Party] = MessageSettings.centerGreen,
70+ [MessageModes.PartyManagement] = MessageSettings.centerWhite,
71+ [MessageModes.TutorialHint] = MessageSettings.centerWhite,
72+ [MessageModes.BeyondLast] = MessageSettings.centerWhite,
73+ [MessageModes.Report] = MessageSettings.consoleRed,
74+ [MessageModes.HotkeyUse] = MessageSettings.centerGreen,
75+
76+ [254] = MessageSettings.private
77+}
78+
79+MessageTypes[50] = MessageTypes[MessageModes.Warning] or MessageSettings.centerRed
80+
81+messagesPanel = nil
82+
83+function init()
84+ for messageMode, _ in pairs(MessageTypes) do
85+ registerMessageMode(messageMode, displayMessage)
86+ end
87+
88+ connect(g_game, 'onGameEnd', clearMessages)
89+ messagesPanel = g_ui.loadUI('textmessage', modules.game_interface.getRootPanel())
90+end
91+
92+function terminate()
93+ for messageMode, _ in pairs(MessageTypes) do
94+ unregisterMessageMode(messageMode, displayMessage)
95+ end
96+
97+ disconnect(g_game, 'onGameEnd', clearMessages)
98+ clearMessages()
99+ messagesPanel:destroy()
100+end
101+
102+function calculateVisibleTime(text)
103+ return math.max(#text * 50, 3000)
104+end
105+
106+function displayMessage(mode, text)
107+ if not g_game.isOnline() then return end
108+
109+ local msgtype = MessageTypes[mode]
110+ if not msgtype then
111+ -- Fallback: handle color|text messages for unmapped modes
112+ if text:find('|') then
113+ local parts = text:explode('|')
114+ local color = parts[1]
115+ local message = parts[2]
116+ if type(color) == "string" then
117+ color = Color.fromString(color)
118+ end
119+ local label = messagesPanel:recursiveGetChildById('customMessage')
120+ label:setText(message)
121+ label:setColor(color)
122+ label:setBorderColor(color)
123+ label:setWidth(label:getTextSize().width+110)
124+ label:show()
125+ addEvent(function() g_effects.fadeIn(label, 250) end)
126+ removeEvent(label.hideEvent)
127+ label.hideEvent = scheduleEvent(function()
128+ addEvent(function() g_effects.fadeOut(label, 250) end)
129+ scheduleEvent(function() label:hide() end, 250)
130+ end, calculateVisibleTime(message))
131+ end
132+ return
133+ end
134+
135+ if msgtype == MessageSettings.none then return end
136+
137+ if mode == MessageModes.Warning then
138+ local label = messagesPanel:recursiveGetChildById('customMessage')
139+ local color = msgtype.color
140+ if text:find('|') then
141+ local ex = text:explode('|')
142+ color = ex[1]
143+ if type(color) == "string" then
144+ color = Color.fromString(color)
145+ end
146+ text = ex[2]
147+ else
148+ if type(color) == "string" then
149+ color = Color.fromString(color)
150+ end
151+ end
152+
153+ label:setText(text)
154+ label:setColor(color)
155+ label:setBorderColor(color)
156+ label:setWidth(label:getTextSize().width+110)
157+ label:show()
158+
159+ addEvent(function() g_effects.fadeIn(label, 250) end)
160+ removeEvent(label.hideEvent)
161+ label.hideEvent = scheduleEvent(function()
162+ addEvent(function() g_effects.fadeOut(label, 250) end)
163+ scheduleEvent(function() label:hide() end, 250)
164+ end, calculateVisibleTime(text))
165+ return
166+ end
167+
168+ if msgtype.consoleTab ~= nil and (msgtype.consoleOption == nil or modules.client_options.getOption(msgtype.consoleOption)) then
169+ modules.game_chat.addText(text, msgtype, tr(msgtype.consoleTab))
170+ --TODO move to game_console
171+ end
172+
173+ if msgtype.screenTarget then
174+ local label = messagesPanel:recursiveGetChildById(msgtype.screenTarget)
175+ local col = msgtype.color
176+ if type(col) == "string" then
177+ col = Color.fromString(col)
178+ end
179+ if text:match('@') then
180+ label:setText('')
181+ else
182+ label:setText(text)
183+ end
184+ label:setColor(col)
185+ label:setVisible(true)
186+ removeEvent(label.hideEvent)
187+ label.hideEvent = scheduleEvent(function() label:setVisible(false) end, calculateVisibleTime(text))
188+ end
189+end
190+
191+function displayPrivateMessage(text)
192+ displayMessage(254, text)
193+end
194+
195+function displayPrivateAlert(name, message)
196+ local msgtype = MessageSettings.privateAlert
197+ local window = messagesPanel:recursiveGetChildById(msgtype.screenTarget)
198+ if not window then return end
199+
200+ -- Truncar mensaje si es muy largo para la alerta (según conteo del usuario: 46 caracteres)
201+ local displayMessage = message
202+ if #displayMessage > 46 then
203+ displayMessage = displayMessage:sub(1, 46) .. "...."
204+ end
205+
206+ window:recursiveGetChildById('titleLabel'):setText("Private from " .. name .. ":")
207+ window:recursiveGetChildById('messageLabel'):setText(displayMessage)
208+ window:show()
209+
210+ addEvent(function() g_effects.fadeIn(window, 250) end)
211+ removeEvent(window.hideEvent)
212+ window.hideEvent = scheduleEvent(function()
213+ addEvent(function() g_effects.fadeOut(window, 250) end)
214+ scheduleEvent(function() window:hide() end, 250)
215+ end, calculateVisibleTime(message))
216+end
217+
218+function displayStatusMessage(text)
219+ displayMessage(MessageModes.Status, text)
220+end
221+
222+function displayFailureMessage(text)
223+ displayMessage(MessageModes.Failure, text)
224+end
225+
226+function displayGameMessage(text)
227+ displayMessage(MessageModes.Game, text)
228+end
229+
230+function displayBroadcastMessage(text)
231+ displayMessage(MessageModes.Warning, text)
232+end
233+
234+function clearMessages()
235+ for _i,child in pairs(messagesPanel:recursiveGetChildren()) do
236+ if child:getId():match('Label') then
237+ child:hide()
238+ removeEvent(child.hideEvent)
239+ end
240+ end
241+end
242+function getSilentPMAlert()
243+ return messagesPanel:recursiveGetChildById('silentPMAlert')
244+end
245+
246+
247+function LocalPlayer:onAutoWalkFail(player)
248+ modules.game_textmessage.displayFailureMessage(tr('There is no way.'))
249+end
A RD_Client/modules/game_textmessage/textmessage.otmod +9-0 View file
@@ -0,0 +1,9 @@
1+Module
2+ name: game_textmessage
3+ description: Manage game text messages
4+ author: edubart
5+ website: https://github.com/edubart/otclient
6+ sandboxed: true
7+ scripts: [ textmessage ]
8+ @onLoad: init()
9+ @onUnload: terminate()
Top