| @@ -0,0 +1,100 @@ | |||
| 1 | + | TextMessageLabel < UILabel | |
| 2 | + | font: sono_medium_border_14 | |
| 3 | + | text-align: center | |
| 4 | + | text-wrap: true | |
| 5 | + | text-auto-resize: true | |
| 6 | + | margin-bottom: 2 | |
| 7 | + | visible: false | |
| 8 | + | ||
| 9 | + | Panel | |
| 10 | + | anchors.fill: gameMapPanel | |
| 11 | + | focusable: false | |
| 12 | + | ||
| 13 | + | Panel | |
| 14 | + | id: centerTextMessagePanel | |
| 15 | + | layout: | |
| 16 | + | type: verticalBox | |
| 17 | + | fit-children: true | |
| 18 | + | width: 360 | |
| 19 | + | anchors.centerIn: parent | |
| 20 | + | ||
| 21 | + | TextMessageLabel | |
| 22 | + | id: highCenterLabel | |
| 23 | + | TextMessageLabel | |
| 24 | + | id: middleCenterLabel | |
| 25 | + | TextMessageLabel | |
| 26 | + | id: lowCenterLabel | |
| 27 | + | ||
| 28 | + | TextMessageLabel | |
| 29 | + | id: privateLabel | |
| 30 | + | anchors.top: parent.top | |
| 31 | + | anchors.bottom: centerTextMessagePanel.top | |
| 32 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 33 | + | text-auto-resize: false | |
| 34 | + | width: 275 | |
| 35 | + | ||
| 36 | + | TextMessageLabel | |
| 37 | + | id: statusLabel | |
| 38 | + | anchors.bottom: parent.bottom | |
| 39 | + | anchors.left: parent.left | |
| 40 | + | anchors.right: parent.right | |
| 41 | + | ||
| 42 | + | TextMessageLabel | |
| 43 | + | id: customMessage | |
| 44 | + | height: 32 | |
| 45 | + | anchors.top: parent.top | |
| 46 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 47 | + | background-color: #00000890 | |
| 48 | + | border: 1 #cc0c0c | |
| 49 | + | margin-top: 80 | |
| 50 | + | text-auto-resize: false | |
| 51 | + | text-wrap: false | |
| 52 | + | ||
| 53 | + | UIWidget | |
| 54 | + | id: privateAlertWindow | |
| 55 | + | size: 256 128 | |
| 56 | + | anchors.top: parent.top | |
| 57 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 58 | + | margin-top: 80 | |
| 59 | + | image-source: /images/game/private_mensaje_alert | |
| 60 | + | image-fixed-ratio: true | |
| 61 | + | visible: false | |
| 62 | + | phantom: true | |
| 63 | + | ||
| 64 | + | UIWidget | |
| 65 | + | id: textContainer | |
| 66 | + | anchors.left: parent.left | |
| 67 | + | anchors.verticalCenter: parent.verticalCenter | |
| 68 | + | margin-left: 24 | |
| 69 | + | margin-top: 12 | |
| 70 | + | width: 224 | |
| 71 | + | layout: | |
| 72 | + | type: verticalBox | |
| 73 | + | fit-children: true | |
| 74 | + | ||
| 75 | + | Label | |
| 76 | + | id: titleLabel | |
| 77 | + | font: sono_medium_border_14 | |
| 78 | + | color: #FF00FF | |
| 79 | + | text-auto-resize: true | |
| 80 | + | ||
| 81 | + | Label | |
| 82 | + | id: messageLabel | |
| 83 | + | font: sono_medium_border_14 | |
| 84 | + | color: #FFFFFF | |
| 85 | + | text-wrap: true | |
| 86 | + | text-auto-resize: true | |
| 87 | + | text-align: left | |
| 88 | + | ||
| 89 | + | UIWidget | |
| 90 | + | id: silentPMAlert | |
| 91 | + | size: 64 64 | |
| 92 | + | anchors.top: parent.top | |
| 93 | + | anchors.right: parent.right | |
| 94 | + | margin-top: 120 | |
| 95 | + | margin-right: 20 | |
| 96 | + | image-source: /images/game/1 | |
| 97 | + | visible: false | |
| 98 | + | phantom: false | |
| 99 | + | @onClick: modules.game_chat.onSilentAlertClick() | |
| 100 | + |
| @@ -0,0 +1,144 @@ | |||
| 1 | + | windows = {} | |
| 2 | + | ||
| 3 | + | function init() | |
| 4 | + | g_ui.importStyle('textwindow') | |
| 5 | + | ||
| 6 | + | connect(g_game, { onEditText = onGameEditText, | |
| 7 | + | onEditList = onGameEditList, | |
| 8 | + | onGameEnd = destroyWindows }) | |
| 9 | + | end | |
| 10 | + | ||
| 11 | + | function terminate() | |
| 12 | + | disconnect(g_game, { onEditText = onGameEditText, | |
| 13 | + | onEditList = onGameEditList, | |
| 14 | + | onGameEnd = destroyWindows }) | |
| 15 | + | ||
| 16 | + | destroyWindows() | |
| 17 | + | end | |
| 18 | + | ||
| 19 | + | function destroyWindows() | |
| 20 | + | for _,window in pairs(windows) do | |
| 21 | + | window:destroy() | |
| 22 | + | end | |
| 23 | + | windows = {} | |
| 24 | + | end | |
| 25 | + | ||
| 26 | + | function onGameEditText(id, itemId, maxLength, text, writer, time) | |
| 27 | + | local textWindow = g_ui.createWidget('TextWindow', rootWidget) | |
| 28 | + | ||
| 29 | + | local writeable = #text < maxLength and maxLength > 0 | |
| 30 | + | local textItem = textWindow:getChildById('textItem') | |
| 31 | + | local description = textWindow:getChildById('description') | |
| 32 | + | local textEdit = textWindow:getChildById('text') | |
| 33 | + | local okButton = textWindow:getChildById('okButton') | |
| 34 | + | local cancelButton = textWindow:getChildById('cancelButton') | |
| 35 | + | ||
| 36 | + | local textScroll = textWindow:getChildById('textScroll') | |
| 37 | + | ||
| 38 | + | if textItem:isHidden() then | |
| 39 | + | textItem:show() | |
| 40 | + | end | |
| 41 | + | ||
| 42 | + | textItem:setItemId(itemId) | |
| 43 | + | textEdit:setMaxLength(maxLength) | |
| 44 | + | textEdit:setText(text) | |
| 45 | + | textEdit:setEditable(writeable) | |
| 46 | + | textEdit:setCursorVisible(writeable) | |
| 47 | + | ||
| 48 | + | local desc = '' | |
| 49 | + | if #writer > 0 then | |
| 50 | + | desc = tr('You read the following, written by \n%s\n', writer) | |
| 51 | + | if #time > 0 then | |
| 52 | + | desc = desc .. tr('on %s.\n', time) | |
| 53 | + | end | |
| 54 | + | elseif #time > 0 then | |
| 55 | + | desc = tr('You read the following, written on \n%s.\n', time) | |
| 56 | + | end | |
| 57 | + | ||
| 58 | + | if #text == 0 and not writeable then | |
| 59 | + | desc = tr("It is empty.") | |
| 60 | + | elseif writeable then | |
| 61 | + | desc = desc .. tr('You can enter new text.') | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | local lines = #{string.find(desc, '\n')} | |
| 65 | + | if lines < 2 then desc = desc .. '\n' end | |
| 66 | + | ||
| 67 | + | description:setText(desc) | |
| 68 | + | ||
| 69 | + | if not writeable then | |
| 70 | + | textWindow:setText(tr('Show Text')) | |
| 71 | + | cancelButton:hide() | |
| 72 | + | cancelButton:setWidth(0) | |
| 73 | + | okButton:setMarginRight(0) | |
| 74 | + | else | |
| 75 | + | textWindow:setText(tr('Edit Text')) | |
| 76 | + | end | |
| 77 | + | ||
| 78 | + | if description:getHeight() < 64 then | |
| 79 | + | description:setHeight(64) | |
| 80 | + | end | |
| 81 | + | ||
| 82 | + | local function destroy() | |
| 83 | + | textWindow:destroy() | |
| 84 | + | table.removevalue(windows, textWindow) | |
| 85 | + | end | |
| 86 | + | ||
| 87 | + | local doneFunc = function() | |
| 88 | + | if writeable then | |
| 89 | + | g_game.editText(id, textEdit:getText()) | |
| 90 | + | end | |
| 91 | + | destroy() | |
| 92 | + | end | |
| 93 | + | ||
| 94 | + | okButton.onClick = doneFunc | |
| 95 | + | cancelButton.onClick = destroy | |
| 96 | + | ||
| 97 | + | if not writeable then | |
| 98 | + | textWindow.onEnter = doneFunc | |
| 99 | + | end | |
| 100 | + | ||
| 101 | + | textWindow.onEscape = destroy | |
| 102 | + | ||
| 103 | + | table.insert(windows, textWindow) | |
| 104 | + | end | |
| 105 | + | ||
| 106 | + | function onGameEditList(id, doorId, text) | |
| 107 | + | local textWindow = g_ui.createWidget('TextWindow', rootWidget) | |
| 108 | + | ||
| 109 | + | local textEdit = textWindow:getChildById('text') | |
| 110 | + | local description = textWindow:getChildById('description') | |
| 111 | + | local okButton = textWindow:getChildById('okButton') | |
| 112 | + | local cancelButton = textWindow:getChildById('cancelButton') | |
| 113 | + | ||
| 114 | + | local textItem = textWindow:getChildById('textItem') | |
| 115 | + | if textItem and not textItem:isHidden() then | |
| 116 | + | textItem:hide() | |
| 117 | + | end | |
| 118 | + | ||
| 119 | + | textEdit:setMaxLength(8192) | |
| 120 | + | textEdit:setText(text) | |
| 121 | + | textEdit:setEditable(true) | |
| 122 | + | description:setText(tr('Enter one name per line.')) | |
| 123 | + | textWindow:setText(tr('Edit List')) | |
| 124 | + | ||
| 125 | + | if description:getHeight() < 64 then | |
| 126 | + | description:setHeight(64) | |
| 127 | + | end | |
| 128 | + | ||
| 129 | + | local function destroy() | |
| 130 | + | textWindow:destroy() | |
| 131 | + | table.removevalue(windows, textWindow) | |
| 132 | + | end | |
| 133 | + | ||
| 134 | + | local doneFunc = function() | |
| 135 | + | g_game.editList(id, doorId, textEdit:getText()) | |
| 136 | + | destroy() | |
| 137 | + | end | |
| 138 | + | ||
| 139 | + | okButton.onClick = doneFunc | |
| 140 | + | cancelButton.onClick = destroy | |
| 141 | + | textWindow.onEscape = destroy | |
| 142 | + | ||
| 143 | + | table.insert(windows, textWindow) | |
| 144 | + | end |
| @@ -0,0 +1,10 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_textwindow | |
| 3 | + | description: Allow to edit text books and lists | |
| 4 | + | author: edubart, BeniS | |
| 5 | + | website: https://github.com/edubart/otclient | |
| 6 | + | sandboxed: true | |
| 7 | + | dependencies: [ game_interface ] | |
| 8 | + | scripts: [ textwindow ] | |
| 9 | + | @onLoad: init() | |
| 10 | + | @onUnload: terminate() |
| @@ -0,0 +1,53 @@ | |||
| 1 | + | TextWindow < MainWindow | |
| 2 | + | id: textWindow | |
| 3 | + | size: 300 280 | |
| 4 | + | ||
| 5 | + | Item | |
| 6 | + | id: textItem | |
| 7 | + | virtual: true | |
| 8 | + | anchors.top: parent.top | |
| 9 | + | anchors.left: parent.left | |
| 10 | + | ||
| 11 | + | Label | |
| 12 | + | id: description | |
| 13 | + | anchors.top: parent.top | |
| 14 | + | anchors.left: textItem.right | |
| 15 | + | anchors.right: parent.right | |
| 16 | + | margin-left: 8 | |
| 17 | + | text-auto-resize: true | |
| 18 | + | text-align: left | |
| 19 | + | text-wrap: true | |
| 20 | + | ||
| 21 | + | MultilineTextEdit | |
| 22 | + | id: text | |
| 23 | + | anchors.top: textScroll.top | |
| 24 | + | anchors.left: parent.left | |
| 25 | + | anchors.right: textScroll.left | |
| 26 | + | anchors.bottom: textScroll.bottom | |
| 27 | + | vertical-scrollbar: textScroll | |
| 28 | + | text-wrap: true | |
| 29 | + | ||
| 30 | + | VerticalScrollBar | |
| 31 | + | id: textScroll | |
| 32 | + | anchors.top: description.bottom | |
| 33 | + | anchors.bottom: okButton.top | |
| 34 | + | anchors.right: parent.right | |
| 35 | + | margin-top: 10 | |
| 36 | + | margin-bottom: 10 | |
| 37 | + | step: 16 | |
| 38 | + | pixels-scroll: true | |
| 39 | + | ||
| 40 | + | Button | |
| 41 | + | id: okButton | |
| 42 | + | !text: tr('Ok') | |
| 43 | + | anchors.bottom: parent.bottom | |
| 44 | + | anchors.right: next.left | |
| 45 | + | margin-right: 10 | |
| 46 | + | width: 60 | |
| 47 | + | ||
| 48 | + | Button | |
| 49 | + | id: cancelButton | |
| 50 | + | !text: tr('Cancel') | |
| 51 | + | anchors.bottom: parent.bottom | |
| 52 | + | anchors.right: parent.right | |
| 53 | + | width: 60 |
| @@ -0,0 +1,85 @@ | |||
| 1 | + | filename = nil | |
| 2 | + | loaded = false | |
| 3 | + | loadedPaths = nil | |
| 4 | + | ||
| 5 | + | function setFileName(name) | |
| 6 | + | filename = name | |
| 7 | + | end | |
| 8 | + | ||
| 9 | + | function isLoaded() | |
| 10 | + | return loaded | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | function load() | |
| 14 | + | local version = g_game.getClientVersion() | |
| 15 | + | local things = g_settings.getNode('things') | |
| 16 | + | g_game.enableFeature(GameBlueNpcNameColor) | |
| 17 | + | g_game.enableFeature(GameSpritesAlphaChannel) | |
| 18 | + | g_game.enableFeature(GameChangeMapAwareRange) | |
| 19 | + | g_game.enableFeature(GameSpritesU32) | |
| 20 | + | ||
| 21 | + | local datPath, sprPath | |
| 22 | + | if things["data"] ~= nil and things["sprites"] ~= nil then | |
| 23 | + | datPath = '/things/' .. things["data"] | |
| 24 | + | if G.hdSprites and things["sprites_hd"] then | |
| 25 | + | sprPath = '/things/' .. things["sprites_hd"] | |
| 26 | + | else | |
| 27 | + | sprPath = '/things/' .. things["sprites"] | |
| 28 | + | end | |
| 29 | + | else | |
| 30 | + | if filename then | |
| 31 | + | datPath = resolvepath('/things/' .. filename) | |
| 32 | + | sprPath = resolvepath('/things/' .. filename) | |
| 33 | + | if G.hdSprites then | |
| 34 | + | local hdsprPath = resolvepath('/things/' .. filename .. '_hd') | |
| 35 | + | if g_resources.fileExists(hdsprPath) then | |
| 36 | + | sprPath = hdsprPath | |
| 37 | + | end | |
| 38 | + | end | |
| 39 | + | else | |
| 40 | + | datPath = resolvepath('/things/' .. version .. '/Tibia') | |
| 41 | + | sprPath = resolvepath('/things/' .. version .. '/Tibia') | |
| 42 | + | if G.hdSprites then | |
| 43 | + | local hdsprPath = resolvepath('/things/' .. version .. '/Tibia_hd') | |
| 44 | + | if g_resources.fileExists(hdsprPath) then | |
| 45 | + | sprPath = hdsprPath | |
| 46 | + | end | |
| 47 | + | end | |
| 48 | + | end | |
| 49 | + | if g_sounds then | |
| 50 | + | g_sounds.loadClientFiles(resolvepath(string.format('/sounds/%d/', version))) | |
| 51 | + | end | |
| 52 | + | return | |
| 53 | + | end | |
| 54 | + | ||
| 55 | + | local paths = datPath .. '|' .. sprPath | |
| 56 | + | if loaded and loadedPaths == paths then | |
| 57 | + | return | |
| 58 | + | end | |
| 59 | + | ||
| 60 | + | local errorMessage = '' | |
| 61 | + | if not g_things.loadDat(datPath) then | |
| 62 | + | if not g_game.getFeature(GameSpritesU32) then | |
| 63 | + | g_game.enableFeature(GameSpritesU32) | |
| 64 | + | if not g_things.loadDat(datPath) then | |
| 65 | + | errorMessage = errorMessage .. tr("Unable to load dat file, please place a valid dat in '%s'", datPath) .. '\n' | |
| 66 | + | end | |
| 67 | + | else | |
| 68 | + | errorMessage = errorMessage .. tr("Unable to load dat file, please place a valid dat in '%s'", datPath) .. '\n' | |
| 69 | + | end | |
| 70 | + | end | |
| 71 | + | if not g_sprites.loadSpr(sprPath, G.hdSprites or false) then | |
| 72 | + | errorMessage = errorMessage .. tr("Unable to load spr file, please place a valid spr in '%s'", sprPath) | |
| 73 | + | end | |
| 74 | + | ||
| 75 | + | loaded = (errorMessage:len() == 0) | |
| 76 | + | loadedPaths = loaded and paths or nil | |
| 77 | + | ||
| 78 | + | if errorMessage:len() > 0 then | |
| 79 | + | local messageBox = displayErrorBox(tr('Error'), errorMessage) | |
| 80 | + | addEvent(function() messageBox:raise() messageBox:focus() end) | |
| 81 | + | ||
| 82 | + | g_game.setClientVersion(0) | |
| 83 | + | g_game.setProtocolVersion(0) | |
| 84 | + | end | |
| 85 | + | end |
| @@ -0,0 +1,6 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_things | |
| 3 | + | description: Contains things spr and dat | |
| 4 | + | reloadable: false | |
| 5 | + | sandboxed: true | |
| 6 | + | scripts: [things] |
| @@ -0,0 +1,148 @@ | |||
| 1 | + | unjustifiedPointsWindow = nil | |
| 2 | + | unjustifiedPointsButton = nil | |
| 3 | + | contentsPanel = nil | |
| 4 | + | ||
| 5 | + | openPvpSituationsLabel = nil | |
| 6 | + | currentSkullWidget = nil | |
| 7 | + | skullTimeLabel = nil | |
| 8 | + | ||
| 9 | + | dayProgressBar = nil | |
| 10 | + | weekProgressBar = nil | |
| 11 | + | monthProgressBar = nil | |
| 12 | + | ||
| 13 | + | daySkullWidget = nil | |
| 14 | + | weekSkullWidget = nil | |
| 15 | + | monthSkullWidget = nil | |
| 16 | + | ||
| 17 | + | function init() | |
| 18 | + | connect(g_game, { onGameStart = online, | |
| 19 | + | onUnjustifiedPointsChange = onUnjustifiedPointsChange, | |
| 20 | + | onOpenPvpSituationsChange = onOpenPvpSituationsChange }) | |
| 21 | + | connect(LocalPlayer, { onSkullChange = onSkullChange } ) | |
| 22 | + | ||
| 23 | + | unjustifiedPointsButton = modules.client_topmenu.addRightGameToggleButton('unjustifiedPointsButton', | |
| 24 | + | tr('Unjustified Points'), '/images/topbuttons/unjustifiedpoints', toggle) | |
| 25 | + | unjustifiedPointsButton:setOn(true) | |
| 26 | + | unjustifiedPointsButton:hide() | |
| 27 | + | ||
| 28 | + | unjustifiedPointsWindow = g_ui.loadUI('unjustifiedpoints', modules.game_interface.getRightPanel()) | |
| 29 | + | unjustifiedPointsWindow:disableResize() | |
| 30 | + | unjustifiedPointsWindow:setup() | |
| 31 | + | ||
| 32 | + | contentsPanel = unjustifiedPointsWindow:getChildById('contentsPanel') | |
| 33 | + | ||
| 34 | + | openPvpSituationsLabel = contentsPanel:getChildById('openPvpSituationsLabel') | |
| 35 | + | currentSkullWidget = contentsPanel:getChildById('currentSkullWidget') | |
| 36 | + | skullTimeLabel = contentsPanel:getChildById('skullTimeLabel') | |
| 37 | + | ||
| 38 | + | dayProgressBar = contentsPanel:getChildById('dayProgressBar') | |
| 39 | + | weekProgressBar = contentsPanel:getChildById('weekProgressBar') | |
| 40 | + | monthProgressBar = contentsPanel:getChildById('monthProgressBar') | |
| 41 | + | daySkullWidget = contentsPanel:getChildById('daySkullWidget') | |
| 42 | + | weekSkullWidget = contentsPanel:getChildById('weekSkullWidget') | |
| 43 | + | monthSkullWidget = contentsPanel:getChildById('monthSkullWidget') | |
| 44 | + | ||
| 45 | + | if g_game.isOnline() then | |
| 46 | + | online() | |
| 47 | + | end | |
| 48 | + | end | |
| 49 | + | ||
| 50 | + | function terminate() | |
| 51 | + | disconnect(g_game, { onGameStart = online, | |
| 52 | + | onUnjustifiedPointsChange = onUnjustifiedPointsChange, | |
| 53 | + | onOpenPvpSituationsChange = onOpenPvpSituationsChange }) | |
| 54 | + | disconnect(LocalPlayer, { onSkullChange = onSkullChange } ) | |
| 55 | + | ||
| 56 | + | unjustifiedPointsWindow:destroy() | |
| 57 | + | unjustifiedPointsButton:destroy() | |
| 58 | + | end | |
| 59 | + | ||
| 60 | + | function onMiniWindowClose() | |
| 61 | + | unjustifiedPointsButton:setOn(false) | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | function toggle() | |
| 65 | + | if unjustifiedPointsButton:isOn() then | |
| 66 | + | unjustifiedPointsWindow:close() | |
| 67 | + | unjustifiedPointsButton:setOn(false) | |
| 68 | + | else | |
| 69 | + | unjustifiedPointsWindow:open() | |
| 70 | + | unjustifiedPointsButton:setOn(true) | |
| 71 | + | end | |
| 72 | + | end | |
| 73 | + | ||
| 74 | + | function online() | |
| 75 | + | if g_game.getFeature(GameUnjustifiedPoints) then | |
| 76 | + | unjustifiedPointsButton:show() | |
| 77 | + | else | |
| 78 | + | unjustifiedPointsButton:hide() | |
| 79 | + | unjustifiedPointsWindow:close() | |
| 80 | + | end | |
| 81 | + | ||
| 82 | + | refresh() | |
| 83 | + | end | |
| 84 | + | ||
| 85 | + | function refresh() | |
| 86 | + | local localPlayer = g_game.getLocalPlayer() | |
| 87 | + | ||
| 88 | + | local unjustifiedPoints = g_game.getUnjustifiedPoints() | |
| 89 | + | onUnjustifiedPointsChange(unjustifiedPoints) | |
| 90 | + | ||
| 91 | + | onSkullChange(localPlayer, localPlayer:getSkull()) | |
| 92 | + | onOpenPvpSituationsChange(g_game.getOpenPvpSituations()) | |
| 93 | + | end | |
| 94 | + | ||
| 95 | + | function onSkullChange(localPlayer, skull) | |
| 96 | + | if not localPlayer:isLocalPlayer() then return end | |
| 97 | + | ||
| 98 | + | if skull == SkullRed or skull == SkullBlack then | |
| 99 | + | currentSkullWidget:setIcon(getSkullImagePath(skull)) | |
| 100 | + | currentSkullWidget:setTooltip('Remaining skull time') | |
| 101 | + | else | |
| 102 | + | currentSkullWidget:setIcon('') | |
| 103 | + | currentSkullWidget:setTooltip('You have no skull') | |
| 104 | + | end | |
| 105 | + | ||
| 106 | + | daySkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) | |
| 107 | + | weekSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) | |
| 108 | + | monthSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) | |
| 109 | + | end | |
| 110 | + | ||
| 111 | + | function onOpenPvpSituationsChange(amount) | |
| 112 | + | openPvpSituationsLabel:setText(amount) | |
| 113 | + | end | |
| 114 | + | ||
| 115 | + | local function getColorByKills(kills) | |
| 116 | + | if kills < 2 then | |
| 117 | + | return 'red' | |
| 118 | + | elseif kills < 3 then | |
| 119 | + | return 'yellow' | |
| 120 | + | end | |
| 121 | + | ||
| 122 | + | return 'green' | |
| 123 | + | end | |
| 124 | + | ||
| 125 | + | function onUnjustifiedPointsChange(unjustifiedPoints) | |
| 126 | + | if unjustifiedPoints.skullTime == 0 then | |
| 127 | + | skullTimeLabel:setText('No skull') | |
| 128 | + | skullTimeLabel:setTooltip('You have no skull') | |
| 129 | + | else | |
| 130 | + | skullTimeLabel:setText(unjustifiedPoints.skullTime .. ' days') | |
| 131 | + | skullTimeLabel:setTooltip('Remaining skull time') | |
| 132 | + | end | |
| 133 | + | ||
| 134 | + | dayProgressBar:setValue(unjustifiedPoints.killsDay, 0, 100) | |
| 135 | + | dayProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsDayRemaining)) | |
| 136 | + | dayProgressBar:setTooltip(string.format('Unjustified points gained during the last 24 hours.\n%i kill%s left.', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's'))) | |
| 137 | + | dayProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's'))) | |
| 138 | + | ||
| 139 | + | weekProgressBar:setValue(unjustifiedPoints.killsWeek, 0, 100) | |
| 140 | + | weekProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsWeekRemaining)) | |
| 141 | + | weekProgressBar:setTooltip(string.format('Unjustified points gained during the last 7 days.\n%i kill%s left.', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's'))) | |
| 142 | + | weekProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's'))) | |
| 143 | + | ||
| 144 | + | monthProgressBar:setValue(unjustifiedPoints.killsMonth, 0, 100) | |
| 145 | + | monthProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsMonthRemaining)) | |
| 146 | + | monthProgressBar:setTooltip(string.format('Unjustified points gained during the last 30 days.\n%i kill%s left.', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's'))) | |
| 147 | + | monthProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's'))) | |
| 148 | + | end |
| @@ -0,0 +1,8 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_unjustifiedpoints | |
| 3 | + | description: View unjustified points | |
| 4 | + | author: Summ | |
| 5 | + | sandboxed: true | |
| 6 | + | scripts: [ unjustifiedpoints ] | |
| 7 | + | @onLoad: init() | |
| 8 | + | @onUnload: terminate() |
| @@ -0,0 +1,81 @@ | |||
| 1 | + | SkullProgressBar < ProgressBar | |
| 2 | + | height: 13 | |
| 3 | + | margin: 4 18 0 10 | |
| 4 | + | anchors.top: prev.bottom | |
| 5 | + | anchors.left: parent.left | |
| 6 | + | anchors.right: parent.right | |
| 7 | + | ||
| 8 | + | SkullWidget < UIWidget | |
| 9 | + | size: 13 13 | |
| 10 | + | margin-right: 2 | |
| 11 | + | anchors.right: parent.right | |
| 12 | + | image-source: /images/game/skull_socket | |
| 13 | + | ||
| 14 | + | MiniWindow | |
| 15 | + | id: unjustifiedPointsWindow | |
| 16 | + | !text: tr('Unjustified Points') | |
| 17 | + | height: 114 | |
| 18 | + | icon: /images/topbuttons/unjustifiedpoints | |
| 19 | + | @onClose: modules.game_unjustifiedpoints.onMiniWindowClose() | |
| 20 | + | &save: true | |
| 21 | + | &autoOpen: false | |
| 22 | + | ||
| 23 | + | MiniWindowContents | |
| 24 | + | Label | |
| 25 | + | anchors.top: parent.top | |
| 26 | + | anchors.left: parent.left | |
| 27 | + | !text: tr('Open PvP') | |
| 28 | + | !tooltip: tr('Open PvP Situations') | |
| 29 | + | phantom: false | |
| 30 | + | margin-top: 2 | |
| 31 | + | margin-left: 10 | |
| 32 | + | ||
| 33 | + | Label | |
| 34 | + | id: openPvpSituationsLabel | |
| 35 | + | anchors.top: prev.bottom | |
| 36 | + | anchors.left: parent.left | |
| 37 | + | font: verdana-11px-rounded | |
| 38 | + | margin-left: 12 | |
| 39 | + | phantom: false | |
| 40 | + | ||
| 41 | + | Label | |
| 42 | + | anchors.top: parent.top | |
| 43 | + | anchors.right: parent.right | |
| 44 | + | !text: tr('Skull Time') | |
| 45 | + | margin-top: 2 | |
| 46 | + | margin-right: 10 | |
| 47 | + | ||
| 48 | + | SkullWidget | |
| 49 | + | id: currentSkullWidget | |
| 50 | + | anchors.top: prev.bottom | |
| 51 | + | margin-right: 10 | |
| 52 | + | ||
| 53 | + | Label | |
| 54 | + | id: skullTimeLabel | |
| 55 | + | anchors.top: prev.top | |
| 56 | + | anchors.right: prev.left | |
| 57 | + | font: verdana-11px-rounded | |
| 58 | + | margin-right: 6 | |
| 59 | + | phantom: false | |
| 60 | + | ||
| 61 | + | SkullProgressBar | |
| 62 | + | id: dayProgressBar | |
| 63 | + | margin-top: 10 | |
| 64 | + | ||
| 65 | + | SkullWidget | |
| 66 | + | id: daySkullWidget | |
| 67 | + | anchors.top: prev.top | |
| 68 | + | ||
| 69 | + | SkullProgressBar | |
| 70 | + | id: weekProgressBar | |
| 71 | + | ||
| 72 | + | SkullWidget | |
| 73 | + | id: weekSkullWidget | |
| 74 | + | anchors.top: prev.top | |
| 75 | + | ||
| 76 | + | SkullProgressBar | |
| 77 | + | id: monthProgressBar | |
| 78 | + | ||
| 79 | + | SkullWidget | |
| 80 | + | id: monthSkullWidget | |
| 81 | + | anchors.top: prev.top |
| @@ -0,0 +1,39 @@ | |||
| 1 | + | MainWindow | |
| 2 | + | size: 256 128 | |
| 3 | + | !text: tr('Add to VIP list') | |
| 4 | + | @onEnter: modules.game_viplist.addVip() | |
| 5 | + | @onEscape: modules.game_viplist.destroyAddWindow() | |
| 6 | + | ||
| 7 | + | Label | |
| 8 | + | !text: tr('Please enter a character name:') | |
| 9 | + | anchors.top: parent.top | |
| 10 | + | anchors.left: parent.left | |
| 11 | + | anchors.right: parent.right | |
| 12 | + | ||
| 13 | + | TextEdit | |
| 14 | + | id: name | |
| 15 | + | anchors.top: prev.bottom | |
| 16 | + | anchors.left: parent.left | |
| 17 | + | anchors.right: parent.right | |
| 18 | + | margin-top: 4 | |
| 19 | + | ||
| 20 | + | HorizontalSeparator | |
| 21 | + | anchors.left: parent.left | |
| 22 | + | anchors.right: parent.right | |
| 23 | + | anchors.bottom: next.top | |
| 24 | + | margin-bottom: 10 | |
| 25 | + | ||
| 26 | + | Button | |
| 27 | + | !text: tr('Ok') | |
| 28 | + | width: 64 | |
| 29 | + | anchors.right: next.left | |
| 30 | + | anchors.bottom: parent.bottom | |
| 31 | + | margin-right: 10 | |
| 32 | + | @onClick: modules.game_viplist.addVip() | |
| 33 | + | ||
| 34 | + | Button | |
| 35 | + | !text: tr('Cancel') | |
| 36 | + | width: 64 | |
| 37 | + | anchors.right: parent.right | |
| 38 | + | anchors.bottom: parent.bottom | |
| 39 | + | @onClick: modules.game_viplist.destroyAddWindow() |
| @@ -0,0 +1,138 @@ | |||
| 1 | + | IconButton < CheckBox | |
| 2 | + | size: 20 20 | |
| 3 | + | image-source: /images/game/viplist/vipcheckbox | |
| 4 | + | image-size: 20 20 | |
| 5 | + | image-border: 3 | |
| 6 | + | margin: 2 | |
| 7 | + | icon-source: /images/game/viplist/icons | |
| 8 | + | icon-size: 12 12 | |
| 9 | + | icon-rect: 0 0 12 12 | |
| 10 | + | icon-clip: 0 0 12 12 | |
| 11 | + | icon-offset: 4 6 | |
| 12 | + | ||
| 13 | + | $first: | |
| 14 | + | margin-left: 0 | |
| 15 | + | ||
| 16 | + | $!checked: | |
| 17 | + | image-clip: 26 0 26 26 | |
| 18 | + | ||
| 19 | + | $hover !checked: | |
| 20 | + | image-clip: 78 0 26 26 | |
| 21 | + | ||
| 22 | + | $checked: | |
| 23 | + | image-clip: 0 0 26 26 | |
| 24 | + | ||
| 25 | + | $hover checked: | |
| 26 | + | image-clip: 52 0 26 26 | |
| 27 | + | ||
| 28 | + | MainWindow | |
| 29 | + | size: 272 170 | |
| 30 | + | !text: tr('Edit VIP list entry') | |
| 31 | + | ||
| 32 | + | Label | |
| 33 | + | id: nameLabel | |
| 34 | + | text: Name | |
| 35 | + | anchors.top: parent.top | |
| 36 | + | anchors.left: parent.left | |
| 37 | + | color: green | |
| 38 | + | width: 180 | |
| 39 | + | ||
| 40 | + | Label | |
| 41 | + | !text: tr('Description') .. ':' | |
| 42 | + | anchors.top: prev.bottom | |
| 43 | + | anchors.left: parent.left | |
| 44 | + | text-offset: 0 3 | |
| 45 | + | height: 20 | |
| 46 | + | margin-top: 5 | |
| 47 | + | ||
| 48 | + | TextEdit | |
| 49 | + | id: descriptionText | |
| 50 | + | anchors.top: prev.top | |
| 51 | + | anchors.left: prev.right | |
| 52 | + | anchors.right: parent.right | |
| 53 | + | margin: 0 5 | |
| 54 | + | ||
| 55 | + | Label | |
| 56 | + | !text: tr('Notify-Login') .. ':' | |
| 57 | + | anchors.top: prev.bottom | |
| 58 | + | anchors.left: parent.left | |
| 59 | + | text-offset: 0 3 | |
| 60 | + | height: 20 | |
| 61 | + | margin-top: 5 | |
| 62 | + | ||
| 63 | + | CheckBox | |
| 64 | + | id: checkBoxNotify | |
| 65 | + | anchors.top: prev.top | |
| 66 | + | anchors.left: prev.right | |
| 67 | + | margin: 2 6 | |
| 68 | + | ||
| 69 | + | UIWidget | |
| 70 | + | layout: horizontalBox | |
| 71 | + | anchors.top: prev.bottom | |
| 72 | + | anchors.left: parent.left | |
| 73 | + | anchors.right: parent.right | |
| 74 | + | height: 24 | |
| 75 | + | ||
| 76 | + | IconButton | |
| 77 | + | id: icon0 | |
| 78 | + | ||
| 79 | + | IconButton | |
| 80 | + | id: icon1 | |
| 81 | + | icon-clip: 12 0 12 12 | |
| 82 | + | ||
| 83 | + | IconButton | |
| 84 | + | id: icon2 | |
| 85 | + | icon-clip: 24 0 12 12 | |
| 86 | + | ||
| 87 | + | IconButton | |
| 88 | + | id: icon3 | |
| 89 | + | icon-clip: 36 0 12 12 | |
| 90 | + | ||
| 91 | + | IconButton | |
| 92 | + | id: icon4 | |
| 93 | + | icon-clip: 48 0 12 12 | |
| 94 | + | ||
| 95 | + | IconButton | |
| 96 | + | id: icon5 | |
| 97 | + | icon-clip: 60 0 12 12 | |
| 98 | + | ||
| 99 | + | IconButton | |
| 100 | + | id: icon6 | |
| 101 | + | icon-clip: 72 0 12 12 | |
| 102 | + | ||
| 103 | + | IconButton | |
| 104 | + | id: icon7 | |
| 105 | + | icon-clip: 84 0 12 12 | |
| 106 | + | ||
| 107 | + | IconButton | |
| 108 | + | id: icon8 | |
| 109 | + | icon-clip: 96 0 12 12 | |
| 110 | + | ||
| 111 | + | IconButton | |
| 112 | + | id: icon9 | |
| 113 | + | icon-clip: 108 0 12 12 | |
| 114 | + | ||
| 115 | + | IconButton | |
| 116 | + | id: icon10 | |
| 117 | + | icon-clip: 120 0 12 12 | |
| 118 | + | ||
| 119 | + | HorizontalSeparator | |
| 120 | + | anchors.left: parent.left | |
| 121 | + | anchors.right: parent.right | |
| 122 | + | anchors.bottom: next.top | |
| 123 | + | margin-bottom: 10 | |
| 124 | + | ||
| 125 | + | Button | |
| 126 | + | id: buttonOK | |
| 127 | + | !text: tr('Ok') | |
| 128 | + | width: 64 | |
| 129 | + | anchors.right: next.left | |
| 130 | + | anchors.bottom: parent.bottom | |
| 131 | + | margin-right: 10 | |
| 132 | + | ||
| 133 | + | Button | |
| 134 | + | id: buttonCancel | |
| 135 | + | !text: tr('Cancel') | |
| 136 | + | width: 64 | |
| 137 | + | anchors.right: parent.right | |
| 138 | + | anchors.bottom: parent.bottom |
| @@ -0,0 +1,447 @@ | |||
| 1 | + | vipWindow = nil | |
| 2 | + | vipButton = nil | |
| 3 | + | addVipWindow = nil | |
| 4 | + | editVipWindow = nil | |
| 5 | + | vipInfo = {} | |
| 6 | + | ||
| 7 | + | function init() | |
| 8 | + | connect(g_game, { onGameStart = refresh, | |
| 9 | + | onGameEnd = clear, | |
| 10 | + | onAddVip = onAddVip, | |
| 11 | + | onVipStateChange = onVipStateChange }) | |
| 12 | + | ||
| 13 | + | ||
| 14 | + | g_keyboard.bindKeyDown('Ctrl+P', toggle) | |
| 15 | + | ||
| 16 | + | vipButton = modules.client_topmenu.addRightGameToggleButton('vipListButton', tr('VIP List') .. ' (Ctrl+P)', '/images/topbuttons/viplist', toggle, false, 3) | |
| 17 | + | vipButton:setOn(true) | |
| 18 | + | vipWindow = g_ui.loadUI('viplist', modules.game_interface.getRightPanel()) | |
| 19 | + | ||
| 20 | + | if not g_game.getFeature(GameAdditionalVipInfo) then | |
| 21 | + | loadVipInfo() | |
| 22 | + | end | |
| 23 | + | refresh() | |
| 24 | + | vipWindow:setup() | |
| 25 | + | end | |
| 26 | + | ||
| 27 | + | function terminate() | |
| 28 | + | g_keyboard.unbindKeyDown('Ctrl+P') | |
| 29 | + | disconnect(g_game, { onGameStart = refresh, | |
| 30 | + | onGameEnd = clear, | |
| 31 | + | onAddVip = onAddVip, | |
| 32 | + | onVipStateChange = onVipStateChange }) | |
| 33 | + | ||
| 34 | + | if not g_game.getFeature(GameAdditionalVipInfo) then | |
| 35 | + | saveVipInfo() | |
| 36 | + | end | |
| 37 | + | ||
| 38 | + | if addVipWindow then | |
| 39 | + | addVipWindow:destroy() | |
| 40 | + | end | |
| 41 | + | ||
| 42 | + | if editVipWindow then | |
| 43 | + | editVipWindow:destroy() | |
| 44 | + | end | |
| 45 | + | ||
| 46 | + | vipWindow:destroy() | |
| 47 | + | vipButton:destroy() | |
| 48 | + | end | |
| 49 | + | ||
| 50 | + | function loadVipInfo() | |
| 51 | + | local settings = g_settings.getNode('VipList') | |
| 52 | + | if not settings then | |
| 53 | + | vipInfo = {} | |
| 54 | + | return | |
| 55 | + | end | |
| 56 | + | vipInfo = settings['VipInfo'] or {} | |
| 57 | + | end | |
| 58 | + | ||
| 59 | + | function saveVipInfo() | |
| 60 | + | settings = {} | |
| 61 | + | settings['VipInfo'] = vipInfo | |
| 62 | + | g_settings.mergeNode('VipList', settings) | |
| 63 | + | end | |
| 64 | + | ||
| 65 | + | ||
| 66 | + | function refresh() | |
| 67 | + | clear() | |
| 68 | + | for id,vip in pairs(g_game.getVips()) do | |
| 69 | + | onAddVip(id, unpack(vip)) | |
| 70 | + | end | |
| 71 | + | ||
| 72 | + | vipWindow:setContentMinimumHeight(38) | |
| 73 | + | end | |
| 74 | + | ||
| 75 | + | function clear() | |
| 76 | + | local vipList = vipWindow:getChildById('contentsPanel') | |
| 77 | + | vipList:destroyChildren() | |
| 78 | + | end | |
| 79 | + | ||
| 80 | + | function toggle() | |
| 81 | + | if vipButton:isOn() then | |
| 82 | + | vipWindow:close() | |
| 83 | + | vipButton:setOn(false) | |
| 84 | + | else | |
| 85 | + | vipWindow:open() | |
| 86 | + | vipButton:setOn(true) | |
| 87 | + | end | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | function onMiniWindowClose() | |
| 91 | + | vipButton:setOn(false) | |
| 92 | + | end | |
| 93 | + | ||
| 94 | + | function createAddWindow() | |
| 95 | + | if not addVipWindow then | |
| 96 | + | addVipWindow = g_ui.displayUI('addvip') | |
| 97 | + | end | |
| 98 | + | end | |
| 99 | + | ||
| 100 | + | function createEditWindow(widget) | |
| 101 | + | if editVipWindow then | |
| 102 | + | return | |
| 103 | + | end | |
| 104 | + | ||
| 105 | + | editVipWindow = g_ui.displayUI('editvip') | |
| 106 | + | ||
| 107 | + | local name = widget:getText() | |
| 108 | + | local id = widget:getId():sub(4) | |
| 109 | + | ||
| 110 | + | local okButton = editVipWindow:getChildById('buttonOK') | |
| 111 | + | local cancelButton = editVipWindow:getChildById('buttonCancel') | |
| 112 | + | ||
| 113 | + | local nameLabel = editVipWindow:getChildById('nameLabel') | |
| 114 | + | nameLabel:setText(name) | |
| 115 | + | ||
| 116 | + | local descriptionText = editVipWindow:getChildById('descriptionText') | |
| 117 | + | descriptionText:appendText(widget:getTooltip()) | |
| 118 | + | ||
| 119 | + | local notifyCheckBox = editVipWindow:getChildById('checkBoxNotify') | |
| 120 | + | notifyCheckBox:setChecked(widget.notifyLogin) | |
| 121 | + | ||
| 122 | + | local iconRadioGroup = UIRadioGroup.create() | |
| 123 | + | for i = VipIconFirst, VipIconLast do | |
| 124 | + | iconRadioGroup:addWidget(editVipWindow:recursiveGetChildById('icon' .. i)) | |
| 125 | + | end | |
| 126 | + | iconRadioGroup:selectWidget(editVipWindow:recursiveGetChildById('icon' .. widget.iconId)) | |
| 127 | + | ||
| 128 | + | local cancelFunction = function() | |
| 129 | + | editVipWindow:destroy() | |
| 130 | + | iconRadioGroup:destroy() | |
| 131 | + | editVipWindow = nil | |
| 132 | + | end | |
| 133 | + | ||
| 134 | + | local saveFunction = function() | |
| 135 | + | local vipList = vipWindow:getChildById('contentsPanel') | |
| 136 | + | if not widget or not vipList:hasChild(widget) then | |
| 137 | + | cancelFunction() | |
| 138 | + | return | |
| 139 | + | end | |
| 140 | + | ||
| 141 | + | local name = widget:getText() | |
| 142 | + | local state = widget.vipState | |
| 143 | + | local description = descriptionText:getText() | |
| 144 | + | local iconId = tonumber(iconRadioGroup:getSelectedWidget():getId():sub(5)) | |
| 145 | + | local notify = notifyCheckBox:isChecked() | |
| 146 | + | ||
| 147 | + | if g_game.getFeature(GameAdditionalVipInfo) then | |
| 148 | + | g_game.editVip(id, description, iconId, notify) | |
| 149 | + | else | |
| 150 | + | if notify ~= false or #description > 0 or iconId > 0 then | |
| 151 | + | vipInfo[id] = {description = description, iconId = iconId, notifyLogin = notify} | |
| 152 | + | else | |
| 153 | + | vipInfo[id] = nil | |
| 154 | + | end | |
| 155 | + | end | |
| 156 | + | ||
| 157 | + | widget:destroy() | |
| 158 | + | onAddVip(id, name, state, description, iconId, notify) | |
| 159 | + | ||
| 160 | + | editVipWindow:destroy() | |
| 161 | + | iconRadioGroup:destroy() | |
| 162 | + | editVipWindow = nil | |
| 163 | + | end | |
| 164 | + | ||
| 165 | + | cancelButton.onClick = cancelFunction | |
| 166 | + | okButton.onClick = saveFunction | |
| 167 | + | ||
| 168 | + | editVipWindow.onEscape = cancelFunction | |
| 169 | + | editVipWindow.onEnter = saveFunction | |
| 170 | + | end | |
| 171 | + | ||
| 172 | + | function destroyAddWindow() | |
| 173 | + | addVipWindow:destroy() | |
| 174 | + | addVipWindow = nil | |
| 175 | + | end | |
| 176 | + | ||
| 177 | + | function addVip() | |
| 178 | + | g_game.addVip(addVipWindow:getChildById('name'):getText()) | |
| 179 | + | destroyAddWindow() | |
| 180 | + | end | |
| 181 | + | ||
| 182 | + | function removeVip(widgetOrName) | |
| 183 | + | if not widgetOrName then | |
| 184 | + | return | |
| 185 | + | end | |
| 186 | + | ||
| 187 | + | local widget | |
| 188 | + | local vipList = vipWindow:getChildById('contentsPanel') | |
| 189 | + | if type(widgetOrName) == 'string' then | |
| 190 | + | local entries = vipList:getChildren() | |
| 191 | + | for i = 1, #entries do | |
| 192 | + | if entries[i]:getText():lower() == widgetOrName:lower() then | |
| 193 | + | widget = entries[i] | |
| 194 | + | break | |
| 195 | + | end | |
| 196 | + | end | |
| 197 | + | if not widget then | |
| 198 | + | return | |
| 199 | + | end | |
| 200 | + | else | |
| 201 | + | widget = widgetOrName | |
| 202 | + | end | |
| 203 | + | ||
| 204 | + | if widget then | |
| 205 | + | local id = widget:getId():sub(4) | |
| 206 | + | g_game.removeVip(id) | |
| 207 | + | vipList:removeChild(widget) | |
| 208 | + | if vipInfo[id] and g_game.getFeature(GameAdditionalVipInfo) then | |
| 209 | + | vipInfo[id] = nil | |
| 210 | + | end | |
| 211 | + | end | |
| 212 | + | end | |
| 213 | + | ||
| 214 | + | function hideOffline(state) | |
| 215 | + | settings = {} | |
| 216 | + | settings['hideOffline'] = state | |
| 217 | + | g_settings.mergeNode('VipList', settings) | |
| 218 | + | ||
| 219 | + | refresh() | |
| 220 | + | end | |
| 221 | + | ||
| 222 | + | function isHiddingOffline() | |
| 223 | + | local settings = g_settings.getNode('VipList') | |
| 224 | + | if not settings then | |
| 225 | + | return false | |
| 226 | + | end | |
| 227 | + | return settings['hideOffline'] | |
| 228 | + | end | |
| 229 | + | ||
| 230 | + | function getSortedBy() | |
| 231 | + | local settings = g_settings.getNode('VipList') | |
| 232 | + | if not settings or not settings['sortedBy'] then | |
| 233 | + | return 'status' | |
| 234 | + | end | |
| 235 | + | return settings['sortedBy'] | |
| 236 | + | end | |
| 237 | + | ||
| 238 | + | function sortBy(state) | |
| 239 | + | settings = {} | |
| 240 | + | settings['sortedBy'] = state | |
| 241 | + | g_settings.mergeNode('VipList', settings) | |
| 242 | + | ||
| 243 | + | refresh() | |
| 244 | + | end | |
| 245 | + | ||
| 246 | + | function onAddVip(id, name, state, description, iconId, notify) | |
| 247 | + | if not name or name:len() == 0 then | |
| 248 | + | return | |
| 249 | + | end | |
| 250 | + | ||
| 251 | + | local vipList = vipWindow:getChildById('contentsPanel') | |
| 252 | + | local childrenCount = vipList:getChildCount() | |
| 253 | + | for i=1,childrenCount do | |
| 254 | + | local child = vipList:getChildByIndex(i) | |
| 255 | + | if child:getText() == name then | |
| 256 | + | return -- don't add duplicated vips | |
| 257 | + | end | |
| 258 | + | end | |
| 259 | + | ||
| 260 | + | local label = g_ui.createWidget('VipListLabel') | |
| 261 | + | label.onMousePress = onVipListLabelMousePress | |
| 262 | + | label:setId('vip' .. id) | |
| 263 | + | label:setText(name) | |
| 264 | + | ||
| 265 | + | if not g_game.getFeature(GameAdditionalVipInfo) then | |
| 266 | + | local tmpVipInfo = vipInfo[tostring(id)] | |
| 267 | + | label.iconId = 0 | |
| 268 | + | label.notifyLogin = false | |
| 269 | + | if tmpVipInfo then | |
| 270 | + | if tmpVipInfo.iconId then | |
| 271 | + | label:setImageClip(torect((tmpVipInfo.iconId * 12) .. ' 0 12 12')) | |
| 272 | + | label.iconId = tmpVipInfo.iconId | |
| 273 | + | end | |
| 274 | + | if tmpVipInfo.description then | |
| 275 | + | label:setTooltip(tmpVipInfo.description) | |
| 276 | + | end | |
| 277 | + | label.notifyLogin = tmpVipInfo.notifyLogin or false | |
| 278 | + | end | |
| 279 | + | else | |
| 280 | + | label:setTooltip(description) | |
| 281 | + | label:setImageClip(torect((iconId * 12) .. ' 0 12 12')) | |
| 282 | + | label.iconId = iconId | |
| 283 | + | label.notifyLogin = notify | |
| 284 | + | end | |
| 285 | + | ||
| 286 | + | if state == VipState.Online then | |
| 287 | + | label:setColor('#00ff00') | |
| 288 | + | elseif state == VipState.Pending then | |
| 289 | + | label:setColor('#ffca38') | |
| 290 | + | else | |
| 291 | + | label:setColor('#ff0000') | |
| 292 | + | end | |
| 293 | + | ||
| 294 | + | label.vipState = state | |
| 295 | + | ||
| 296 | + | label:setPhantom(false) | |
| 297 | + | connect(label, { | |
| 298 | + | onDoubleClick = function () | |
| 299 | + | g_game.openPrivateChannel(label:getText()) | |
| 300 | + | local privateTab = modules.game_chat.getTab(label:getText()) | |
| 301 | + | if privateTab then | |
| 302 | + | modules.game_chat.consoleTabBar:selectTab(privateTab) | |
| 303 | + | end | |
| 304 | + | return true | |
| 305 | + | end | |
| 306 | + | } ) | |
| 307 | + | ||
| 308 | + | if state == VipState.Offline and isHiddingOffline() then | |
| 309 | + | label:setVisible(false) | |
| 310 | + | end | |
| 311 | + | ||
| 312 | + | local nameLower = name:lower() | |
| 313 | + | local childrenCount = vipList:getChildCount() | |
| 314 | + | ||
| 315 | + | for i=1,childrenCount do | |
| 316 | + | local child = vipList:getChildByIndex(i) | |
| 317 | + | if (state == VipState.Online and child.vipState ~= VipState.Online and getSortedBy() == 'status') | |
| 318 | + | or (label.iconId > child.iconId and getSortedBy() == 'type') then | |
| 319 | + | vipList:insertChild(i, label) | |
| 320 | + | return | |
| 321 | + | end | |
| 322 | + | ||
| 323 | + | if (((state ~= VipState.Online and child.vipState ~= VipState.Online) or (state == VipState.Online and child.vipState == VipState.Online)) and getSortedBy() == 'status') | |
| 324 | + | or (label.iconId == child.iconId and getSortedBy() == 'type') or getSortedBy() == 'name' then | |
| 325 | + | ||
| 326 | + | local childText = child:getText():lower() | |
| 327 | + | local length = math.min(childText:len(), nameLower:len()) | |
| 328 | + | ||
| 329 | + | for j=1,length do | |
| 330 | + | if nameLower:byte(j) < childText:byte(j) then | |
| 331 | + | vipList:insertChild(i, label) | |
| 332 | + | return | |
| 333 | + | elseif nameLower:byte(j) > childText:byte(j) then | |
| 334 | + | break | |
| 335 | + | elseif j == nameLower:len() then -- We are at the end of nameLower, and its shorter than childText, thus insert before | |
| 336 | + | vipList:insertChild(i, label) | |
| 337 | + | return | |
| 338 | + | end | |
| 339 | + | end | |
| 340 | + | end | |
| 341 | + | end | |
| 342 | + | ||
| 343 | + | vipList:insertChild(childrenCount+1, label) | |
| 344 | + | end | |
| 345 | + | ||
| 346 | + | function onVipStateChange(id, state) | |
| 347 | + | local vipList = vipWindow:getChildById('contentsPanel') | |
| 348 | + | local label = vipList:getChildById('vip' .. id) | |
| 349 | + | if not label then | |
| 350 | + | return | |
| 351 | + | end | |
| 352 | + | local name = label:getText() | |
| 353 | + | local description = label:getTooltip() | |
| 354 | + | local iconId = label.iconId | |
| 355 | + | local notify = label.notifyLogin | |
| 356 | + | label:destroy() | |
| 357 | + | ||
| 358 | + | onAddVip(id, name, state, description, iconId, notify) | |
| 359 | + | ||
| 360 | + | if notify and state ~= VipState.Pending then | |
| 361 | + | modules.game_textmessage.displayFailureMessage(tr('%s has logged %s.', name, (state == VipState.Online and 'in' or 'out'))) | |
| 362 | + | end | |
| 363 | + | end | |
| 364 | + | ||
| 365 | + | function onVipListMousePress(widget, mousePos, mouseButton) | |
| 366 | + | if mouseButton ~= MouseRightButton then return end | |
| 367 | + | ||
| 368 | + | local vipList = vipWindow:getChildById('contentsPanel') | |
| 369 | + | ||
| 370 | + | local menu = g_ui.createWidget('PopupMenu') | |
| 371 | + | menu:setGameMenu(true) | |
| 372 | + | menu:addOption(tr('Add new VIP'), function() createAddWindow() end) | |
| 373 | + | ||
| 374 | + | menu:addSeparator() | |
| 375 | + | if not isHiddingOffline() then | |
| 376 | + | menu:addOption(tr('Hide Offline'), function() hideOffline(true) end) | |
| 377 | + | else | |
| 378 | + | menu:addOption(tr('Show Offline'), function() hideOffline(false) end) | |
| 379 | + | end | |
| 380 | + | ||
| 381 | + | if not(getSortedBy() == 'name') then | |
| 382 | + | menu:addOption(tr('Sort by name'), function() sortBy('name') end) | |
| 383 | + | end | |
| 384 | + | ||
| 385 | + | if not(getSortedBy() == 'status') then | |
| 386 | + | menu:addOption(tr('Sort by status'), function() sortBy('status') end) | |
| 387 | + | end | |
| 388 | + | ||
| 389 | + | if not(getSortedBy() == 'type') then | |
| 390 | + | menu:addOption(tr('Sort by type'), function() sortBy('type') end) | |
| 391 | + | end | |
| 392 | + | ||
| 393 | + | menu:display(mousePos) | |
| 394 | + | ||
| 395 | + | return true | |
| 396 | + | end | |
| 397 | + | ||
| 398 | + | function onVipListLabelMousePress(widget, mousePos, mouseButton) | |
| 399 | + | if mouseButton ~= MouseRightButton then return end | |
| 400 | + | ||
| 401 | + | local vipList = vipWindow:getChildById('contentsPanel') | |
| 402 | + | ||
| 403 | + | local menu = g_ui.createWidget('PopupMenu') | |
| 404 | + | menu:setGameMenu(true) | |
| 405 | + | menu:addOption(tr('Send Message'), function() | |
| 406 | + | -- Se abre el chat (si no está visible) y se solicita el canal privado | |
| 407 | + | if not modules.game_chat.chatWindow or not modules.game_chat.chatWindow:isVisible() then | |
| 408 | + | modules.game_chat.onOpen() | |
| 409 | + | end | |
| 410 | + | g_game.openPrivateChannel(widget:getText()) | |
| 411 | + | -- NUEVO: seleccionar la pestaña privada correspondiente | |
| 412 | + | local privateTab = modules.game_chat.getTab(widget:getText()) | |
| 413 | + | if privateTab then | |
| 414 | + | modules.game_chat.consoleTabBar:selectTab(privateTab) | |
| 415 | + | end | |
| 416 | + | end) | |
| 417 | + | menu:addOption(tr('Add new VIP'), function() createAddWindow() end) | |
| 418 | + | menu:addOption(tr('Edit %s', widget:getText()), function() if widget then createEditWindow(widget) end end) | |
| 419 | + | menu:addOption(tr('Remove %s', widget:getText()), function() if widget then removeVip(widget) end end) | |
| 420 | + | menu:addSeparator() | |
| 421 | + | menu:addOption(tr('Copy Name'), function() g_window.setClipboardText(widget:getText()) end) | |
| 422 | + | ||
| 423 | + | -- Guard against modules.game_console being nil | |
| 424 | + | if modules.game_console and modules.game_console.getOwnPrivateTab then | |
| 425 | + | menu:addSeparator() | |
| 426 | + | menu:addOption(tr('Invite to private chat'), function() g_game.inviteToOwnChannel(widget:getText()) end) | |
| 427 | + | menu:addOption(tr('Exclude from private chat'), function() g_game.excludeFromOwnChannel(widget:getText()) end) | |
| 428 | + | end | |
| 429 | + | ||
| 430 | + | if not isHiddingOffline() then | |
| 431 | + | menu:addOption(tr('Hide Offline'), function() hideOffline(true) end) | |
| 432 | + | else | |
| 433 | + | menu:addOption(tr('Show Offline'), function() hideOffline(false) end) | |
| 434 | + | end | |
| 435 | + | ||
| 436 | + | if not(getSortedBy() == 'name') then | |
| 437 | + | menu:addOption(tr('Sort by name'), function() sortBy('name') end) | |
| 438 | + | end | |
| 439 | + | ||
| 440 | + | if not(getSortedBy() == 'status') then | |
| 441 | + | menu:addOption(tr('Sort by status'), function() sortBy('status') end) | |
| 442 | + | end | |
| 443 | + | ||
| 444 | + | menu:display(mousePos) | |
| 445 | + | ||
| 446 | + | return true | |
| 447 | + | end |
| @@ -0,0 +1,9 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_viplist | |
| 3 | + | description: Manage vip list window | |
| 4 | + | author: baxnie, edubart | |
| 5 | + | website: https://github.com/edubart/otclient | |
| 6 | + | sandboxed: true | |
| 7 | + | scripts: [ viplist ] | |
| 8 | + | @onLoad: init() | |
| 9 | + | @onUnload: terminate() |
| @@ -0,0 +1,26 @@ | |||
| 1 | + | VipListLabel < GameLabel | |
| 2 | + | margin-top: 2 | |
| 3 | + | text-offset: 16 0 | |
| 4 | + | image-rect: 0 0 12 12 | |
| 5 | + | image-clip: 0 0 12 12 | |
| 6 | + | image-source: /images/game/viplist/icons | |
| 7 | + | font: verdana-11px-monochrome | |
| 8 | + | phantom: false | |
| 9 | + | ||
| 10 | + | $first: | |
| 11 | + | margin-top: 5 | |
| 12 | + | ||
| 13 | + | MiniWindow | |
| 14 | + | id: vipWindow | |
| 15 | + | !text: tr('VIP List') | |
| 16 | + | height: 100 | |
| 17 | + | icon: /images/topbuttons/viplist | |
| 18 | + | @onClose: modules.game_viplist.onMiniWindowClose() | |
| 19 | + | &save: true | |
| 20 | + | &autoOpen: false | |
| 21 | + | ||
| 22 | + | MiniWindowContents | |
| 23 | + | layout: verticalBox | |
| 24 | + | padding-left: 5 | |
| 25 | + | padding-right: 5 | |
| 26 | + | &onMousePress: modules.game_viplist.onVipListMousePress |
| @@ -0,0 +1,544 @@ | |||
| 1 | + | smartWalkDirs = {} | |
| 2 | + | smartWalkDir = nil | |
| 3 | + | wsadWalking = false | |
| 4 | + | nextWalkDir = nil | |
| 5 | + | lastWalkDir = nil | |
| 6 | + | lastFinishedStep = 0 | |
| 7 | + | autoWalkEvent = nil | |
| 8 | + | firstStep = true | |
| 9 | + | reloadAutoCura = nil | |
| 10 | + | walkLock = 0 | |
| 11 | + | walkEvent = nil | |
| 12 | + | lastWalk = 0 | |
| 13 | + | lastTurn = 0 | |
| 14 | + | lastTurnDirection = 0 | |
| 15 | + | lastStop = 0 | |
| 16 | + | AtkKeyPress = -1 | |
| 17 | + | CuraKeyPress = -1 | |
| 18 | + | lastManualWalk = 0 | |
| 19 | + | autoFinishNextServerWalk = 0 | |
| 20 | + | pressKeyAction = 0 | |
| 21 | + | botMonitoreActive = nil | |
| 22 | + | opcodeWalkKeyPress = 121 | |
| 23 | + | turnKeys = {} | |
| 24 | + | ||
| 25 | + | function init() | |
| 26 | + | connect(LocalPlayer, { | |
| 27 | + | onPositionChange = onPositionChange, | |
| 28 | + | onWalk = onWalk, | |
| 29 | + | onTeleport = onTeleport, | |
| 30 | + | onWalkFinish = onWalkFinish, | |
| 31 | + | onCancelWalk = onCancelWalk | |
| 32 | + | }) | |
| 33 | + | ||
| 34 | + | ProtocolGame.registerExtendedOpcode(opcodeWalkKeyPress, function(protocol, opcode, buffer) end) | |
| 35 | + | modules.game_interface.getRootPanel().onFocusChange = stopSmartWalk | |
| 36 | + | g_modules.ensureModuleLoaded("game_alert") | |
| 37 | + | bindKeys() | |
| 38 | + | end | |
| 39 | + | ||
| 40 | + | function terminate() | |
| 41 | + | disconnect(LocalPlayer, { | |
| 42 | + | onPositionChange = onPositionChange, | |
| 43 | + | onWalk = onWalk, | |
| 44 | + | onTeleport = onTeleport, | |
| 45 | + | onWalkFinish = onWalkFinish | |
| 46 | + | }) | |
| 47 | + | ||
| 48 | + | removeEvent(autoWalkEvent) | |
| 49 | + | ProtocolGame.unregisterExtendedOpcode(opcodeWalkKeyPress) | |
| 50 | + | stopSmartWalk() | |
| 51 | + | unbindKeys() | |
| 52 | + | disableWSAD() | |
| 53 | + | end | |
| 54 | + | ||
| 55 | + | function bindKeys() | |
| 56 | + | if modules.game_alert.onGetCinematic() then | |
| 57 | + | return | |
| 58 | + | end | |
| 59 | + | bindWalkKey('Up', North) | |
| 60 | + | bindWalkKey('Right', East) | |
| 61 | + | bindWalkKey('Down', South) | |
| 62 | + | bindWalkKey('Left', West) | |
| 63 | + | bindWalkKey('Numpad8', North) | |
| 64 | + | bindWalkKey('Numpad9', NorthEast) | |
| 65 | + | bindWalkKey('Numpad6', East) | |
| 66 | + | bindWalkKey('Numpad3', SouthEast) | |
| 67 | + | bindWalkKey('Numpad2', South) | |
| 68 | + | bindWalkKey('Numpad1', SouthWest) | |
| 69 | + | bindWalkKey('Numpad4', West) | |
| 70 | + | bindWalkKey('Numpad7', NorthWest) | |
| 71 | + | ||
| 72 | + | bindTurnKey('Ctrl+Up', North) | |
| 73 | + | bindTurnKey('Ctrl+Right', East) | |
| 74 | + | bindTurnKey('Ctrl+Down', South) | |
| 75 | + | bindTurnKey('Ctrl+Left', West) | |
| 76 | + | bindTurnKey('Ctrl+Numpad8', North) | |
| 77 | + | bindTurnKey('Ctrl+Numpad6', East) | |
| 78 | + | bindTurnKey('Ctrl+Numpad2', South) | |
| 79 | + | bindTurnKey('Ctrl+Numpad4', West) | |
| 80 | + | end | |
| 81 | + | ||
| 82 | + | function unbindKeys() | |
| 83 | + | unbindWalkKey('Up', North) | |
| 84 | + | unbindWalkKey('Right', East) | |
| 85 | + | unbindWalkKey('Down', South) | |
| 86 | + | unbindWalkKey('Left', West) | |
| 87 | + | unbindWalkKey('Numpad8', North) | |
| 88 | + | unbindWalkKey('Numpad9', NorthEast) | |
| 89 | + | unbindWalkKey('Numpad6', East) | |
| 90 | + | unbindWalkKey('Numpad3', SouthEast) | |
| 91 | + | unbindWalkKey('Numpad2', South) | |
| 92 | + | unbindWalkKey('Numpad1', SouthWest) | |
| 93 | + | unbindWalkKey('Numpad4', West) | |
| 94 | + | unbindWalkKey('Numpad7', NorthWest) | |
| 95 | + | ||
| 96 | + | unbindTurnKey('Ctrl+Up', North) | |
| 97 | + | unbindTurnKey('Ctrl+Right', East) | |
| 98 | + | unbindTurnKey('Ctrl+Down', South) | |
| 99 | + | unbindTurnKey('Ctrl+Left', West) | |
| 100 | + | unbindTurnKey('Ctrl+Numpad8', North) | |
| 101 | + | unbindTurnKey('Ctrl+Numpad6', East) | |
| 102 | + | unbindTurnKey('Ctrl+Numpad2', South) | |
| 103 | + | unbindTurnKey('Ctrl+Numpad4', West) | |
| 104 | + | end | |
| 105 | + | ||
| 106 | + | function enableWSAD() | |
| 107 | + | if modules.game_alert.onGetCinematic() then | |
| 108 | + | return | |
| 109 | + | end | |
| 110 | + | if wsadWalking then | |
| 111 | + | return | |
| 112 | + | end | |
| 113 | + | wsadWalking = true | |
| 114 | + | local player = g_game.getLocalPlayer() | |
| 115 | + | if player then | |
| 116 | + | player:lockWalk(100) -- 100 ms walk lock for all directions | |
| 117 | + | end | |
| 118 | + | ||
| 119 | + | bindWalkKey("W", North) | |
| 120 | + | bindWalkKey("D", East) | |
| 121 | + | bindWalkKey("S", South) | |
| 122 | + | bindWalkKey("A", West) | |
| 123 | + | ||
| 124 | + | --bindTurnKey("Ctrl+W", North) | |
| 125 | + | --bindTurnKey("Ctrl+D", East) | |
| 126 | + | --bindTurnKey("Ctrl+S", South) | |
| 127 | + | --bindTurnKey("Ctrl+A", West) | |
| 128 | + | ||
| 129 | + | bindWalkKey("E", NorthEast) | |
| 130 | + | bindWalkKey("Q", NorthWest) | |
| 131 | + | bindWalkKey("C", SouthEast) | |
| 132 | + | bindWalkKey("Z", SouthWest) | |
| 133 | + | end | |
| 134 | + | ||
| 135 | + | function disableWSAD() | |
| 136 | + | if not wsadWalking then | |
| 137 | + | return | |
| 138 | + | end | |
| 139 | + | wsadWalking = false | |
| 140 | + | ||
| 141 | + | unbindWalkKey("W") | |
| 142 | + | unbindWalkKey("D") | |
| 143 | + | unbindWalkKey("S") | |
| 144 | + | unbindWalkKey("A") | |
| 145 | + | ||
| 146 | + | --unbindTurnKey("Ctrl+W") | |
| 147 | + | --unbindTurnKey("Ctrl+D") | |
| 148 | + | --unbindTurnKey("Ctrl+S") | |
| 149 | + | --unbindTurnKey("Ctrl+A") | |
| 150 | + | ||
| 151 | + | unbindWalkKey("E") | |
| 152 | + | unbindWalkKey("Q") | |
| 153 | + | unbindWalkKey("C") | |
| 154 | + | unbindWalkKey("Z") | |
| 155 | + | end | |
| 156 | + | ||
| 157 | + | function bindWalkKey(key, dir) | |
| 158 | + | local gameRootPanel = modules.game_interface.getRootPanel() | |
| 159 | + | g_keyboard.bindKeyDown(key, function() | |
| 160 | + | pressKeyAction = 1 | |
| 161 | + | changeWalkDir(dir) | |
| 162 | + | end, gameRootPanel, true) | |
| 163 | + | ||
| 164 | + | g_keyboard.bindKeyUp(key, function() | |
| 165 | + | pressKeyAction = 1 | |
| 166 | + | changeWalkDir(dir, true) | |
| 167 | + | end, gameRootPanel, true) | |
| 168 | + | ||
| 169 | + | g_keyboard.bindKeyPress(key, function(c, k, ticks) | |
| 170 | + | pressKeyAction = 1 | |
| 171 | + | smartWalk(dir, ticks) | |
| 172 | + | end, gameRootPanel) | |
| 173 | + | end | |
| 174 | + | ||
| 175 | + | function onCheckAutoCura() | |
| 176 | + | if CuraKeyPress == 01 then | |
| 177 | + | else | |
| 178 | + | CuraKeyPress = 00 | |
| 179 | + | end | |
| 180 | + | end | |
| 181 | + | ||
| 182 | + | function setPressKeyAction(value) | |
| 183 | + | if value == 05 then | |
| 184 | + | AtkKeyPress = 01 | |
| 185 | + | end | |
| 186 | + | if value == 06 then | |
| 187 | + | CuraKeyPress = 01 | |
| 188 | + | if reloadAutoCura ~= nil then | |
| 189 | + | removeEvent(reloadAutoCura) | |
| 190 | + | reloadAutoCura = nil | |
| 191 | + | end | |
| 192 | + | reloadAutoCura = scheduleEvent(function() | |
| 193 | + | CuraKeyPress = -1 | |
| 194 | + | end, 1000) | |
| 195 | + | end | |
| 196 | + | pressKeyAction = value | |
| 197 | + | end | |
| 198 | + | ||
| 199 | + | function onRestartMonitore(value) | |
| 200 | + | if value == 00 then | |
| 201 | + | if botMonitoreActive ~= nil then | |
| 202 | + | removeEvent(botMonitoreActive) | |
| 203 | + | botMonitoreActive = nil | |
| 204 | + | end | |
| 205 | + | ||
| 206 | + | return | |
| 207 | + | end | |
| 208 | + | if value == 01 then | |
| 209 | + | if botMonitoreActive ~= nil then | |
| 210 | + | removeEvent(botMonitoreActive) | |
| 211 | + | botMonitoreActive = nil | |
| 212 | + | end | |
| 213 | + | onSendInfoAdm() | |
| 214 | + | return | |
| 215 | + | end | |
| 216 | + | end | |
| 217 | + | ||
| 218 | + | function onSendInfoAdm() | |
| 219 | + | if modules.game_interface.onMonitoreBotCheck() then | |
| 220 | + | -- CHECANDO BOT V8 | |
| 221 | + | print("BOT CHECK: TRUE") | |
| 222 | + | local BotV8 = 00 | |
| 223 | + | local settings = g_settings.getNode('bot') or {} | |
| 224 | + | local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() | |
| 225 | + | if settings[index] ~= nil then | |
| 226 | + | if settings[index].enabled == true then | |
| 227 | + | BotV8 = 01 | |
| 228 | + | else | |
| 229 | + | settings[index] = { | |
| 230 | + | enabled=false, | |
| 231 | + | config="" | |
| 232 | + | } | |
| 233 | + | end | |
| 234 | + | end | |
| 235 | + | if not g_game.isOnline() then | |
| 236 | + | return | |
| 237 | + | else | |
| 238 | + | local attacking = g_game.getAttackingCreature() | |
| 239 | + | if not attacking then | |
| 240 | + | if AtkKeyPress ~= 00 then | |
| 241 | + | AtkKeyPress = -1 | |
| 242 | + | end | |
| 243 | + | end | |
| 244 | + | ||
| 245 | + | local localPlayer = g_game.getLocalPlayer() | |
| 246 | + | g_game.getProtocolGame():sendExtendedOpcode(opcodeWalkKeyPress, pressKeyAction.. "," ..localPlayer:getPosition().x.. "," ..localPlayer:getPosition().y.. "," ..localPlayer:getPosition().z.. "," ..localPlayer:getName().. "," ..BotV8.. "," ..AtkKeyPress.. "," ..CuraKeyPress) | |
| 247 | + | pressKeyAction = 3 | |
| 248 | + | if attacking then | |
| 249 | + | if AtkKeyPress == -1 then | |
| 250 | + | AtkKeyPress = 00 | |
| 251 | + | end | |
| 252 | + | end | |
| 253 | + | botMonitoreActive = scheduleEvent(function() | |
| 254 | + | onSendInfoAdm() | |
| 255 | + | end, 1000) | |
| 256 | + | end | |
| 257 | + | end | |
| 258 | + | end | |
| 259 | + | ||
| 260 | + | function unbindWalkKey(key) | |
| 261 | + | local gameRootPanel = modules.game_interface.getRootPanel() | |
| 262 | + | g_keyboard.unbindKeyDown(key, gameRootPanel) | |
| 263 | + | g_keyboard.unbindKeyUp(key, gameRootPanel) | |
| 264 | + | g_keyboard.unbindKeyPress(key, gameRootPanel) | |
| 265 | + | end | |
| 266 | + | ||
| 267 | + | function bindTurnKey(key, dir) | |
| 268 | + | turnKeys[key] = dir | |
| 269 | + | local gameRootPanel = modules.game_interface.getRootPanel() | |
| 270 | + | g_keyboard.bindKeyDown(key, function() turn(dir, false) end, gameRootPanel) | |
| 271 | + | g_keyboard.bindKeyPress(key, function() turn(dir, true) end, gameRootPanel) | |
| 272 | + | g_keyboard.bindKeyUp(key, function() local player = g_game.getLocalPlayer() if player then player:lockWalk(200) end end, gameRootPanel) | |
| 273 | + | end | |
| 274 | + | ||
| 275 | + | function unbindTurnKey(key) | |
| 276 | + | turnKeys[key] = nil | |
| 277 | + | local gameRootPanel = modules.game_interface.getRootPanel() | |
| 278 | + | g_keyboard.unbindKeyDown(key, gameRootPanel) | |
| 279 | + | g_keyboard.unbindKeyPress(key, gameRootPanel) | |
| 280 | + | g_keyboard.unbindKeyUp(key, gameRootPanel) | |
| 281 | + | end | |
| 282 | + | ||
| 283 | + | function stopSmartWalk() | |
| 284 | + | smartWalkDirs = {} | |
| 285 | + | smartWalkDir = nil | |
| 286 | + | end | |
| 287 | + | ||
| 288 | + | function changeWalkDir(dir, pop) | |
| 289 | + | while table.removevalue(smartWalkDirs, dir) do end | |
| 290 | + | if pop then | |
| 291 | + | if #smartWalkDirs == 0 then | |
| 292 | + | stopSmartWalk() | |
| 293 | + | return | |
| 294 | + | end | |
| 295 | + | else | |
| 296 | + | table.insert(smartWalkDirs, 1, dir) | |
| 297 | + | end | |
| 298 | + | ||
| 299 | + | smartWalkDir = smartWalkDirs[1] | |
| 300 | + | if modules.client_options.getOption('smartWalk') and #smartWalkDirs > 1 then | |
| 301 | + | for _,d in pairs(smartWalkDirs) do | |
| 302 | + | if (smartWalkDir == North and d == West) or (smartWalkDir == West and d == North) then | |
| 303 | + | smartWalkDir = NorthWest | |
| 304 | + | break | |
| 305 | + | elseif (smartWalkDir == North and d == East) or (smartWalkDir == East and d == North) then | |
| 306 | + | smartWalkDir = NorthEast | |
| 307 | + | break | |
| 308 | + | elseif (smartWalkDir == South and d == West) or (smartWalkDir == West and d == South) then | |
| 309 | + | smartWalkDir = SouthWest | |
| 310 | + | break | |
| 311 | + | elseif (smartWalkDir == South and d == East) or (smartWalkDir == East and d == South) then | |
| 312 | + | smartWalkDir = SouthEast | |
| 313 | + | break | |
| 314 | + | end | |
| 315 | + | end | |
| 316 | + | end | |
| 317 | + | end | |
| 318 | + | ||
| 319 | + | function smartWalk(dir, ticks) | |
| 320 | + | walkEvent = scheduleEvent(function() | |
| 321 | + | if g_keyboard.getModifiers() == KeyboardNoModifier then | |
| 322 | + | local direction = smartWalkDir or dir | |
| 323 | + | walk(direction, ticks) | |
| 324 | + | return true | |
| 325 | + | end | |
| 326 | + | return false | |
| 327 | + | end, 20) | |
| 328 | + | end | |
| 329 | + | ||
| 330 | + | function canChangeFloorDown(pos) | |
| 331 | + | pos.z = pos.z + 1 | |
| 332 | + | toTile = g_map.getTile(pos) | |
| 333 | + | return toTile and toTile:hasElevation(3) | |
| 334 | + | end | |
| 335 | + | ||
| 336 | + | function canChangeFloorUp(pos) | |
| 337 | + | pos.z = pos.z - 1 | |
| 338 | + | toTile = g_map.getTile(pos) | |
| 339 | + | return toTile and toTile:isWalkable() | |
| 340 | + | end | |
| 341 | + | ||
| 342 | + | function onPositionChange(player, newPos, oldPos) | |
| 343 | + | end | |
| 344 | + | ||
| 345 | + | function onWalk(player, newPos, oldPos) | |
| 346 | + | if autoFinishNextServerWalk + 200 > g_clock.millis() then | |
| 347 | + | player:finishServerWalking() | |
| 348 | + | end | |
| 349 | + | end | |
| 350 | + | ||
| 351 | + | function onTeleport(player, newPos, oldPos) | |
| 352 | + | if not newPos or not oldPos then | |
| 353 | + | return | |
| 354 | + | end | |
| 355 | + | -- floor change is also teleport | |
| 356 | + | if math.abs(newPos.x - oldPos.x) >= 3 or math.abs(newPos.y - oldPos.y) >= 3 or math.abs(newPos.z - oldPos.z) >= 2 then | |
| 357 | + | -- far teleport, lock walk for 100ms | |
| 358 | + | walkLock = g_clock.millis() + g_settings.getNumber('walkTeleportDelay') | |
| 359 | + | else | |
| 360 | + | walkLock = g_clock.millis() + g_settings.getNumber('walkStairsDelay') | |
| 361 | + | end | |
| 362 | + | nextWalkDir = nil -- cancel autowalk | |
| 363 | + | end | |
| 364 | + | ||
| 365 | + | function onWalkFinish(player) | |
| 366 | + | lastFinishedStep = g_clock.millis() | |
| 367 | + | if nextWalkDir ~= nil then | |
| 368 | + | removeEvent(autoWalkEvent) | |
| 369 | + | autoWalkEvent = addEvent(function() if nextWalkDir ~= nil then walk(nextWalkDir, 0) end end, false) | |
| 370 | + | end | |
| 371 | + | end | |
| 372 | + | ||
| 373 | + | function onCancelWalk(player) | |
| 374 | + | pressKeyAction = 3 | |
| 375 | + | player:lockWalk(50) | |
| 376 | + | end | |
| 377 | + | ||
| 378 | + | function walk(dir, ticks) | |
| 379 | + | --pressKeyAction = 1 | |
| 380 | + | lastManualWalk = g_clock.millis() | |
| 381 | + | local player = g_game.getLocalPlayer() | |
| 382 | + | if not player or g_game.isDead() or player:isDead() then | |
| 383 | + | return | |
| 384 | + | end | |
| 385 | + | ||
| 386 | + | if player:isWalkLocked() then | |
| 387 | + | nextWalkDir = nil | |
| 388 | + | return | |
| 389 | + | end | |
| 390 | + | ||
| 391 | + | if g_game.isFollowing() then | |
| 392 | + | g_game.cancelFollow() | |
| 393 | + | end | |
| 394 | + | ||
| 395 | + | if player:isAutoWalking() then | |
| 396 | + | if lastStop + 100 < g_clock.millis() then | |
| 397 | + | lastStop = g_clock.millis() | |
| 398 | + | player:stopAutoWalk() | |
| 399 | + | g_game.stop() | |
| 400 | + | end | |
| 401 | + | end | |
| 402 | + | ||
| 403 | + | local dash = false | |
| 404 | + | local ignoredCanWalk = false | |
| 405 | + | if not g_game.getFeature(GameNewWalking) then | |
| 406 | + | dash = g_settings.getBoolean("dash", false) | |
| 407 | + | end | |
| 408 | + | local ticksToNextWalk = player:getStepTicksLeft() | |
| 409 | + | if not player:canWalk(dir) then -- canWalk return false when previous walk is not finished or not confirmed by server | |
| 410 | + | if dash then | |
| 411 | + | ignoredCanWalk = true | |
| 412 | + | else | |
| 413 | + | if ticksToNextWalk < 500 and (lastWalkDir ~= dir or ticks == 0) then | |
| 414 | + | nextWalkDir = dir | |
| 415 | + | end | |
| 416 | + | if ticksToNextWalk < 30 and lastFinishedStep + 400 > g_clock.millis() and nextWalkDir == nil then -- clicked walk 20 ms too early, try to execute again as soon possible to keep smooth walking | |
| 417 | + | nextWalkDir = dir | |
| 418 | + | end | |
| 419 | + | return | |
| 420 | + | end | |
| 421 | + | end | |
| 422 | + | ||
| 423 | + | --if nextWalkDir ~= nil and lastFinishedStep + 200 < g_clock.millis() then | |
| 424 | + | -- print("Cancel " .. nextWalkDir) | |
| 425 | + | -- nextWalkDir = nil | |
| 426 | + | --end | |
| 427 | + | if nextWalkDir ~= nil and nextWalkDir ~= lastWalkDir then | |
| 428 | + | dir = nextWalkDir | |
| 429 | + | end | |
| 430 | + | ||
| 431 | + | local toPos = player:getPrewalkingPosition(true) | |
| 432 | + | if dir == North then | |
| 433 | + | toPos.y = toPos.y - 1 | |
| 434 | + | elseif dir == East then | |
| 435 | + | toPos.x = toPos.x + 1 | |
| 436 | + | elseif dir == South then | |
| 437 | + | toPos.y = toPos.y + 1 | |
| 438 | + | elseif dir == West then | |
| 439 | + | toPos.x = toPos.x - 1 | |
| 440 | + | elseif dir == NorthEast then | |
| 441 | + | toPos.x = toPos.x + 1 | |
| 442 | + | toPos.y = toPos.y - 1 | |
| 443 | + | elseif dir == SouthEast then | |
| 444 | + | toPos.x = toPos.x + 1 | |
| 445 | + | toPos.y = toPos.y + 1 | |
| 446 | + | elseif dir == SouthWest then | |
| 447 | + | toPos.x = toPos.x - 1 | |
| 448 | + | toPos.y = toPos.y + 1 | |
| 449 | + | elseif dir == NorthWest then | |
| 450 | + | toPos.x = toPos.x - 1 | |
| 451 | + | toPos.y = toPos.y - 1 | |
| 452 | + | end | |
| 453 | + | local toTile = g_map.getTile(toPos) | |
| 454 | + | ||
| 455 | + | if walkLock >= g_clock.millis() and lastWalkDir == dir then | |
| 456 | + | nextWalkDir = nil | |
| 457 | + | return | |
| 458 | + | end | |
| 459 | + | ||
| 460 | + | if firstStep and lastWalkDir == dir and lastWalk + g_settings.getNumber('walkFirstStepDelay') > g_clock.millis() then | |
| 461 | + | firstStep = false | |
| 462 | + | walkLock = lastWalk + g_settings.getNumber('walkFirstStepDelay') | |
| 463 | + | return | |
| 464 | + | end | |
| 465 | + | ||
| 466 | + | if dash and lastWalkDir == dir and lastWalk + 50 > g_clock.millis() then | |
| 467 | + | return | |
| 468 | + | end | |
| 469 | + | ||
| 470 | + | firstStep = (not player:isWalking() and lastFinishedStep + 100 < g_clock.millis() and walkLock + 100 < g_clock.millis()) | |
| 471 | + | if player:isServerWalking() and not dash then | |
| 472 | + | walkLock = walkLock + math.max(g_settings.getNumber('walkFirstStepDelay'), 100) | |
| 473 | + | end | |
| 474 | + | ||
| 475 | + | nextWalkDir = nil | |
| 476 | + | removeEvent(autoWalkEvent) | |
| 477 | + | autoWalkEvent = nil | |
| 478 | + | local preWalked = false | |
| 479 | + | if toTile and toTile:isWalkable() then | |
| 480 | + | if not player:isServerWalking() and not ignoredCanWalk then | |
| 481 | + | player:preWalk(dir) | |
| 482 | + | preWalked = true | |
| 483 | + | end | |
| 484 | + | else | |
| 485 | + | local playerTile = player:getTile() | |
| 486 | + | if (playerTile and playerTile:hasElevation(3) and canChangeFloorUp(toPos)) or canChangeFloorDown(toPos) or (toTile and toTile:isEmpty() and not toTile:isBlocking()) then | |
| 487 | + | player:lockWalk(100) | |
| 488 | + | elseif player:isServerWalking() then | |
| 489 | + | g_game.stop() | |
| 490 | + | return | |
| 491 | + | elseif not toTile then | |
| 492 | + | player:lockWalk(100) -- bug fix for missing stairs down on map | |
| 493 | + | else | |
| 494 | + | if g_app.isMobile() and dir <= Directions.West then | |
| 495 | + | turn(dir, ticks > 0) | |
| 496 | + | end | |
| 497 | + | return -- not walkable tile | |
| 498 | + | end | |
| 499 | + | end | |
| 500 | + | ||
| 501 | + | if player:isServerWalking() and not dash then | |
| 502 | + | g_game.stop() | |
| 503 | + | player:finishServerWalking() | |
| 504 | + | autoFinishNextServerWalk = g_clock.millis() + 200 | |
| 505 | + | end | |
| 506 | + | g_game.walk(dir, preWalked) | |
| 507 | + | ||
| 508 | + | if not firstStep and lastWalkDir ~= dir then | |
| 509 | + | walkLock = g_clock.millis() + g_settings.getNumber('walkTurnDelay') | |
| 510 | + | end | |
| 511 | + | ||
| 512 | + | lastWalkDir = dir | |
| 513 | + | lastWalk = g_clock.millis() | |
| 514 | + | return true | |
| 515 | + | end | |
| 516 | + | ||
| 517 | + | function turn(dir, repeated) | |
| 518 | + | local player = g_game.getLocalPlayer() | |
| 519 | + | if player:isWalking() and player:getWalkDirection() == dir and not player:isServerWalking() then | |
| 520 | + | return | |
| 521 | + | end | |
| 522 | + | ||
| 523 | + | removeEvent(walkEvent) | |
| 524 | + | ||
| 525 | + | if not repeated or (lastTurn + 100 < g_clock.millis()) then | |
| 526 | + | g_game.turn(dir) | |
| 527 | + | changeWalkDir(dir) | |
| 528 | + | lastTurn = g_clock.millis() | |
| 529 | + | if not repeated then | |
| 530 | + | lastTurn = g_clock.millis() + 50 | |
| 531 | + | end | |
| 532 | + | lastTurnDirection = dir | |
| 533 | + | nextWalkDir = nil | |
| 534 | + | player:lockWalk(g_settings.getNumber('walkCtrlTurnDelay')) | |
| 535 | + | end | |
| 536 | + | end | |
| 537 | + | ||
| 538 | + | function checkTurn() | |
| 539 | + | for keys, direction in pairs(turnKeys) do | |
| 540 | + | if g_keyboard.areKeysPressed(keys) then | |
| 541 | + | turn(direction, false) | |
| 542 | + | end | |
| 543 | + | end | |
| 544 | + | end |
| @@ -0,0 +1,9 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_walking | |
| 3 | + | description: Control walking and turns | |
| 4 | + | author: otclient.ovh | |
| 5 | + | website: http://otclient.ovh | |
| 6 | + | scripts: [ walking ] | |
| 7 | + | dependencies: [ game_interface ] | |
| 8 | + | @onLoad: init() | |
| 9 | + | @onUnload: terminate() |
| @@ -0,0 +1,165 @@ | |||
| 1 | + | local typesWingedOffSet = { | |
| 2 | + | [0] = { -- Is for clone | |
| 3 | + | [North] = {x = 0, y = 0}, | |
| 4 | + | [East] = {x = 0, y = 0}, | |
| 5 | + | [South] = {x = 0, y = 0}, | |
| 6 | + | [West] = {x = 0, y = 0}, | |
| 7 | + | }, | |
| 8 | + | [1] = { --Jouster | |
| 9 | + | [North] = {x = 0, y = -2}, | |
| 10 | + | [East] = {x = -4, y = 1}, | |
| 11 | + | [South] = {x = 1, y = -4}, | |
| 12 | + | [West] = {x = -2, y = 0}, | |
| 13 | + | }, | |
| 14 | + | [2] = { --Moth Cape | |
| 15 | + | [North] = {x = -9, y = -5}, | |
| 16 | + | [East] = {x = -5, y = -5}, | |
| 17 | + | [South] = {x = -5, y = -6}, | |
| 18 | + | [West] = {x = -5, y = -9}, | |
| 19 | + | }, | |
| 20 | + | [3] = { | |
| 21 | + | [North] = {x = -10, y = 10}, | |
| 22 | + | [East] = {x = 0, y = 0}, | |
| 23 | + | [South] = {x = 0, y = 0}, | |
| 24 | + | [West] = {x = 0, y = 0}, | |
| 25 | + | }, | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | local typesWingedMountedOffSet = { | |
| 29 | + | [0] = { | |
| 30 | + | [North] = {x = 0, y = 0}, | |
| 31 | + | [East] = {x = 0, y = 0}, | |
| 32 | + | [South] = {x = 0, y = 0}, | |
| 33 | + | [West] = {x = 0, y = 0}, | |
| 34 | + | }, | |
| 35 | + | [1] = { | |
| 36 | + | [North] = {x = -11, y = -8}, | |
| 37 | + | [East] = {x = -9, y = 0}, | |
| 38 | + | [South] = {x = 0, y = -8}, | |
| 39 | + | [West] = {x = -6, y = -13}, | |
| 40 | + | }, | |
| 41 | + | [2] = { | |
| 42 | + | [North] = {x = -11, y = -8}, | |
| 43 | + | [East] = {x = -9, y = 0}, | |
| 44 | + | [South] = {x = 0, y = -9}, | |
| 45 | + | [West] = {x = -6, y = -13}, | |
| 46 | + | }, | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | local WingsOffsets = { | |
| 50 | + | [1592] = { | |
| 51 | + | [1331] = typesWingedOffSet[1], | |
| 52 | + | [1338] = typesWingedOffSet[2], | |
| 53 | + | }, | |
| 54 | + | [1593] = { | |
| 55 | + | [1331] = typesWingedOffSet[1], | |
| 56 | + | [1338] = typesWingedOffSet[2], | |
| 57 | + | }, | |
| 58 | + | [1594] = { | |
| 59 | + | [1331] = typesWingedOffSet[1], | |
| 60 | + | [1338] = typesWingedOffSet[2], | |
| 61 | + | }, | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | local WingsMountedOffsets = { | |
| 65 | + | [1592] = { | |
| 66 | + | [1331] = typesWingedMountedOffSet[1], | |
| 67 | + | [1338] = typesWingedMountedOffSet[2], | |
| 68 | + | }, | |
| 69 | + | [1593] = { | |
| 70 | + | [1331] = typesWingedMountedOffSet[1], | |
| 71 | + | [1338] = typesWingedMountedOffSet[1], | |
| 72 | + | }, | |
| 73 | + | [1594] = { | |
| 74 | + | [1331] = typesWingedMountedOffSet[1], | |
| 75 | + | [1338] = typesWingedMountedOffSet[1], | |
| 76 | + | }, | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | local function translateDir(dir) | |
| 80 | + | if dir == NorthEast or dir == SouthEast then | |
| 81 | + | return East | |
| 82 | + | elseif dir == NorthWest or dir == SouthWest then | |
| 83 | + | return West | |
| 84 | + | end | |
| 85 | + | return dir | |
| 86 | + | end | |
| 87 | + | ||
| 88 | + | local function getWingsInformationOffset(wing, outfit, dir, isMounted) | |
| 89 | + | local offsetType = isMounted and WingsMountedOffsets[wing][outfit] or WingsOffsets[wing][outfit] | |
| 90 | + | ||
| 91 | + | if not offsetType then | |
| 92 | + | return {x = 0, y = 0} | |
| 93 | + | end | |
| 94 | + | ||
| 95 | + | local translatedDir = translateDir(dir) | |
| 96 | + | ||
| 97 | + | if not translatedDir then | |
| 98 | + | return {x = 0, y = 0} | |
| 99 | + | end | |
| 100 | + | ||
| 101 | + | local offset = offsetType[translatedDir] | |
| 102 | + | ||
| 103 | + | if not offset then | |
| 104 | + | return {x = 0, y = 0} | |
| 105 | + | end | |
| 106 | + | ||
| 107 | + | -- Manage auras | |
| 108 | + | if type(offset) == "table" and #offset > 0 then | |
| 109 | + | -- Here you can manipulate auras as needed | |
| 110 | + | -- For example, you can return a specific offset for auras | |
| 111 | + | return {x = offset[1], y = offset[2]} | |
| 112 | + | end | |
| 113 | + | ||
| 114 | + | return offset | |
| 115 | + | end | |
| 116 | + | ||
| 117 | + | local function setWingsOffset(creature) | |
| 118 | + | local outfit = creature:getOutfit() | |
| 119 | + | if outfit.wings and outfit.wings > 1 then | |
| 120 | + | local wings = creature:getWings() | |
| 121 | + | local direction = creature:getDirection() | |
| 122 | + | ||
| 123 | + | if wings and direction then | |
| 124 | + | local Offset = getWingsInformationOffset(wings, outfit.type, direction, outfit.mount and outfit.mount > 0) | |
| 125 | + | local scale = 0.5 -- factor de escala para el widget en la lista | |
| 126 | + | creature:setWingsOffset(Offset.x * scale, Offset.y * scale) | |
| 127 | + | else | |
| 128 | + | creature:setWingsOffset(0, 0) | |
| 129 | + | end | |
| 130 | + | else | |
| 131 | + | creature:setWingsOffset(0, 0) | |
| 132 | + | end | |
| 133 | + | end | |
| 134 | + | ||
| 135 | + | local function onDirectionChange(creature, oldDirection, newDirection) | |
| 136 | + | setWingsOffset(creature) | |
| 137 | + | end | |
| 138 | + | ||
| 139 | + | local function onPositionChange(player, newPos, oldPos) | |
| 140 | + | setWingsOffset(player) | |
| 141 | + | end | |
| 142 | + | ||
| 143 | + | function init() | |
| 144 | + | connect(LocalPlayer, {onOutfitChange = setWingsOffset, onWalk = setWingsOffset, onDirectionChange = onDirectionChange, onPositionChange = onPositionChange}) | |
| 145 | + | connect(Creature, { | |
| 146 | + | onAppear = setWingsOffset, | |
| 147 | + | onWalk = setWingsOffset, | |
| 148 | + | onOutfitChange = setWingsOffset, | |
| 149 | + | onDirectionChange = onDirectionChange, | |
| 150 | + | onPositionChange = onPositionChange | |
| 151 | + | }) | |
| 152 | + | end | |
| 153 | + | ||
| 154 | + | function terminate() | |
| 155 | + | disconnect(LocalPlayer, {onOutfitChange = setWingsOffset, onWalk = setWingsOffset, onDirectionChange = onDirectionChange, onPositionChange = onPositionChange}) | |
| 156 | + | disconnect(Creature, { | |
| 157 | + | onAppear = setWingsOffset, | |
| 158 | + | onWalk = setWingsOffset, | |
| 159 | + | onOutfitChange = setWingsOffset, | |
| 160 | + | onDirectionChange = onDirectionChange, | |
| 161 | + | onPositionChange = onPositionChange | |
| 162 | + | }) | |
| 163 | + | end | |
| 164 | + | ||
| 165 | + | game_wings = { setWingsOffset = setWingsOffset } |
| @@ -0,0 +1,10 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_wings | |
| 3 | + | description: changes the wings offset depending on the direction the player is going. | |
| 4 | + | autoload: true | |
| 5 | + | reloadable: true | |
| 6 | + | sandboxed: true | |
| 7 | + | version: 1.0 | |
| 8 | + | scripts: [ game_wings ] | |
| 9 | + | @onLoad: init() | |
| 10 | + | @onUnload: terminate() |
| @@ -0,0 +1,162 @@ | |||
| 1 | + | local typesWingedOffSet = { | |
| 2 | + | [0] = { -- Is for clone | |
| 3 | + | [North] = {x = 0, y = 0}, | |
| 4 | + | [East] = {x = 0, y = 0}, | |
| 5 | + | [South] = {x = 0, y = 0}, | |
| 6 | + | [West] = {x = 0, y = 0}, | |
| 7 | + | }, | |
| 8 | + | [1] = { --Jouster | |
| 9 | + | [North] = {x = 0, y = -2}, | |
| 10 | + | [East] = {x = -4, y = 1}, | |
| 11 | + | [South] = {x = 1, y = -4}, | |
| 12 | + | [West] = {x = -2, y = 0}, | |
| 13 | + | }, | |
| 14 | + | [2] = { --Moth Cape | |
| 15 | + | [North] = {x = -9, y = -5}, | |
| 16 | + | [East] = {x = -5, y = -5}, | |
| 17 | + | [South] = {x = -5, y = -6}, | |
| 18 | + | [West] = {x = -5, y = -9}, | |
| 19 | + | }, | |
| 20 | + | [3] = { | |
| 21 | + | [North] = {x = -10, y = 10}, | |
| 22 | + | [East] = {x = 0, y = 0}, | |
| 23 | + | [South] = {x = 0, y = 0}, | |
| 24 | + | [West] = {x = 0, y = 0}, | |
| 25 | + | }, | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | local typesWingedMountedOffSet = { | |
| 29 | + | [0] = { | |
| 30 | + | [North] = {x = 0, y = 0}, | |
| 31 | + | [East] = {x = 0, y = 0}, | |
| 32 | + | [South] = {x = 0, y = 0}, | |
| 33 | + | [West] = {x = 0, y = 0}, | |
| 34 | + | }, | |
| 35 | + | [1] = { | |
| 36 | + | [North] = {x = -11, y = -8}, | |
| 37 | + | [East] = {x = -9, y = 0}, | |
| 38 | + | [South] = {x = 0, y = -8}, | |
| 39 | + | [West] = {x = -6, y = -13}, | |
| 40 | + | }, | |
| 41 | + | [2] = { | |
| 42 | + | [North] = {x = -11, y = -8}, | |
| 43 | + | [East] = {x = -9, y = 0}, | |
| 44 | + | [South] = {x = 0, y = -9}, | |
| 45 | + | [West] = {x = -6, y = -13}, | |
| 46 | + | }, | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | local WingsOffsets = { | |
| 50 | + | [1592] = { | |
| 51 | + | [1331] = typesWingedOffSet[1], | |
| 52 | + | [1338] = typesWingedOffSet[2], | |
| 53 | + | }, | |
| 54 | + | [1593] = { | |
| 55 | + | [1331] = typesWingedOffSet[1], | |
| 56 | + | [1338] = typesWingedOffSet[2], | |
| 57 | + | }, | |
| 58 | + | [1594] = { | |
| 59 | + | [1331] = typesWingedOffSet[1], | |
| 60 | + | [1338] = typesWingedOffSet[2], | |
| 61 | + | }, | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | local WingsMountedOffsets = { | |
| 65 | + | [1592] = { | |
| 66 | + | [1331] = typesWingedMountedOffSet[1], | |
| 67 | + | [1338] = typesWingedMountedOffSet[2], | |
| 68 | + | }, | |
| 69 | + | [1593] = { | |
| 70 | + | [1331] = typesWingedMountedOffSet[1], | |
| 71 | + | [1338] = typesWingedMountedOffSet[1], | |
| 72 | + | }, | |
| 73 | + | [1594] = { | |
| 74 | + | [1331] = typesWingedMountedOffSet[1], | |
| 75 | + | [1338] = typesWingedMountedOffSet[1], | |
| 76 | + | }, | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | local function translateDir(dir) | |
| 80 | + | if dir == NorthEast or dir == SouthEast then | |
| 81 | + | return East | |
| 82 | + | elseif dir == NorthWest or dir == SouthWest then | |
| 83 | + | return West | |
| 84 | + | end | |
| 85 | + | return dir | |
| 86 | + | end | |
| 87 | + | ||
| 88 | + | local function getWingsInformationOffset(wing, outfit, dir, isMounted) | |
| 89 | + | local offsetType = isMounted and WingsMountedOffsets[wing][outfit] or WingsOffsets[wing][outfit] | |
| 90 | + | ||
| 91 | + | if not offsetType then | |
| 92 | + | return {x = 0, y = 0} | |
| 93 | + | end | |
| 94 | + | ||
| 95 | + | local translatedDir = translateDir(dir) | |
| 96 | + | ||
| 97 | + | if not translatedDir then | |
| 98 | + | return {x = 0, y = 0} | |
| 99 | + | end | |
| 100 | + | ||
| 101 | + | local offset = offsetType[translatedDir] | |
| 102 | + | ||
| 103 | + | if not offset then | |
| 104 | + | return {x = 0, y = 0} | |
| 105 | + | end | |
| 106 | + | ||
| 107 | + | -- Manage auras | |
| 108 | + | if type(offset) == "table" and #offset > 0 then | |
| 109 | + | -- Here you can manipulate auras as needed | |
| 110 | + | -- For example, you can return a specific offset for auras | |
| 111 | + | return {x = offset[1], y = offset[2]} | |
| 112 | + | end | |
| 113 | + | ||
| 114 | + | return offset | |
| 115 | + | end | |
| 116 | + | ||
| 117 | + | local function setWingsOffset(creature) | |
| 118 | + | local outfit = creature:getOutfit() | |
| 119 | + | if outfit.wings and outfit.wings > 1 then | |
| 120 | + | local wings = creature:getWings() | |
| 121 | + | local direction = creature:getDirection() | |
| 122 | + | ||
| 123 | + | if wings and direction then | |
| 124 | + | local Offset = getWingsInformationOffset(wings, outfit.type, direction, outfit.mount and outfit.mount > 0) | |
| 125 | + | creature:setWingsOffset(Offset.x, Offset.y) | |
| 126 | + | else | |
| 127 | + | creature:setWingsOffset(0, 0) | |
| 128 | + | end | |
| 129 | + | else | |
| 130 | + | creature:setWingsOffset(0, 0) | |
| 131 | + | end | |
| 132 | + | end | |
| 133 | + | ||
| 134 | + | local function onDirectionChange(creature, oldDirection, newDirection) | |
| 135 | + | setWingsOffset(creature) | |
| 136 | + | end | |
| 137 | + | ||
| 138 | + | local function onPositionChange(player, newPos, oldPos) | |
| 139 | + | setWingsOffset(player) | |
| 140 | + | end | |
| 141 | + | ||
| 142 | + | function init() | |
| 143 | + | connect(LocalPlayer, {onOutfitChange = setWingsOffset, onWalk = setWingsOffset, onDirectionChange = onDirectionChange, onPositionChange = onPositionChange}) | |
| 144 | + | connect(Creature, { | |
| 145 | + | onAppear = setWingsOffset, | |
| 146 | + | onWalk = setWingsOffset, | |
| 147 | + | onOutfitChange = setWingsOffset, | |
| 148 | + | onDirectionChange = onDirectionChange, | |
| 149 | + | onPositionChange = onPositionChange | |
| 150 | + | }) | |
| 151 | + | end | |
| 152 | + | ||
| 153 | + | function terminate() | |
| 154 | + | disconnect(LocalPlayer, {onOutfitChange = setWingsOffset, onWalk = setWingsOffset, onDirectionChange = onDirectionChange, onPositionChange = onPositionChange}) | |
| 155 | + | disconnect(Creature, { | |
| 156 | + | onAppear = setWingsOffset, | |
| 157 | + | onWalk = setWingsOffset, | |
| 158 | + | onOutfitChange = setWingsOffset, | |
| 159 | + | onDirectionChange = onDirectionChange, | |
| 160 | + | onPositionChange = onPositionChange | |
| 161 | + | }) | |
| 162 | + | end |
| @@ -0,0 +1,10 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_wings | |
| 3 | + | description: changes the wings offset depending on the direction the player is going. | |
| 4 | + | autoload: true | |
| 5 | + | reloadable: true | |
| 6 | + | sandboxed: true | |
| 7 | + | version: 1.0 | |
| 8 | + | scripts: [ game_wings ] | |
| 9 | + | @onLoad: init() | |
| 10 | + | @onUnload: terminate() |
| @@ -0,0 +1,140 @@ | |||
| 1 | + | <!DOCTYPE html> | |
| 2 | + | <html lang="es"> | |
| 3 | + | <head> | |
| 4 | + | <meta charset="UTF-8"> | |
| 5 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 6 | + | <title>Offsets Editor</title> | |
| 7 | + | <link rel="stylesheet" href="styles.css"> | |
| 8 | + | </head> | |
| 9 | + | <body> | |
| 10 | + | <h1>Offsets Editor</h1> | |
| 11 | + | ||
| 12 | + | <div class="editor-container"> | |
| 13 | + | <div class="tab"> | |
| 14 | + | <button class="tablinks" onclick="openTab(event, 'typesWingedOffSet')">Wings OffSet List</button> | |
| 15 | + | <button class="tablinks" onclick="openTab(event, 'typesWingedMountedOffSet')">Wings Mounted OffSet List</button> | |
| 16 | + | <button class="tablinks" onclick="openTab(event, 'WingsOffsets')">Combiner Unmount</button> | |
| 17 | + | <button class="tablinks" onclick="openTab(event, 'WingsMountedOffsets')">Combiner Mounted</button> | |
| 18 | + | </div> | |
| 19 | + | ||
| 20 | + | <div id="typesWingedOffSet" class="tabcontent"> | |
| 21 | + | <h2>Wings OffSet</h2> | |
| 22 | + | <div id="typesWingedOffSetEntries"></div> | |
| 23 | + | <div class="entry-fields"> | |
| 24 | + | <label for="offsetIndex">Wings OffSet List:</label> | |
| 25 | + | <input type="number" id="offsetIndex" min="0" value="0"> | |
| 26 | + | <label for="comment1">OffSet Name:</label> | |
| 27 | + | <input type="text" id="comment1" placeholder="Name"> | |
| 28 | + | </div> | |
| 29 | + | <div class="offset-fields"> | |
| 30 | + | <div class="offset-group"> | |
| 31 | + | <label for="northX">North X:</label> | |
| 32 | + | <input type="number" id="northX" value="0"> | |
| 33 | + | <label for="northY">North Y:</label> | |
| 34 | + | <input type="number" id="northY" value="0"> | |
| 35 | + | </div> | |
| 36 | + | <div class="offset-group"> | |
| 37 | + | <label for="eastX">East X:</label> | |
| 38 | + | <input type="number" id="eastX" value="0"> | |
| 39 | + | <label for="eastY">East Y:</label> | |
| 40 | + | <input type="number" id="eastY" value="0"> | |
| 41 | + | </div> | |
| 42 | + | <div class="offset-group"> | |
| 43 | + | <label for="southX">South X:</label> | |
| 44 | + | <input type="number" id="southX" value="0"> | |
| 45 | + | <label for="southY">South Y:</label> | |
| 46 | + | <input type="number" id="southY" value="0"> | |
| 47 | + | </div> | |
| 48 | + | <div class="offset-group"> | |
| 49 | + | <label for="westX">West X:</label> | |
| 50 | + | <input type="number" id="westX" value="0"> | |
| 51 | + | <label for="westY">West Y:</label> | |
| 52 | + | <input type="number" id="westY" value="0"> | |
| 53 | + | </div> | |
| 54 | + | </div> | |
| 55 | + | <button onclick="addEntry('typesWingedOffSet')">Agregar Entrada</button> | |
| 56 | + | </div> | |
| 57 | + | ||
| 58 | + | <div id="typesWingedMountedOffSet" class="tabcontent" style="display:none"> | |
| 59 | + | <h2>Wings Mounted OffSet</h2> | |
| 60 | + | <div id="typesWingedMountedOffSetEntries"></div> | |
| 61 | + | <div class="entry-fields"> | |
| 62 | + | <label for="mountedOffsetIndex">Wings Mounted Offsets List:</label> | |
| 63 | + | <input type="number" id="mountedOffsetIndex" min="0" value="0"> | |
| 64 | + | <label for="comment2">OffSet Name:</label> | |
| 65 | + | <input type="text" id="comment2" placeholder="Name"> | |
| 66 | + | </div> | |
| 67 | + | <div class="offset-fields"> | |
| 68 | + | <div class="offset-group"> | |
| 69 | + | <label for="mountedNorthX">North X:</label> | |
| 70 | + | <input type="number" id="mountedNorthX" value="0"> | |
| 71 | + | <label for="mountedNorthY">North Y:</label> | |
| 72 | + | <input type="number" id="mountedNorthY" value="0"> | |
| 73 | + | </div> | |
| 74 | + | <div class="offset-group"> | |
| 75 | + | <label for="mountedEastX">East X:</label> | |
| 76 | + | <input type="number" id="mountedEastX" value="0"> | |
| 77 | + | <label for="mountedEastY">East Y:</label> | |
| 78 | + | <input type="number" id="mountedEastY" value="0"> | |
| 79 | + | </div> | |
| 80 | + | <div class="offset-group"> | |
| 81 | + | <label for="mountedSouthX">South X:</label> | |
| 82 | + | <input type="number" id="mountedSouthX" value="0"> | |
| 83 | + | <label for="mountedSouthY">South Y:</label> | |
| 84 | + | <input type="number" id="mountedSouthY" value="0"> | |
| 85 | + | </div> | |
| 86 | + | <div class="offset-group"> | |
| 87 | + | <label for="mountedWestX">West X:</label> | |
| 88 | + | <input type="number" id="mountedWestX" value="0"> | |
| 89 | + | <label for="mountedWestY">West Y:</label> | |
| 90 | + | <input type="number" id="mountedWestY" value="0"> | |
| 91 | + | </div> | |
| 92 | + | </div> | |
| 93 | + | <button onclick="addEntry('typesWingedMountedOffSet')">Agregar Entrada</button> | |
| 94 | + | </div> | |
| 95 | + | ||
| 96 | + | <div id="WingsOffsets" class="tabcontent" style="display:none"> | |
| 97 | + | <h2>Wings + Outfits or Aura Combination List</h2> | |
| 98 | + | <div id="WingsOffsetsEntries"></div> | |
| 99 | + | <div class="entry-fields"> | |
| 100 | + | <label for="offsetIndexWings">Wings Offsets List:</label> | |
| 101 | + | <input type="number" id="offsetIndexWings" min="0" value="0"> | |
| 102 | + | <label for="comment3">Combination Name:</label> | |
| 103 | + | <input type="text" id="comment3" placeholder="Name"> | |
| 104 | + | </div> | |
| 105 | + | <div class="entry-fields"> | |
| 106 | + | <label for="wingID">WingID:</label> | |
| 107 | + | <input type="number" id="wingID" min="0" value="1592"> | |
| 108 | + | <label for="outfitID">OutfitID:</label> | |
| 109 | + | <input type="number" id="outfitID" min="0" value="1331"> | |
| 110 | + | </div> | |
| 111 | + | <button onclick="addEntry('WingsOffsets')">Agregar Entrada</button> | |
| 112 | + | </div> | |
| 113 | + | ||
| 114 | + | <div id="WingsMountedOffsets" class="tabcontent" style="display:none"> | |
| 115 | + | <h2>Wings + Outfits or Aura Mounted Combination</h2> | |
| 116 | + | <div id="WingsMountedOffsetsEntries"></div> | |
| 117 | + | <div class="entry-fields"> | |
| 118 | + | <label for="offsetIndexMounted">Wings Mounted Offsets List:</label> | |
| 119 | + | <input type="number" id="offsetIndexMounted" min="0" value="0"> | |
| 120 | + | <label for="comment4">Combination Name:</label> | |
| 121 | + | <input type="text" id="comment4" placeholder="Name"> | |
| 122 | + | </div> | |
| 123 | + | <div class="entry-fields"> | |
| 124 | + | <label for="mountedWingID">WingID:</label> | |
| 125 | + | <input type="number" id="mountedWingID" min="0" value="1592"> | |
| 126 | + | <label for="mountedOutfitID">OutfitID:</label> | |
| 127 | + | <input type="number" id="mountedOutfitID" min="0" value="1331"> | |
| 128 | + | </div> | |
| 129 | + | <button onclick="addEntry('WingsMountedOffsets')">Agregar Entrada</button> | |
| 130 | + | </div> | |
| 131 | + | ||
| 132 | + | <div class="editor-section"> | |
| 133 | + | <h2>Guardar Cambios</h2> | |
| 134 | + | <button id="saveButton">Guardar</button> | |
| 135 | + | </div> | |
| 136 | + | </div> | |
| 137 | + | ||
| 138 | + | <script src="script.js"></script> | |
| 139 | + | </body> | |
| 140 | + | </html> |
| @@ -0,0 +1,216 @@ | |||
| 1 | + | const saveButton = document.getElementById('saveButton'); | |
| 2 | + | ||
| 3 | + | function openTab(evt, tabName) { | |
| 4 | + | const tabContents = document.getElementsByClassName("tabcontent"); | |
| 5 | + | for (let i = 0; i < tabContents.length; i++) { | |
| 6 | + | tabContents[i].style.display = "none"; | |
| 7 | + | } | |
| 8 | + | document.getElementById(tabName).style.display = "block"; | |
| 9 | + | ||
| 10 | + | const tablinks = document.getElementsByClassName("tablinks"); | |
| 11 | + | for (let i = 0; i < tablinks.length; i++) { | |
| 12 | + | tablinks[i].className = tablinks[i].className.replace(" active", ""); | |
| 13 | + | } | |
| 14 | + | evt.currentTarget.className += " active"; | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | function addEntry(type) { | |
| 18 | + | let entryContainer; | |
| 19 | + | let newEntry; | |
| 20 | + | ||
| 21 | + | switch (type) { | |
| 22 | + | case 'typesWingedOffSet': | |
| 23 | + | entryContainer = document.getElementById('typesWingedOffSetEntries'); | |
| 24 | + | const offsetIndex = document.getElementById('offsetIndex'); | |
| 25 | + | const comment1 = document.getElementById('comment1'); | |
| 26 | + | const northX = document.getElementById('northX'); | |
| 27 | + | const northY = document.getElementById('northY'); | |
| 28 | + | const eastX = document.getElementById('eastX'); | |
| 29 | + | const eastY = document.getElementById('eastY'); | |
| 30 | + | const southX = document.getElementById('southX'); | |
| 31 | + | const southY = document.getElementById('southY'); | |
| 32 | + | const westX = document.getElementById('westX'); | |
| 33 | + | const westY = document.getElementById('westY'); | |
| 34 | + | ||
| 35 | + | if (!entryContainer || !offsetIndex || !comment1 || !northX || !northY || !eastX || !eastY || !southX || !southY || !westX || !westY) { | |
| 36 | + | console.error('Uno o más elementos están faltando para typesWingedOffSet'); | |
| 37 | + | return; | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | const offsetIndexValue = parseInt(offsetIndex.value); | |
| 41 | + | const northXValue = parseInt(northX.value); | |
| 42 | + | const northYValue = parseInt(northY.value); | |
| 43 | + | const eastXValue = parseInt(eastX.value); | |
| 44 | + | const eastYValue = parseInt(eastY.value); | |
| 45 | + | const southXValue = parseInt(southX.value); | |
| 46 | + | const southYValue = parseInt(southY.value); | |
| 47 | + | const westXValue = parseInt(westX.value); | |
| 48 | + | const westYValue = parseInt(westY.value); | |
| 49 | + | ||
| 50 | + | const existingEntry1 = Array.from(entryContainer.getElementsByClassName('entry')).find(entry => { | |
| 51 | + | return entry.innerText.includes(`[${offsetIndexValue}]`); | |
| 52 | + | }); | |
| 53 | + | ||
| 54 | + | if (existingEntry1) { | |
| 55 | + | existingEntry1.innerHTML = ` | |
| 56 | + | [${offsetIndexValue}] = { -- ${comment1.value} | |
| 57 | + | [North] = {x = ${northXValue}, y = ${northYValue}}, | |
| 58 | + | [East] = {x = ${eastXValue}, y = ${eastYValue}}, | |
| 59 | + | [South] = {x = ${southXValue}, y = ${southYValue}}, | |
| 60 | + | [West] = {x = ${westXValue}, y = ${westYValue}}, | |
| 61 | + | },`; | |
| 62 | + | } else { | |
| 63 | + | newEntry = ` | |
| 64 | + | <div class="entry"> | |
| 65 | + | [${offsetIndexValue}] = { -- ${comment1.value} | |
| 66 | + | [North] = {x = ${northXValue}, y = ${northYValue}}, | |
| 67 | + | [East] = {x = ${eastXValue}, y = ${eastYValue}}, | |
| 68 | + | [South] = {x = ${southXValue}, y = ${southYValue}}, | |
| 69 | + | [West] = {x = ${westXValue}, y = ${westYValue}}, | |
| 70 | + | }, | |
| 71 | + | </div>`; | |
| 72 | + | entryContainer.innerHTML += newEntry; | |
| 73 | + | } | |
| 74 | + | break; | |
| 75 | + | ||
| 76 | + | case 'typesWingedMountedOffSet': | |
| 77 | + | entryContainer = document.getElementById('typesWingedMountedOffSetEntries'); | |
| 78 | + | const mountedOffsetIndex = parseInt(document.getElementById('mountedOffsetIndex').value); | |
| 79 | + | const comment2 = document.getElementById('comment2').value; | |
| 80 | + | const mountedNorthX = parseInt(document.getElementById('mountedNorthX').value); | |
| 81 | + | const mountedNorthY = parseInt(document.getElementById('mountedNorthY').value); | |
| 82 | + | const mountedEastX = parseInt(document.getElementById('mountedEastX').value); | |
| 83 | + | const mountedEastY = parseInt(document.getElementById('mountedEastY').value); | |
| 84 | + | const mountedSouthX = parseInt(document.getElementById('mountedSouthX').value); | |
| 85 | + | const mountedSouthY = parseInt(document.getElementById('mountedSouthY').value); | |
| 86 | + | const mountedWestX = parseInt(document.getElementById('mountedWestX').value); | |
| 87 | + | const mountedWestY = parseInt(document.getElementById('mountedWestY').value); | |
| 88 | + | ||
| 89 | + | if (!entryContainer || isNaN(mountedOffsetIndex) || isNaN(mountedNorthX) || isNaN(mountedNorthY) || isNaN(mountedEastX) || isNaN(mountedEastY) || isNaN(mountedSouthX) || isNaN(mountedSouthY) || isNaN(mountedWestX) || isNaN(mountedWestY) || comment2 === undefined) { | |
| 90 | + | console.error('Uno o más elementos están faltando para typesWingedMountedOffSet'); | |
| 91 | + | return; | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | const existingEntry2 = Array.from(entryContainer.getElementsByClassName('entry')).find(entry => { | |
| 95 | + | return entry.innerText.includes(`[${mountedOffsetIndex}]`); | |
| 96 | + | }); | |
| 97 | + | ||
| 98 | + | if (existingEntry2) { | |
| 99 | + | existingEntry2.innerHTML = ` | |
| 100 | + | [${mountedOffsetIndex}] = { -- ${comment2} | |
| 101 | + | [North] = {x = ${mountedNorthX}, y = ${mountedNorthY}}, | |
| 102 | + | [East] = {x = ${mountedEastX}, y = ${mountedEastY}}, | |
| 103 | + | [South] = {x = ${mountedSouthX}, y = ${mountedSouthY}}, | |
| 104 | + | [West] = {x = ${mountedWestX}, y = ${mountedWestY}}, | |
| 105 | + | },`; | |
| 106 | + | } else { | |
| 107 | + | newEntry = ` | |
| 108 | + | <div class="entry"> | |
| 109 | + | [${mountedOffsetIndex}] = { -- ${comment2} | |
| 110 | + | [North] = {x = ${mountedNorthX}, y = ${mountedNorthY}}, | |
| 111 | + | [East] = {x = ${mountedEastX}, y = ${mountedEastY}}, | |
| 112 | + | [South] = {x = ${mountedSouthX}, y = ${mountedSouthY}}, | |
| 113 | + | [West] = {x = ${mountedWestX}, y = ${mountedWestY}}, | |
| 114 | + | }, | |
| 115 | + | </div>`; | |
| 116 | + | entryContainer.innerHTML += newEntry; | |
| 117 | + | } | |
| 118 | + | break; | |
| 119 | + | ||
| 120 | + | case 'WingsOffsets': | |
| 121 | + | entryContainer = document.getElementById('WingsOffsetsEntries'); | |
| 122 | + | const wingID = parseInt(document.getElementById('wingID').value); | |
| 123 | + | const outfitID = parseInt(document.getElementById('outfitID').value); | |
| 124 | + | const comment3 = document.getElementById('comment3').value; | |
| 125 | + | ||
| 126 | + | if (!entryContainer || isNaN(wingID) || isNaN(outfitID) || comment3 === undefined) { | |
| 127 | + | console.error('Uno o más elementos están faltando para WingsOffsets'); | |
| 128 | + | return; | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | const existingWingsEntry = Array.from(entryContainer.getElementsByClassName('entry')).find(entry => { | |
| 132 | + | return entry.innerText.includes(`[${wingID}]`); | |
| 133 | + | }); | |
| 134 | + | ||
| 135 | + | if (existingWingsEntry) { | |
| 136 | + | existingWingsEntry.innerHTML = ` | |
| 137 | + | [${wingID}] = { -- ${comment3} | |
| 138 | + | [${outfitID}] = typesWingedOffSet[1], | |
| 139 | + | },`; | |
| 140 | + | } else { | |
| 141 | + | newEntry = ` | |
| 142 | + | <div class="entry"> | |
| 143 | + | [${wingID}] = { -- ${comment3} | |
| 144 | + | [${outfitID}] = typesWingedOffSet[], | |
| 145 | + | }, | |
| 146 | + | </div>`; | |
| 147 | + | entryContainer.innerHTML += newEntry; | |
| 148 | + | } | |
| 149 | + | break; | |
| 150 | + | ||
| 151 | + | case 'WingsMountedOffsets': | |
| 152 | + | entryContainer = document.getElementById('WingsMountedOffsetsEntries'); | |
| 153 | + | const mountedWingID = parseInt(document.getElementById('mountedWingID').value); | |
| 154 | + | const mountedOutfitID = parseInt(document.getElementById('mountedOutfitID').value); | |
| 155 | + | const comment4 = document.getElementById('comment4').value; | |
| 156 | + | ||
| 157 | + | if (!entryContainer || isNaN(mountedWingID) || isNaN(mountedOutfitID) || comment4 === undefined) { | |
| 158 | + | console.error('Uno o más elementos están faltando para WingsMountedOffsets'); | |
| 159 | + | return; | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | const existingMountedWingsEntry = Array.from(entryContainer.getElementsByClassName('entry')).find(entry => { | |
| 163 | + | return entry.innerText.includes(`[${mountedWingID}]`); | |
| 164 | + | }); | |
| 165 | + | ||
| 166 | + | if (existingMountedWingsEntry) { | |
| 167 | + | existingMountedWingsEntry.innerHTML = ` | |
| 168 | + | [${mountedWingID}] = { -- ${comment4} | |
| 169 | + | [${mountedOutfitID}] = typesWingedMountedOffSet[1], | |
| 170 | + | },`; | |
| 171 | + | } else { | |
| 172 | + | newEntry = ` | |
| 173 | + | <div class="entry"> | |
| 174 | + | [${mountedWingID}] = { -- ${comment4} | |
| 175 | + | [${mountedOutfitID}] = typesWingedMountedOffSet[1], | |
| 176 | + | }, | |
| 177 | + | </div>`; | |
| 178 | + | entryContainer.innerHTML += newEntry; | |
| 179 | + | } | |
| 180 | + | break; | |
| 181 | + | } | |
| 182 | + | } | |
| 183 | + | ||
| 184 | + | ||
| 185 | + | // Rueda del mouse para cambiar valores | |
| 186 | + | document.querySelectorAll('input[type="number"]').forEach(input => { | |
| 187 | + | input.addEventListener('wheel', (event) => { | |
| 188 | + | event.preventDefault(); | |
| 189 | + | const delta = event.deltaY < 0 ? 1 : -1; | |
| 190 | + | input.value = parseInt(input.value) + delta; | |
| 191 | + | }); | |
| 192 | + | }); | |
| 193 | + | ||
| 194 | + | saveButton.addEventListener('click', saveStructure); | |
| 195 | + | ||
| 196 | + | function saveStructure() { | |
| 197 | + | const typesWingedOffSetContent = document.getElementById('typesWingedOffSetEntries').innerText.trim(); | |
| 198 | + | const typesWingedMountedOffSetContent = document.getElementById('typesWingedMountedOffSetEntries').innerText.trim(); | |
| 199 | + | const wingsOffsetsContent = document.getElementById('WingsOffsetsEntries').innerText.trim(); | |
| 200 | + | const wingsMountedOffsetsContent = document.getElementById('WingsMountedOffsetsEntries').innerText.trim(); | |
| 201 | + | ||
| 202 | + | const content = `typesWingedOffSet = {\n${typesWingedOffSetContent}\n}\n\n` + | |
| 203 | + | `typesWingedMountedOffSet = {\n${typesWingedMountedOffSetContent}\n}\n\n` + | |
| 204 | + | `WingsOffsets = {\n${wingsOffsetsContent}\n}\n\n` + | |
| 205 | + | `WingsMountedOffsets = {\n${wingsMountedOffsetsContent}\n}`; | |
| 206 | + | ||
| 207 | + | const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); | |
| 208 | + | const url = window.URL.createObjectURL(blob); | |
| 209 | + | ||
| 210 | + | const downloadLink = document.createElement('a'); | |
| 211 | + | downloadLink.href = url; | |
| 212 | + | downloadLink.download = 'types_wings_structure.lua'; | |
| 213 | + | downloadLink.click(); | |
| 214 | + | ||
| 215 | + | window.URL.revokeObjectURL(url); | |
| 216 | + | } |
| @@ -0,0 +1,131 @@ | |||
| 1 | + | body { | |
| 2 | + | font-family: Arial, sans-serif; | |
| 3 | + | margin: 0; | |
| 4 | + | padding: 50px; | |
| 5 | + | background-color: #f9f9f9; | |
| 6 | + | } | |
| 7 | + | ||
| 8 | + | .editor-container { | |
| 9 | + | display: flex; | |
| 10 | + | flex-direction: column; | |
| 11 | + | max-width: 800px; | |
| 12 | + | margin: 0 auto; | |
| 13 | + | gap: 20px; | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | .tab { | |
| 17 | + | display: flex; | |
| 18 | + | justify-content: center; | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | .tab button { | |
| 22 | + | background-color: #007BFF; | |
| 23 | + | color: white; | |
| 24 | + | border: none; | |
| 25 | + | padding: 10px 20px; | |
| 26 | + | cursor: pointer; | |
| 27 | + | transition: background-color 0.3s; | |
| 28 | + | border-radius: 5px; | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | .tab button:hover { | |
| 32 | + | background-color: #0056b3; | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | .tabcontent { | |
| 36 | + | padding: 10px; | |
| 37 | + | background-color: black; | |
| 38 | + | border-radius: 5px; | |
| 39 | + | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | |
| 40 | + | color: white; | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | h1, h2 { | |
| 44 | + | text-align: center; | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | .entry-fields { | |
| 48 | + | display: flex; | |
| 49 | + | justify-content: center; | |
| 50 | + | align-items: center; | |
| 51 | + | margin-bottom: 10px; | |
| 52 | + | gap: 10px; | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | .entry-fields input { | |
| 56 | + | width: 80px; | |
| 57 | + | padding: 5px; | |
| 58 | + | border: 1px solid #ccc; | |
| 59 | + | border-radius: 3px; | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | .offset-fields { | |
| 63 | + | display: flex; | |
| 64 | + | justify-content: center; | |
| 65 | + | gap: 20px; | |
| 66 | + | flex-wrap: wrap; | |
| 67 | + | margin-top: 10px; | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | .offset-group { | |
| 71 | + | display: flex; | |
| 72 | + | flex-direction: column; | |
| 73 | + | align-items: center; | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | .offset-group label { | |
| 77 | + | margin-bottom: 5px; | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | .offset-group input { | |
| 81 | + | width: 60px; | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | .entry { | |
| 85 | + | margin-bottom: 10px; | |
| 86 | + | white-space: pre-wrap; /* Mantener el formato de los espacios */ | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | .editor-section { | |
| 90 | + | text-align: center; | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | button { | |
| 94 | + | padding: 10px 15px; | |
| 95 | + | background-color: #28a745; | |
| 96 | + | color: white; | |
| 97 | + | border: none; | |
| 98 | + | border-radius: 5px; | |
| 99 | + | cursor: pointer; | |
| 100 | + | transition: background-color 0.3s; | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | button:hover { | |
| 104 | + | background-color: #218838; | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | <script> | |
| 108 | + | function openTab(evt, tabName) { | |
| 109 | + | var i, tabcontent, tablinks; | |
| 110 | + | tabcontent = document.getElementsByClassName("tabcontent"); | |
| 111 | + | for (i = 0; i < tabcontent.length; i++) { | |
| 112 | + | tabcontent[i].style.display = "none"; | |
| 113 | + | } | |
| 114 | + | tablinks = document.getElementsByClassName("tablinks"); | |
| 115 | + | for (i = 0; i < tablinks.length; i++) { | |
| 116 | + | tablinks[i].className = tablinks[i].className.replace(" active", ""); | |
| 117 | + | } | |
| 118 | + | document.getElementById(tabName).style.display = "block"; | |
| 119 | + | evt.currentTarget.className += " active"; | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | // Abre la primera pestaña por defecto | |
| 123 | + | document.addEventListener('DOMContentLoaded', function() { | |
| 124 | + | var firstTab = document.getElementsByClassName("tablinks")[0]; | |
| 125 | + | if (firstTab) { | |
| 126 | + | firstTab.click(); | |
| 127 | + | } | |
| 128 | + | }); | |
| 129 | + | </script> | |
| 130 | + | ||
| 131 | + | ``` |
| @@ -0,0 +1,140 @@ | |||
| 1 | + | <!DOCTYPE html> | |
| 2 | + | <html lang="es"> | |
| 3 | + | <head> | |
| 4 | + | <meta charset="UTF-8"> | |
| 5 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 6 | + | <title>Offsets Editor</title> | |
| 7 | + | <link rel="stylesheet" href="styles.css"> | |
| 8 | + | </head> | |
| 9 | + | <body> | |
| 10 | + | <h1>Offsets Editor</h1> | |
| 11 | + | ||
| 12 | + | <div class="editor-container"> | |
| 13 | + | <div class="tab"> | |
| 14 | + | <button class="tablinks" onclick="openTab(event, 'typesWingedOffSet')">Wings OffSet List</button> | |
| 15 | + | <button class="tablinks" onclick="openTab(event, 'typesWingedMountedOffSet')">Wings Mounted OffSet List</button> | |
| 16 | + | <button class="tablinks" onclick="openTab(event, 'WingsOffsets')">Combiner Unmount</button> | |
| 17 | + | <button class="tablinks" onclick="openTab(event, 'WingsMountedOffsets')">Combiner Mounted</button> | |
| 18 | + | </div> | |
| 19 | + | ||
| 20 | + | <div id="typesWingedOffSet" class="tabcontent"> | |
| 21 | + | <h2>Wings OffSet</h2> | |
| 22 | + | <div id="typesWingedOffSetEntries"></div> | |
| 23 | + | <div class="entry-fields"> | |
| 24 | + | <label for="offsetIndex">Wings OffSet List:</label> | |
| 25 | + | <input type="number" id="offsetIndex" min="0" value="0"> | |
| 26 | + | <label for="comment1">OffSet Name:</label> | |
| 27 | + | <input type="text" id="comment1" placeholder="Name"> | |
| 28 | + | </div> | |
| 29 | + | <div class="offset-fields"> | |
| 30 | + | <div class="offset-group"> | |
| 31 | + | <label for="northX">North X:</label> | |
| 32 | + | <input type="number" id="northX" value="0"> | |
| 33 | + | <label for="northY">North Y:</label> | |
| 34 | + | <input type="number" id="northY" value="0"> | |
| 35 | + | </div> | |
| 36 | + | <div class="offset-group"> | |
| 37 | + | <label for="eastX">East X:</label> | |
| 38 | + | <input type="number" id="eastX" value="0"> | |
| 39 | + | <label for="eastY">East Y:</label> | |
| 40 | + | <input type="number" id="eastY" value="0"> | |
| 41 | + | </div> | |
| 42 | + | <div class="offset-group"> | |
| 43 | + | <label for="southX">South X:</label> | |
| 44 | + | <input type="number" id="southX" value="0"> | |
| 45 | + | <label for="southY">South Y:</label> | |
| 46 | + | <input type="number" id="southY" value="0"> | |
| 47 | + | </div> | |
| 48 | + | <div class="offset-group"> | |
| 49 | + | <label for="westX">West X:</label> | |
| 50 | + | <input type="number" id="westX" value="0"> | |
| 51 | + | <label for="westY">West Y:</label> | |
| 52 | + | <input type="number" id="westY" value="0"> | |
| 53 | + | </div> | |
| 54 | + | </div> | |
| 55 | + | <button onclick="addEntry('typesWingedOffSet')">Agregar Entrada</button> | |
| 56 | + | </div> | |
| 57 | + | ||
| 58 | + | <div id="typesWingedMountedOffSet" class="tabcontent" style="display:none"> | |
| 59 | + | <h2>Wings Mounted OffSet</h2> | |
| 60 | + | <div id="typesWingedMountedOffSetEntries"></div> | |
| 61 | + | <div class="entry-fields"> | |
| 62 | + | <label for="mountedOffsetIndex">Wings Mounted Offsets List:</label> | |
| 63 | + | <input type="number" id="mountedOffsetIndex" min="0" value="0"> | |
| 64 | + | <label for="comment2">OffSet Name:</label> | |
| 65 | + | <input type="text" id="comment2" placeholder="Name"> | |
| 66 | + | </div> | |
| 67 | + | <div class="offset-fields"> | |
| 68 | + | <div class="offset-group"> | |
| 69 | + | <label for="mountedNorthX">North X:</label> | |
| 70 | + | <input type="number" id="mountedNorthX" value="0"> | |
| 71 | + | <label for="mountedNorthY">North Y:</label> | |
| 72 | + | <input type="number" id="mountedNorthY" value="0"> | |
| 73 | + | </div> | |
| 74 | + | <div class="offset-group"> | |
| 75 | + | <label for="mountedEastX">East X:</label> | |
| 76 | + | <input type="number" id="mountedEastX" value="0"> | |
| 77 | + | <label for="mountedEastY">East Y:</label> | |
| 78 | + | <input type="number" id="mountedEastY" value="0"> | |
| 79 | + | </div> | |
| 80 | + | <div class="offset-group"> | |
| 81 | + | <label for="mountedSouthX">South X:</label> | |
| 82 | + | <input type="number" id="mountedSouthX" value="0"> | |
| 83 | + | <label for="mountedSouthY">South Y:</label> | |
| 84 | + | <input type="number" id="mountedSouthY" value="0"> | |
| 85 | + | </div> | |
| 86 | + | <div class="offset-group"> | |
| 87 | + | <label for="mountedWestX">West X:</label> | |
| 88 | + | <input type="number" id="mountedWestX" value="0"> | |
| 89 | + | <label for="mountedWestY">West Y:</label> | |
| 90 | + | <input type="number" id="mountedWestY" value="0"> | |
| 91 | + | </div> | |
| 92 | + | </div> | |
| 93 | + | <button onclick="addEntry('typesWingedMountedOffSet')">Agregar Entrada</button> | |
| 94 | + | </div> | |
| 95 | + | ||
| 96 | + | <div id="WingsOffsets" class="tabcontent" style="display:none"> | |
| 97 | + | <h2>Wings + Outfits or Aura Combination List</h2> | |
| 98 | + | <div id="WingsOffsetsEntries"></div> | |
| 99 | + | <div class="entry-fields"> | |
| 100 | + | <label for="offsetIndexWings">Wings Offsets List:</label> | |
| 101 | + | <input type="number" id="offsetIndexWings" min="0" value="0"> | |
| 102 | + | <label for="comment3">Combination Name:</label> | |
| 103 | + | <input type="text" id="comment3" placeholder="Name"> | |
| 104 | + | </div> | |
| 105 | + | <div class="entry-fields"> | |
| 106 | + | <label for="wingID">WingID:</label> | |
| 107 | + | <input type="number" id="wingID" min="0" value="1592"> | |
| 108 | + | <label for="outfitID">OutfitID:</label> | |
| 109 | + | <input type="number" id="outfitID" min="0" value="1331"> | |
| 110 | + | </div> | |
| 111 | + | <button onclick="addEntry('WingsOffsets')">Agregar Entrada</button> | |
| 112 | + | </div> | |
| 113 | + | ||
| 114 | + | <div id="WingsMountedOffsets" class="tabcontent" style="display:none"> | |
| 115 | + | <h2>Wings + Outfits or Aura Mounted Combination</h2> | |
| 116 | + | <div id="WingsMountedOffsetsEntries"></div> | |
| 117 | + | <div class="entry-fields"> | |
| 118 | + | <label for="offsetIndexMounted">Wings Mounted Offsets List:</label> | |
| 119 | + | <input type="number" id="offsetIndexMounted" min="0" value="0"> | |
| 120 | + | <label for="comment4">Combination Name:</label> | |
| 121 | + | <input type="text" id="comment4" placeholder="Name"> | |
| 122 | + | </div> | |
| 123 | + | <div class="entry-fields"> | |
| 124 | + | <label for="mountedWingID">WingID:</label> | |
| 125 | + | <input type="number" id="mountedWingID" min="0" value="1592"> | |
| 126 | + | <label for="mountedOutfitID">OutfitID:</label> | |
| 127 | + | <input type="number" id="mountedOutfitID" min="0" value="1331"> | |
| 128 | + | </div> | |
| 129 | + | <button onclick="addEntry('WingsMountedOffsets')">Agregar Entrada</button> | |
| 130 | + | </div> | |
| 131 | + | ||
| 132 | + | <div class="editor-section"> | |
| 133 | + | <h2>Guardar Cambios</h2> | |
| 134 | + | <button id="saveButton">Guardar</button> | |
| 135 | + | </div> | |
| 136 | + | </div> | |
| 137 | + | ||
| 138 | + | <script src="script.js"></script> | |
| 139 | + | </body> | |
| 140 | + | </html> |