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_shaders/shaders.otmod +9-0 View file
@@ -0,0 +1,9 @@
1+Module
2+ name: game_shaders
3+ description: Load shaders
4+ author: otclientv8
5+ website: http://otclient.ovh
6+ scripts: [ shaders ]
7+ sandboxed: true
8+ @onLoad: init()
9+ @onUnload: terminate()
A RD_Client/modules/game_shop/shop.lua +562-0 View file
@@ -0,0 +1,562 @@
1+-- private variables
2+local SHOP_EXTENTED_OPCODE = 201
3+
4+shop = nil
5+local otcv8shop = false
6+local shopButton = nil
7+local msgWindow = nil
8+local browsingHistory = false
9+
10+-- for classic store
11+local storeUrl = ""
12+local coinsPacketSize = 0
13+
14+local CATEGORIES = {}
15+local HISTORY = {}
16+local STATUS = {}
17+local AD = {}
18+
19+local selectedOffer = {}
20+
21+local function sendAction(action, data)
22+ if not g_game.getFeature(GameExtendedOpcode) then
23+ return
24+ end
25+
26+ local protocolGame = g_game.getProtocolGame()
27+ if data == nil then
28+ data = {}
29+ end
30+ if protocolGame then
31+ protocolGame:sendExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, {action = action, data = data})
32+ end
33+end
34+
35+-- public functions
36+function init()
37+ connect(g_game, {
38+ onGameStart = check,
39+ onGameEnd = hide,
40+ onStoreInit = onStoreInit,
41+ onStoreCategories = onStoreCategories,
42+ onStoreOffers = onStoreOffers,
43+ onStoreTransactionHistory = onStoreTransactionHistory,
44+ onStorePurchase = onStorePurchase,
45+ onStoreError = onStoreError,
46+ onCoinBalance = onCoinBalance
47+ })
48+
49+ ProtocolGame.registerExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, onExtendedJSONOpcode)
50+
51+ if g_game.isOnline() then
52+ check()
53+ end
54+end
55+
56+function terminate()
57+ disconnect(g_game, {
58+ onGameStart = check,
59+ onGameEnd = hide,
60+ onStoreInit = onStoreInit,
61+ onStoreCategories = onStoreCategories,
62+ onStoreOffers = onStoreOffers,
63+ onStoreTransactionHistory = onStoreTransactionHistory,
64+ onStorePurchase = onStorePurchase,
65+ onStoreError = onStoreError,
66+ onCoinBalance = onCoinBalance
67+ })
68+
69+ ProtocolGame.unregisterExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, onExtendedJSONOpcode)
70+
71+ --if shopButton then
72+ -- shopButton:destroy()
73+ -- shopButton = nil
74+ --end
75+ if shop then
76+ disconnect(shop.categories, { onChildFocusChange = changeCategory })
77+ shop:destroy()
78+ shop = nil
79+ end
80+ if msgWindow then
81+ msgWindow:destroy()
82+ end
83+end
84+
85+function check()
86+ otcv8shop = false
87+ sendAction("init")
88+end
89+
90+function hide()
91+ if not shop then
92+ return
93+ end
94+ shop:hide()
95+end
96+
97+function show()
98+ if not shop then
99+ return
100+ end
101+ if g_game.getFeature(GameIngameStore) then
102+ g_game.openStore(0)
103+ end
104+
105+ shop:show()
106+ shop:raise()
107+ shop:focus()
108+end
109+
110+function toggle()
111+ if not shop then
112+ return
113+ end
114+ if shop:isVisible() then
115+ return hide()
116+ end
117+ show()
118+ check()
119+end
120+
121+function createShop()
122+ if shop then return end
123+ shop = g_ui.displayUI('shop')
124+ shop:hide()
125+ --shopButton = modules.client_topmenu.addRightGameToggleButton('shopButton', tr('Shop'), '/images/topbuttons/shop', toggle, false, 8)
126+ connect(shop.categories, { onChildFocusChange = changeCategory })
127+end
128+
129+
130+function onStoreInit(url, coins)
131+ if otcv8shop then return end
132+ storeUrl = url
133+ if storeUrl:len() > 0 then
134+ if storeUrl:sub(storeUrl:len(), storeUrl:len()) ~= "/" then
135+ storeUrl = storeUrl .. "/"
136+ end
137+ storeUrl = storeUrl .. "64/"
138+ if storeUrl:sub(1, 4):lower() ~= "http" then
139+ storeUrl = "http://" .. storeUrl
140+ end
141+ end
142+ coinsPacketSize = coins
143+ createShop()
144+end
145+
146+function onStoreCategories(categories)
147+ if not shop or otcv8shop then return end
148+ local correctCategories = {}
149+ for i, category in ipairs(categories) do
150+ local image = ""
151+ if category.icon:len() > 0 then
152+ image = storeUrl .. category.icon
153+ end
154+ table.insert(correctCategories, {
155+ type = "image",
156+ image = image,
157+ name = category.name,
158+ offers = {}
159+ })
160+ end
161+ processCategories(correctCategories)
162+end
163+
164+function onStoreOffers(categoryName, offers)
165+ if not shop or otcv8shop then return end
166+ local updated = false
167+
168+ for i, category in ipairs(CATEGORIES) do
169+ if category.name == categoryName then
170+ if #category.offers ~= #offers then
171+ updated = true
172+ end
173+ for i=1,#category.offers do
174+ if category.offers[i].title ~= offers[i].name or category.offers[i].id ~= offers[i].id or category.offers[i].cost ~= offers[i].price then
175+ updated = true
176+ end
177+ end
178+ if updated then
179+ for offer in pairs(category.offers) do
180+ category.offers[offer] = nil
181+ end
182+ for i, offer in ipairs(offers) do
183+ local image = ""
184+ if offer.icon:len() > 0 then
185+ image = storeUrl .. offer.icon
186+ end
187+ table.insert(category.offers, {
188+ id=offer.id,
189+ type="image",
190+ image=image,
191+ cost=offer.price,
192+ title=offer.name,
193+ description=offer.description
194+ })
195+ end
196+ end
197+ end
198+ end
199+ if not updated then
200+ return
201+ end
202+
203+ local activeCategory = shop.categories:getFocusedChild()
204+ changeCategory(activeCategory, activeCategory)
205+end
206+
207+function onStoreTransactionHistory(currentPage, hasNextPage, offers)
208+ if not shop or otcv8shop then return end
209+ HISTORY = {}
210+ for i, offer in ipairs(offers) do
211+ table.insert(HISTORY, {
212+ id=offer.id,
213+ type="image",
214+ image=storeUrl .. offer.icon,
215+ cost=offer.price,
216+ title=offer.name,
217+ description=offer.description
218+ })
219+ end
220+
221+ if not browsingHistory then return end
222+ clearOffers()
223+ shop.categories:focusChild(nil)
224+ for i, transaction in ipairs(HISTORY) do
225+ addOffer(0, transaction)
226+ end
227+end
228+
229+function onStorePurchase(message)
230+ if not shop or otcv8shop then return end
231+ processMessage({title="Successful shop purchase", msg=message})
232+end
233+
234+function onStoreError(errorType, message)
235+ if not shop or otcv8shop then return end
236+ processMessage({title="Shop error", msg=message})
237+end
238+
239+function onCoinBalance(coins, transferableCoins)
240+ if not shop or otcv8shop then return end
241+ shop.infoPanel.points:setText(tr("Points:") .. " " .. coins)
242+ shop.infoPanel.buy:hide()
243+ shop.infoPanel:setHeight(20)
244+end
245+
246+function onExtendedJSONOpcode(protocol, code, json_data)
247+ createShop()
248+
249+ local action = json_data['action']
250+ local data = json_data['data']
251+ local status = json_data['status']
252+ if not action or not data then
253+ return false
254+ end
255+
256+ otcv8shop = true
257+ if action == 'categories' then
258+ processCategories(data)
259+ elseif action == 'history' then
260+ processHistory(data)
261+ elseif action == 'message' then
262+ processMessage(data)
263+ end
264+
265+ if status then
266+ processStatus(status)
267+ end
268+end
269+
270+function clearOffers()
271+ while shop.offers:getChildCount() > 0 do
272+ local child = shop.offers:getLastChild()
273+ shop.offers:destroyChildren(child)
274+ end
275+end
276+
277+function clearCategories()
278+ CATEGORIES = {}
279+ clearOffers()
280+ while shop.categories:getChildCount() > 0 do
281+ local child = shop.categories:getLastChild()
282+ shop.categories:destroyChildren(child)
283+ end
284+end
285+
286+function clearHistory()
287+ HISTORY = {}
288+ if browsingHistory then
289+ clearOffers()
290+ end
291+end
292+
293+function processCategories(data)
294+ if table.equal(CATEGORIES,data) then
295+ return
296+ end
297+ clearCategories()
298+ CATEGORIES = data
299+ for i, category in ipairs(data) do
300+ addCategory(category)
301+ end
302+ if not browsingHistory then
303+ local firstCategory = shop.categories:getChildByIndex(1)
304+ if firstCategory then
305+ firstCategory:focus()
306+ end
307+ end
308+end
309+
310+function processHistory(data)
311+ if table.equal(HISTORY,data) then
312+ return
313+ end
314+ HISTORY = data
315+ if browsingHistory then
316+ showHistory(true)
317+ end
318+end
319+
320+function processMessage(data)
321+ if msgWindow then
322+ msgWindow:destroy()
323+ end
324+
325+ local title = tr(data["title"])
326+ local msg = data["msg"]
327+ msgWindow = displayInfoBox(title, msg)
328+ msgWindow.onDestroy = function(widget)
329+ if widget == msgWindow then
330+ msgWindow = nil
331+ end
332+ end
333+ msgWindow:show()
334+ msgWindow:raise()
335+ msgWindow:focus()
336+end
337+
338+function processStatus(data)
339+ if table.equal(STATUS,data) then
340+ return
341+ end
342+ STATUS = data
343+
344+ if data['ad'] then
345+ processAd(data['ad'])
346+ end
347+ if data['points'] then
348+ shop.infoPanel.points:setText(tr("Points:") .. " " .. data['points'])
349+ end
350+ if data['buyUrl'] and data['buyUrl']:sub(1, 4):lower() == "http" then
351+ shop.infoPanel.buy:show()
352+ shop.infoPanel.buy.onMouseRelease = function()
353+ scheduleEvent(function() g_platform.openUrl(data['buyUrl']) end, 50)
354+ end
355+ else
356+ shop.infoPanel.buy:hide()
357+ shop.infoPanel:setHeight(20)
358+ end
359+end
360+
361+function processAd(data)
362+ if table.equal(AD,data) then
363+ return
364+ end
365+ AD = data
366+
367+ if data['image'] and data['image']:sub(1, 4):lower() == "http" then
368+ HTTP.downloadImage(data['image'], function(path, err)
369+ if err then g_logger.warning("HTTP error: " .. err .. " - " .. data['image']) return end
370+ shop.adPanel:setHeight(shop.infoPanel:getHeight())
371+ shop.adPanel.ad:setText("")
372+ shop.adPanel.ad:setImageSource(path)
373+ shop.adPanel.ad:setImageFixedRatio(true)
374+ shop.adPanel.ad:setImageAutoResize(true)
375+ shop.adPanel.ad:setHeight(shop.infoPanel:getHeight())
376+ end)
377+ elseif data['text'] and data['text']:len() > 0 then
378+ shop.adPanel:setHeight(shop.infoPanel:getHeight())
379+ shop.adPanel.ad:setText(data['text'])
380+ shop.adPanel.ad:setHeight(shop.infoPanel:getHeight())
381+ else
382+ shop.adPanel:setHeight(0)
383+ end
384+ if data['url'] and data['url']:sub(1, 4):lower() == "http" then
385+ shop.adPanel.ad.onMouseRelease = function()
386+ scheduleEvent(function() g_platform.openUrl(data['url']) end, 50)
387+ end
388+ else
389+ shop.adPanel.ad.onMouseRelease = nil
390+ end
391+end
392+
393+function addCategory(data)
394+ local category
395+ if data["type"] == "item" then
396+ category = g_ui.createWidget('ShopCategoryItem', shop.categories)
397+ category.item:setItemId(data["item"])
398+ category.item:setItemCount(data["count"])
399+ category.item:setShowCount(false)
400+ elseif data["type"] == "outfit" then
401+ category = g_ui.createWidget('ShopCategoryCreature', shop.categories)
402+ category.creature:setOutfit(data["outfit"])
403+ if data["outfit"]["rotating"] then
404+ category.creature:setAutoRotating(true)
405+ end
406+ elseif data["type"] == "image" then
407+ category = g_ui.createWidget('ShopCategoryImage', shop.categories)
408+ if data["image"] and data["image"]:sub(1, 4):lower() == "http" then
409+ HTTP.downloadImage(data['image'], function(path, err)
410+ if err then g_logger.warning("HTTP error: " .. err .. " - " .. data["image"]) return end
411+ category.image:setImageSource(path)
412+ end)
413+ else
414+ category.image:setImageSource(data["image"])
415+ end
416+ else
417+ g_logger.error("Invalid shop category type: " .. tostring(data["type"]))
418+ return
419+ end
420+ category:setId("category_" .. shop.categories:getChildCount())
421+ category.name:setText(data["name"])
422+end
423+
424+function showHistory(force)
425+ if browsingHistory and not force then
426+ return
427+ end
428+
429+ if g_game.getFeature(GameIngameStore) and not otcv8shop then
430+ g_game.openTransactionHistory(100)
431+ end
432+ sendAction("history")
433+
434+ browsingHistory = true
435+ clearOffers()
436+ shop.categories:focusChild(nil)
437+ for i, transaction in ipairs(HISTORY) do
438+ addOffer(0, transaction)
439+ end
440+end
441+
442+function addOffer(category, data)
443+ local offer
444+ if data["type"] == "item" then
445+ offer = g_ui.createWidget('ShopOfferItem', shop.offers)
446+ offer.item:setItemId(data["item"])
447+ offer.item:setItemCount(data["count"])
448+ offer.item:setShowCount(false)
449+ elseif data["type"] == "outfit" then
450+ offer = g_ui.createWidget('ShopOfferCreature', shop.offers)
451+ offer.creature:setOutfit(data["outfit"])
452+ if data["outfit"]["rotating"] then
453+ offer.creature:setAutoRotating(true)
454+ end
455+ elseif data["type"] == "image" then
456+ offer = g_ui.createWidget('ShopOfferImage', shop.offers)
457+ if data["image"] and data["image"]:sub(1, 4):lower() == "http" then
458+ HTTP.downloadImage(data['image'], function(path, err)
459+ if err then g_logger.warning("HTTP error: " .. err .. " - " .. data['image']) return end
460+ if not offer.image then return end
461+ offer.image:setImageSource(path)
462+ end)
463+ elseif data["image"] and data["image"]:len() > 1 then
464+ offer.image:setImageSource(data["image"])
465+ end
466+ else
467+ g_logger.error("Invalid shop offer type: " .. tostring(data["type"]))
468+ return
469+ end
470+ offer:setId("offer_" .. category .. "_" .. shop.offers:getChildCount())
471+ offer.title:setText(data["title"] .. " (" .. data["cost"] .. " points)")
472+ offer.description:setText(data["description"])
473+ offer.offerId = data["id"]
474+ if category ~= 0 then
475+ offer.onDoubleClick = buyOffer
476+ offer.buyButton.onClick = function() buyOffer(offer) end
477+ else
478+ offer.buyButton:hide()
479+ end
480+end
481+
482+
483+function changeCategory(widget, newCategory)
484+ if not newCategory then
485+ return
486+ end
487+
488+ if g_game.getFeature(GameIngameStore) and widget ~= newCategory and not otcv8shop then
489+ local serviceType = 0
490+ if g_game.getFeature(GameTibia12Protocol) then
491+ serviceType = 2
492+ end
493+ g_game.requestStoreOffers(newCategory.name:getText(), serviceType)
494+ end
495+
496+ browsingHistory = false
497+ local id = tonumber(newCategory:getId():split("_")[2])
498+ clearOffers()
499+ for i, offer in ipairs(CATEGORIES[id]["offers"]) do
500+ addOffer(id, offer)
501+ end
502+end
503+
504+function buyOffer(widget)
505+ if not widget then
506+ return
507+ end
508+ local split = widget:getId():split("_")
509+ if #split ~= 3 then
510+ return
511+ end
512+ local category = tonumber(split[2])
513+ local offer = tonumber(split[3])
514+ local item = CATEGORIES[category]["offers"][offer]
515+ if not item then
516+ return
517+ end
518+
519+ selectedOffer = {category=category, offer=offer, title=item.title, cost=item.cost, id=widget.offerId}
520+
521+ scheduleEvent(function()
522+ if msgWindow then
523+ msgWindow:destroy()
524+ end
525+
526+ local title = tr("Buying from shop")
527+ local msg = "Do you want to buy " .. item.title .. " for " .. item.cost .. " premium points?"
528+ msgWindow = displayGeneralBox(title, msg, {
529+ { text=tr('Yes'), callback=buyConfirmed },
530+ { text=tr('No'), callback=buyCanceled },
531+ anchor=AnchorHorizontalCenter}, buyConfirmed, buyCanceled)
532+ msgWindow:show()
533+ msgWindow:raise()
534+ msgWindow:focus()
535+ msgWindow:raise()
536+ end, 50)
537+end
538+
539+function buyConfirmed()
540+ msgWindow:destroy()
541+ msgWindow = nil
542+ sendAction("buy", selectedOffer)
543+ if g_game.getFeature(GameIngameStore) and selectedOffer.id and not otcv8shop then
544+ local offerName = selectedOffer.title:lower()
545+ if string.find(offerName, "name") and string.find(offerName, "change") and modules.client_textedit then
546+ modules.client_textedit.singlelineEditor("", function(newName)
547+ if newName:len() == 0 then
548+ return
549+ end
550+ g_game.buyStoreOffer(selectedOffer.id, 1, newName)
551+ end)
552+ else
553+ g_game.buyStoreOffer(selectedOffer.id, 0, "")
554+ end
555+ end
556+end
557+
558+function buyCanceled()
559+ msgWindow:destroy()
560+ msgWindow = nil
561+ selectedOffer = {}
562+end
A RD_Client/modules/game_shop/shop.otmod +10-0 View file
@@ -0,0 +1,10 @@
1+Module
2+ name: game_shop
3+ description: Game shop
4+ author: otclient.ovh
5+ website: http://otclient.ovh
6+ sandboxed: true
7+ scripts: [ shop ]
8+ dependencies: [ client_topmenu ]
9+ @onLoad: init()
10+ @onUnload: terminate()
A RD_Client/modules/game_shop/shop.otui +236-0 View file
@@ -0,0 +1,236 @@
1+ShopCategory < Panel
2+ height: 36
3+ focusable: true
4+ background: alpha
5+
6+ $focus:
7+ background: #99999999
8+
9+ Label
10+ id: name
11+ anchors.top: parent.top
12+ anchors.left: parent.left
13+ anchors.right: parent.right
14+ anchors.bottom: parent.bottom
15+ margin-left: 40
16+ text-align: left
17+ color: white
18+ font: verdana-11px-rounded
19+
20+ShopCategoryItem < ShopCategory
21+ UIItem
22+ id: item
23+ anchors.top: parent.top
24+ anchors.left: parent.left
25+ anchors.bottom: parent.bottom
26+ margin-top: 2
27+ margin-bottom: 2
28+ margin-left: 2
29+ virtual: true
30+ size: 32 32
31+
32+ShopCategoryCreature < ShopCategory
33+ UICreature
34+ id: creature
35+ anchors.top: parent.top
36+ anchors.left: parent.left
37+ anchors.bottom: parent.bottom
38+ margin-top: 2
39+ margin-bottom: 2
40+ margin-left: 2
41+ size: 32 32
42+
43+ShopCategoryImage < ShopCategory
44+ Label
45+ id: image
46+ anchors.top: parent.top
47+ anchors.left: parent.left
48+ anchors.bottom: parent.bottom
49+ margin-top: 2
50+ margin-bottom: 2
51+ margin-left: 2
52+ size: 32 32
53+
54+
55+
56+ShopOffer < Panel
57+ height: 56
58+ background: alpha
59+
60+ $focus:
61+ background: #99999999
62+
63+ Label
64+ id: title
65+ anchors.left: parent.left
66+ anchors.right: parent.right
67+ anchors.top: parent.top
68+ margin-top: 4
69+ margin-left: 55
70+ text-align: topleft
71+ color: white
72+ font: verdana-11px-rounded
73+
74+ Label
75+ id: description
76+ anchors.top: prev.bottom
77+ anchors.left: parent.left
78+ anchors.right: parent.right
79+ anchors.bottom: parent.bottom
80+ margin-left: 55
81+ margin-right: 55
82+ text-align: topleft
83+ text-auto-resize: true
84+ text-wrap: true
85+ color: white
86+ font: verdana-11px-rounded
87+
88+ Button
89+ id: buyButton
90+ text: BUY
91+ height: 25
92+ anchors.verticalCenter: parent.verticalCenter
93+ anchors.left: prev.right
94+ anchors.right: parent.right
95+ margin-right: 15
96+ text-align: center
97+
98+ShopOfferItem < ShopOffer
99+ UIItem
100+ id: item
101+ anchors.top: parent.top
102+ anchors.left: parent.left
103+ anchors.bottom: parent.bottom
104+ margin-top: 4
105+ margin-bottom: 4
106+ margin-left: 2
107+ virtual: true
108+ size: 48 48
109+
110+ShopOfferCreature < ShopOffer
111+ UICreature
112+ id: creature
113+ anchors.top: parent.top
114+ anchors.left: parent.left
115+ anchors.bottom: parent.bottom
116+ margin-top: 4
117+ margin-bottom: 4
118+ margin-left: 2
119+ size: 48 48
120+
121+ShopOfferImage < ShopOffer
122+ Label
123+ id: image
124+ anchors.top: parent.top
125+ anchors.left: parent.left
126+ anchors.bottom: parent.bottom
127+ margin-top: 4
128+ margin-bottom: 4
129+ margin-left: 2
130+ size: 48 48
131+
132+MainWindow
133+ id: shopWindow
134+ !text: tr('Shop')
135+ size: 750 500
136+ @onEscape: modules.game_shop.hide()
137+ $mobile:
138+ size: 500 360
139+
140+ Panel
141+ id: infoPanel
142+ anchors.top: parent.top
143+ anchors.left: parent.left
144+ width: 230
145+ height: 60
146+
147+ Label
148+ id: points
149+ anchors.horizontalCenter: parent.horizontalCenter
150+ anchors.top: parent.top
151+ margin-top: 10
152+ text: -
153+ text-auto-resize: true
154+
155+ Button
156+ id: buy
157+ anchors.horizontalCenter: parent.horizontalCenter
158+ width: 150
159+ anchors.top: prev.bottom
160+ margin-top: 10
161+ visible: false
162+ !text: tr("Buy points")
163+
164+ Panel
165+ id: adPanel
166+ anchors.top: parent.top
167+ anchors.left: infoPanel.right
168+ anchors.right: parent.right
169+ margin-left: 10
170+ height: 0
171+
172+ Label
173+ id: ad
174+ anchors.horizontalCenter: parent.horizontalCenter
175+ anchors.top: parent.top
176+ anchors.bottom: parent.bottom
177+ text-auto-resize: true
178+ text-wrap: true
179+ text-align: center
180+ font: sans-bold-16px
181+
182+ TextList
183+ id: categories
184+ vertical-scrollbar: categoriesScrollBar
185+ anchors.top: infoPanel.bottom
186+ anchors.left: infoPanel.left
187+ anchors.right: infoPanel.right
188+ anchors.bottom: transactionHistory.top
189+ margin-top: 10
190+ margin-bottom: 10
191+ padding: 1
192+ focusable: false
193+
194+ VerticalScrollBar
195+ id: categoriesScrollBar
196+ anchors.top: categories.top
197+ anchors.bottom: categories.bottom
198+ anchors.right: categories.right
199+ step: 50
200+ pixels-scroll: true
201+
202+ TextList
203+ id: offers
204+ vertical-scrollbar: offersScrollBar
205+ anchors.top: adPanel.bottom
206+ anchors.left: adPanel.left
207+ anchors.right: adPanel.right
208+ anchors.bottom: transactionHistory.top
209+ margin-top: 10
210+ margin-bottom: 10
211+ padding: 1
212+ focusable: false
213+
214+ VerticalScrollBar
215+ id: offersScrollBar
216+ anchors.top: offers.top
217+ anchors.bottom: offers.bottom
218+ anchors.right: offers.right
219+ step: 50
220+ pixels-scroll: true
221+
222+ Button
223+ id: transactionHistory
224+ !text: tr('Transaction history')
225+ width: 128
226+ anchors.left: parent.left
227+ anchors.bottom: parent.bottom
228+ @onClick: modules.game_shop.showHistory()
229+
230+ Button
231+ id: buttonCancel
232+ !text: tr('Close')
233+ width: 64
234+ anchors.right: parent.right
235+ anchors.bottom: parent.bottom
236+ @onClick: modules.game_shop.hide()
A RD_Client/modules/game_skills/skills.lua +442-0 View file
@@ -0,0 +1,442 @@
1+skillsWindow = nil
2+skillsButton = nil
3+
4+function init()
5+ connect(LocalPlayer, {
6+ onExperienceChange = onExperienceChange,
7+ onLevelChange = onLevelChange,
8+ onHealthChange = onHealthChange,
9+ onManaChange = onManaChange,
10+ onSoulChange = onSoulChange,
11+ onFreeCapacityChange = onFreeCapacityChange,
12+ onTotalCapacityChange = onTotalCapacityChange,
13+ onStaminaChange = onStaminaChange,
14+ onOfflineTrainingChange = onOfflineTrainingChange,
15+ onRegenerationChange = onRegenerationChange,
16+ onSpeedChange = onSpeedChange,
17+ onBaseSpeedChange = onBaseSpeedChange,
18+ onMagicLevelChange = onMagicLevelChange,
19+ onBaseMagicLevelChange = onBaseMagicLevelChange,
20+ onSkillChange = onSkillChange,
21+ onBaseSkillChange = onBaseSkillChange
22+ })
23+ connect(g_game, {
24+ onGameStart = refresh,
25+ onGameEnd = offline
26+ })
27+
28+ skillsButton = modules.client_topmenu.addRightGameToggleButton('skillsButton', tr('Skills'), '/images/topbuttons/skills', toggle, false, 1)
29+ --skillsButton:setOn(true)
30+ skillsButton:hide()
31+ skillsWindow = g_ui.loadUI('skills', modules.game_interface.getRightPanel())
32+ skillsWindow:hide()
33+
34+ refresh()
35+ --skillsWindow:setup()
36+end
37+
38+function terminate()
39+ disconnect(LocalPlayer, {
40+ onExperienceChange = onExperienceChange,
41+ onLevelChange = onLevelChange,
42+ onHealthChange = onHealthChange,
43+ onManaChange = onManaChange,
44+ onSoulChange = onSoulChange,
45+ onFreeCapacityChange = onFreeCapacityChange,
46+ onTotalCapacityChange = onTotalCapacityChange,
47+ onStaminaChange = onStaminaChange,
48+ onOfflineTrainingChange = onOfflineTrainingChange,
49+ onRegenerationChange = onRegenerationChange,
50+ onSpeedChange = onSpeedChange,
51+ onBaseSpeedChange = onBaseSpeedChange,
52+ onMagicLevelChange = onMagicLevelChange,
53+ onBaseMagicLevelChange = onBaseMagicLevelChange,
54+ onSkillChange = onSkillChange,
55+ onBaseSkillChange = onBaseSkillChange
56+ })
57+ disconnect(g_game, {
58+ onGameStart = refresh,
59+ onGameEnd = offline
60+ })
61+
62+ skillsWindow:destroy()
63+ skillsButton:destroy()
64+end
65+
66+function expForLevel(level)
67+ return math.floor((50*level*level*level)/3 - 100*level*level + (850*level)/3 - 200)
68+end
69+
70+function expToAdvance(currentLevel, currentExp)
71+ return expForLevel(currentLevel+1) - currentExp
72+end
73+
74+function resetSkillColor(id)
75+ local skill = skillsWindow:recursiveGetChildById(id)
76+ local widget = skill:getChildById('value')
77+ widget:setColor('#bbbbbb')
78+end
79+
80+function toggleSkill(id, state)
81+ local skill = skillsWindow:recursiveGetChildById(id)
82+ skill:setVisible(state)
83+end
84+
85+function setSkillBase(id, value, baseValue)
86+ if baseValue <= 0 or value < 0 then
87+ return
88+ end
89+ local skill = skillsWindow:recursiveGetChildById(id)
90+ local widget = skill:getChildById('value')
91+
92+ if value > baseValue then
93+ widget:setColor('#008b00') -- green
94+ skill:setTooltip(baseValue .. ' +' .. (value - baseValue))
95+ elseif value < baseValue then
96+ widget:setColor('#b22222') -- red
97+ skill:setTooltip(baseValue .. ' ' .. (value - baseValue))
98+ else
99+ widget:setColor('#bbbbbb') -- default
100+ skill:removeTooltip()
101+ end
102+end
103+
104+function setSkillValue(id, value)
105+ local skill = skillsWindow:recursiveGetChildById(id)
106+ local widget = skill:getChildById('value')
107+ widget:setText(value)
108+end
109+
110+function setSkillColor(id, value)
111+ local skill = skillsWindow:recursiveGetChildById(id)
112+ local widget = skill:getChildById('value')
113+ widget:setColor(value)
114+end
115+
116+function setSkillTooltip(id, value)
117+ local skill = skillsWindow:recursiveGetChildById(id)
118+ local widget = skill:getChildById('value')
119+ widget:setTooltip(value)
120+end
121+
122+function setSkillPercent(id, percent, tooltip, color)
123+ local skill = skillsWindow:recursiveGetChildById(id)
124+ local widget = skill:getChildById('percent')
125+ if widget then
126+ widget:setPercent(math.floor(percent))
127+
128+ if tooltip then
129+ widget:setTooltip(tooltip)
130+ end
131+
132+ if color then
133+ widget:setBackgroundColor(color)
134+ end
135+ end
136+end
137+
138+function checkAlert(id, value, maxValue, threshold, greaterThan)
139+ if greaterThan == nil then greaterThan = false end
140+ local alert = false
141+
142+ -- maxValue can be set to false to check value and threshold
143+ -- used for regeneration checking
144+ if type(maxValue) == 'boolean' then
145+ if maxValue then
146+ return
147+ end
148+
149+ if greaterThan then
150+ if value > threshold then
151+ alert = true
152+ end
153+ else
154+ if value < threshold then
155+ alert = true
156+ end
157+ end
158+ elseif type(maxValue) == 'number' then
159+ if maxValue < 0 then
160+ return
161+ end
162+
163+ local percent = math.floor((value / maxValue) * 100)
164+ if greaterThan then
165+ if percent > threshold then
166+ alert = true
167+ end
168+ else
169+ if percent < threshold then
170+ alert = true
171+ end
172+ end
173+ end
174+
175+ if alert then
176+ setSkillColor(id, '#b22222') -- red
177+ else
178+ resetSkillColor(id)
179+ end
180+end
181+
182+function update()
183+ local offlineTraining = skillsWindow:recursiveGetChildById('offlineTraining')
184+ if not g_game.getFeature(GameOfflineTrainingTime) then
185+ offlineTraining:hide()
186+ else
187+ offlineTraining:show()
188+ end
189+
190+ local regenerationTime = skillsWindow:recursiveGetChildById('regenerationTime')
191+ if not g_game.getFeature(GamePlayerRegenerationTime) then
192+ regenerationTime:hide()
193+ else
194+ regenerationTime:show()
195+ end
196+end
197+
198+function refresh()
199+ local player = g_game.getLocalPlayer()
200+ if not player then return end
201+
202+ if expSpeedEvent then expSpeedEvent:cancel() end
203+ expSpeedEvent = cycleEvent(checkExpSpeed, 30*1000)
204+
205+ onExperienceChange(player, player:getExperience())
206+ onLevelChange(player, player:getLevel(), player:getLevelPercent())
207+ onHealthChange(player, player:getHealth(), player:getMaxHealth())
208+ onManaChange(player, player:getMana(), player:getMaxMana())
209+ onSoulChange(player, player:getSoul())
210+ onFreeCapacityChange(player, player:getFreeCapacity())
211+ onStaminaChange(player, player:getStamina())
212+ onMagicLevelChange(player, player:getMagicLevel(), player:getMagicLevelPercent())
213+ onOfflineTrainingChange(player, player:getOfflineTrainingTime())
214+ onRegenerationChange(player, player:getRegenerationTime())
215+ onSpeedChange(player, player:getSpeed())
216+
217+ local hasAdditionalSkills = g_game.getFeature(GameAdditionalSkills)
218+ for i = Skill.Fist, Skill.ManaLeechAmount do
219+ onSkillChange(player, i, player:getSkillLevel(i), player:getSkillLevelPercent(i))
220+ onBaseSkillChange(player, i, player:getSkillBaseLevel(i))
221+
222+ if i > Skill.Fishing then
223+ toggleSkill('skillId'..i, hasAdditionalSkills)
224+ end
225+ end
226+
227+ update()
228+
229+ local contentsPanel = skillsWindow:getChildById('contentsPanel')
230+ skillsWindow:setContentMinimumHeight(44)
231+ if hasAdditionalSkills then
232+ skillsWindow:setContentMaximumHeight(480)
233+ else
234+ skillsWindow:setContentMaximumHeight(390)
235+ end
236+end
237+
238+function offline()
239+ if expSpeedEvent then expSpeedEvent:cancel() expSpeedEvent = nil end
240+end
241+
242+function Meclose()
243+ skillsWindow:close()
244+end
245+function toggle()
246+ if skillsButton:isOn() then
247+ skillsWindow:close()
248+ skillsButton:setOn(false)
249+ else
250+ skillsWindow:open()
251+ skillsButton:setOn(true)
252+ end
253+end
254+
255+function checkExpSpeed()
256+ local player = g_game.getLocalPlayer()
257+ if not player then return end
258+
259+ local currentExp = player:getExperience()
260+ local currentTime = g_clock.seconds()
261+ if player.lastExps ~= nil then
262+ player.expSpeed = (currentExp - player.lastExps[1][1])/(currentTime - player.lastExps[1][2])
263+ onLevelChange(player, player:getLevel(), player:getLevelPercent())
264+ else
265+ player.lastExps = {}
266+ end
267+ table.insert(player.lastExps, {currentExp, currentTime})
268+ if #player.lastExps > 30 then
269+ table.remove(player.lastExps, 1)
270+ end
271+end
272+
273+function onMiniWindowClose()
274+ skillsButton:setOn(false)
275+end
276+
277+function onSkillButtonClick(button)
278+ local percentBar = button:getChildById('percent')
279+ if percentBar then
280+ percentBar:setVisible(not percentBar:isVisible())
281+ if percentBar:isVisible() then
282+ button:setHeight(21)
283+ else
284+ button:setHeight(21 - 6)
285+ end
286+ end
287+end
288+
289+function onExperienceChange(localPlayer, value)
290+ local postFix = ""
291+ if value > 1e15 then
292+ postFix = "B"
293+ value = math.floor(value / 1e9)
294+ elseif value > 1e12 then
295+ postFix = "M"
296+ value = math.floor(value / 1e6)
297+ elseif value > 1e9 then
298+ postFix = "K"
299+ value = math.floor(value / 1e3)
300+ end
301+ setSkillValue('experience', comma_value(value) .. postFix)
302+end
303+
304+function onLevelChange(localPlayer, value, percent)
305+ setSkillValue('level', value)
306+ local text = tr('You have %s percent to go', 100 - percent) .. '\n' ..
307+ tr('%s of experience left', expToAdvance(localPlayer:getLevel(), localPlayer:getExperience()))
308+
309+ if localPlayer.expSpeed ~= nil then
310+ local expPerHour = math.floor(localPlayer.expSpeed * 3600)
311+ if expPerHour > 0 then
312+ local nextLevelExp = expForLevel(localPlayer:getLevel()+1)
313+ local hoursLeft = (nextLevelExp - localPlayer:getExperience()) / expPerHour
314+ local minutesLeft = math.floor((hoursLeft - math.floor(hoursLeft))*60)
315+ hoursLeft = math.floor(hoursLeft)
316+ text = text .. '\n' .. tr('%d of experience per hour', expPerHour)
317+ text = text .. '\n' .. tr('Next level in %d hours and %d minutes', hoursLeft, minutesLeft)
318+ end
319+ end
320+
321+ setSkillPercent('level', percent, text)
322+end
323+
324+function onHealthChange(localPlayer, health, maxHealth)
325+ setSkillValue('health', health)
326+ checkAlert('health', health, maxHealth, 30)
327+end
328+
329+function onManaChange(localPlayer, mana, maxMana)
330+ setSkillValue('mana', mana)
331+ checkAlert('mana', mana, maxMana, 30)
332+end
333+
334+function onSoulChange(localPlayer, soul)
335+ setSkillValue('soul', soul)
336+end
337+
338+function onFreeCapacityChange(localPlayer, freeCapacity)
339+ setSkillValue('capacity', freeCapacity)
340+ checkAlert('capacity', freeCapacity, localPlayer:getTotalCapacity(), 20)
341+end
342+
343+function onTotalCapacityChange(localPlayer, totalCapacity)
344+ checkAlert('capacity', localPlayer:getFreeCapacity(), totalCapacity, 20)
345+end
346+
347+function onStaminaChange(localPlayer, stamina)
348+ local hours = math.floor(stamina / 60)
349+ local minutes = stamina % 60
350+ if minutes < 10 then
351+ minutes = '0' .. minutes
352+ end
353+ local percent = math.floor(100 * stamina / (42 * 60)) -- max is 42 hours --TODO not in all client versions
354+
355+ setSkillValue('stamina', hours .. ":" .. minutes)
356+
357+ --TODO not all client versions have premium time
358+ if stamina > 2400 and g_game.getClientVersion() >= 1038 and localPlayer:isPremium() then
359+ local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' ..
360+ tr("Now you will gain 50%% more experience")
361+ setSkillPercent('stamina', percent, text, 'green')
362+ elseif stamina > 2400 and g_game.getClientVersion() >= 1038 and not localPlayer:isPremium() then
363+ local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' ..
364+ tr("You will not gain 50%% more experience because you aren't premium player, now you receive only 1x experience points")
365+ setSkillPercent('stamina', percent, text, '#89F013')
366+ elseif stamina > 2400 and g_game.getClientVersion() < 1038 then
367+ local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' ..
368+ tr("If you are premium player, you will gain 50%% more experience")
369+ setSkillPercent('stamina', percent, text, 'green')
370+ elseif stamina <= 2400 and stamina > 840 then
371+ setSkillPercent('stamina', percent, tr("You have %s hours and %s minutes left", hours, minutes), 'orange')
372+ elseif stamina <= 840 and stamina > 0 then
373+ local text = tr("You have %s hours and %s minutes left", hours, minutes) .. "\n" ..
374+ tr("You gain only 50%% experience and you don't may gain loot from monsters")
375+ setSkillPercent('stamina', percent, text, 'red')
376+ elseif stamina == 0 then
377+ local text = tr("You have %s hours and %s minutes left", hours, minutes) .. "\n" ..
378+ tr("You don't may receive experience and loot from monsters")
379+ setSkillPercent('stamina', percent, text, 'black')
380+ end
381+end
382+
383+function onOfflineTrainingChange(localPlayer, offlineTrainingTime)
384+ if not g_game.getFeature(GameOfflineTrainingTime) then
385+ return
386+ end
387+ local hours = math.floor(offlineTrainingTime / 60)
388+ local minutes = offlineTrainingTime % 60
389+ if minutes < 10 then
390+ minutes = '0' .. minutes
391+ end
392+ local percent = 100 * offlineTrainingTime / (12 * 60) -- max is 12 hours
393+
394+ setSkillValue('offlineTraining', hours .. ":" .. minutes)
395+ setSkillPercent('offlineTraining', percent, tr('You have %s percent', percent))
396+end
397+
398+function onRegenerationChange(localPlayer, regenerationTime)
399+ if not g_game.getFeature(GamePlayerRegenerationTime) or regenerationTime < 0 then
400+ return
401+ end
402+ local minutes = math.floor(regenerationTime / 60)
403+ local seconds = regenerationTime % 60
404+ if seconds < 10 then
405+ seconds = '0' .. seconds
406+ end
407+
408+ setSkillValue('regenerationTime', minutes .. ":" .. seconds)
409+ checkAlert('regenerationTime', regenerationTime, false, 300)
410+end
411+
412+function onSpeedChange(localPlayer, speed)
413+ setSkillValue('speed', speed)
414+
415+ onBaseSpeedChange(localPlayer, localPlayer:getBaseSpeed())
416+end
417+
418+function onBaseSpeedChange(localPlayer, baseSpeed)
419+ setSkillBase('speed', localPlayer:getSpeed(), baseSpeed)
420+end
421+
422+function onMagicLevelChange(localPlayer, magiclevel, percent)
423+ setSkillValue('magiclevel', magiclevel)
424+ setSkillPercent('magiclevel', percent, tr('You have %s percent to go', 100 - percent))
425+
426+ onBaseMagicLevelChange(localPlayer, localPlayer:getBaseMagicLevel())
427+end
428+
429+function onBaseMagicLevelChange(localPlayer, baseMagicLevel)
430+ setSkillBase('magiclevel', localPlayer:getMagicLevel(), baseMagicLevel)
431+end
432+
433+function onSkillChange(localPlayer, id, level, percent)
434+ setSkillValue('skillId' .. id, level)
435+ setSkillPercent('skillId' .. id, percent, tr('You have %s percent to go', 100 - percent))
436+
437+ onBaseSkillChange(localPlayer, id, localPlayer:getSkillBaseLevel(id))
438+end
439+
440+function onBaseSkillChange(localPlayer, id, baseLevel)
441+ setSkillBase('skillId'..id, localPlayer:getSkillLevel(id), baseLevel)
442+end
A RD_Client/modules/game_skills/skills.otmod +11-0 View file
@@ -0,0 +1,11 @@
1+Module
2+ name: game_skills
3+ description: Manage skills window
4+ author: baxnie, edubart
5+ website: https://github.com/edubart/otclient
6+ sandboxed: true
7+ scripts: [ skills ]
8+ @onLoad: init()
9+ @onUnload: terminate()
10+ dependencies:
11+ - game_interface
A RD_Client/modules/game_skills/skills.otui +212-0 View file
@@ -0,0 +1,212 @@
1+SkillFirstWidget < UIWidget
2+
3+SkillButton < UIButton
4+ height: 21
5+ margin-bottom: 2
6+ &onClick: onSkillButtonClick
7+
8+SmallSkillButton < SkillButton
9+ height: 14
10+
11+SkillNameLabel < GameLabel
12+ font: verdana-11px-monochrome
13+ anchors.left: parent.left
14+ anchors.top: parent.top
15+ anchors.bottom: parent.bottom
16+
17+SkillValueLabel < GameLabel
18+ id: value
19+ font: verdana-11px-monochrome
20+ text-align: topright
21+ anchors.right: parent.right
22+ anchors.top: parent.top
23+ anchors.bottom: parent.bottom
24+ anchors.left: prev.left
25+
26+SkillPercentPanel < ProgressBar
27+ id: percent
28+ background-color: green
29+ height: 5
30+ margin-top: 15
31+ anchors.left: parent.left
32+ anchors.right: parent.right
33+ anchors.top: parent.top
34+ phantom: false
35+
36+MiniWindow
37+ id: skillWindow
38+ !text: tr('Skills')
39+ height: 150
40+ icon: /images/topbuttons/skills
41+ @onClose: modules.game_skills.onMiniWindowClose()
42+ &save: true
43+ &autoOpen: false
44+
45+ MiniWindowContents
46+ padding-left: 5
47+ padding-right: 5
48+ layout: verticalBox
49+
50+ SkillButton
51+ margin-top: 5
52+ id: experience
53+ height: 15
54+ SkillNameLabel
55+ !text: tr('Experience')
56+ SkillValueLabel
57+
58+ SkillButton
59+ id: level
60+ SkillNameLabel
61+ !text: tr('Level')
62+ SkillValueLabel
63+ SkillPercentPanel
64+ background-color: red
65+
66+ SkillButton
67+ id: health
68+ height: 15
69+ SkillNameLabel
70+ !text: tr('Hit Points')
71+ SkillValueLabel
72+
73+ SkillButton
74+ id: mana
75+ height: 15
76+ SkillNameLabel
77+ !text: tr('Mana')
78+ SkillValueLabel
79+
80+ SkillButton
81+ id: soul
82+ height: 15
83+ SkillNameLabel
84+ !text: tr('Soul Points')
85+ SkillValueLabel
86+
87+ SkillButton
88+ id: capacity
89+ height: 15
90+ SkillNameLabel
91+ !text: tr('Capacity')
92+ SkillValueLabel
93+
94+ SkillButton
95+ id: speed
96+ height: 15
97+ SkillNameLabel
98+ !text: tr('Speed')
99+ SkillValueLabel
100+
101+ SkillButton
102+ id: regenerationTime
103+ SkillNameLabel
104+ !text: tr('Regeneration Time')
105+ SkillValueLabel
106+
107+ SkillButton
108+ id: stamina
109+ SkillNameLabel
110+ !text: tr('Stamina')
111+ SkillValueLabel
112+ SkillPercentPanel
113+
114+ SkillButton
115+ id: offlineTraining
116+ SkillNameLabel
117+ !text: tr('Offline Training')
118+ SkillValueLabel
119+ SkillPercentPanel
120+
121+ SkillButton
122+ id: magiclevel
123+ SkillNameLabel
124+ !text: tr('Magic Level')
125+ SkillValueLabel
126+ SkillPercentPanel
127+ background-color: red
128+
129+ SkillButton
130+ id: skillId0
131+ SkillNameLabel
132+ !text: tr('Fist Fighting')
133+ SkillValueLabel
134+ SkillPercentPanel
135+
136+ SkillButton
137+ id: skillId1
138+ SkillNameLabel
139+ !text: tr('Club Fighting')
140+ SkillValueLabel
141+ SkillPercentPanel
142+
143+ SkillButton
144+ id: skillId2
145+ SkillNameLabel
146+ !text: tr('Sword Fighting')
147+ SkillValueLabel
148+ SkillPercentPanel
149+
150+ SkillButton
151+ id: skillId3
152+ SkillNameLabel
153+ !text: tr('Axe Fighting')
154+ SkillValueLabel
155+ SkillPercentPanel
156+
157+ SkillButton
158+ id: skillId4
159+ SkillNameLabel
160+ !text: tr('Distance Fighting')
161+ SkillValueLabel
162+ SkillPercentPanel
163+
164+ SkillButton
165+ id: skillId5
166+ SkillNameLabel
167+ !text: tr('Shielding')
168+ SkillValueLabel
169+ SkillPercentPanel
170+
171+ SkillButton
172+ id: skillId6
173+ SkillNameLabel
174+ !text: tr('Fishing')
175+ SkillValueLabel
176+ SkillPercentPanel
177+
178+ SmallSkillButton
179+ id: skillId7
180+ SkillNameLabel
181+ !text: tr('Critical Hit Chance')
182+ SkillValueLabel
183+
184+ SmallSkillButton
185+ id: skillId8
186+ SkillNameLabel
187+ !text: tr('Critical Hit Damage')
188+ SkillValueLabel
189+
190+ SmallSkillButton
191+ id: skillId9
192+ SkillNameLabel
193+ !text: tr('Life Leech Chance')
194+ SkillValueLabel
195+
196+ SmallSkillButton
197+ id: skillId10
198+ SkillNameLabel
199+ !text: tr('Life Leech Amount')
200+ SkillValueLabel
201+
202+ SmallSkillButton
203+ id: skillId11
204+ SkillNameLabel
205+ !text: tr('Mana Leech Chance')
206+ SkillValueLabel
207+
208+ SmallSkillButton
209+ id: skillId12
210+ SkillNameLabel
211+ !text: tr('Mana Leech Amount')
212+ SkillValueLabel
A RD_Client/modules/game_spellbar/game_spellbar.otmod +10-0 View file
@@ -0,0 +1,10 @@
1+Module
2+ name: game_spellbar
3+ description: Displays spells in icons
4+ author: ALS
5+ website: none
6+ sandboxed: true
7+ autoload: true
8+ scripts: [ game_spellbar ]
9+ @onLoad: init()
10+ @onUnload: terminate()
A RD_Client/modules/game_spellbar/game_spellbar.otui +95-0 View file
@@ -0,0 +1,95 @@
1+// Slots are positioned by relayout() in game_spellbar.lua (one row of 10,
2+// or two rows of 5 when the game window is too narrow).
3+SlotSpell < UIWidget
4+ anchors.top: parent.top
5+ anchors.left: parent.left
6+ size: 36 36
7+ image-source: img/slot
8+
9+ UIButton
10+ id: spellButton
11+ anchors.top: parent.top
12+ anchors.left: parent.left
13+ margin: 2 0 0 2
14+ size: 32 32
15+ focusable: false
16+
17+ UIProgressRect
18+ id: spellProgress
19+ anchors.top: parent.top
20+ anchors.left: parent.left
21+ margin: 2 0 0 2
22+ size: 32 32
23+ phantom: true
24+ focusable: false
25+ font: verdana-11px-rounded
26+ percent: 100
27+ color: white
28+ background: #000000AA
29+
30+ UIWidget
31+ id: spellHotkey
32+ anchors.top: parent.top
33+ anchors.horizontalCenter: parent.horizontalCenter
34+ margin: 30 0 0 0
35+ image-source: img/htknum
36+
37+SlotHotkey < MainWindow
38+ id: slotHotkey
39+ !text: tr('Change Hotkey')
40+ size: 360 150
41+ @onEscape: self:destroy()
42+
43+ Label
44+ !text: tr('Please, press the key you wish to change onto')
45+ anchors.left: parent.left
46+ anchors.horizontalCenter: parent.horizontalCenter
47+ anchors.top: parent.top
48+ text-auto-resize: true
49+ text-align: left
50+
51+ Label
52+ id: comboPreview
53+ !text: tr('Current hotkey to change: %s', 'none')
54+ anchors.horizontalCenter: parent.horizontalCenter
55+ anchors.top: prev.bottom
56+ margin-top: 10
57+ text-auto-resize: true
58+
59+ HorizontalSeparator
60+ id: separator
61+ anchors.left: parent.left
62+ anchors.right: parent.right
63+ anchors.bottom: next.top
64+ margin-bottom: 10
65+
66+ Button
67+ id: addButton
68+ !text: tr('Change')
69+ width: 64
70+ anchors.right: next.left
71+ anchors.bottom: parent.bottom
72+ margin-right: 10
73+ @onClick: modules.game_spellbar.changeKey() self:getParent():destroy()
74+
75+ Button
76+ id: cancelButton
77+ !text: tr('Close')
78+ width: 64
79+ anchors.right: parent.right
80+ anchors.bottom: parent.bottom
81+ @onClick: self:getParent():destroy()
82+
83+// Size and position are set by relayout()/applyPosition() in game_spellbar.lua:
84+// it stays anchored to the bottom-center of the game screen (plus the offset
85+// the player dragged it to), so it follows window resizes.
86+UIWindow
87+ id: spellBarWindow
88+ size: 450 45
89+ anchors.bottom: parent.bottom
90+ anchors.horizontalCenter: parent.horizontalCenter
91+ margin-bottom: 90
92+ draggable: true
93+ resizable: false
94+ focusable: false
95+ visible: false
A RD_Client/modules/game_spellbar/img/-lockedicon.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/-slot.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/htknum.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/lockedicon.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/slot.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/spellbar.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/- None/-empt.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/- None/empt.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/3.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/4.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/5.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/6.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/godspeed/Denki inparusu.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/godspeed/Denki tenohira.png +0-0 View file
Binary file not shown.
A RD_Client/modules/game_spellbar/img/vocations/HXH/godspeed/Denkosekka no supido.png +0-0 View file
Binary file not shown.
Top