Merge pull request #116 from TheSumm/spellSystem
Spell Cooldowns for 8.70+
This commit is contained in:
commit
adc89f132f
|
@ -10,6 +10,7 @@ mouseGrabberWidget = nil
|
||||||
countWindow = nil
|
countWindow = nil
|
||||||
logoutWindow = nil
|
logoutWindow = nil
|
||||||
exitWindow = nil
|
exitWindow = nil
|
||||||
|
cooldownPanel = nil
|
||||||
|
|
||||||
function init()
|
function init()
|
||||||
g_ui.importStyle('styles/countwindow.otui')
|
g_ui.importStyle('styles/countwindow.otui')
|
||||||
|
@ -22,6 +23,8 @@ function init()
|
||||||
gameRootPanel:hide()
|
gameRootPanel:hide()
|
||||||
gameRootPanel:lower()
|
gameRootPanel:lower()
|
||||||
|
|
||||||
|
cooldownPanel = gameRootPanel:getChildById('spellPanel')
|
||||||
|
|
||||||
mouseGrabberWidget = gameRootPanel:getChildById('mouseGrabber')
|
mouseGrabberWidget = gameRootPanel:getChildById('mouseGrabber')
|
||||||
mouseGrabberWidget.onMouseRelease = onMouseGrabberRelease
|
mouseGrabberWidget.onMouseRelease = onMouseGrabberRelease
|
||||||
|
|
||||||
|
@ -79,6 +82,8 @@ function terminate()
|
||||||
onLoginAdvice = onLoginAdvice })
|
onLoginAdvice = onLoginAdvice })
|
||||||
disconnect(gameLeftPanel, { onVisibilityChange = onLeftPanelVisibilityChange })
|
disconnect(gameLeftPanel, { onVisibilityChange = onLeftPanelVisibilityChange })
|
||||||
|
|
||||||
|
cooldownPanel:destroy()
|
||||||
|
|
||||||
logoutButton:destroy()
|
logoutButton:destroy()
|
||||||
gameRootPanel:destroy()
|
gameRootPanel:destroy()
|
||||||
end
|
end
|
||||||
|
@ -90,6 +95,8 @@ function show()
|
||||||
gameRootPanel:show()
|
gameRootPanel:show()
|
||||||
gameRootPanel:focus()
|
gameRootPanel:focus()
|
||||||
gameMapPanel:followCreature(g_game.getLocalPlayer())
|
gameMapPanel:followCreature(g_game.getLocalPlayer())
|
||||||
|
|
||||||
|
cooldownPanel:setVisible((g_game.getClientVersion() >= 870))
|
||||||
end
|
end
|
||||||
|
|
||||||
function hide()
|
function hide()
|
||||||
|
@ -545,3 +552,30 @@ function onLeftPanelVisibilityChange(leftPanel, visible)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function setGroupCooldown(groupId, duration)
|
||||||
|
if not SpellGroups[groupId] then return end
|
||||||
|
|
||||||
|
local icon = gameRootPanel:getChildById('groupIcon' .. SpellGroups[groupId])
|
||||||
|
if icon then
|
||||||
|
icon:setOn(true)
|
||||||
|
removeEvent(icon.event)
|
||||||
|
icon.event = scheduleEvent(function() icon:setOn(false) end, duration)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function setCooldown(iconFile, iconId, spellformula, duration)
|
||||||
|
local icon = cooldownPanel:getChildById(spellformula)
|
||||||
|
|
||||||
|
if icon then
|
||||||
|
removeEvent(icon.event)
|
||||||
|
icon.event = scheduleEvent(function() icon:destroy() end, duration)
|
||||||
|
else
|
||||||
|
icon = g_ui.createWidget('SpellIcon', cooldownPanel)
|
||||||
|
icon:setId(spellformula)
|
||||||
|
icon:setImageSource('/game_spelllist/icons/' .. iconFile)
|
||||||
|
icon:setTooltip(spellformula)
|
||||||
|
icon:setImageClip(modules.game_spelllist.getIconImageClip(iconId))
|
||||||
|
icon.event = scheduleEvent(function() icon:destroy() end, duration)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
SpellGroupIcon < UIWidget
|
||||||
|
size: 22 22
|
||||||
|
image-size: 22 22
|
||||||
|
image-source: /game_spelllist/icons/cooldownIcons.png
|
||||||
|
focusable: false
|
||||||
|
|
||||||
|
SpellIcon < UIWidget
|
||||||
|
size: 22 22
|
||||||
|
image-size: 22 22
|
||||||
|
margin-right: 3
|
||||||
|
margin-top: 6
|
||||||
|
margin-left: 3
|
||||||
|
focusable: false
|
||||||
|
$first:
|
||||||
|
margin-left: 100
|
||||||
|
|
||||||
GameSidePanel < UIMiniWindowContainer
|
GameSidePanel < UIMiniWindowContainer
|
||||||
image-source: /images/sidepanel.png
|
image-source: /images/sidepanel.png
|
||||||
image-border: 4
|
image-border: 4
|
||||||
|
@ -55,6 +71,67 @@ UIWidget
|
||||||
focusable: false
|
focusable: false
|
||||||
on: true
|
on: true
|
||||||
|
|
||||||
|
Panel
|
||||||
|
id: spellPanel
|
||||||
|
layout:
|
||||||
|
type: horizontalBox
|
||||||
|
height: 32
|
||||||
|
width: 250
|
||||||
|
anchors.left: gameLeftPanel.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
relative-margin: bottom
|
||||||
|
margin-bottom: 187
|
||||||
|
margin-left: 10
|
||||||
|
border-width: 1
|
||||||
|
border-color: #00000077
|
||||||
|
background-color: #ffffff11
|
||||||
|
focusable: false
|
||||||
|
|
||||||
|
SpellGroupIcon
|
||||||
|
id: groupIconAttack
|
||||||
|
parent: spellPanel
|
||||||
|
image-clip: 0 32 32 32
|
||||||
|
anchors.top: spellPanel.top
|
||||||
|
anchors.left: spellPanel.left
|
||||||
|
!tooltip: tr('Attack')
|
||||||
|
margin-top: 3
|
||||||
|
margin-left: 3
|
||||||
|
$on:
|
||||||
|
image-clip: 0 0 32 32
|
||||||
|
|
||||||
|
SpellGroupIcon
|
||||||
|
id: groupIconHealing
|
||||||
|
image-clip: 32 32 32 32
|
||||||
|
anchors.top: spellPanel.top
|
||||||
|
anchors.left: groupIconAttack.right
|
||||||
|
!tooltip: tr('Healing')
|
||||||
|
margin-top: 3
|
||||||
|
margin-left: 3
|
||||||
|
$on:
|
||||||
|
image-clip: 32 0 32 32
|
||||||
|
|
||||||
|
SpellGroupIcon
|
||||||
|
id: groupIconSupport
|
||||||
|
image-clip: 64 32 32 32
|
||||||
|
anchors.top: spellPanel.top
|
||||||
|
anchors.left: groupIconHealing.right
|
||||||
|
!tooltip: tr('Support')
|
||||||
|
margin-top: 3
|
||||||
|
margin-left: 3
|
||||||
|
$on:
|
||||||
|
image-clip: 64 0 32 32
|
||||||
|
|
||||||
|
SpellGroupIcon
|
||||||
|
id: groupIconSpecial
|
||||||
|
image-clip: 96 32 32 32
|
||||||
|
anchors.top: spellPanel.top
|
||||||
|
anchors.left: groupIconSupport.right
|
||||||
|
!tooltip: tr('Special')
|
||||||
|
margin-top: 3
|
||||||
|
margin-left: 3
|
||||||
|
$on:
|
||||||
|
image-clip: 96 0 32 32
|
||||||
|
|
||||||
Splitter
|
Splitter
|
||||||
id: bottomSplitter
|
id: bottomSplitter
|
||||||
anchors.left: gameLeftPanel.right
|
anchors.left: gameLeftPanel.right
|
||||||
|
@ -63,7 +140,7 @@ UIWidget
|
||||||
relative-margin: bottom
|
relative-margin: bottom
|
||||||
margin-bottom: 172
|
margin-bottom: 172
|
||||||
@canUpdateMargin: function(self, newMargin) return math.max(math.min(newMargin, self:getParent():getHeight() - 300), 100) end
|
@canUpdateMargin: function(self, newMargin) return math.max(math.min(newMargin, self:getParent():getHeight() - 300), 100) end
|
||||||
@onGeometryChange: function(self) self:setMarginBottom(math.min(math.max(self:getParent():getHeight() - 300, 100), self:getMarginBottom())) end
|
@onGeometryChange: function(self) self:setMarginBottom(math.min(math.max(self:getParent():getHeight() - 300, 100), self:getMarginBottom())) self:getParent():getChildById('spellPanel'):setMarginBottom(15 + math.min(math.max(self:getParent():getHeight() - 300, 100), self:getMarginBottom())) end
|
||||||
|
|
||||||
UIWidget
|
UIWidget
|
||||||
id: mouseGrabber
|
id: mouseGrabber
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
|
@ -1,19 +1,3 @@
|
||||||
SpelllistSettings = {
|
|
||||||
['Default'] = {
|
|
||||||
iconFile = 'icons.png',
|
|
||||||
iconSize = {width = 32, height = 32},
|
|
||||||
spellListWidth = 210,
|
|
||||||
spellWindowWidth = 550,
|
|
||||||
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},
|
|
||||||
spellOrder = {'Critical Strike', 'Firefly', 'Fire Breath', 'Moonglaives', 'Wind Walk'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local SpelllistProfile = 'Default'
|
local SpelllistProfile = 'Default'
|
||||||
|
|
||||||
spelllistWindow = nil
|
spelllistWindow = nil
|
||||||
|
@ -99,9 +83,24 @@ function setOptions()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function onSpellCooldown(iconId, duration)
|
||||||
|
local spellName = SpelllistSettings[SpelllistProfile].spellIcons[iconId]
|
||||||
|
if not spellName then return end
|
||||||
|
|
||||||
|
local otcIconId = tonumber(SpellInfo[SpelllistProfile][spellName].icon)
|
||||||
|
if not otcIconId and SpellIcons[SpellInfo[SpelllistProfile][spellName].icon] then
|
||||||
|
otcIconId = SpellIcons[SpellInfo[SpelllistProfile][spellName].icon][1]
|
||||||
|
end
|
||||||
|
|
||||||
|
if not otcIconId then return end
|
||||||
|
modules.game_interface.setCooldown(SpelllistSettings[SpelllistProfile].iconFile, otcIconId, spellName, duration)
|
||||||
|
end
|
||||||
|
|
||||||
function init()
|
function init()
|
||||||
connect(g_game, { onGameStart = setOptions,
|
connect(g_game, { onGameStart = setOptions,
|
||||||
onGameEnd = resetWindow })
|
onGameEnd = resetWindow,
|
||||||
|
onSpellGroupCooldown = modules.game_interface.setGroupCooldown,
|
||||||
|
onSpellCooldown = onSpellCooldown })
|
||||||
|
|
||||||
spelllistWindow = g_ui.displayUI('spelllist.otui', modules.game_interface.getRightPanel())
|
spelllistWindow = g_ui.displayUI('spelllist.otui', modules.game_interface.getRightPanel())
|
||||||
spelllistWindow:hide()
|
spelllistWindow:hide()
|
||||||
|
@ -173,7 +172,9 @@ end
|
||||||
|
|
||||||
function terminate()
|
function terminate()
|
||||||
disconnect(g_game, { onGameStart = setOptions,
|
disconnect(g_game, { onGameStart = setOptions,
|
||||||
onGameEnd = resetWindow })
|
onGameEnd = resetWindow,
|
||||||
|
onSpellGroupCooldown = modules.game_interface.setGroupCooldown,
|
||||||
|
onSpellCooldown = onSpellCooldown })
|
||||||
|
|
||||||
disconnect(spellList, { onChildFocusChange = function(self, focusedChild)
|
disconnect(spellList, { onChildFocusChange = function(self, focusedChild)
|
||||||
if focusedChild == nil then return end
|
if focusedChild == nil then return end
|
||||||
|
|
|
@ -1,3 +1,21 @@
|
||||||
|
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}}
|
-- ['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 = {
|
SpellInfo = {
|
||||||
['Default'] = {
|
['Default'] = {
|
||||||
|
@ -294,5 +312,5 @@ SpellGroups = {
|
||||||
[1] = 'Attack',
|
[1] = 'Attack',
|
||||||
[2] = 'Healing',
|
[2] = 'Healing',
|
||||||
[3] = 'Support',
|
[3] = 'Support',
|
||||||
[4] = 'Powerstrikes'
|
[4] = 'Special'
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue