diff --git a/data/fonts/cipsoftFont.otfont b/data/fonts/cipsoftFont.otfont index 6fb2f70..14de8dd 100644 --- a/data/fonts/cipsoftFont.otfont +++ b/data/fonts/cipsoftFont.otfont @@ -1,6 +1,6 @@ -Font - name: cipsoftFont - texture: cipsoftFont - height: 8 - glyph-size: 8 8 - space-width: 2 +Font + name: cipsoftFont + texture: cipsoftFont + height: 8 + glyph-size: 8 8 + space-width: 2 diff --git a/data/images/shaders/brazil.png b/data/images/shaders/brazil.png new file mode 100644 index 0000000..0c75047 Binary files /dev/null and b/data/images/shaders/brazil.png differ diff --git a/data/images/shaders/gold.png b/data/images/shaders/gold.png new file mode 100644 index 0000000..5bb7700 Binary files /dev/null and b/data/images/shaders/gold.png differ diff --git a/data/images/shaders/rainbow.png b/data/images/shaders/rainbow.png new file mode 100644 index 0000000..2ee8654 Binary files /dev/null and b/data/images/shaders/rainbow.png differ diff --git a/data/images/shaders/stars.png b/data/images/shaders/stars.png new file mode 100644 index 0000000..722e390 Binary files /dev/null and b/data/images/shaders/stars.png differ diff --git a/data/images/shaders/sweden.png b/data/images/shaders/sweden.png new file mode 100644 index 0000000..e7a52d4 Binary files /dev/null and b/data/images/shaders/sweden.png differ diff --git a/data/shaders/map_default_fragment.frag b/data/shaders/map_default_fragment.frag new file mode 100644 index 0000000..4e7785a --- /dev/null +++ b/data/shaders/map_default_fragment.frag @@ -0,0 +1,11 @@ + +varying vec2 v_TexCoord; +uniform vec4 u_Color; +uniform sampler2D u_Tex0; + +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord) * u_Color; + if(gl_FragColor.a < 0.01) + discard; +} diff --git a/data/shaders/map_default_vertex.frag b/data/shaders/map_default_vertex.frag new file mode 100644 index 0000000..0d5f690 --- /dev/null +++ b/data/shaders/map_default_vertex.frag @@ -0,0 +1,14 @@ + +attribute vec2 a_Vertex; +attribute vec2 a_TexCoord; + +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; +uniform mat3 u_TextureMatrix; +varying vec2 v_TexCoord; + +void main() +{ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; +} diff --git a/data/shaders/map_rainbow_fragment.frag b/data/shaders/map_rainbow_fragment.frag new file mode 100644 index 0000000..6757750 --- /dev/null +++ b/data/shaders/map_rainbow_fragment.frag @@ -0,0 +1,15 @@ + +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; + +uniform vec4 u_Color; +uniform sampler2D u_Tex0; +uniform sampler2D u_Tex1; + +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord) * u_Color; + gl_FragColor += texture2D(u_Tex1, v_TexCoord2); + if(gl_FragColor.a < 0.01) + discard; +} diff --git a/data/shaders/map_rainbow_vertex.frag b/data/shaders/map_rainbow_vertex.frag new file mode 100644 index 0000000..f38475b --- /dev/null +++ b/data/shaders/map_rainbow_vertex.frag @@ -0,0 +1,33 @@ +attribute vec2 a_TexCoord; +attribute vec2 a_Vertex; + +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; + +uniform mat3 u_TextureMatrix; +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; + +uniform vec2 u_Offset; +uniform vec2 u_Center; +uniform float u_Time; + +vec2 effectTextureSize = vec2(466.0, 342.0); +vec2 direction = vec2(1.0,0.2); +float speed = 200.0; + +vec2 rotate(vec2 v, float a) { + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +void main() +{ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; + + v_TexCoord2 = ((a_Vertex + direction * u_Time * speed) / effectTextureSize); +} + diff --git a/data/shaders/outfit_rainbow_fragment.frag b/data/shaders/outfit_rainbow_fragment.frag new file mode 100644 index 0000000..3c884a8 --- /dev/null +++ b/data/shaders/outfit_rainbow_fragment.frag @@ -0,0 +1,16 @@ +uniform mat4 u_Color; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +varying vec2 v_TexCoord3; +uniform sampler2D u_Tex0; +uniform sampler2D u_Tex1; +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord); + vec4 texcolor = texture2D(u_Tex0, v_TexCoord2); + vec4 effectColor = texture2D(u_Tex1, v_TexCoord3); + if(texcolor.a > 0.1) { + gl_FragColor *= effectColor; + } + if(gl_FragColor.a < 0.01) discard; +} \ No newline at end of file diff --git a/data/shaders/outfit_rainbow_vertex.frag b/data/shaders/outfit_rainbow_vertex.frag new file mode 100644 index 0000000..ca554bd --- /dev/null +++ b/data/shaders/outfit_rainbow_vertex.frag @@ -0,0 +1,47 @@ +attribute vec2 a_TexCoord; +uniform mat3 u_TextureMatrix; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +varying vec2 v_TexCoord3; +attribute vec2 a_Vertex; +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; +uniform vec2 u_Offset; +uniform vec2 u_Center; +uniform float u_Time; + +vec2 effectTextureSize = vec2(466.0, 342.0); +vec2 direction = vec2(1.0,0.2); +float speed = 200.0; + +vec2 rotate(vec2 v, float a) { + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +void main() +{ + vec2 offset = direction * speed * u_Time; + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; + v_TexCoord2 = (u_TextureMatrix * vec3(a_TexCoord + u_Offset,1.0)).xy; + + vec2 vertex = a_Vertex; + if(vertex.x < u_Center.x) { + vertex.x = effectTextureSize.x / 10.0; + } + if(vertex.x > u_Center.x) { + vertex.x = effectTextureSize.x - effectTextureSize.x / 10.0; + } + if(vertex.y < u_Center.y) { + vertex.y = effectTextureSize.y / 10.0; + } + if(vertex.y > u_Center.y) { + vertex.y = effectTextureSize.y - effectTextureSize.y / 10.0; + } + + v_TexCoord3 = ((vertex + direction * u_Time * speed) / effectTextureSize); +} + diff --git a/data/styles/20-smallscrollbar.otui b/data/styles/20-smallscrollbar.otui index 8327973..4396a0b 100644 --- a/data/styles/20-smallscrollbar.otui +++ b/data/styles/20-smallscrollbar.otui @@ -1,60 +1,60 @@ -SmallScrollBar < UIScrollBar - orientation: vertical - margin-bottom: 1 - step: 20 - width: 8 - image-source: /images/ui/scrollbar - image-clip: 39 0 13 65 - image-border: 1 - pixels-scroll: true - - UIButton - id: decrementButton - anchors.top: parent.top - anchors.left: parent.left - image-source: /images/ui/scrollbar - image-clip: 0 0 13 13 - image-color: #ffffffff - size: 8 8 - $hover: - image-clip: 13 0 13 13 - $pressed: - image-clip: 26 0 13 13 - $disabled: - image-color: #ffffff66 - - UIButton - id: incrementButton - anchors.bottom: parent.bottom - anchors.right: parent.right - size: 8 8 - image-source: /images/ui/scrollbar - image-clip: 0 13 13 13 - image-color: #ffffffff - $hover: - image-clip: 13 13 13 13 - $pressed: - image-clip: 26 13 13 13 - $disabled: - image-color: #ffffff66 - - UIButton - id: sliderButton - anchors.centerIn: parent - size: 8 11 - image-source: /images/ui/scrollbar - image-clip: 0 26 13 13 - image-border: 2 - image-color: #ffffffff - $hover: - image-clip: 13 26 13 13 - $pressed: - image-clip: 26 26 13 13 - $disabled: - image-color: #ffffff66 - - Label - id: valueLabel - anchors.fill: parent - color: white +SmallScrollBar < UIScrollBar + orientation: vertical + margin-bottom: 1 + step: 20 + width: 8 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 8 8 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 8 8 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: sliderButton + anchors.centerIn: parent + size: 8 11 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + + Label + id: valueLabel + anchors.fill: parent + color: white text-align: center \ No newline at end of file diff --git a/data/styles/40-console.otui b/data/styles/40-console.otui index c7e160b..ed9e0ae 100644 --- a/data/styles/40-console.otui +++ b/data/styles/40-console.otui @@ -1,186 +1,186 @@ -ConsoleLabel < UITextEdit - font: verdana-11px-antialised - height: 14 - color: yellow - margin-left: 2 - text-wrap: true - text-auto-resize: true - selection-color: #111416 - selection-background-color: #999999 - change-cursor-image: false - cursor-visible: false - editable: false - draggable: true - selectable: false - focusable: false - -ConsolePhantomLabel < UILabel - font: verdana-11px-antialised - height: 14 - color: yellow - text-wrap: true - text-auto-resize: true - selection-color: #111416 - selection-background-color: #999999 - -ConsoleTabBar < MoveableTabBar - height: 28 - -ConsoleTabBarPanel < MoveableTabBarPanel - id: consoleTab - - ScrollablePanel - id: consoleBuffer - anchors.fill: parent - margin-right: 12 - vertical-scrollbar: consoleScrollBar - layout: - type: verticalBox - align-bottom: true - border-width: 1 - border-color: #202327 - background: #00000066 - inverted-scroll: true - padding: 1 - - VerticalScrollBar - id: consoleScrollBar - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - step: 14 - pixels-scroll: true - -ConsoleTabBarButton < MoveableTabBarButton - height: 28 - padding: 15 - -ConsolePanel < Panel - image-source: /images/ui/panel_bottom - image-border: 4 - - $first: - anchors.fill: parent - - $!first: - anchors.top: prev.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - - CheckBox - id: toggleChat - !tooltip: tr('Disable chat mode, allow to walk using ASDW') - anchors.left: parent.left - anchors.top: parent.top - margin-left: 13 - margin-top: 8 - @onCheckChange: toggleChat() - - TabButton - id: prevChannelButton - icon: /images/game/console/leftarrow - anchors.left: toggleChat.right - anchors.top: parent.top - margin-left: 3 - margin-top: 6 - - ConsoleTabBar - id: consoleTabBar - anchors.left: prev.right - anchors.top: parent.top - anchors.right: next.left - margin-left: 5 - margin-top: 3 - margin-right: 5 - tab-spacing: 2 - movable: true - - TabButton - id: nextChannelButton - icon: /images/game/console/rightarrow - anchors.right: next.left - anchors.top: parent.top - margin-right: 5 - margin-top: 6 - - TabButton - id: closeChannelButton - !tooltip: tr('Close this channel') .. ' (Ctrl+E)' - icon: /images/game/console/closechannel - anchors.right: next.left - anchors.top: parent.top - enabled: false - margin-right: 5 - margin-top: 6 - @onClick: removeCurrentTab() - - TabButton - id: clearChannelButton - !tooltip: tr('Clear current message window') - icon: /images/game/console/clearchannel - anchors.right: next.left - anchors.top: parent.top - margin-right: 5 - margin-top: 6 - @onClick: | - local consoleTabBar = self:getParent():getChildById('consoleTabBar') - clearChannel(consoleTabBar) - - TabButton - id: channelsButton - !tooltip: tr('Open new channel') .. ' (Ctrl+O)' - icon: /images/game/console/channels - anchors.right: next.left - anchors.top: parent.top - margin-right: 5 - margin-top: 6 - @onClick: g_game.requestChannels() - - TabButton - id: ignoreButton - !tooltip: tr('Ignore players') - icon: /images/game/console/ignore - anchors.right: parent.right - anchors.top: parent.top - margin-right: 5 - margin-top: 6 - @onClick: onClickIgnoreButton() - - Panel - id: consoleContentPanel - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: consoleTextEdit.top - margin-left: 6 - margin-right: 6 - margin-bottom: 4 - margin-top: 4 - padding: 1 - focusable: false - phantom: true - - TabButton - id: sayModeButton - icon: /images/game/console/say - !tooltip: tr('Adjust volume') - &sayMode: 2 - size: 20 20 - anchors.left: parent.left - anchors.bottom: parent.bottom - margin-left: 6 - margin-bottom: 6 - @onClick: sayModeChange() - - TextEdit - id: consoleTextEdit - anchors.left: sayModeButton.right - anchors.right: parent.right - anchors.bottom: parent.bottom - margin-right: 6 - margin-left: 6 - margin-bottom: 6 - shift-navigation: true - max-length: 255 - text-auto-submit: true +ConsoleLabel < UITextEdit + font: verdana-11px-antialised + height: 14 + color: yellow + margin-left: 2 + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + change-cursor-image: false + cursor-visible: false + editable: false + draggable: true + selectable: false + focusable: false + +ConsolePhantomLabel < UILabel + font: verdana-11px-antialised + height: 14 + color: yellow + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + +ConsoleTabBar < MoveableTabBar + height: 28 + +ConsoleTabBarPanel < MoveableTabBarPanel + id: consoleTab + + ScrollablePanel + id: consoleBuffer + anchors.fill: parent + margin-right: 12 + vertical-scrollbar: consoleScrollBar + layout: + type: verticalBox + align-bottom: true + border-width: 1 + border-color: #202327 + background: #00000066 + inverted-scroll: true + padding: 1 + + VerticalScrollBar + id: consoleScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + +ConsoleTabBarButton < MoveableTabBarButton + height: 28 + padding: 15 + +ConsolePanel < Panel + image-source: /images/ui/panel_bottom + image-border: 4 + + $first: + anchors.fill: parent + + $!first: + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + CheckBox + id: toggleChat + !tooltip: tr('Disable chat mode, allow to walk using ASDW') + anchors.left: parent.left + anchors.top: parent.top + margin-left: 13 + margin-top: 8 + @onCheckChange: toggleChat() + + TabButton + id: prevChannelButton + icon: /images/game/console/leftarrow + anchors.left: toggleChat.right + anchors.top: parent.top + margin-left: 3 + margin-top: 6 + + ConsoleTabBar + id: consoleTabBar + anchors.left: prev.right + anchors.top: parent.top + anchors.right: next.left + margin-left: 5 + margin-top: 3 + margin-right: 5 + tab-spacing: 2 + movable: true + + TabButton + id: nextChannelButton + icon: /images/game/console/rightarrow + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + + TabButton + id: closeChannelButton + !tooltip: tr('Close this channel') .. ' (Ctrl+E)' + icon: /images/game/console/closechannel + anchors.right: next.left + anchors.top: parent.top + enabled: false + margin-right: 5 + margin-top: 6 + @onClick: removeCurrentTab() + + TabButton + id: clearChannelButton + !tooltip: tr('Clear current message window') + icon: /images/game/console/clearchannel + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: | + local consoleTabBar = self:getParent():getChildById('consoleTabBar') + clearChannel(consoleTabBar) + + TabButton + id: channelsButton + !tooltip: tr('Open new channel') .. ' (Ctrl+O)' + icon: /images/game/console/channels + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: g_game.requestChannels() + + TabButton + id: ignoreButton + !tooltip: tr('Ignore players') + icon: /images/game/console/ignore + anchors.right: parent.right + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: onClickIgnoreButton() + + Panel + id: consoleContentPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: consoleTextEdit.top + margin-left: 6 + margin-right: 6 + margin-bottom: 4 + margin-top: 4 + padding: 1 + focusable: false + phantom: true + + TabButton + id: sayModeButton + icon: /images/game/console/say + !tooltip: tr('Adjust volume') + &sayMode: 2 + size: 20 20 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 6 + margin-bottom: 6 + @onClick: sayModeChange() + + TextEdit + id: consoleTextEdit + anchors.left: sayModeButton.right + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-right: 6 + margin-left: 6 + margin-bottom: 6 + shift-navigation: true + max-length: 255 + text-auto-submit: true diff --git a/data/styles/40-container.otui b/data/styles/40-container.otui index 66f5da8..ddfada8 100644 --- a/data/styles/40-container.otui +++ b/data/styles/40-container.otui @@ -1,74 +1,74 @@ -PageButton < Button - size: 30 18 - margin: 1 - - -ContainerWindow < MiniWindow - height: 150 - &save: true - &containerWindow: true - - UIItem - id: containerItemWidget - virtual: true - size: 16 16 - anchors.top: parent.top - anchors.left: parent.left - margin-top: 1 - margin-left: 3 - - UIButton - id: upButton - anchors.top: lockButton.top - anchors.right: lockButton.left - margin-right: 3 - size: 14 14 - image-source: /images/ui/miniwindow_buttons - image-clip: 42 0 14 14 - - $hover: - image-clip: 42 14 14 14 - - $pressed: - image-clip: 42 28 14 14 - - Panel - id: pagePanel - anchors.left: parent.left - anchors.right: parent.right - anchors.top: miniwindowTopBar.bottom - margin: 1 3 0 3 - background: #00000066 - height: 20 - - $on: - visible: true - - $!on: - visible: false - - Label - id: pageLabel - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - margin-top: 2 - text-auto-resize: true - - PageButton - id: prevPageButton - text: < - anchors.top: parent.top - anchors.left: parent.left - - PageButton - id: nextPageButton - text: > - anchors.top: parent.top - anchors.right: parent.right - - MiniWindowContents - padding-right: 0 - layout: - type: grid - cell-size: 34 34 - flow: true +PageButton < Button + size: 30 18 + margin: 1 + + +ContainerWindow < MiniWindow + height: 150 + &save: true + &containerWindow: true + + UIItem + id: containerItemWidget + virtual: true + size: 16 16 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 1 + margin-left: 3 + + UIButton + id: upButton + anchors.top: lockButton.top + anchors.right: lockButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 42 0 14 14 + + $hover: + image-clip: 42 14 14 14 + + $pressed: + image-clip: 42 28 14 14 + + Panel + id: pagePanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: miniwindowTopBar.bottom + margin: 1 3 0 3 + background: #00000066 + height: 20 + + $on: + visible: true + + $!on: + visible: false + + Label + id: pageLabel + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 2 + text-auto-resize: true + + PageButton + id: prevPageButton + text: < + anchors.top: parent.top + anchors.left: parent.left + + PageButton + id: nextPageButton + text: > + anchors.top: parent.top + anchors.right: parent.right + + MiniWindowContents + padding-right: 0 + layout: + type: grid + cell-size: 34 34 + flow: true diff --git a/data/styles/40-entergame.otui b/data/styles/40-entergame.otui index 609e4bb..71f850b 100644 --- a/data/styles/40-entergame.otui +++ b/data/styles/40-entergame.otui @@ -1,3 +1,3 @@ -EnterGameWindow < StaticMainWindow - !text: tr('Enter Game') +EnterGameWindow < StaticMainWindow + !text: tr('Enter Game') size: 260 354 \ No newline at end of file diff --git a/data/styles/40-gamebuttons.otui b/data/styles/40-gamebuttons.otui index 72b3c7a..76c3b9e 100644 --- a/data/styles/40-gamebuttons.otui +++ b/data/styles/40-gamebuttons.otui @@ -1,2 +1,2 @@ -GameButtonsWindow < MiniWindow - height: 26 +GameButtonsWindow < MiniWindow + height: 26 diff --git a/data/styles/40-healthinfo.otui b/data/styles/40-healthinfo.otui index 4e45c69..f676a1c 100644 --- a/data/styles/40-healthinfo.otui +++ b/data/styles/40-healthinfo.otui @@ -1,147 +1,147 @@ -ExperienceBar < ProgressBar - id: experienceBar - background-color: #B6E866 - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - margin: 1 - margin-top: 3 - -SoulLabel < GameLabel - id: soulLabel - text-align: right - color: white - font: verdana-11px-rounded - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.horizontalCenter - margin-top: 5 - margin-right: 3 - on: true - - $!on: - visible: false - margin-top: 0 - height: 0 - -CapLabel < GameLabel - id: capLabel - color: white - font: verdana-11px-rounded - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.horizontalCenter - margin-top: 5 - margin-left: 3 - on: true - - $!on: - visible: false - margin-top: 0 - height: 0 - -ConditionWidget < UIWidget - size: 18 18 - - $!first: - margin-left: 2 - -HealthOverlay < UIWidget - id: healthOverlay - anchors.fill: parent - phantom: true - - HealthBar - id: topHealthBar - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.horizontalCenter - phantom: true - - ManaBar - id: topManaBar - anchors.top: parent.top - anchors.right: parent.right - anchors.left: parent.horizontalCenter - phantom: true - - UIProgressBar - id: healthCircle - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - image-source: /images/game/circle/left_empty - margin-right: 169 - margin-bottom: 16 - opacity: 0.5 - phantom: true - - UIProgressBar - id: healthCircleFront - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - image-source: /images/game/circle/left_full - margin-right: 169 - margin-bottom: 16 - opacity: 0.5 - phantom: true - - UIProgressBar - id: manaCircle - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - image-source: /images/game/circle/right_empty - image-auto-resize: true - margin-left: 130 - margin-bottom: 16 - opacity: 0.5 - phantom: true - - UIProgressBar - id: manaCircleFront - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - image-source: /images/game/circle/right_full - margin-left: 130 - margin-bottom: 16 - opacity: 0.4 - image-color: #0000FFFF - phantom: true - -HealthInfoWindow < MiniWindow - icon: /images/topbuttons/healthinfo - !text: tr('Health Info') - height: 123 - - MiniWindowContents - HealthBar - id: healthBar - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - margin: 2 - margin-top: 1 - - ManaBar - id: manaBar - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - margin: 2 - - ExperienceBar - Panel - id: conditionPanel - layout: - type: horizontalBox - height: 22 - margin-top: 4 - padding: 2 - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - border-width: 1 - border-color: #00000077 - background-color: #ffffff11 - SoulLabel - CapLabel - +ExperienceBar < ProgressBar + id: experienceBar + background-color: #B6E866 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 1 + margin-top: 3 + +SoulLabel < GameLabel + id: soulLabel + text-align: right + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.horizontalCenter + margin-top: 5 + margin-right: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +CapLabel < GameLabel + id: capLabel + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +ConditionWidget < UIWidget + size: 18 18 + + $!first: + margin-left: 2 + +HealthOverlay < UIWidget + id: healthOverlay + anchors.fill: parent + phantom: true + + HealthBar + id: topHealthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + phantom: true + + ManaBar + id: topManaBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.horizontalCenter + phantom: true + + UIProgressBar + id: healthCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_empty + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: healthCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_full + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_empty + image-auto-resize: true + margin-left: 130 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_full + margin-left: 130 + margin-bottom: 16 + opacity: 0.4 + image-color: #0000FFFF + phantom: true + +HealthInfoWindow < MiniWindow + icon: /images/topbuttons/healthinfo + !text: tr('Health Info') + height: 123 + + MiniWindowContents + HealthBar + id: healthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-top: 1 + + ManaBar + id: manaBar + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + + ExperienceBar + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + margin-top: 4 + padding: 2 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + border-width: 1 + border-color: #00000077 + background-color: #ffffff11 + SoulLabel + CapLabel + diff --git a/data/styles/40-inventory.otui b/data/styles/40-inventory.otui index 37e7263..f6fa1c5 100644 --- a/data/styles/40-inventory.otui +++ b/data/styles/40-inventory.otui @@ -1,299 +1,299 @@ -InventoryItem < Item - $on: - image-source: /images/ui/item-blessed - -HeadSlot < InventoryItem - id: slot1 - image-source: /images/game/slots/head - &position: {x=65535, y=1, z=0} - $on: - image-source: /images/game/slots/head-blessed - -BodySlot < InventoryItem - id: slot4 - image-source: /images/game/slots/body - &position: {x=65535, y=4, z=0} - $on: - image-source: /images/game/slots/body-blessed - -LegSlot < InventoryItem - id: slot7 - image-source: /images/game/slots/legs - &position: {x=65535, y=7, z=0} - $on: - image-source: /images/game/slots/legs-blessed - -FeetSlot < InventoryItem - id: slot8 - image-source: /images/game/slots/feet - &position: {x=65535, y=8, z=0} - $on: - image-source: /images/game/slots/feet-blessed - -NeckSlot < InventoryItem - id: slot2 - image-source: /images/game/slots/neck - &position: {x=65535, y=2, z=0} - $on: - image-source: /images/game/slots/neck-blessed - -LeftSlot < InventoryItem - id: slot6 - image-source: /images/game/slots/left-hand - &position: {x=65535, y=6, z=0} - $on: - image-source: /images/game/slots/left-hand-blessed - -FingerSlot < InventoryItem - id: slot9 - image-source: /images/game/slots/finger - &position: {x=65535, y=9, z=0} - $on: - image-source: /images/game/slots/finger-blessed - -BackSlot < InventoryItem - id: slot3 - image-source: /images/game/slots/back - &position: {x=65535, y=3, z=0} - $on: - image-source: /images/game/slots/back-blessed - -RightSlot < InventoryItem - id: slot5 - image-source: /images/game/slots/right-hand - &position: {x=65535, y=5, z=0} - $on: - image-source: /images/game/slots/right-hand-blessed - -AmmoSlot < InventoryItem - id: slot10 - image-source: /images/game/slots/ammo - &position: {x=65535, y=10, z=0} - $on: - image-source: /images/game/slots/ammo-blessed - -PurseButton < UIButton - id: purseButton - size: 34 12 - !tooltip: tr('Open purse') - icon-source: /images/game/slots/purse - icon-clip: 0 0 34 12 - - $on: - icon-clip: 0 12 34 12 - - $pressed: - icon-clip: 0 12 34 12 - -CombatBox < UICheckBox - size: 20 20 - image-clip: 0 0 20 20 - margin-left: 4 - - $checked: - image-clip: 0 20 20 20 - - -InventoryButton < Button - font: verdana-11px-antialised - height: 18 - margin-top: 2 - text-align: center - -SoulCapLabel < GameLabel - text-align: center - color: #FFFFFF - font: cipsoftFont - margin-top: 4 - text-offset: 0 3 - width: 36 - height: 20 - icon-source: /images/game/slots/soulcap - -FightOffensiveBox < CombatBox - image-source: /images/game/combatmodes/fightoffensive -FightBalancedBox < CombatBox - image-source: /images/game/combatmodes/fightbalanced -FightDefensiveBox < CombatBox - image-source: /images/game/combatmodes/fightdefensive -ChaseModeBox < CombatBox - image-source: /images/game/combatmodes/chasemode -SafeFightBox < CombatBox - image-source: /images/game/combatmodes/safefight - -MountButton < CombatBox - image-source: /images/game/combatmodes/mount - -InventoryWindow < MiniWindow - icon: /images/topbuttons/inventory - height: 200 - id: inventoryWindow - @onClose: modules.game_inventory.onMiniWindowClose() - &save: true - &autoOpen: 3 - - MiniWindowContents - anchors.left: parent.left - - Panel - id: inventoryPanel - margin-right: 63 - margin-top: 2 - anchors.fill: parent - - HeadSlot - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - margin-top: 3 - - BodySlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - LegSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - FeetSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - NeckSlot - anchors.top: slot1.top - anchors.right: slot1.left - margin-top: 13 - margin-right: 5 - - LeftSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - FingerSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - BackSlot - anchors.top: slot1.top - anchors.left: slot1.right - margin-top: 13 - margin-left: 5 - - RightSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - AmmoSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - SoulCapLabel - id: soulLabel - anchors.top: slot10.bottom - anchors.horizontalCenter: slot10.horizontalCenter - - SoulCapLabel - id: capLabel - anchors.top: slot9.bottom - anchors.horizontalCenter: slot9.horizontalCenter - - PurseButton - anchors.left: slot3.left - anchors.bottom: slot3.top - margin-bottom: 3 - - Panel - id: conditionPanel - layout: - type: horizontalBox - height: 22 - padding: 2 - anchors.top: slot8.bottom - anchors.left: slot6.left - anchors.right: slot5.right - margin-top: 4 - border-width: 1 - border-color: #00000077 - background-color: #ffffff22 - - Panel - margin-top: 5 - anchors.fill: parent - anchors.left: prev.right - - FightOffensiveBox - id: fightOffensiveBox - anchors.left: parent.left - anchors.top: parent.top - margin-left: 8 - - ChaseModeBox - id: chaseModeBox - anchors.left: prev.right - anchors.top: parent.top - - FightBalancedBox - id: fightBalancedBox - margin-top: 22 - anchors.left: parent.left - anchors.top: parent.top - margin-left: 8 - - SafeFightBox - id: safeFightBox - margin-top: 22 - anchors.left: prev.right - anchors.top: parent.top - - FightDefensiveBox - id: fightDefensiveBox - margin-top: 44 - anchors.left: parent.left - anchors.top: parent.top - margin-left: 8 - - MountButton - id: mountButton - margin-top: 44 - anchors.left: prev.right - anchors.top: parent.top - - Panel - id: buttonsPanel - margin-top: 4 - margin-right: 5 - anchors.fill: parent - anchors.top: prev.bottom - layout: - type: verticalBox - - UIButton - id: buttonPvp - height: 20 - icon: /images/game/combatmodes/pvp - icon-clip: 0 0 42 20 - - $on: - icon-clip: 0 20 42 20 - - InventoryButton - !text: tr('Stop') - @onClick: g_game.stop(); g_game.cancelAttackAndFollow() - - InventoryButton - !text: tr('Options') - @onClick: modules.client_options.toggle() - - InventoryButton - !text: tr('Hotkeys') - @onClick: modules.game_hotkeys.toggle() - - InventoryButton - !text: tr('Logout') - @onClick: modules.game_interface.tryLogout() +InventoryItem < Item + $on: + image-source: /images/ui/item-blessed + +HeadSlot < InventoryItem + id: slot1 + image-source: /images/game/slots/head + &position: {x=65535, y=1, z=0} + $on: + image-source: /images/game/slots/head-blessed + +BodySlot < InventoryItem + id: slot4 + image-source: /images/game/slots/body + &position: {x=65535, y=4, z=0} + $on: + image-source: /images/game/slots/body-blessed + +LegSlot < InventoryItem + id: slot7 + image-source: /images/game/slots/legs + &position: {x=65535, y=7, z=0} + $on: + image-source: /images/game/slots/legs-blessed + +FeetSlot < InventoryItem + id: slot8 + image-source: /images/game/slots/feet + &position: {x=65535, y=8, z=0} + $on: + image-source: /images/game/slots/feet-blessed + +NeckSlot < InventoryItem + id: slot2 + image-source: /images/game/slots/neck + &position: {x=65535, y=2, z=0} + $on: + image-source: /images/game/slots/neck-blessed + +LeftSlot < InventoryItem + id: slot6 + image-source: /images/game/slots/left-hand + &position: {x=65535, y=6, z=0} + $on: + image-source: /images/game/slots/left-hand-blessed + +FingerSlot < InventoryItem + id: slot9 + image-source: /images/game/slots/finger + &position: {x=65535, y=9, z=0} + $on: + image-source: /images/game/slots/finger-blessed + +BackSlot < InventoryItem + id: slot3 + image-source: /images/game/slots/back + &position: {x=65535, y=3, z=0} + $on: + image-source: /images/game/slots/back-blessed + +RightSlot < InventoryItem + id: slot5 + image-source: /images/game/slots/right-hand + &position: {x=65535, y=5, z=0} + $on: + image-source: /images/game/slots/right-hand-blessed + +AmmoSlot < InventoryItem + id: slot10 + image-source: /images/game/slots/ammo + &position: {x=65535, y=10, z=0} + $on: + image-source: /images/game/slots/ammo-blessed + +PurseButton < UIButton + id: purseButton + size: 34 12 + !tooltip: tr('Open purse') + icon-source: /images/game/slots/purse + icon-clip: 0 0 34 12 + + $on: + icon-clip: 0 12 34 12 + + $pressed: + icon-clip: 0 12 34 12 + +CombatBox < UICheckBox + size: 20 20 + image-clip: 0 0 20 20 + margin-left: 4 + + $checked: + image-clip: 0 20 20 20 + + +InventoryButton < Button + font: verdana-11px-antialised + height: 18 + margin-top: 2 + text-align: center + +SoulCapLabel < GameLabel + text-align: center + color: #FFFFFF + font: cipsoftFont + margin-top: 4 + text-offset: 0 3 + width: 36 + height: 20 + icon-source: /images/game/slots/soulcap + +FightOffensiveBox < CombatBox + image-source: /images/game/combatmodes/fightoffensive +FightBalancedBox < CombatBox + image-source: /images/game/combatmodes/fightbalanced +FightDefensiveBox < CombatBox + image-source: /images/game/combatmodes/fightdefensive +ChaseModeBox < CombatBox + image-source: /images/game/combatmodes/chasemode +SafeFightBox < CombatBox + image-source: /images/game/combatmodes/safefight + +MountButton < CombatBox + image-source: /images/game/combatmodes/mount + +InventoryWindow < MiniWindow + icon: /images/topbuttons/inventory + height: 200 + id: inventoryWindow + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 + + MiniWindowContents + anchors.left: parent.left + + Panel + id: inventoryPanel + margin-right: 63 + margin-top: 2 + anchors.fill: parent + + HeadSlot + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 3 + + BodySlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + LegSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FeetSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + NeckSlot + anchors.top: slot1.top + anchors.right: slot1.left + margin-top: 13 + margin-right: 5 + + LeftSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FingerSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + BackSlot + anchors.top: slot1.top + anchors.left: slot1.right + margin-top: 13 + margin-left: 5 + + RightSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + AmmoSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + SoulCapLabel + id: soulLabel + anchors.top: slot10.bottom + anchors.horizontalCenter: slot10.horizontalCenter + + SoulCapLabel + id: capLabel + anchors.top: slot9.bottom + anchors.horizontalCenter: slot9.horizontalCenter + + PurseButton + anchors.left: slot3.left + anchors.bottom: slot3.top + margin-bottom: 3 + + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + padding: 2 + anchors.top: slot8.bottom + anchors.left: slot6.left + anchors.right: slot5.right + margin-top: 4 + border-width: 1 + border-color: #00000077 + background-color: #ffffff22 + + Panel + margin-top: 5 + anchors.fill: parent + anchors.left: prev.right + + FightOffensiveBox + id: fightOffensiveBox + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + ChaseModeBox + id: chaseModeBox + anchors.left: prev.right + anchors.top: parent.top + + FightBalancedBox + id: fightBalancedBox + margin-top: 22 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + SafeFightBox + id: safeFightBox + margin-top: 22 + anchors.left: prev.right + anchors.top: parent.top + + FightDefensiveBox + id: fightDefensiveBox + margin-top: 44 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + MountButton + id: mountButton + margin-top: 44 + anchors.left: prev.right + anchors.top: parent.top + + Panel + id: buttonsPanel + margin-top: 4 + margin-right: 5 + anchors.fill: parent + anchors.top: prev.bottom + layout: + type: verticalBox + + UIButton + id: buttonPvp + height: 20 + icon: /images/game/combatmodes/pvp + icon-clip: 0 0 42 20 + + $on: + icon-clip: 0 20 42 20 + + InventoryButton + !text: tr('Stop') + @onClick: g_game.stop(); g_game.cancelAttackAndFollow() + + InventoryButton + !text: tr('Options') + @onClick: modules.client_options.toggle() + + InventoryButton + !text: tr('Hotkeys') + @onClick: modules.game_hotkeys.toggle() + + InventoryButton + !text: tr('Logout') + @onClick: modules.game_interface.tryLogout() diff --git a/init.lua b/init.lua index ee47767..e2abdc6 100644 --- a/init.lua +++ b/init.lua @@ -1,6 +1,6 @@ -- CONFIG APP_NAME = "otclientv8" -- important, change it, it's name for config dir and files in appdata -APP_VERSION = 1343 -- client version for updater and login to identify outdated client +APP_VERSION = 1341 -- client version for updater and login to identify outdated client DEFAULT_LAYOUT = "retro" -- on android it's forced to "mobile", check code bellow -- If you don't use updater or other service, set it to updater = "" @@ -16,11 +16,16 @@ Services = { -- Servers accept http login url, websocket login url or ip:port:version Servers = { --[[ OTClientV8 = "http://otclient.ovh/api/login.php", - OTClientV8c = "otclient.ovh:7171", + OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1", + OTClientV8c = "otclient.ovh:7171:1099:25:30:80:90", OTClientV8Test = "http://otclient.ovh/api/login2.php", + Evoulinia = "evolunia.net:7171:1098", + GarneraTest = "garnera-global.net:7171:1100", LocalTestServ = "127.0.0.1:7171:1098:110:30:93" ]] } +--Server = "ws://otclient.ovh:3000/" +--Server = "ws://127.0.0.1:88/" --USE_NEW_ENERGAME = true -- uses entergamev2 based on websockets instead of entergame ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list @@ -84,5 +89,4 @@ if type(Services.updater) == 'string' and Services.updater:len() > 4 g_modules.ensureModuleLoaded("updater") return Updater.init(loadModules) end - loadModules() diff --git a/layouts/README.md b/layouts/README.md index f675d1e..4cbc6ea 100644 --- a/layouts/README.md +++ b/layouts/README.md @@ -1,4 +1,4 @@ -## Layouts overwrite files from `/data` -Foe example, if you have file `/data/images/background.png` and `/layouts/dragonball/images/background.png`, and dragonball layout is selected, then `/layouts/dragonball/images/background.png` will be loaded instead of `/data/images/background.png`. - -## Dont make layout named `default`, this name is reserved +## Layouts overwrite files from `/data` +Foe example, if you have file `/data/images/background.png` and `/layouts/dragonball/images/background.png`, and dragonball layout is selected, then `/layouts/dragonball/images/background.png` will be loaded instead of `/data/images/background.png`. + +## Dont make layout named `default`, this name is reserved diff --git a/layouts/retro/styles/40-console.otui b/layouts/retro/styles/40-console.otui index 555752b..7943fe1 100644 --- a/layouts/retro/styles/40-console.otui +++ b/layouts/retro/styles/40-console.otui @@ -1,219 +1,219 @@ -ConsoleLabel < UITextEdit - font: verdana-11px-antialised - height: 14 - color: yellow - margin-left: 1 - text-wrap: true - text-auto-resize: true - selection-color: #111416 - selection-background-color: #808080 - change-cursor-image: false - cursor-visible: false - editable: false - draggable: true - selectable: false - focusable: false - -ConsolePhantomLabel < UILabel - font: verdana-11px-antialised - height: 14 - color: yellow - text-wrap: true - text-auto-resize: true - selection-color: #111416 - selection-background-color: #999999 - -ConsoleTabBar < MoveableTabBar - height: 16 - -ConsoleTabBarPanel < MoveableTabBarPanel - id: consoleTab - - ScrollablePanel - id: consoleBuffer - anchors.fill: parent - margin-right: 12 - vertical-scrollbar: consoleScrollBar - layout: - type: verticalBox - align-bottom: true - border-width: 1 - border-color: #202327 - background: #00000066 - inverted-scroll: true - padding: 1 - - VerticalScrollBar - id: consoleScrollBar - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - step: 14 - pixels-scroll: true - -ConsoleTabBarButton < MoveableTabBarButton - height: 16 - padding: 15 - -ConsolePanel < Panel - image-source: /images/ui/panel_bottom - image-border: 7 - image-border-top: 29 - - $first: - anchors.fill: parent - - $!first: - anchors.top: prev.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - - CheckBox - id: toggleChat - !tooltip: tr('Disable chat mode, allow to walk using ASDW') - anchors.left: parent.left - anchors.top: parent.top - margin-left: 6 - margin-top: 3 - @onCheckChange: toggleChat() - - TabButton - id: prevChannelButton - icon: /images/game/console/leftarrow - anchors.left: toggleChat.right - anchors.top: parent.top - margin-top: 1 - size: 16 16 - - ConsoleTabBar - id: consoleTabBar - anchors.left: prev.right - anchors.top: parent.top - anchors.right: next.left - margin-top: 0 - tab-spacing: 2 - movable: true - - TabButton - id: nextChannelButton - icon: /images/game/console/rightarrow - anchors.right: next.left - anchors.top: parent.top - margin-top: 1 - size: 16 16 - margin-right: 5 - - TabButton - id: closeChannelButton - !tooltip: tr('Close this channel') .. ' (Ctrl+E)' - icon: /images/game/console/closechannel - anchors.right: next.left - anchors.top: parent.top - enabled: false - margin-right: 5 - margin-top: 1 - size: 16 16 - icon-clip: 0 0 16 16 - - $pressed: - icon-clip: 0 16 16 16 - - @onClick: removeCurrentTab() - - TabButton - id: clearChannelButton - !tooltip: tr('Clear current message window') - icon: /images/game/console/clearchannel - anchors.right: next.left - anchors.top: parent.top - margin-right: 5 - margin-top: 1 - size: 16 16 - icon-clip: 0 0 16 16 - - $pressed: - icon-clip: 0 16 16 16 - - @onClick: | - local consoleTabBar = self:getParent():getChildById('consoleTabBar') - clearChannel(consoleTabBar) - - TabButton - id: channelsButton - !tooltip: tr('Open new channel') .. ' (Ctrl+O)' - icon: /images/game/console/channels - anchors.right: next.left - anchors.top: parent.top - margin-right: 5 - margin-top: 1 - size: 16 16 - icon-clip: 0 0 16 16 - - $pressed: - icon-clip: 0 16 16 16 - - @onClick: g_game.requestChannels() - - TabButton - id: ignoreButton - !tooltip: tr('Ignore players') - icon: /images/game/console/ignore - anchors.right: parent.right - anchors.top: parent.top - margin-right: 5 - margin-top: 1 - size: 16 16 - icon-clip: 0 0 16 16 - - $pressed: - icon-clip: 0 16 16 16 - - @onClick: onClickIgnoreButton() - - Panel - id: consoleContentPanel - anchors.top: consoleTabBar.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: consoleTextEdit.top - margin-left: 6 - margin-right: 6 - margin-bottom: 2 - margin-top: 6 - padding: 1 - focusable: false - phantom: true - - TabButton - id: sayModeButton - icon: /images/game/console/say - !tooltip: tr('Adjust volume') - &sayMode: 2 - size: 18 18 - anchors.left: parent.left - anchors.bottom: parent.bottom - margin-left: 8 - margin-bottom: 4 - @onClick: sayModeChange() - - HorizontalSeparator - id: separator - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: prev.top - margin-bottom: 3 - margin-left: 7 - margin-right: 7 - - TextEdit - id: consoleTextEdit - anchors.left: sayModeButton.right - anchors.right: parent.right - anchors.bottom: parent.bottom - margin-right: 7 - margin-left: 2 - margin-bottom: 2 - shift-navigation: true - max-length: 255 - text-auto-submit: true +ConsoleLabel < UITextEdit + font: verdana-11px-antialised + height: 14 + color: yellow + margin-left: 1 + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #808080 + change-cursor-image: false + cursor-visible: false + editable: false + draggable: true + selectable: false + focusable: false + +ConsolePhantomLabel < UILabel + font: verdana-11px-antialised + height: 14 + color: yellow + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + +ConsoleTabBar < MoveableTabBar + height: 16 + +ConsoleTabBarPanel < MoveableTabBarPanel + id: consoleTab + + ScrollablePanel + id: consoleBuffer + anchors.fill: parent + margin-right: 12 + vertical-scrollbar: consoleScrollBar + layout: + type: verticalBox + align-bottom: true + border-width: 1 + border-color: #202327 + background: #00000066 + inverted-scroll: true + padding: 1 + + VerticalScrollBar + id: consoleScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + +ConsoleTabBarButton < MoveableTabBarButton + height: 16 + padding: 15 + +ConsolePanel < Panel + image-source: /images/ui/panel_bottom + image-border: 7 + image-border-top: 29 + + $first: + anchors.fill: parent + + $!first: + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + CheckBox + id: toggleChat + !tooltip: tr('Disable chat mode, allow to walk using ASDW') + anchors.left: parent.left + anchors.top: parent.top + margin-left: 6 + margin-top: 3 + @onCheckChange: toggleChat() + + TabButton + id: prevChannelButton + icon: /images/game/console/leftarrow + anchors.left: toggleChat.right + anchors.top: parent.top + margin-top: 1 + size: 16 16 + + ConsoleTabBar + id: consoleTabBar + anchors.left: prev.right + anchors.top: parent.top + anchors.right: next.left + margin-top: 0 + tab-spacing: 2 + movable: true + + TabButton + id: nextChannelButton + icon: /images/game/console/rightarrow + anchors.right: next.left + anchors.top: parent.top + margin-top: 1 + size: 16 16 + margin-right: 5 + + TabButton + id: closeChannelButton + !tooltip: tr('Close this channel') .. ' (Ctrl+E)' + icon: /images/game/console/closechannel + anchors.right: next.left + anchors.top: parent.top + enabled: false + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: removeCurrentTab() + + TabButton + id: clearChannelButton + !tooltip: tr('Clear current message window') + icon: /images/game/console/clearchannel + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: | + local consoleTabBar = self:getParent():getChildById('consoleTabBar') + clearChannel(consoleTabBar) + + TabButton + id: channelsButton + !tooltip: tr('Open new channel') .. ' (Ctrl+O)' + icon: /images/game/console/channels + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: g_game.requestChannels() + + TabButton + id: ignoreButton + !tooltip: tr('Ignore players') + icon: /images/game/console/ignore + anchors.right: parent.right + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: onClickIgnoreButton() + + Panel + id: consoleContentPanel + anchors.top: consoleTabBar.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: consoleTextEdit.top + margin-left: 6 + margin-right: 6 + margin-bottom: 2 + margin-top: 6 + padding: 1 + focusable: false + phantom: true + + TabButton + id: sayModeButton + icon: /images/game/console/say + !tooltip: tr('Adjust volume') + &sayMode: 2 + size: 18 18 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 8 + margin-bottom: 4 + @onClick: sayModeChange() + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: prev.top + margin-bottom: 3 + margin-left: 7 + margin-right: 7 + + TextEdit + id: consoleTextEdit + anchors.left: sayModeButton.right + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-right: 7 + margin-left: 2 + margin-bottom: 2 + shift-navigation: true + max-length: 255 + text-auto-submit: true diff --git a/layouts/retro/styles/40-entergame.otui b/layouts/retro/styles/40-entergame.otui index 7df877d..9f82297 100644 --- a/layouts/retro/styles/40-entergame.otui +++ b/layouts/retro/styles/40-entergame.otui @@ -1,3 +1,3 @@ -EnterGameWindow < StaticMainWindow - !text: tr('Enter Game') +EnterGameWindow < StaticMainWindow + !text: tr('Enter Game') size: 260 340 \ No newline at end of file diff --git a/layouts/retro/styles/40-gamebuttons.otui b/layouts/retro/styles/40-gamebuttons.otui index a67c5e4..9a0a06a 100644 --- a/layouts/retro/styles/40-gamebuttons.otui +++ b/layouts/retro/styles/40-gamebuttons.otui @@ -1,16 +1,16 @@ -GameButtonsWindow < HeadlessMiniWindow - height: 26 - &forceOpen: true - &autoOpen: 4 - - MiniWindowContents - margin-top: 2 - - Panel - id: buttons - anchors.fill: parent - layout: - type: grid - cell-spacing: 3 - cell-size: 20 20 +GameButtonsWindow < HeadlessMiniWindow + height: 26 + &forceOpen: true + &autoOpen: 4 + + MiniWindowContents + margin-top: 2 + + Panel + id: buttons + anchors.fill: parent + layout: + type: grid + cell-spacing: 3 + cell-size: 20 20 flow: true \ No newline at end of file diff --git a/layouts/retro/styles/40-healthinfo.otui b/layouts/retro/styles/40-healthinfo.otui index 886af16..b3b4c5f 100644 --- a/layouts/retro/styles/40-healthinfo.otui +++ b/layouts/retro/styles/40-healthinfo.otui @@ -1,154 +1,154 @@ -ExperienceBar < ProgressBar - id: experienceBar - background-color: #B6E866 - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - margin: 1 - margin-top: 3 - -SoulLabel < GameLabel - id: soulLabel - text-align: right - color: white - font: verdana-11px-rounded - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.horizontalCenter - margin-top: 5 - margin-right: 3 - on: true - - $!on: - visible: false - margin-top: 0 - height: 0 - -CapLabel < GameLabel - id: capLabel - color: white - font: verdana-11px-rounded - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.horizontalCenter - margin-top: 5 - margin-left: 3 - on: true - - $!on: - visible: false - margin-top: 0 - height: 0 - -ConditionWidget < UIWidget - size: 18 18 - - $!first: - margin-left: 2 - -HealthOverlay < UIWidget - id: healthOverlay - anchors.fill: parent - phantom: true - - HealthBar - id: topHealthBar - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.horizontalCenter - phantom: true - - ManaBar - id: topManaBar - anchors.top: parent.top - anchors.right: parent.right - anchors.left: parent.horizontalCenter - phantom: true - - UIProgressBar - id: healthCircle - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - image-source: /images/game/circle/left_empty - margin-right: 169 - margin-bottom: 16 - opacity: 0.5 - phantom: true - - UIProgressBar - id: healthCircleFront - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - image-source: /images/game/circle/left_full - margin-right: 169 - margin-bottom: 16 - opacity: 0.5 - phantom: true - - UIProgressBar - id: manaCircle - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - image-source: /images/game/circle/right_empty - margin-left: 130 - margin-bottom: 16 - opacity: 0.5 - phantom: true - - UIProgressBar - id: manaCircleFront - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - image-source: /images/game/circle/right_full - margin-left: 130 - margin-bottom: 16 - opacity: 0.4 - image-color: #0000FFFF - phantom: true - -HealthInfoWindow < HeadlessMiniWindow - icon: - text: - height: 100 - &forceOpen: true - icon: /images/topbuttons/healthinfo - !text: tr('Health Info') - - MiniWindowContents - margin-top: 2 - - HealthBar - id: healthBar - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - margin: 2 - margin-top: 0 - phantom: true - - ManaBar - id: manaBar - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - margin: 2 - margin-bottom: 0 - phantom: true - - ExperienceBar - Panel - id: conditionPanel - layout: - type: horizontalBox - height: 22 - margin-top: 4 - padding: 2 - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - border-width: 1 - border-color: #00000077 - background-color: #ffffff11 - SoulLabel - CapLabel - +ExperienceBar < ProgressBar + id: experienceBar + background-color: #B6E866 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 1 + margin-top: 3 + +SoulLabel < GameLabel + id: soulLabel + text-align: right + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.horizontalCenter + margin-top: 5 + margin-right: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +CapLabel < GameLabel + id: capLabel + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +ConditionWidget < UIWidget + size: 18 18 + + $!first: + margin-left: 2 + +HealthOverlay < UIWidget + id: healthOverlay + anchors.fill: parent + phantom: true + + HealthBar + id: topHealthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + phantom: true + + ManaBar + id: topManaBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.horizontalCenter + phantom: true + + UIProgressBar + id: healthCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_empty + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: healthCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_full + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_empty + margin-left: 130 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_full + margin-left: 130 + margin-bottom: 16 + opacity: 0.4 + image-color: #0000FFFF + phantom: true + +HealthInfoWindow < HeadlessMiniWindow + icon: + text: + height: 100 + &forceOpen: true + icon: /images/topbuttons/healthinfo + !text: tr('Health Info') + + MiniWindowContents + margin-top: 2 + + HealthBar + id: healthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-top: 0 + phantom: true + + ManaBar + id: manaBar + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-bottom: 0 + phantom: true + + ExperienceBar + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + margin-top: 4 + padding: 2 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + border-width: 1 + border-color: #00000077 + background-color: #ffffff11 + SoulLabel + CapLabel + diff --git a/layouts/retro/styles/40-inventory.otui b/layouts/retro/styles/40-inventory.otui index 65233ff..4e173a0 100644 --- a/layouts/retro/styles/40-inventory.otui +++ b/layouts/retro/styles/40-inventory.otui @@ -1,333 +1,333 @@ -InventoryItem < Item - $on: - image-source: /images/ui/item-blessed - -HeadSlot < InventoryItem - id: slot1 - image-source: /images/game/slots/head - &position: {x=65535, y=1, z=0} - $on: - image-source: /images/game/slots/head-blessed - -BodySlot < InventoryItem - id: slot4 - image-source: /images/game/slots/body - &position: {x=65535, y=4, z=0} - $on: - image-source: /images/game/slots/body-blessed - -LegSlot < InventoryItem - id: slot7 - image-source: /images/game/slots/legs - &position: {x=65535, y=7, z=0} - $on: - image-source: /images/game/slots/legs-blessed - -FeetSlot < InventoryItem - id: slot8 - image-source: /images/game/slots/feet - &position: {x=65535, y=8, z=0} - $on: - image-source: /images/game/slots/feet-blessed - -NeckSlot < InventoryItem - id: slot2 - image-source: /images/game/slots/neck - &position: {x=65535, y=2, z=0} - $on: - image-source: /images/game/slots/neck-blessed - -LeftSlot < InventoryItem - id: slot6 - image-source: /images/game/slots/left-hand - &position: {x=65535, y=6, z=0} - $on: - image-source: /images/game/slots/left-hand-blessed - -FingerSlot < InventoryItem - id: slot9 - image-source: /images/game/slots/finger - &position: {x=65535, y=9, z=0} - $on: - image-source: /images/game/slots/finger-blessed - -BackSlot < InventoryItem - id: slot3 - image-source: /images/game/slots/back - &position: {x=65535, y=3, z=0} - $on: - image-source: /images/game/slots/back-blessed - -RightSlot < InventoryItem - id: slot5 - image-source: /images/game/slots/right-hand - &position: {x=65535, y=5, z=0} - $on: - image-source: /images/game/slots/right-hand-blessed - -AmmoSlot < InventoryItem - id: slot10 - image-source: /images/game/slots/ammo - &position: {x=65535, y=10, z=0} - $on: - image-source: /images/game/slots/ammo-blessed - -PurseButton < UIButton - id: purseButton - size: 34 12 - !tooltip: tr('Open purse') - icon-source: /images/game/slots/purse - icon-clip: 0 0 34 12 - - $on: - icon-clip: 0 12 34 12 - - $pressed: - icon-clip: 0 12 34 12 - -CombatBox < UICheckBox - size: 20 20 - image-clip: 0 0 20 20 - margin-left: 4 - - $checked: - image-clip: 0 20 20 20 - - -InventoryButton < Button - height: 18 - margin-top: 2 - text-align: center - font: cipsoftFont - color: white - size: 45 20 - text-offset: 2 2 - -SoulCapLabel < GameLabel - text-align: center - color: #FFFFFF - font: cipsoftFont - margin-top: 4 - text-offset: 0 3 - width: 36 - height: 20 - icon-source: /images/game/slots/soulcap - -FightOffensiveBox < CombatBox - image-source: /images/game/combatmodes/fightoffensive -FightBalancedBox < CombatBox - image-source: /images/game/combatmodes/fightbalanced -FightDefensiveBox < CombatBox - image-source: /images/game/combatmodes/fightdefensive -ChaseModeBox < CombatBox - image-source: /images/game/combatmodes/chasemode -SafeFightBox < CombatBox - image-source: /images/game/combatmodes/safefight - -MountButton < CombatBox - image-source: /images/game/combatmodes/mount - -InventoryWindow < HeadlessMiniWindow - icon: /images/topbuttons/inventory - height: 178 - id: inventoryWindow - @onClose: modules.game_inventory.onMiniWindowClose() - &save: true - &autoOpen: 3 - &forceOpen: true - - MiniWindowContents - anchors.left: parent.left - margin-top: 0 - - UIButton - id: minimizeButton - anchors.top: parent.top - anchors.left: parent.left - size: 14 14 - image-source: /images/ui/miniwindow_buttons - image-clip: 0 0 14 14 - margin-top: 3 - margin-left: 4 - - $hover: - image-clip: 0 14 14 14 - - $pressed: - image-clip: 0 28 14 14 - - $on: - image-clip: 14 0 14 14 - - $on hover: - image-clip: 14 14 14 14 - - $on pressed: - image-clip: 14 28 14 14 - - @onClick: | - self:getParent():getParent().minimizeButton:onClick() - - Panel - id: inventoryPanel - margin-right: 63 - margin-top: 2 - anchors.fill: parent - - HeadSlot - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - margin-top: 3 - - BodySlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - LegSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - FeetSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - NeckSlot - anchors.top: slot1.top - anchors.right: slot1.left - margin-top: 13 - margin-right: 5 - - LeftSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - FingerSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - BackSlot - anchors.top: slot1.top - anchors.left: slot1.right - margin-top: 13 - margin-left: 5 - - RightSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - AmmoSlot - anchors.top: prev.bottom - anchors.horizontalCenter: prev.horizontalCenter - margin-top: 3 - - SoulCapLabel - id: soulLabel - anchors.top: slot10.bottom - anchors.horizontalCenter: slot10.horizontalCenter - - SoulCapLabel - id: capLabel - anchors.top: slot9.bottom - anchors.horizontalCenter: slot9.horizontalCenter - - PurseButton - anchors.left: slot3.left - anchors.bottom: slot3.top - margin-bottom: 3 - - Panel - id: conditionPanel - layout: - type: horizontalBox - height: 22 - padding: 2 - anchors.top: slot8.bottom - anchors.left: slot6.left - anchors.right: slot5.right - margin-top: 4 - border-width: 1 - border-color: #00000077 - background-color: #ffffff22 - - Panel - margin-top: 5 - anchors.fill: parent - anchors.left: prev.right - - FightOffensiveBox - id: fightOffensiveBox - anchors.left: parent.left - anchors.top: parent.top - margin-left: 8 - - ChaseModeBox - id: chaseModeBox - anchors.left: prev.right - anchors.top: parent.top - - FightBalancedBox - id: fightBalancedBox - margin-top: 22 - anchors.left: parent.left - anchors.top: parent.top - margin-left: 8 - - SafeFightBox - id: safeFightBox - margin-top: 22 - anchors.left: prev.right - anchors.top: parent.top - - FightDefensiveBox - id: fightDefensiveBox - margin-top: 44 - anchors.left: parent.left - anchors.top: parent.top - margin-left: 8 - - MountButton - id: mountButton - margin-top: 44 - anchors.left: prev.right - anchors.top: parent.top - - Panel - id: buttonsPanel - margin-top: 4 - margin-right: 5 - anchors.fill: parent - anchors.top: prev.bottom - layout: - type: verticalBox - - UIButton - id: buttonPvp - height: 20 - icon: /images/game/combatmodes/pvp - icon-clip: 0 0 42 20 - - $on: - icon-clip: 0 20 42 20 - - InventoryButton - !text: tr('Stop') - @onClick: g_game.stop(); g_game.cancelAttackAndFollow() - - InventoryButton - !text: tr('Options') - @onClick: modules.client_options.toggle() - - InventoryButton - !text: tr('Hotkeys') - @onClick: modules.game_hotkeys.toggle() - - InventoryButton - !text: tr('Logout') - @onClick: modules.game_interface.tryLogout() - +InventoryItem < Item + $on: + image-source: /images/ui/item-blessed + +HeadSlot < InventoryItem + id: slot1 + image-source: /images/game/slots/head + &position: {x=65535, y=1, z=0} + $on: + image-source: /images/game/slots/head-blessed + +BodySlot < InventoryItem + id: slot4 + image-source: /images/game/slots/body + &position: {x=65535, y=4, z=0} + $on: + image-source: /images/game/slots/body-blessed + +LegSlot < InventoryItem + id: slot7 + image-source: /images/game/slots/legs + &position: {x=65535, y=7, z=0} + $on: + image-source: /images/game/slots/legs-blessed + +FeetSlot < InventoryItem + id: slot8 + image-source: /images/game/slots/feet + &position: {x=65535, y=8, z=0} + $on: + image-source: /images/game/slots/feet-blessed + +NeckSlot < InventoryItem + id: slot2 + image-source: /images/game/slots/neck + &position: {x=65535, y=2, z=0} + $on: + image-source: /images/game/slots/neck-blessed + +LeftSlot < InventoryItem + id: slot6 + image-source: /images/game/slots/left-hand + &position: {x=65535, y=6, z=0} + $on: + image-source: /images/game/slots/left-hand-blessed + +FingerSlot < InventoryItem + id: slot9 + image-source: /images/game/slots/finger + &position: {x=65535, y=9, z=0} + $on: + image-source: /images/game/slots/finger-blessed + +BackSlot < InventoryItem + id: slot3 + image-source: /images/game/slots/back + &position: {x=65535, y=3, z=0} + $on: + image-source: /images/game/slots/back-blessed + +RightSlot < InventoryItem + id: slot5 + image-source: /images/game/slots/right-hand + &position: {x=65535, y=5, z=0} + $on: + image-source: /images/game/slots/right-hand-blessed + +AmmoSlot < InventoryItem + id: slot10 + image-source: /images/game/slots/ammo + &position: {x=65535, y=10, z=0} + $on: + image-source: /images/game/slots/ammo-blessed + +PurseButton < UIButton + id: purseButton + size: 34 12 + !tooltip: tr('Open purse') + icon-source: /images/game/slots/purse + icon-clip: 0 0 34 12 + + $on: + icon-clip: 0 12 34 12 + + $pressed: + icon-clip: 0 12 34 12 + +CombatBox < UICheckBox + size: 20 20 + image-clip: 0 0 20 20 + margin-left: 4 + + $checked: + image-clip: 0 20 20 20 + + +InventoryButton < Button + height: 18 + margin-top: 2 + text-align: center + font: cipsoftFont + color: white + size: 45 20 + text-offset: 2 2 + +SoulCapLabel < GameLabel + text-align: center + color: #FFFFFF + font: cipsoftFont + margin-top: 4 + text-offset: 0 3 + width: 36 + height: 20 + icon-source: /images/game/slots/soulcap + +FightOffensiveBox < CombatBox + image-source: /images/game/combatmodes/fightoffensive +FightBalancedBox < CombatBox + image-source: /images/game/combatmodes/fightbalanced +FightDefensiveBox < CombatBox + image-source: /images/game/combatmodes/fightdefensive +ChaseModeBox < CombatBox + image-source: /images/game/combatmodes/chasemode +SafeFightBox < CombatBox + image-source: /images/game/combatmodes/safefight + +MountButton < CombatBox + image-source: /images/game/combatmodes/mount + +InventoryWindow < HeadlessMiniWindow + icon: /images/topbuttons/inventory + height: 178 + id: inventoryWindow + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 + &forceOpen: true + + MiniWindowContents + anchors.left: parent.left + margin-top: 0 + + UIButton + id: minimizeButton + anchors.top: parent.top + anchors.left: parent.left + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + margin-top: 3 + margin-left: 4 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + @onClick: | + self:getParent():getParent().minimizeButton:onClick() + + Panel + id: inventoryPanel + margin-right: 63 + margin-top: 2 + anchors.fill: parent + + HeadSlot + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 3 + + BodySlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + LegSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FeetSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + NeckSlot + anchors.top: slot1.top + anchors.right: slot1.left + margin-top: 13 + margin-right: 5 + + LeftSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FingerSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + BackSlot + anchors.top: slot1.top + anchors.left: slot1.right + margin-top: 13 + margin-left: 5 + + RightSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + AmmoSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + SoulCapLabel + id: soulLabel + anchors.top: slot10.bottom + anchors.horizontalCenter: slot10.horizontalCenter + + SoulCapLabel + id: capLabel + anchors.top: slot9.bottom + anchors.horizontalCenter: slot9.horizontalCenter + + PurseButton + anchors.left: slot3.left + anchors.bottom: slot3.top + margin-bottom: 3 + + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + padding: 2 + anchors.top: slot8.bottom + anchors.left: slot6.left + anchors.right: slot5.right + margin-top: 4 + border-width: 1 + border-color: #00000077 + background-color: #ffffff22 + + Panel + margin-top: 5 + anchors.fill: parent + anchors.left: prev.right + + FightOffensiveBox + id: fightOffensiveBox + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + ChaseModeBox + id: chaseModeBox + anchors.left: prev.right + anchors.top: parent.top + + FightBalancedBox + id: fightBalancedBox + margin-top: 22 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + SafeFightBox + id: safeFightBox + margin-top: 22 + anchors.left: prev.right + anchors.top: parent.top + + FightDefensiveBox + id: fightDefensiveBox + margin-top: 44 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + MountButton + id: mountButton + margin-top: 44 + anchors.left: prev.right + anchors.top: parent.top + + Panel + id: buttonsPanel + margin-top: 4 + margin-right: 5 + anchors.fill: parent + anchors.top: prev.bottom + layout: + type: verticalBox + + UIButton + id: buttonPvp + height: 20 + icon: /images/game/combatmodes/pvp + icon-clip: 0 0 42 20 + + $on: + icon-clip: 0 20 42 20 + + InventoryButton + !text: tr('Stop') + @onClick: g_game.stop(); g_game.cancelAttackAndFollow() + + InventoryButton + !text: tr('Options') + @onClick: modules.client_options.toggle() + + InventoryButton + !text: tr('Hotkeys') + @onClick: modules.game_hotkeys.toggle() + + InventoryButton + !text: tr('Logout') + @onClick: modules.game_interface.tryLogout() + diff --git a/modules/client_background/background.lua b/modules/client_background/background.lua index 9ecc98c..657424f 100644 --- a/modules/client_background/background.lua +++ b/modules/client_background/background.lua @@ -8,7 +8,7 @@ function init() background:lower() clientVersionLabel = background:getChildById('clientVersionLabel') - clientVersionLabel:setText('OTClientV8 ' .. g_app.getVersion() .. '\nMade by:\n' .. g_app.getAuthor() .. "\notclient@otclient.ovh") + clientVersionLabel:setText('OTClientV8 ' .. g_app.getVersion() .. '\nrev ' .. g_app.getBuildRevision() .. '\nMade by:\n' .. g_app.getAuthor() .. "") if not g_game.isOnline() then addEvent(function() g_effects.fadeIn(clientVersionLabel, 1500) end) diff --git a/modules/client_entergame/entergame.lua b/modules/client_entergame/entergame.lua index c46ede7..b2b71b1 100644 --- a/modules/client_entergame/entergame.lua +++ b/modules/client_entergame/entergame.lua @@ -15,7 +15,7 @@ local serverSelector local clientVersionSelector local serverHostTextEdit local rememberPasswordBox -local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "910", "961", "1000", "1077", "1090", "1096", "1098", "1099", "1100", "1200", "1220", "1230", "1240", "1250", "1252"} +local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "910", "961", "1000", "1077", "1090", "1096", "1098", "1099", "1100", "1200", "1220"} local checkedByUpdater = {} local waitingForHttpResults = 0 diff --git a/modules/corelib/corelib.otmod b/modules/corelib/corelib.otmod index 1c7f068..75cce3b 100644 --- a/modules/corelib/corelib.otmod +++ b/modules/corelib/corelib.otmod @@ -31,4 +31,4 @@ Module dofile 'base64' dofile 'json' dofile 'http' - \ No newline at end of file + dofile 'test' diff --git a/modules/corelib/test.lua b/modules/corelib/test.lua new file mode 100644 index 0000000..2123e65 --- /dev/null +++ b/modules/corelib/test.lua @@ -0,0 +1,62 @@ +Test = { + tests = {}, + activeTest = 0, + screenShot = 1 +} + +Test.Test = function(name, func) + local testId = #Test.tests + 1 + Test.tests[testId] = { + name = name, + actions = {}, + delay = 0, + start = 0 + } + local test = function(testFunc) + table.insert(Test.tests[testId].actions, {type = "test", value = testFunc}) + end + local wait = function(millis) + Test.tests[testId].delay = Test.tests[testId].delay + millis + table.insert(Test.tests[testId].actions, {type = "wait", value = Test.tests[testId].delay}) + end + local ss = function() + table.insert(Test.tests[testId].actions, {type = "screenshot"}) + end + local fail = function(message) + g_logger.fatal("Test " .. name .. " failed: " .. message) + end + func(test, wait, ss, fail) +end + +Test.run = function() + if Test.activeTest > #Test.tests then + g_logger.info("[TEST] Finished tests. Exiting...") + return g_app.exit() + end + local test = Test.tests[Test.activeTest] + if not test or #test.actions == 0 then + Test.activeTest = Test.activeTest + 1 + local nextTest = Test.tests[Test.activeTest] + if nextTest then + nextTest.start = g_clock.millis() + g_logger.info("[TEST] Starting test: " .. nextTest.name) + end + return scheduleEvent(Test.run, 500) + end + + local action = test.actions[1] + if action.type == "test" then + table.remove(test.actions, 1) + action.value() + elseif action.type == "screenshot" then + table.remove(test.actions, 1) + g_app.doScreenshot(Test.screenShot .. ".png") + Test.screenShot = Test.screenShot + 1 + elseif action.type == "wait" then + if action.value + test.start < g_clock.millis() then + table.remove(test.actions, 1) + end + end + + scheduleEvent(Test.run, 100) +end diff --git a/modules/corelib/ui/uiminiwindowcontainer.lua b/modules/corelib/ui/uiminiwindowcontainer.lua index 514ed05..6c6eea1 100644 --- a/modules/corelib/ui/uiminiwindowcontainer.lua +++ b/modules/corelib/ui/uiminiwindowcontainer.lua @@ -194,12 +194,20 @@ function UIMiniWindowContainer:order() if not children[i].miniLoaded then return end end + table.sort(children, function(a, b) + local indexA = a.miniIndex or a.autoOpen or 999 + local indexB = b.miniIndex or b.autoOpen or 999 + return indexA < indexB + end) + + self:reorderChildren(children) + local ignoreIndex = 0 for i=1,#children do - if children[i].miniIndex then - self:swapInsert(children[i], children[i].miniIndex) - elseif children[i].autoOpen then - self:swapInsert(children[i], children[i].autoOpen) - end + if children[i].save then + children[i].miniIndex = i - ignoreIndex + else + ignoreIndex = ignoreIndex + 1 + end end end diff --git a/modules/game_actionbar/actionbar.lua b/modules/game_actionbar/actionbar.lua index 344cb24..32e7b1a 100644 --- a/modules/game_actionbar/actionbar.lua +++ b/modules/game_actionbar/actionbar.lua @@ -1,455 +1,455 @@ -actionPanel1 = nil -actionPanel2 = nil - -local actionConfig -local hotkeyAssignWindow -local actionButtonsInPanel = 50 - -ActionTypes = { - USE = 0, - USE_SELF = 1, - USE_TARGET = 2, - USE_WITH = 3, - EQUIP = 4 -} - -ActionColors = { - empty = '#00000033', - text = '#00000033', - itemUse = '#8888FF88', - itemUseSelf = '#00FF0088', - itemUseTarget = '#FF000088', - itemUseWith = '#F5B32588', - itemEquip = '#FFFFFF88' -} - -function init() - local bottomPanel = modules.game_interface.getActionPanel() - actionPanel1 = g_ui.loadUI('actionbar', bottomPanel) - bottomPanel:moveChildToIndex(actionPanel1, 1) - actionPanel2 = g_ui.loadUI('actionbar', bottomPanel) - bottomPanel:moveChildToIndex(actionPanel2, 1) - - actionConfig = g_configs.create("/actionbar.otml") - - connect(g_game, { - onGameStart = online, - onGameEnd = offline, - onSpellGroupCooldown = onSpellGroupCooldown, - onSpellCooldown = onSpellCooldown - }) - - if g_game.isOnline() then - online() - end -end - -function terminate() - disconnect(g_game, { - onGameStart = online, - onGameEnd = offline, - onSpellGroupCooldown = onSpellGroupCooldown, - onSpellCooldown = onSpellCooldown - }) - - -- remove hotkeys, also saves config - if actionPanel1.tabBar:getChildCount() > 0 and actionPanel2.tabBar:getChildCount() > 0 then - offline() - end - - actionPanel1:destroy() - actionPanel2:destroy() -end - -function show() - if not g_game.isOnline() then return end - actionPanel1:setOn(g_settings.getBoolean("actionBar1", false)) - actionPanel2:setOn(g_settings.getBoolean("actionBar2", false)) -end - -function hide() - actionPanel1:setOn(false) - actionPanel2:setOn(false) -end - -function switchMode(newMode) - if newMode then - actionPanel1:setImageColor('#ffffff88') - actionPanel2:setImageColor('#ffffff88') - else - actionPanel1:setImageColor('white') - actionPanel2:setImageColor('white') - end -end - -function online() - setupActionPanel(1, actionPanel1) - setupActionPanel(2, actionPanel2) - show() -end - -function offline() - hide() - if hotkeyAssignWindow then - hotkeyAssignWindow:destroy() - hotkeyAssignWindow = nil - end - - local gameRootPanel = modules.game_interface.getRootPanel() - for index, panel in ipairs({actionPanel1, actionPanel2}) do - local config = {} - for i, child in ipairs(panel.tabBar:getChildren()) do - if child.config then - table.insert(config, child.config) - if type(child.config.hotkey) == 'string' and child.config.hotkey:len() > 0 then - g_keyboard.unbindKeyPress(child.config.hotkey, child.callback, gameRootPanel) - end - else - table.insert(config, {}) - end - if child.cooldownEvent then - removeEvent(child.cooldownEvent) - end - end - actionConfig:setNode('actions_' .. index, config) - panel.tabBar:destroyChildren() - end - actionConfig:save() -end - -function setupActionPanel(index, panel) - local rawConfig = actionConfig:getNode('actions_' .. index) or {} - local config = {} - for i, buttonConfig in pairs(rawConfig) do -- sorting, because key in rawConfig is string - config[tonumber(i)] = buttonConfig - end - - for i=1,actionButtonsInPanel do - local action = g_ui.createWidget('ActionButton', panel.tabBar) - action.config = config[i] or {} - setupAction(action) - end - - panel.nextButton.onClick = function() - panel.tabBar:moveChildToIndex(panel.tabBar:getFirstChild(), panel.tabBar:getChildCount()) - end - panel.prevButton.onClick = function() - panel.tabBar:moveChildToIndex(panel.tabBar:getLastChild(), 1) - end -end - -function setupAction(action) - local config = action.config - action.item:setShowCount(false) - action.onMouseRelease = actionOnMouseRelease - action.onTouchRelease = actionOnMouseRelease - action.callback = function(k, c, ticks) executeAction(action, ticks) end - action.item.onItemChange = nil -- disable callbacks for setup - - if config then - if type(config.text) == 'number' then - config.text = tostring(config.text) - end - if type(config.hotkey) == 'number' then - config.hotkey = tostring(config.hotkey) - end - if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then - local gameRootPanel = modules.game_interface.getRootPanel() - g_keyboard.bindKeyPress(config.hotkey, action.callback, gameRootPanel) - action.hotkeyLabel:setText(config.hotkey) - else - action.hotkeyLabel:setText("") - end - - action.text:setImageSource("") - action.cooldownTill = 0 - action.cooldownStart = 0 - if type(config.text) == 'string' and config.text:len() > 0 then - action.text:setText(config.text) - action.item:setBorderColor(ActionColors.text) - action.item:setOn(true) -- removes background - action.item:setItemId(0) - if Spells then - local spell, profile = Spells.getSpellByWords(config.text:lower()) - action.spell = spell - if action.spell and action.spell.icon and profile then - action.text:setImageSource(SpelllistSettings[profile].iconFile) - action.text:setImageClip(Spells.getImageClip(SpellIcons[action.spell.icon][1], profile)) - action.text:setText("") - end - end - else - action.text:setText("") - action.spell = nil - if type(config.item) == 'number' and config.item > 100 then - action.item:setOn(true) - action.item:setItemId(config.item) - action.item:setItemCount(config.count or 1) - setupActionType(action, config.actionType) - else - action.item:setItemId(0) - action.item:setOn(false) - action.item:setBorderColor(ActionColors.empty) - end - end - end - - action.item.onItemChange = actionOnItemChange -end - -function setupActionType(action, actionType) - local item = action.item:getItem() - if action.item:getItem():isMultiUse() then - if not actionType or actionType <= ActionTypes.USE then - actionType = ActionTypes.USE_WITH - end - elseif g_game.getClientVersion() >= 910 then - if actionType ~= ActionTypes.USE and actionType ~= ActionTypes.EQUIP then - actionType = ActionTypes.USE - end - else - actionType = ActionTypes.USE - end - - action.config.actionType = actionType - if action.config.actionType == ActionTypes.USE then - action.item:setBorderColor(ActionColors.itemUse) - elseif action.config.actionType == ActionTypes.USE_SELF then - action.item:setBorderColor(ActionColors.itemUseSelf) - elseif action.config.actionType == ActionTypes.USE_TARGET then - action.item:setBorderColor(ActionColors.itemUseTarget) - elseif action.config.actionType == ActionTypes.USE_WITH then - action.item:setBorderColor(ActionColors.itemUseWith) - elseif action.config.actionType == ActionTypes.EQUIP then - action.item:setBorderColor(ActionColors.itemEquip) - end -end - -function updateAction(action, newConfig) - local config = action.config - if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then - local gameRootPanel = modules.game_interface.getRootPanel() - g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel) - end - for key, val in pairs(newConfig) do - action.config[key] = val - end - setupAction(action) -end - -function actionOnMouseRelease(action, mousePosition, mouseButton) - if mouseButton == MouseTouch then return end - if mouseButton == MouseRightButton or not action.item:isOn() then - local menu = g_ui.createWidget('PopupMenu') - menu:setGameMenu(true) - if action.item:getItemId() > 0 then - if action.item:getItem():isMultiUse() then - menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end) - menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end) - menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end) - end - if g_game.getClientVersion() >= 910 then - if not action.item:getItem():isMultiUse() then - menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end) - end - menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end) - end - else - menu:addOption(tr('Select item'), function() return modules.game_itemselector.show(action.item) end) - end - menu:addSeparator() - menu:addOption(tr('Set text'), function() - modules.client_textedit.singlelineEditor(action.config.text or "", function(newText) - updateAction(action, {text=newText, item=0}) - end) - end) - menu:addOption(tr('Set hotkey'), function() - if hotkeyAssignWindow then - hotkeyAssignWindow:destroy() - end - local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget) - assignWindow:grabKeyboard() - assignWindow.comboPreview.keyCombo = '' - assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers) - local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) - assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo)) - assignWindow.comboPreview.keyCombo = keyCombo - assignWindow.comboPreview:resizeToText() - return true - end - assignWindow.onDestroy = function(widget) - if widget == hotkeyAssignWindow then - hotkeyAssignWindow = nil - end - end - assignWindow.addButton.onClick = function() - updateAction(action, {hotkey=tostring(assignWindow.comboPreview.keyCombo)}) - assignWindow:destroy() - end - hotkeyAssignWindow = assignWindow - end) - menu:addSeparator() - menu:addOption(tr('Clear'), function() - updateAction(action, {hotkey="", text="", item=0, count=1}) - end) - menu:display(mousePosition) - return true - elseif mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3 then - action.callback() - return true - end - return false -end - -function actionOnItemChange(widget) - updateAction(widget:getParent(), {text="", item=widget:getItemId(), count=widget:getItemCountOrSubType()}) -end - -function onSpellCooldown(iconId, duration) - for index, panel in ipairs({actionPanel1, actionPanel2}) do - for i, child in ipairs(panel.tabBar:getChildren()) do - if child.spell and child.spell.id == iconId then - startCooldown(child, duration) - end - end - end -end - -function onSpellGroupCooldown(groupId, duration) - for index, panel in ipairs({actionPanel1, actionPanel2}) do - for i, child in ipairs(panel.tabBar:getChildren()) do - if child.spell and child.spell.group then - for group, duration in pairs(child.spell.group) do - if groupId == group then - startCooldown(child, duration) - end - end - end - end - end -end - -function startCooldown(action, duration) - if type(action.cooldownTill) == 'number' and action.cooldownTill > g_clock.millis() + duration then - return -- already has cooldown with greater duration - end - action.cooldownStart = g_clock.millis() - action.cooldownTill = g_clock.millis() + duration - updateCooldown(action) -end - -function updateCooldown(action) - if not action or not action.cooldownTill then return end - local timeleft = action.cooldownTill - g_clock.millis() - if timeleft <= 30 then - action.cooldown:setPercent(100) - action.cooldownEvent = nil - return - end - local duration = action.cooldownTill - action.cooldownStart - action.cooldown:setPercent(100 - math.floor(100 * timeleft / duration)) - action.cooldownEvent = scheduleEvent(function() updateCooldown(action) end, 30) -end - -function executeAction(action, ticks) - if not action.config then return end - if type(ticks) ~= 'number' then ticks = 0 end - - local actionDelay = 100 - if ticks == 0 then - actionDelay = 200 -- for first use - elseif action.actionDelayTo ~= nil and g_clock.millis() < action.actionDelayTo then - return - end - - local actionType = action.config.actionType - - if type(action.config.text) == 'string' and action.config.text:len() > 0 then - if g_app.isMobile() then -- turn to direction of targer - local target = g_game.getAttackingCreature() - if target then - local pos = g_game.getLocalPlayer():getPosition() - local tpos = target:getPosition() - if pos and tpos then - local offx = tpos.x - pos.x - local offy = tpos.y - pos.y - if offy < 0 and offx <= 0 and math.abs(offx) < math.abs(offy) then - g_game.turn(Directions.North) - elseif offy > 0 and offx >= 0 and math.abs(offx) < math.abs(offy) then - g_game.turn(Directions.South) - elseif offx < 0 and offy <= 0 and math.abs(offx) > math.abs(offy) then - g_game.turn(Directions.West) - elseif offx > 0 and offy >= 0 and math.abs(offx) > math.abs(offy) then - g_game.turn(Directions.East) - end - end - end - end - if modules.game_interface.isChatVisible() then - modules.game_console.sendMessage(action.config.text) - else - g_game.talk(action.config.text) - end - action.actionDelayTo = g_clock.millis() + actionDelay - elseif action.item:getItemId() > 0 then - if actionType == ActionTypes.USE then - if g_game.getClientVersion() < 780 then - local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) - if item then - g_game.use(item) - end - else - g_game.useInventoryItem(action.item:getItemId()) - end - action.actionDelayTo = g_clock.millis() + actionDelay - elseif actionType == ActionTypes.USE_SELF then - if g_game.getClientVersion() < 780 then - local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) - if item then - g_game.useWith(item, g_game.getLocalPlayer()) - end - else - g_game.useInventoryItemWith(action.item:getItemId(), g_game.getLocalPlayer(), action.item:getItemSubType() or -1) - end - action.actionDelayTo = g_clock.millis() + actionDelay - elseif actionType == ActionTypes.USE_TARGET then - local attackingCreature = g_game.getAttackingCreature() - if not attackingCreature then - local item = Item.create(action.item:getItemId()) - if g_game.getClientVersion() < 780 then - local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) - if not tmpItem then return end - item = tmpItem - end - - modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) - return - end - - if not attackingCreature:getTile() then return end - if g_game.getClientVersion() < 780 then - local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) - if item then - g_game.useWith(item, attackingCreature, action.item:getItemSubType() or -1) - end - else - g_game.useInventoryItemWith(action.item:getItemId(), attackingCreature, action.item:getItemSubType() or -1) - end - action.actionDelayTo = g_clock.millis() + actionDelay - elseif actionType == ActionTypes.USE_WITH then - local item = Item.create(action.item:getItemId()) - if g_game.getClientVersion() < 780 then - local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) - if not tmpItem then return true end - item = tmpItem - end - modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) - elseif actionType == ActionTypes.EQUIP then - if g_game.getClientVersion() >= 910 then - local item = Item.create(action.item:getItemId()) - g_game.equipItem(item) - action.actionDelayTo = g_clock.millis() + actionDelay - end - end - end +actionPanel1 = nil +actionPanel2 = nil + +local actionConfig +local hotkeyAssignWindow +local actionButtonsInPanel = 50 + +ActionTypes = { + USE = 0, + USE_SELF = 1, + USE_TARGET = 2, + USE_WITH = 3, + EQUIP = 4 +} + +ActionColors = { + empty = '#00000033', + text = '#00000033', + itemUse = '#8888FF88', + itemUseSelf = '#00FF0088', + itemUseTarget = '#FF000088', + itemUseWith = '#F5B32588', + itemEquip = '#FFFFFF88' +} + +function init() + local bottomPanel = modules.game_interface.getActionPanel() + actionPanel1 = g_ui.loadUI('actionbar', bottomPanel) + bottomPanel:moveChildToIndex(actionPanel1, 1) + actionPanel2 = g_ui.loadUI('actionbar', bottomPanel) + bottomPanel:moveChildToIndex(actionPanel2, 1) + + actionConfig = g_configs.create("/actionbar.otml") + + connect(g_game, { + onGameStart = online, + onGameEnd = offline, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown + }) + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown + }) + + -- remove hotkeys, also saves config + if actionPanel1.tabBar:getChildCount() > 0 and actionPanel2.tabBar:getChildCount() > 0 then + offline() + end + + actionPanel1:destroy() + actionPanel2:destroy() +end + +function show() + if not g_game.isOnline() then return end + actionPanel1:setOn(g_settings.getBoolean("actionBar1", false)) + actionPanel2:setOn(g_settings.getBoolean("actionBar2", false)) +end + +function hide() + actionPanel1:setOn(false) + actionPanel2:setOn(false) +end + +function switchMode(newMode) + if newMode then + actionPanel1:setImageColor('#ffffff88') + actionPanel2:setImageColor('#ffffff88') + else + actionPanel1:setImageColor('white') + actionPanel2:setImageColor('white') + end +end + +function online() + setupActionPanel(1, actionPanel1) + setupActionPanel(2, actionPanel2) + show() +end + +function offline() + hide() + if hotkeyAssignWindow then + hotkeyAssignWindow:destroy() + hotkeyAssignWindow = nil + end + + local gameRootPanel = modules.game_interface.getRootPanel() + for index, panel in ipairs({actionPanel1, actionPanel2}) do + local config = {} + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.config then + table.insert(config, child.config) + if type(child.config.hotkey) == 'string' and child.config.hotkey:len() > 0 then + g_keyboard.unbindKeyPress(child.config.hotkey, child.callback, gameRootPanel) + end + else + table.insert(config, {}) + end + if child.cooldownEvent then + removeEvent(child.cooldownEvent) + end + end + actionConfig:setNode('actions_' .. index, config) + panel.tabBar:destroyChildren() + end + actionConfig:save() +end + +function setupActionPanel(index, panel) + local rawConfig = actionConfig:getNode('actions_' .. index) or {} + local config = {} + for i, buttonConfig in pairs(rawConfig) do -- sorting, because key in rawConfig is string + config[tonumber(i)] = buttonConfig + end + + for i=1,actionButtonsInPanel do + local action = g_ui.createWidget('ActionButton', panel.tabBar) + action.config = config[i] or {} + setupAction(action) + end + + panel.nextButton.onClick = function() + panel.tabBar:moveChildToIndex(panel.tabBar:getFirstChild(), panel.tabBar:getChildCount()) + end + panel.prevButton.onClick = function() + panel.tabBar:moveChildToIndex(panel.tabBar:getLastChild(), 1) + end +end + +function setupAction(action) + local config = action.config + action.item:setShowCount(false) + action.onMouseRelease = actionOnMouseRelease + action.onTouchRelease = actionOnMouseRelease + action.callback = function(k, c, ticks) executeAction(action, ticks) end + action.item.onItemChange = nil -- disable callbacks for setup + + if config then + if type(config.text) == 'number' then + config.text = tostring(config.text) + end + if type(config.hotkey) == 'number' then + config.hotkey = tostring(config.hotkey) + end + if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyPress(config.hotkey, action.callback, gameRootPanel) + action.hotkeyLabel:setText(config.hotkey) + else + action.hotkeyLabel:setText("") + end + + action.text:setImageSource("") + action.cooldownTill = 0 + action.cooldownStart = 0 + if type(config.text) == 'string' and config.text:len() > 0 then + action.text:setText(config.text) + action.item:setBorderColor(ActionColors.text) + action.item:setOn(true) -- removes background + action.item:setItemId(0) + if Spells then + local spell, profile = Spells.getSpellByWords(config.text:lower()) + action.spell = spell + if action.spell and action.spell.icon and profile then + action.text:setImageSource(SpelllistSettings[profile].iconFile) + action.text:setImageClip(Spells.getImageClip(SpellIcons[action.spell.icon][1], profile)) + action.text:setText("") + end + end + else + action.text:setText("") + action.spell = nil + if type(config.item) == 'number' and config.item > 100 then + action.item:setOn(true) + action.item:setItemId(config.item) + action.item:setItemCount(config.count or 1) + setupActionType(action, config.actionType) + else + action.item:setItemId(0) + action.item:setOn(false) + action.item:setBorderColor(ActionColors.empty) + end + end + end + + action.item.onItemChange = actionOnItemChange +end + +function setupActionType(action, actionType) + local item = action.item:getItem() + if action.item:getItem():isMultiUse() then + if not actionType or actionType <= ActionTypes.USE then + actionType = ActionTypes.USE_WITH + end + elseif g_game.getClientVersion() >= 910 then + if actionType ~= ActionTypes.USE and actionType ~= ActionTypes.EQUIP then + actionType = ActionTypes.USE + end + else + actionType = ActionTypes.USE + end + + action.config.actionType = actionType + if action.config.actionType == ActionTypes.USE then + action.item:setBorderColor(ActionColors.itemUse) + elseif action.config.actionType == ActionTypes.USE_SELF then + action.item:setBorderColor(ActionColors.itemUseSelf) + elseif action.config.actionType == ActionTypes.USE_TARGET then + action.item:setBorderColor(ActionColors.itemUseTarget) + elseif action.config.actionType == ActionTypes.USE_WITH then + action.item:setBorderColor(ActionColors.itemUseWith) + elseif action.config.actionType == ActionTypes.EQUIP then + action.item:setBorderColor(ActionColors.itemEquip) + end +end + +function updateAction(action, newConfig) + local config = action.config + if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel) + end + for key, val in pairs(newConfig) do + action.config[key] = val + end + setupAction(action) +end + +function actionOnMouseRelease(action, mousePosition, mouseButton) + if mouseButton == MouseTouch then return end + if mouseButton == MouseRightButton or not action.item:isOn() then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + if action.item:getItemId() > 0 then + if action.item:getItem():isMultiUse() then + menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end) + menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end) + menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end) + end + if g_game.getClientVersion() >= 910 then + if not action.item:getItem():isMultiUse() then + menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end) + end + menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end) + end + else + menu:addOption(tr('Select item'), function() return modules.game_itemselector.show(action.item) end) + end + menu:addSeparator() + menu:addOption(tr('Set text'), function() + modules.client_textedit.singlelineEditor(action.config.text or "", function(newText) + updateAction(action, {text=newText, item=0}) + end) + end) + menu:addOption(tr('Set hotkey'), function() + if hotkeyAssignWindow then + hotkeyAssignWindow:destroy() + end + local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget) + assignWindow:grabKeyboard() + assignWindow.comboPreview.keyCombo = '' + assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers) + local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) + assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo)) + assignWindow.comboPreview.keyCombo = keyCombo + assignWindow.comboPreview:resizeToText() + return true + end + assignWindow.onDestroy = function(widget) + if widget == hotkeyAssignWindow then + hotkeyAssignWindow = nil + end + end + assignWindow.addButton.onClick = function() + updateAction(action, {hotkey=tostring(assignWindow.comboPreview.keyCombo)}) + assignWindow:destroy() + end + hotkeyAssignWindow = assignWindow + end) + menu:addSeparator() + menu:addOption(tr('Clear'), function() + updateAction(action, {hotkey="", text="", item=0, count=1}) + end) + menu:display(mousePosition) + return true + elseif mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3 then + action.callback() + return true + end + return false +end + +function actionOnItemChange(widget) + updateAction(widget:getParent(), {text="", item=widget:getItemId(), count=widget:getItemCountOrSubType()}) +end + +function onSpellCooldown(iconId, duration) + for index, panel in ipairs({actionPanel1, actionPanel2}) do + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.spell and child.spell.id == iconId then + startCooldown(child, duration) + end + end + end +end + +function onSpellGroupCooldown(groupId, duration) + for index, panel in ipairs({actionPanel1, actionPanel2}) do + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.spell and child.spell.group then + for group, duration in pairs(child.spell.group) do + if groupId == group then + startCooldown(child, duration) + end + end + end + end + end +end + +function startCooldown(action, duration) + if type(action.cooldownTill) == 'number' and action.cooldownTill > g_clock.millis() + duration then + return -- already has cooldown with greater duration + end + action.cooldownStart = g_clock.millis() + action.cooldownTill = g_clock.millis() + duration + updateCooldown(action) +end + +function updateCooldown(action) + if not action or not action.cooldownTill then return end + local timeleft = action.cooldownTill - g_clock.millis() + if timeleft <= 30 then + action.cooldown:setPercent(100) + action.cooldownEvent = nil + return + end + local duration = action.cooldownTill - action.cooldownStart + action.cooldown:setPercent(100 - math.floor(100 * timeleft / duration)) + action.cooldownEvent = scheduleEvent(function() updateCooldown(action) end, 30) +end + +function executeAction(action, ticks) + if not action.config then return end + if type(ticks) ~= 'number' then ticks = 0 end + + local actionDelay = 100 + if ticks == 0 then + actionDelay = 200 -- for first use + elseif action.actionDelayTo ~= nil and g_clock.millis() < action.actionDelayTo then + return + end + + local actionType = action.config.actionType + + if type(action.config.text) == 'string' and action.config.text:len() > 0 then + if g_app.isMobile() then -- turn to direction of targer + local target = g_game.getAttackingCreature() + if target then + local pos = g_game.getLocalPlayer():getPosition() + local tpos = target:getPosition() + if pos and tpos then + local offx = tpos.x - pos.x + local offy = tpos.y - pos.y + if offy < 0 and offx <= 0 and math.abs(offx) < math.abs(offy) then + g_game.turn(Directions.North) + elseif offy > 0 and offx >= 0 and math.abs(offx) < math.abs(offy) then + g_game.turn(Directions.South) + elseif offx < 0 and offy <= 0 and math.abs(offx) > math.abs(offy) then + g_game.turn(Directions.West) + elseif offx > 0 and offy >= 0 and math.abs(offx) > math.abs(offy) then + g_game.turn(Directions.East) + end + end + end + end + if modules.game_interface.isChatVisible() then + modules.game_console.sendMessage(action.config.text) + else + g_game.talk(action.config.text) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif action.item:getItemId() > 0 then + if actionType == ActionTypes.USE then + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if item then + g_game.use(item) + end + else + g_game.useInventoryItem(action.item:getItemId()) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_SELF then + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if item then + g_game.useWith(item, g_game.getLocalPlayer()) + end + else + g_game.useInventoryItemWith(action.item:getItemId(), g_game.getLocalPlayer(), action.item:getItemSubType() or -1) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_TARGET then + local attackingCreature = g_game.getAttackingCreature() + if not attackingCreature then + local item = Item.create(action.item:getItemId()) + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if not tmpItem then return end + item = tmpItem + end + + modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) + return + end + + if not attackingCreature:getTile() then return end + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if item then + g_game.useWith(item, attackingCreature, action.item:getItemSubType() or -1) + end + else + g_game.useInventoryItemWith(action.item:getItemId(), attackingCreature, action.item:getItemSubType() or -1) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_WITH then + local item = Item.create(action.item:getItemId()) + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if not tmpItem then return true end + item = tmpItem + end + modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) + elseif actionType == ActionTypes.EQUIP then + if g_game.getClientVersion() >= 910 then + local item = Item.create(action.item:getItemId()) + g_game.equipItem(item) + action.actionDelayTo = g_clock.millis() + actionDelay + end + end + end end \ No newline at end of file diff --git a/modules/game_actionbar/actionbar.otui b/modules/game_actionbar/actionbar.otui index fc81d82..2f30eef 100644 --- a/modules/game_actionbar/actionbar.otui +++ b/modules/game_actionbar/actionbar.otui @@ -1,145 +1,145 @@ -ActionButton < Panel - font: cipsoftFont - anchors.top: parent.top - anchors.bottom: parent.bottom - width: 40 - padding: 1 1 1 1 - margin-left: 1 - - $first: - anchors.left: parent.left - - $!first: - anchors.left: prev.right - - Item - id: item - anchors.fill: parent - &selectable: true - &editable: false - virtual: true - border-width: 1 - - border-color: #00000000 - - $!on: - image-source: /images/game/actionbarslot - - Label - id: text - anchors.fill: parent - text-auto-resize: true - text-wrap: true - phantom: true - text-align: center - font: verdana-9px - - Label - id: hotkeyLabel - anchors.top: parent.top - anchors.left: parent.left - margin: 2 3 3 3 - text-auto-resize: true - text-wrap: false - phantom: true - font: small-9px - color: yellow - - UIProgressRect - id: cooldown - background: #585858AA - percent: 100 - focusable: false - phantom: true - anchors.fill: parent - margin: 1 1 1 1 - -Panel - id: actionBar - focusable: false - image-source: /images/ui/panel_side - image-border: 4 - margin-top: -1 - - $on: - height: 40 - visible: true - - $!on: - height: 0 - visible: false - - TabButton - id: prevButton - icon: /images/game/console/leftarrow - anchors.left: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - margin-left: 1 - margin-top: 1 - margin-bottom: 2 - - Panel - id: tabBar - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: prev.right - anchors.right: next.left - margin-right: 3 - clipping: true - - TabButton - id: nextButton - icon: /images/game/console/rightarrow - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - margin-right: 1 - margin-top: 1 - margin-bottom: 2 - - -ActionAssignWindow < MainWindow - id: assignWindow - !text: tr('Button Assign') - size: 360 150 - @onEscape: self:destroy() - - Label - !text: tr('Please, press the key you wish to use for action') - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - text-auto-resize: true - text-align: left - - Label - id: comboPreview - !text: tr('Current action hotkey: %s', 'none') - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: prev.bottom - margin-top: 10 - text-auto-resize: true - - HorizontalSeparator - id: separator - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: next.top - margin-bottom: 10 - - Button - id: addButton - !text: tr('Add') - width: 64 - anchors.right: next.left - anchors.bottom: parent.bottom - margin-right: 10 - - Button - id: cancelButton - !text: tr('Cancel') - width: 64 - anchors.right: parent.right - anchors.bottom: parent.bottom +ActionButton < Panel + font: cipsoftFont + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 40 + padding: 1 1 1 1 + margin-left: 1 + + $first: + anchors.left: parent.left + + $!first: + anchors.left: prev.right + + Item + id: item + anchors.fill: parent + &selectable: true + &editable: false + virtual: true + border-width: 1 + + border-color: #00000000 + + $!on: + image-source: /images/game/actionbarslot + + Label + id: text + anchors.fill: parent + text-auto-resize: true + text-wrap: true + phantom: true + text-align: center + font: verdana-9px + + Label + id: hotkeyLabel + anchors.top: parent.top + anchors.left: parent.left + margin: 2 3 3 3 + text-auto-resize: true + text-wrap: false + phantom: true + font: small-9px + color: yellow + + UIProgressRect + id: cooldown + background: #585858AA + percent: 100 + focusable: false + phantom: true + anchors.fill: parent + margin: 1 1 1 1 + +Panel + id: actionBar + focusable: false + image-source: /images/ui/panel_side + image-border: 4 + margin-top: -1 + + $on: + height: 40 + visible: true + + $!on: + height: 0 + visible: false + + TabButton + id: prevButton + icon: /images/game/console/leftarrow + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-left: 1 + margin-top: 1 + margin-bottom: 2 + + Panel + id: tabBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + anchors.right: next.left + margin-right: 3 + clipping: true + + TabButton + id: nextButton + icon: /images/game/console/rightarrow + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 1 + margin-top: 1 + margin-bottom: 2 + + +ActionAssignWindow < MainWindow + id: assignWindow + !text: tr('Button Assign') + size: 360 150 + @onEscape: self:destroy() + + Label + !text: tr('Please, press the key you wish to use for action') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-auto-resize: true + text-align: left + + Label + id: comboPreview + !text: tr('Current action hotkey: %s', 'none') + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 10 + text-auto-resize: true + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: addButton + !text: tr('Add') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom @onClick: self:getParent():destroy() \ No newline at end of file diff --git a/modules/game_battle/battle.lua b/modules/game_battle/battle.lua index 4853ead..bc2e03c 100644 --- a/modules/game_battle/battle.lua +++ b/modules/game_battle/battle.lua @@ -283,7 +283,7 @@ function checkCreatures() for i=#creatures + 1,maxCreatures do if battleButtons[i]:isHidden() then break end battleButtons[i]:hide() - battleButtons[i]:setOn(false) + battleButton:setOn(false) end battlePanel:getLayout():enableUpdates() diff --git a/modules/game_bot/bot.lua b/modules/game_bot/bot.lua index d64b604..9431394 100644 --- a/modules/game_bot/bot.lua +++ b/modules/game_bot/bot.lua @@ -474,7 +474,9 @@ function initCallbacks() onChannelList = botChannelList, onOpenChannel = botOpenChannel, onCloseChannel = botCloseChannel, - onChannelEvent = botChannelEvent + onChannelEvent = botChannelEvent, + onImbuementWindow = botOnImbuementWindow, + onModalDialog = botOnModalDialog, }) connect(Tile, { @@ -527,7 +529,9 @@ function terminateCallbacks() onChannelList = botChannelList, onOpenChannel = botOpenChannel, onCloseChannel = botCloseChannel, - onChannelEvent = botChannelEvent + onChannelEvent = botChannelEvent, + onImbuementWindow = botOnImbuementWindow, + onModalDialog = botOnModalDialog, }) disconnect(Tile, { @@ -702,4 +706,14 @@ end function botCreatureWalk(creature, oldPos, newPos) if botExecutor == nil then return false end safeBotCall(function() botExecutor.callbacks.onWalk(creature, oldPos, newPos) end) -end \ No newline at end of file +end + +function botOnImbuementWindow(itemId, slots, activeSlots, imbuements, needItems) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onImbuementWindow(itemId, slots, activeSlots, imbuements, needItems) end) +end + +function botOnModalDialog(id, title, message, buttons, enterButton, escapeButton, choices, priority) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onModalDialog(id, title, message, buttons, enterButton, escapeButton, choices, priority) end) +end diff --git a/modules/game_bot/bot.otmod b/modules/game_bot/bot.otmod index 7649270..adaebf2 100644 --- a/modules/game_bot/bot.otmod +++ b/modules/game_bot/bot.otmod @@ -1,8 +1,8 @@ -Module - name: game_bot - description: Advanced OTClientV8 Bot - author: otclient@otclient.ovh - sandboxed: true - scripts: [ bot ] - @onLoad: init() - @onUnload: terminate() +Module + name: game_bot + description: Advanced OTClientV8 Bot + author: otclient@otclient.ovh + sandboxed: true + scripts: [ bot ] + @onLoad: init() + @onUnload: terminate() diff --git a/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.lua b/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.lua index 48f7925..1fb4e76 100644 --- a/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.lua +++ b/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.lua @@ -104,7 +104,7 @@ CaveBot.Editor.setup = function() title="Go to position", description="Go to position (x,y,z)", multiline=false, - validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+),?\\s*([0-9]?)$" + validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$" }) registerAction("use", { value=function() return posx() .. "," .. posy() .. "," .. posz() end, diff --git a/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/config_name.cfg b/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/config_name.cfg new file mode 100644 index 0000000..abff9d8 --- /dev/null +++ b/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/config_name.cfg @@ -0,0 +1,23 @@ +goto:1033,1044,7 +goto:1031,1038,7 +goto:1030,1038,7 +goto:1157,985,7 +goto:1161,981,7 +goto:1033,1042,7 +goto:1034,1038,7 +goto:1169,985,7 +goto:1175,985,7 +goto:1176,983,7 +goto:756,846,7 +goto:756,846,7 +config:{"walk":100,"walk2":false} +extensions:[[ +{ + "Depositer": [ + + ], + "Supply": [ + + ] +} +]] diff --git a/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/fast_walking.cfg b/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/fast_walking.cfg new file mode 100644 index 0000000..a5bb130 --- /dev/null +++ b/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/fast_walking.cfg @@ -0,0 +1,13 @@ +goto:84,112,6 +goto:95,108,6 +config:{"mapClickDelay":100,"walkDelay":10,"ping":250,"ignoreFields":false,"useDelay":400,"mapClick":false} +extensions:[[ +{ + "Depositer": [ + + ], + "Supply": [ + + ] +} +]] diff --git a/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/test_src.cfg b/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/test_src.cfg new file mode 100644 index 0000000..1049ce7 --- /dev/null +++ b/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/test_src.cfg @@ -0,0 +1,104 @@ +goto:93,129,7 +goto:96,123,7 +goto:96,117,7 +goto:101,114,7 +goto:95,111,6 +goto:89,111,6 +goto:83,108,6 +goto:80,102,6 +goto:80,96,6 +goto:85,90,6 +goto:88,92,6 +goto:91,86,7 +goto:97,85,7 +goto:103,84,7 +function:[[ + +TargetBot.enableLuring() + +return true + + +]] +goto:109,79,7 +goto:112,79,7 +goto:112,79,8 +function:[[ + +TargetBot.disableLuring() + +return true + + +]] +goto:112,79,7 +goto:106,84,8 +goto:100,80,8 +goto:100,74,8 +goto:99,80,8 +goto:105,83,8 +function:[[ +TargetBot.setOff() +return true +]] +goto:111,82,8 +goto:112,79,8 +goto:106,82,7 +goto:100,85,7 +goto:94,85,7 +goto:91,91,7 +goto:89,92,7 +goto:83,90,6 +function:[[ +TargetBot.setOff() +return true +]] +goto:77,94,6 +goto:75,95,6 +goto:69,96,7 +goto:63,100,7 +goto:61,102,7 +goto:62,96,8 +use:61,102,8 +goto:62,101,8 +goto:68,99,7 +goto:74,95,7 +goto:75,95,7 +goto:79,101,6 +goto:81,107,6 +goto:87,109,6 +goto:93,112,6 +function:[[ + +TargetBot.disableLuring() + +return true + + +]] +goto:99,116,6 +use:102,114,6 +goto:101,115,6 +use:100,116,5 +goto:101,115,5 +goto:100,116,4 +goto:102,114,5 +goto:101,114,6 +goto:96,120,7 +goto:95,126,7 +function:[[ +g_game.safeLogout() +delay(1000) +return "retry" +]] +config:{"useDelay":400,"mapClickDelay":100,"walkDelay":20,"ping":150,"ignoreFields":false,"skipBlocked":true,"mapClick":false} +extensions:[[ +{ + "Depositer": [ + + ], + "Supply": [ + + ] +} +]] diff --git a/modules/game_bot/default_configs/cavebot_1.3/storage.json b/modules/game_bot/default_configs/cavebot_1.3/storage.json new file mode 100644 index 0000000..afd37c9 --- /dev/null +++ b/modules/game_bot/default_configs/cavebot_1.3/storage.json @@ -0,0 +1,128 @@ +{ + "hpitem1": { + "max": 90, + "title": "HP%", + "subType": 0, + "item": 266, + "min": 51, + "on": false + }, + "foodItems": [ + { + "id": 3582, + "count": 1 + }, + { + "id": 3577, + "count": 1 + } + ], + "autoEquip": [ + { + "item1": 3052, + "title": "Auto Equip", + "item2": 3089, + "on": false, + "slot": 9 + }, + { + "item1": 0, + "title": "Auto Equip", + "item2": 0, + "on": false, + "slot": 0 + }, + { + "item1": 0, + "title": "Auto Equip", + "item2": 0, + "on": false, + "slot": 0 + }, + { + "item1": 0, + "title": "Auto Equip", + "item2": 0, + "on": false, + "slot": 0 + } + ], + "ingame_hotkeys": "singlehotkey(\"f1\", function()\nlocal shaders = {\"stars\", \"gold\", \"rainbow\", \"sweden\", \"brazil\", \"line\", \"3line\", \"circle\", \"outline\"}\nlocal p = 0\nfor i, c in pairs(getSpectators()) do\n c:setOutfitShader(shaders[1 + p % 10])\n p = p + 1\nend\nend)\n\nsinglehotkey(\"1\", function()\n for _, s in ipairs(getSpectators()) do\n if s:canShoot(3) then\n info(s:getName())\n else\n warn(s:getName())\n end\n end\nend)", + "healing2": { + "max": 50, + "title": "HP%", + "on": false, + "min": 1, + "text": "exura vita" + }, + "ingame_macros": "", + "hasteSpell": "utani hur", + "manaitem2": { + "max": 50, + "title": "MP%", + "subType": 0, + "item": 3157, + "min": 0, + "on": false + }, + "_configs": { + "cavebot_configs": { + "selected": "test_src", + "enabled": false + }, + "targetbot_configs": { + "enabled": false, + "selected": "config_name" + } + }, + "healing1": { + "max": 100, + "title": "HP%", + "on": false, + "min": 51, + "text": "exura" + }, + "dropItems": [ + { + "id": 283, + "count": 1 + }, + { + "id": 284, + "count": 1 + }, + { + "id": 285, + "count": 1 + } + ], + "_macros": { + "": false + }, + "manaitem1": { + "max": 90, + "title": "MP%", + "subType": 0, + "item": 268, + "min": 51, + "on": false + }, + "hpitem2": { + "max": 50, + "title": "HP%", + "subType": 0, + "item": 3160, + "min": 0, + "on": false + }, + "manaShield": "utamo vita", + "autoTradeMessage": "I'm using OTClientV8!", + "antiParalyze": "utani hur", + "manaTrain": { + "max": 100, + "title": "MP%", + "on": false, + "min": 80, + "text": "utevo lux" + } +} \ No newline at end of file diff --git a/modules/game_bot/default_configs/cavebot_1.3/targetbot_configs/config_name.json b/modules/game_bot/default_configs/cavebot_1.3/targetbot_configs/config_name.json new file mode 100644 index 0000000..e445631 --- /dev/null +++ b/modules/game_bot/default_configs/cavebot_1.3/targetbot_configs/config_name.json @@ -0,0 +1,53 @@ +{ + "looting": { + "items": [ + + ], + "maxDanger": 10, + "minCapacity": 100, + "containers": [ + { + "count": 1, + "id": 2853 + } + ], + "everyItem": true + }, + "targeting": [ + { + "useSpellAttack": false, + "useRuneAttack": false, + "minMana": 200, + "avoidAttacks": false, + "groupAttackTargets": 2, + "groupAttackSpell": "", + "danger": 1, + "runeAttackDelay": 2000, + "lureCavebot": true, + "dontLoot": false, + "useGroupAttackRune": false, + "groupRuneAttackRadius": 1, + "groupAttackIgnorePlayers": true, + "maxDistance": 10, + "groupAttackIgnoreParty": false, + "lureCount": 5, + "useGroupAttack": false, + "groupRuneAttackTargets": 2, + "attackSpell": "", + "groupAttackRune": 0, + "groupAttackRadius": 1, + "keepDistanceRange": 1, + "groupRuneAttackDelay": 5000, + "priority": 1, + "attackRune": 0, + "groupAttackDelay": 5000, + "minManaGroup": 1500, + "lure": true, + "keepDistance": false, + "attackSpellDelay": 2500, + "chase": true, + "name": "cat, w?lf, snake, troll", + "regex": "^cat$|^w.?lf$|^snake$|^troll$" + } + ] +} \ No newline at end of file diff --git a/modules/game_bot/default_configs/vithrax_1.3/bless_buy.lua b/modules/game_bot/default_configs/vithrax_1.3/bless_buy.lua index d56c639..de4e68c 100644 --- a/modules/game_bot/default_configs/vithrax_1.3/bless_buy.lua +++ b/modules/game_bot/default_configs/vithrax_1.3/bless_buy.lua @@ -2,7 +2,7 @@ if player:getBlessings() == 0 then say("!bless") schedule(2000, function() if player:getBlessings() == 0 then - error("!! Blessings not bought !!") + warn("!! Blessings not bought !!") end end) end diff --git a/modules/game_bot/executor.lua b/modules/game_bot/executor.lua index 30004ac..9c31882 100644 --- a/modules/game_bot/executor.lua +++ b/modules/game_bot/executor.lua @@ -1,348 +1,360 @@ -function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, reloadCallback, websockets) - -- load lua and otui files - local configFiles = g_resources.listDirectoryFiles("/bot/" .. config, true, false) - local luaFiles = {} - local uiFiles = {} - for i, file in ipairs(configFiles) do - local ext = file:split(".") - if ext[#ext]:lower() == "lua" then - table.insert(luaFiles, file) - end - if ext[#ext]:lower() == "ui" or ext[#ext]:lower() == "otui" then - table.insert(uiFiles, file) - end - end - - if #luaFiles == 0 then - return error("Config (/bot/" .. config .. ") doesn't have lua files") - end - - -- init bot variables - local context = {} - context.configDir = "/bot/".. config - context.tabs = tabs - context.mainTab = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content - context.panel = context.mainTab - context.saveConfig = saveConfigCallback - context.reload = reloadCallback - - context.storage = storage - if context.storage._macros == nil then - context.storage._macros = {} -- active macros - end - - -- websockets, macros, hotkeys, scheduler, icons, callbacks - context._websockets = websockets - context._macros = {} - context._hotkeys = {} - context._scheduler = {} - context._callbacks = { - onKeyDown = {}, - onKeyUp = {}, - onKeyPress = {}, - onTalk = {}, - onTextMessage = {}, - onLoginAdvice = {}, - onAddThing = {}, - onRemoveThing = {}, - onCreatureAppear = {}, - onCreatureDisappear = {}, - onCreaturePositionChange = {}, - onCreatureHealthPercentChange = {}, - onUse = {}, - onUseWith = {}, - onContainerOpen = {}, - onContainerClose = {}, - onContainerUpdateItem = {}, - onMissle = {}, - onAnimatedText = {}, - onStaticText = {}, - onChannelList = {}, - onOpenChannel = {}, - onCloseChannel = {}, - onChannelEvent = {}, - onTurn = {}, - onWalk = {} - } - - -- basic functions & classes - context.print = print - context.pairs = pairs - context.ipairs = ipairs - context.tostring = tostring - context.math = math - context.table = table - context.string = string - context.tonumber = tonumber - context.type = type - context.pcall = pcall - context.os = { - time = os.time, - date = os.date, - difftime = os.difftime, - date = os.date, - clock = os.clock - } - context.load = function(str) return assert(load(str, nil, nil, context)) end - context.loadstring = context.load - context.assert = assert - context.dofile = function(file) assert(load(g_resources.readFileContents("/bot/" .. config .. "/" .. file), file, nil, context))() end - context.gcinfo = gcinfo - context.tr = tr - context.json = json - context.base64 = base64 - context.regexMatch = regexMatch - context.getDistanceBetween = function(p1, p2) - return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) - end - context.isMobile = g_app.isMobile - context.getVersion = g_app.getVersion - - -- classes - context.g_resources = g_resources - context.g_game = g_game - context.g_map = g_map - context.g_ui = g_ui - context.g_sounds = g_sounds - context.g_window = g_window - context.g_mouse = g_mouse - context.g_things = g_things - context.g_platform = { - openUrl = g_platform.openUrl, - openDir = g_platform.openDir, - } - - context.Item = Item - context.Creature = Creature - context.ThingType = ThingType - context.Effect = Effect - context.Missile = Missile - context.Player = Player - context.Monster = Monster - context.StaticText = StaticText - context.HTTP = HTTP - context.OutputMessage = OutputMessage - context.modules = modules - - -- log functions - context.info = function(text) return msgCallback("info", tostring(text)) end - context.warn = function(text) return msgCallback("warn", tostring(text)) end - context.error = function(text) return msgCallback("error", tostring(text)) end - context.warning = context.warn - - -- init context - context.now = g_clock.millis() - context.time = g_clock.millis() - context.player = g_game.getLocalPlayer() - - -- init functions - G.botContext = context - dofiles("functions") - context.Panels = {} - dofiles("panels") - G.botContext = nil - - -- run ui scripts - for i, file in ipairs(uiFiles) do - g_ui.importStyle(file) - end - - -- run lua script - for i, file in ipairs(luaFiles) do - assert(load(g_resources.readFileContents(file), file, nil, context))() - context.panel = context.mainTab -- reset default tab - end - - return { - script = function() - context.now = g_clock.millis() - context.time = g_clock.millis() - - for i, macro in ipairs(context._macros) do - if macro.lastExecution + macro.timeout <= context.now and macro.enabled then - local status, result = pcall(function() - if macro.callback(macro) then - macro.lastExecution = context.now - end - end) - if not status then - context.error("Macro: " .. macro.name .. " execution error: " .. result) - end - end - end - - while #context._scheduler > 0 and context._scheduler[1].execution <= g_clock.millis() do - local status, result = pcall(function() - context._scheduler[1].callback() - end) - if not status then - context.error("Schedule execution error: " .. result) - end - table.remove(context._scheduler, 1) - end - end, - callbacks = { - onKeyDown = function(keyCode, keyboardModifiers) - local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) - for i, macro in ipairs(context._macros) do - if macro.switch and macro.hotkey == keyDesc then - macro.switch:onClick() - end - end - local hotkey = context._hotkeys[keyDesc] - if hotkey then - if hotkey.single then - if hotkey.callback() then - hotkey.lastExecution = context.now - end - end - if hotkey.switch then - hotkey.switch:setOn(true) - end - end - for i, callback in ipairs(context._callbacks.onKeyDown) do - callback(keyDesc) - end - end, - onKeyUp = function(keyCode, keyboardModifiers) - local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) - local hotkey = context._hotkeys[keyDesc] - if hotkey then - if hotkey.switch then - hotkey.switch:setOn(false) - end - end - for i, callback in ipairs(context._callbacks.onKeyUp) do - callback(keyDesc) - end - end, - onKeyPress = function(keyCode, keyboardModifiers, autoRepeatTicks) - local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) - local hotkey = context._hotkeys[keyDesc] - if hotkey and not hotkey.single then - if hotkey.callback() then - hotkey.lastExecution = context.now - end - end - for i, callback in ipairs(context._callbacks.onKeyPress) do - callback(keyDesc, autoRepeatTicks) - end - end, - onTalk = function(name, level, mode, text, channelId, pos) - for i, callback in ipairs(context._callbacks.onTalk) do - callback(name, level, mode, text, channelId, pos) - end - end, - onTextMessage = function(mode, text) - for i, callback in ipairs(context._callbacks.onTextMessage) do - callback(mode, text) - end - end, - onLoginAdvice = function(message) - for i, callback in ipairs(context._callbacks.onLoginAdvice) do - callback(message) - end - end, - onAddThing = function(tile, thing) - for i, callback in ipairs(context._callbacks.onAddThing) do - callback(tile, thing) - end - end, - onRemoveThing = function(tile, thing) - for i, callback in ipairs(context._callbacks.onRemoveThing) do - callback(tile, thing) - end - end, - onCreatureAppear = function(creature) - for i, callback in ipairs(context._callbacks.onCreatureAppear) do - callback(creature) - end - end, - onCreatureDisappear = function(creature) - for i, callback in ipairs(context._callbacks.onCreatureDisappear) do - callback(creature) - end - end, - onCreaturePositionChange = function(creature, newPos, oldPos) - for i, callback in ipairs(context._callbacks.onCreaturePositionChange) do - callback(creature, newPos, oldPos) - end - end, - onCreatureHealthPercentChange = function(creature, healthPercent) - for i, callback in ipairs(context._callbacks.onCreatureHealthPercentChange) do - callback(creature, healthPercent) - end - end, - onUse = function(pos, itemId, stackPos, subType) - for i, callback in ipairs(context._callbacks.onUse) do - callback(pos, itemId, stackPos, subType) - end - end, - onUseWith = function(pos, itemId, target, subType) - for i, callback in ipairs(context._callbacks.onUseWith) do - callback(pos, itemId, target, subType) - end - end, - onContainerOpen = function(container, previousContainer) - for i, callback in ipairs(context._callbacks.onContainerOpen) do - callback(container, previousContainer) - end - end, - onContainerClose = function(container) - for i, callback in ipairs(context._callbacks.onContainerClose) do - callback(container) - end - end, - onContainerUpdateItem = function(container, slot, item) - for i, callback in ipairs(context._callbacks.onContainerUpdateItem) do - callback(container, slot, item) - end - end, - onMissle = function(missle) - for i, callback in ipairs(context._callbacks.onMissle) do - callback(missle) - end - end, - onAnimatedText = function(thing, text) - for i, callback in ipairs(context._callbacks.onAnimatedText) do - callback(thing, text) - end - end, - onStaticText = function(thing, text) - for i, callback in ipairs(context._callbacks.onStaticText) do - callback(thing, text) - end - end, - onChannelList = function(channels) - for i, callback in ipairs(context._callbacks.onChannelList) do - callback(channels) - end - end, - onOpenChannel = function(channelId, channelName) - for i, callback in ipairs(context._callbacks.onOpenChannel) do - callback(channels) - end - end, - onCloseChannel = function(channelId) - for i, callback in ipairs(context._callbacks.onCloseChannel) do - callback(channelId) - end - end, - onChannelEvent = function(channelId, name, event) - for i, callback in ipairs(context._callbacks.onChannelEvent) do - callback(channelId, name, event) - end - end, - onTurn = function(creature, direction) - for i, callback in ipairs(context._callbacks.onTurn) do - callback(creature, direction) - end - end, - onWalk = function(creature, oldPos, newPos) - for i, callback in ipairs(context._callbacks.onWalk) do - callback(creature, oldPos, newPos) - end - end, - } - } +function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, reloadCallback, websockets) + -- load lua and otui files + local configFiles = g_resources.listDirectoryFiles("/bot/" .. config, true, false) + local luaFiles = {} + local uiFiles = {} + for i, file in ipairs(configFiles) do + local ext = file:split(".") + if ext[#ext]:lower() == "lua" then + table.insert(luaFiles, file) + end + if ext[#ext]:lower() == "ui" or ext[#ext]:lower() == "otui" then + table.insert(uiFiles, file) + end + end + + if #luaFiles == 0 then + return error("Config (/bot/" .. config .. ") doesn't have lua files") + end + + -- init bot variables + local context = {} + context.configDir = "/bot/".. config + context.tabs = tabs + context.mainTab = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content + context.panel = context.mainTab + context.saveConfig = saveConfigCallback + context.reload = reloadCallback + + context.storage = storage + if context.storage._macros == nil then + context.storage._macros = {} -- active macros + end + + -- websockets, macros, hotkeys, scheduler, icons, callbacks + context._websockets = websockets + context._macros = {} + context._hotkeys = {} + context._scheduler = {} + context._callbacks = { + onKeyDown = {}, + onKeyUp = {}, + onKeyPress = {}, + onTalk = {}, + onTextMessage = {}, + onLoginAdvice = {}, + onAddThing = {}, + onRemoveThing = {}, + onCreatureAppear = {}, + onCreatureDisappear = {}, + onCreaturePositionChange = {}, + onCreatureHealthPercentChange = {}, + onUse = {}, + onUseWith = {}, + onContainerOpen = {}, + onContainerClose = {}, + onContainerUpdateItem = {}, + onMissle = {}, + onAnimatedText = {}, + onStaticText = {}, + onChannelList = {}, + onOpenChannel = {}, + onCloseChannel = {}, + onChannelEvent = {}, + onTurn = {}, + onWalk = {}, + onImbuementWindow = {}, + onModalDialog = {} + } + + -- basic functions & classes + context.print = print + context.pairs = pairs + context.ipairs = ipairs + context.tostring = tostring + context.math = math + context.table = table + context.string = string + context.tonumber = tonumber + context.type = type + context.pcall = pcall + context.os = { + time = os.time, + date = os.date, + difftime = os.difftime, + date = os.date, + clock = os.clock + } + context.load = function(str) return assert(load(str, nil, nil, context)) end + context.loadstring = context.load + context.assert = assert + context.dofile = function(file) assert(load(g_resources.readFileContents("/bot/" .. config .. "/" .. file), file, nil, context))() end + context.gcinfo = gcinfo + context.tr = tr + context.json = json + context.base64 = base64 + context.regexMatch = regexMatch + context.getDistanceBetween = function(p1, p2) + return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) + end + context.isMobile = g_app.isMobile + context.getVersion = g_app.getVersion + + -- classes + context.g_resources = g_resources + context.g_game = g_game + context.g_map = g_map + context.g_ui = g_ui + context.g_sounds = g_sounds + context.g_window = g_window + context.g_mouse = g_mouse + context.g_things = g_things + context.g_platform = { + openUrl = g_platform.openUrl, + openDir = g_platform.openDir, + } + + context.Item = Item + context.Creature = Creature + context.ThingType = ThingType + context.Effect = Effect + context.Missile = Missile + context.Player = Player + context.Monster = Monster + context.StaticText = StaticText + context.HTTP = HTTP + context.OutputMessage = OutputMessage + context.modules = modules + + -- log functions + context.info = function(text) return msgCallback("info", tostring(text)) end + context.warn = function(text) return msgCallback("warn", tostring(text)) end + context.error = function(text) return msgCallback("error", tostring(text)) end + context.warning = context.warn + + -- init context + context.now = g_clock.millis() + context.time = g_clock.millis() + context.player = g_game.getLocalPlayer() + + -- init functions + G.botContext = context + dofiles("functions") + context.Panels = {} + dofiles("panels") + G.botContext = nil + + -- run ui scripts + for i, file in ipairs(uiFiles) do + g_ui.importStyle(file) + end + + -- run lua script + for i, file in ipairs(luaFiles) do + assert(load(g_resources.readFileContents(file), file, nil, context))() + context.panel = context.mainTab -- reset default tab + end + + return { + script = function() + context.now = g_clock.millis() + context.time = g_clock.millis() + + for i, macro in ipairs(context._macros) do + if macro.lastExecution + macro.timeout <= context.now and macro.enabled then + local status, result = pcall(function() + if macro.callback(macro) then + macro.lastExecution = context.now + end + end) + if not status then + context.error("Macro: " .. macro.name .. " execution error: " .. result) + end + end + end + + while #context._scheduler > 0 and context._scheduler[1].execution <= g_clock.millis() do + local status, result = pcall(function() + context._scheduler[1].callback() + end) + if not status then + context.error("Schedule execution error: " .. result) + end + table.remove(context._scheduler, 1) + end + end, + callbacks = { + onKeyDown = function(keyCode, keyboardModifiers) + local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) + for i, macro in ipairs(context._macros) do + if macro.switch and macro.hotkey == keyDesc then + macro.switch:onClick() + end + end + local hotkey = context._hotkeys[keyDesc] + if hotkey then + if hotkey.single then + if hotkey.callback() then + hotkey.lastExecution = context.now + end + end + if hotkey.switch then + hotkey.switch:setOn(true) + end + end + for i, callback in ipairs(context._callbacks.onKeyDown) do + callback(keyDesc) + end + end, + onKeyUp = function(keyCode, keyboardModifiers) + local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) + local hotkey = context._hotkeys[keyDesc] + if hotkey then + if hotkey.switch then + hotkey.switch:setOn(false) + end + end + for i, callback in ipairs(context._callbacks.onKeyUp) do + callback(keyDesc) + end + end, + onKeyPress = function(keyCode, keyboardModifiers, autoRepeatTicks) + local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) + local hotkey = context._hotkeys[keyDesc] + if hotkey and not hotkey.single then + if hotkey.callback() then + hotkey.lastExecution = context.now + end + end + for i, callback in ipairs(context._callbacks.onKeyPress) do + callback(keyDesc, autoRepeatTicks) + end + end, + onTalk = function(name, level, mode, text, channelId, pos) + for i, callback in ipairs(context._callbacks.onTalk) do + callback(name, level, mode, text, channelId, pos) + end + end, + onImbuementWindow = function(itemId, slots, activeSlots, imbuements, needItems) + for i, callback in ipairs(context._callbacks.onImbuementWindow) do + callback(itemId, slots, activeSlots, imbuements, needItems) + end + end, + onTextMessage = function(mode, text) + for i, callback in ipairs(context._callbacks.onTextMessage) do + callback(mode, text) + end + end, + onLoginAdvice = function(message) + for i, callback in ipairs(context._callbacks.onLoginAdvice) do + callback(message) + end + end, + onAddThing = function(tile, thing) + for i, callback in ipairs(context._callbacks.onAddThing) do + callback(tile, thing) + end + end, + onRemoveThing = function(tile, thing) + for i, callback in ipairs(context._callbacks.onRemoveThing) do + callback(tile, thing) + end + end, + onCreatureAppear = function(creature) + for i, callback in ipairs(context._callbacks.onCreatureAppear) do + callback(creature) + end + end, + onCreatureDisappear = function(creature) + for i, callback in ipairs(context._callbacks.onCreatureDisappear) do + callback(creature) + end + end, + onCreaturePositionChange = function(creature, newPos, oldPos) + for i, callback in ipairs(context._callbacks.onCreaturePositionChange) do + callback(creature, newPos, oldPos) + end + end, + onCreatureHealthPercentChange = function(creature, healthPercent) + for i, callback in ipairs(context._callbacks.onCreatureHealthPercentChange) do + callback(creature, healthPercent) + end + end, + onUse = function(pos, itemId, stackPos, subType) + for i, callback in ipairs(context._callbacks.onUse) do + callback(pos, itemId, stackPos, subType) + end + end, + onUseWith = function(pos, itemId, target, subType) + for i, callback in ipairs(context._callbacks.onUseWith) do + callback(pos, itemId, target, subType) + end + end, + onContainerOpen = function(container, previousContainer) + for i, callback in ipairs(context._callbacks.onContainerOpen) do + callback(container, previousContainer) + end + end, + onContainerClose = function(container) + for i, callback in ipairs(context._callbacks.onContainerClose) do + callback(container) + end + end, + onContainerUpdateItem = function(container, slot, item) + for i, callback in ipairs(context._callbacks.onContainerUpdateItem) do + callback(container, slot, item) + end + end, + onMissle = function(missle) + for i, callback in ipairs(context._callbacks.onMissle) do + callback(missle) + end + end, + onAnimatedText = function(thing, text) + for i, callback in ipairs(context._callbacks.onAnimatedText) do + callback(thing, text) + end + end, + onStaticText = function(thing, text) + for i, callback in ipairs(context._callbacks.onStaticText) do + callback(thing, text) + end + end, + onChannelList = function(channels) + for i, callback in ipairs(context._callbacks.onChannelList) do + callback(channels) + end + end, + onOpenChannel = function(channelId, channelName) + for i, callback in ipairs(context._callbacks.onOpenChannel) do + callback(channels) + end + end, + onCloseChannel = function(channelId) + for i, callback in ipairs(context._callbacks.onCloseChannel) do + callback(channelId) + end + end, + onChannelEvent = function(channelId, name, event) + for i, callback in ipairs(context._callbacks.onChannelEvent) do + callback(channelId, name, event) + end + end, + onTurn = function(creature, direction) + for i, callback in ipairs(context._callbacks.onTurn) do + callback(creature, direction) + end + end, + onWalk = function(creature, oldPos, newPos) + for i, callback in ipairs(context._callbacks.onWalk) do + callback(creature, oldPos, newPos) + end + end, + onModalDialog = function(id, title, message, buttons, enterButton, escapeButton, choices, priority) + for i, callback in ipairs(context._callbacks.onModalDialog) do + callback(id, title, message, buttons, enterButton, escapeButton, choices, priority) + end + end, + } + } end \ No newline at end of file diff --git a/modules/game_bot/functions/callbacks.lua b/modules/game_bot/functions/callbacks.lua index cb0db12..703016a 100644 --- a/modules/game_bot/functions/callbacks.lua +++ b/modules/game_bot/functions/callbacks.lua @@ -1,207 +1,217 @@ -local context = G.botContext - --- callback(callbackType, callback) -context.callback = function(callbackType, callback) - if not context._callbacks[callbackType] then - return error("Wrong callback type: " .. callbackType) - end - if callbackType == "onAddThing" or callbackType == "onRemoveThing" then - g_game.enableTileThingLuaCallback(true) - end - - local desc = "lua" - local info = debug.getinfo(2, "Sl") - if info then - desc = info.short_src .. ":" .. info.currentline - end - - local callbackData = {} - table.insert(context._callbacks[callbackType], function(...) - if not callbackData.delay or callbackData.delay < context.now then - local prevExecution = context._currentExecution - context._currentExecution = callbackData - local start = g_clock.realMillis() - callback(...) - local executionTime = g_clock.realMillis() - start - if executionTime > 100 then - context.warning("Slow " .. callbackType .. " (" .. executionTime .. "ms): " .. desc) - end - context._currentExecution = prevExecution - end - end) - local cb = context._callbacks[callbackType] - return { - remove = function() - local index = nil - for i, cb2 in ipairs(context._callbacks[callbackType]) do - if cb == cb2 then - index = i - end - end - if index then - table.remove(context._callbacks[callbackType], index) - end - end - } -end - --- onKeyDown(callback) -- callback = function(keys) -context.onKeyDown = function(callback) - return context.callback("onKeyDown", callback) -end - --- onKeyPress(callback) -- callback = function(keys) -context.onKeyPress = function(callback) - return context.callback("onKeyPress", callback) -end - --- onKeyUp(callback) -- callback = function(keys) -context.onKeyUp = function(callback) - return context.callback("onKeyUp", callback) -end - --- onTalk(callback) -- callback = function(name, level, mode, text, channelId, pos) -context.onTalk = function(callback) - return context.callback("onTalk", callback) -end - --- onTextMessage(callback) -- callback = function(mode, text) -context.onTextMessage = function(callback) - return context.callback("onTextMessage", callback) -end - --- onLoginAdvice(callback) -- callback = function(message) -context.onLoginAdvice = function(callback) - return context.callback("onLoginAdvice", callback) -end - --- onAddThing(callback) -- callback = function(tile, thing) -context.onAddThing = function(callback) - return context.callback("onAddThing", callback) -end - --- onRemoveThing(callback) -- callback = function(tile, thing) -context.onRemoveThing = function(callback) - return context.callback("onRemoveThing", callback) -end - --- onCreatureAppear(callback) -- callback = function(creature) -context.onCreatureAppear = function(callback) - return context.callback("onCreatureAppear", callback) -end - --- onCreatureDisappear(callback) -- callback = function(creature) -context.onCreatureDisappear = function(callback) - return context.callback("onCreatureDisappear", callback) -end - --- onCreaturePositionChange(callback) -- callback = function(creature, newPos, oldPos) -context.onCreaturePositionChange = function(callback) - return context.callback("onCreaturePositionChange", callback) -end - --- onCreatureHealthPercentChange(callback) -- callback = function(creature, healthPercent) -context.onCreatureHealthPercentChange = function(callback) - return context.callback("onCreatureHealthPercentChange", callback) -end - --- onUse(callback) -- callback = function(pos, itemId, stackPos, subType) -context.onUse = function(callback) - return context.callback("onUse", callback) -end - --- onUseWith(callback) -- callback = function(pos, itemId, target, subType) -context.onUseWith = function(callback) - return context.callback("onUseWith", callback) -end - --- onContainerOpen -- callback = function(container, previousContainer) -context.onContainerOpen = function(callback) - return context.callback("onContainerOpen", callback) -end - --- onContainerClose -- callback = function(container) -context.onContainerClose = function(callback) - return context.callback("onContainerClose", callback) -end - --- onContainerUpdateItem -- callback = function(container, slot, item) -context.onContainerUpdateItem = function(callback) - return context.callback("onContainerUpdateItem", callback) -end - --- onMissle -- callback = function(missle) -context.onMissle = function(callback) - return context.callback("onMissle", callback) -end - --- onAnimatedText -- callback = function(thing, text) -context.onAnimatedText = function(callback) - return context.callback("onAnimatedText", callback) -end - --- onStaticText -- callback = function(thing, text) -context.onStaticText = function(callback) - return context.callback("onStaticText", callback) -end - --- onChannelList -- callback = function(channels) -context.onChannelList = function(callback) - return context.callback("onChannelList", callback) -end - --- onOpenChannel -- callback = function(channelId, name) -context.onOpenChannel = function(callback) - return context.callback("onOpenChannel", callback) -end - --- onCloseChannel -- callback = function(channelId) -context.onCloseChannel = function(callback) - return context.callback("onCloseChannel", callback) -end - --- onChannelEvent -- callback = function(channelId, name, event) -context.onChannelEvent = function(callback) - return context.callback("onChannelEvent", callback) -end - --- onTurn -- callback = function(creature, direction) -context.onTurn = function(callback) - return context.callback("onTurn", callback) -end - --- onWalk -- callback = function(creature, oldPos, newPos) -context.onWalk = function(callback) - return context.callback("onWalk", callback) -end - --- CUSTOM CALLBACKS - --- listen(name, callback) -- callback = function(text, channelId, pos) -context.listen = function(name, callback) - if not name then return context.error("listen: invalid name") end - name = name:lower() - return context.onTalk(function(name2, level, mode, text, channelId, pos) - if name == name2:lower() then - callback(text, channelId, pos) - end - end) -end - --- onPlayerPositionChange(callback) -- callback = function(newPos, oldPos) -context.onPlayerPositionChange = function(callback) - return context.onCreaturePositionChange(function(creature, newPos, oldPos) - if creature == context.player then - callback(newPos, oldPos) - end - end) -end - --- onPlayerHealthChange(callback) -- callback = function(healthPercent) -context.onPlayerHealthChange = function(callback) - return context.onCreatureHealthPercentChange(function(creature, healthPercent) - if creature == context.player then - callback(healthPercent) - end - end) +local context = G.botContext + +-- callback(callbackType, callback) +context.callback = function(callbackType, callback) + if not context._callbacks[callbackType] then + return error("Wrong callback type: " .. callbackType) + end + if callbackType == "onAddThing" or callbackType == "onRemoveThing" then + g_game.enableTileThingLuaCallback(true) + end + + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + + local callbackData = {} + table.insert(context._callbacks[callbackType], function(...) + if not callbackData.delay or callbackData.delay < context.now then + local prevExecution = context._currentExecution + context._currentExecution = callbackData + local start = g_clock.realMillis() + callback(...) + local executionTime = g_clock.realMillis() - start + if executionTime > 100 then + context.warning("Slow " .. callbackType .. " (" .. executionTime .. "ms): " .. desc) + end + context._currentExecution = prevExecution + end + end) + local cb = context._callbacks[callbackType] + return { + remove = function() + local index = nil + for i, cb2 in ipairs(context._callbacks[callbackType]) do + if cb == cb2 then + index = i + end + end + if index then + table.remove(context._callbacks[callbackType], index) + end + end + } +end + +-- onKeyDown(callback) -- callback = function(keys) +context.onKeyDown = function(callback) + return context.callback("onKeyDown", callback) +end + +-- onKeyPress(callback) -- callback = function(keys) +context.onKeyPress = function(callback) + return context.callback("onKeyPress", callback) +end + +-- onKeyUp(callback) -- callback = function(keys) +context.onKeyUp = function(callback) + return context.callback("onKeyUp", callback) +end + +-- onTalk(callback) -- callback = function(name, level, mode, text, channelId, pos) +context.onTalk = function(callback) + return context.callback("onTalk", callback) +end + +-- onTextMessage(callback) -- callback = function(mode, text) +context.onTextMessage = function(callback) + return context.callback("onTextMessage", callback) +end + +-- onLoginAdvice(callback) -- callback = function(message) +context.onLoginAdvice = function(callback) + return context.callback("onLoginAdvice", callback) +end + +-- onAddThing(callback) -- callback = function(tile, thing) +context.onAddThing = function(callback) + return context.callback("onAddThing", callback) +end + +-- onRemoveThing(callback) -- callback = function(tile, thing) +context.onRemoveThing = function(callback) + return context.callback("onRemoveThing", callback) +end + +-- onCreatureAppear(callback) -- callback = function(creature) +context.onCreatureAppear = function(callback) + return context.callback("onCreatureAppear", callback) +end + +-- onCreatureDisappear(callback) -- callback = function(creature) +context.onCreatureDisappear = function(callback) + return context.callback("onCreatureDisappear", callback) +end + +-- onCreaturePositionChange(callback) -- callback = function(creature, newPos, oldPos) +context.onCreaturePositionChange = function(callback) + return context.callback("onCreaturePositionChange", callback) +end + +-- onCreatureHealthPercentChange(callback) -- callback = function(creature, healthPercent) +context.onCreatureHealthPercentChange = function(callback) + return context.callback("onCreatureHealthPercentChange", callback) +end + +-- onUse(callback) -- callback = function(pos, itemId, stackPos, subType) +context.onUse = function(callback) + return context.callback("onUse", callback) +end + +-- onUseWith(callback) -- callback = function(pos, itemId, target, subType) +context.onUseWith = function(callback) + return context.callback("onUseWith", callback) +end + +-- onContainerOpen -- callback = function(container, previousContainer) +context.onContainerOpen = function(callback) + return context.callback("onContainerOpen", callback) +end + +-- onContainerClose -- callback = function(container) +context.onContainerClose = function(callback) + return context.callback("onContainerClose", callback) +end + +-- onContainerUpdateItem -- callback = function(container, slot, item) +context.onContainerUpdateItem = function(callback) + return context.callback("onContainerUpdateItem", callback) +end + +-- onMissle -- callback = function(missle) +context.onMissle = function(callback) + return context.callback("onMissle", callback) +end + +-- onAnimatedText -- callback = function(thing, text) +context.onAnimatedText = function(callback) + return context.callback("onAnimatedText", callback) +end + +-- onStaticText -- callback = function(thing, text) +context.onStaticText = function(callback) + return context.callback("onStaticText", callback) +end + +-- onChannelList -- callback = function(channels) +context.onChannelList = function(callback) + return context.callback("onChannelList", callback) +end + +-- onOpenChannel -- callback = function(channelId, name) +context.onOpenChannel = function(callback) + return context.callback("onOpenChannel", callback) +end + +-- onCloseChannel -- callback = function(channelId) +context.onCloseChannel = function(callback) + return context.callback("onCloseChannel", callback) +end + +-- onChannelEvent -- callback = function(channelId, name, event) +context.onChannelEvent = function(callback) + return context.callback("onChannelEvent", callback) +end + +-- onTurn -- callback = function(creature, direction) +context.onTurn = function(callback) + return context.callback("onTurn", callback) +end + +-- onWalk -- callback = function(creature, oldPos, newPos) +context.onWalk = function(callback) + return context.callback("onWalk", callback) +end + +-- onImbuementWindow -- callback = function(itemId, slots, activeSlots, imbuements, needItems) +context.onImbuementWindow = function(callback) + return context.callback("onImbuementWindow", callback) +end + +-- onModalDialog -- callback = function(id, title, message, buttons, enterButton, escapeButton, choices, priority) -- priority is unused, ignore it +context.onModalDialog = function(callback) + return context.callback("onModalDialog", callback) +end + +-- CUSTOM CALLBACKS + +-- listen(name, callback) -- callback = function(text, channelId, pos) +context.listen = function(name, callback) + if not name then return context.error("listen: invalid name") end + name = name:lower() + return context.onTalk(function(name2, level, mode, text, channelId, pos) + if name == name2:lower() then + callback(text, channelId, pos) + end + end) +end + +-- onPlayerPositionChange(callback) -- callback = function(newPos, oldPos) +context.onPlayerPositionChange = function(callback) + return context.onCreaturePositionChange(function(creature, newPos, oldPos) + if creature == context.player then + callback(newPos, oldPos) + end + end) +end + +-- onPlayerHealthChange(callback) -- callback = function(healthPercent) +context.onPlayerHealthChange = function(callback) + return context.onCreatureHealthPercentChange(function(creature, healthPercent) + if creature == context.player then + callback(healthPercent) + end + end) end \ No newline at end of file diff --git a/modules/game_bot/functions/config.lua b/modules/game_bot/functions/config.lua index a9764b1..a46dbda 100644 --- a/modules/game_bot/functions/config.lua +++ b/modules/game_bot/functions/config.lua @@ -1,266 +1,266 @@ ---[[ -Config - create, load and save config file (.json / .cfg) -Used by cavebot and other things -]]-- - -local context = G.botContext -context.Config = {} -local Config = context.Config - -Config.exist = function(dir) - return g_resources.directoryExists(context.configDir .. "/" .. dir) -end - -Config.create = function(dir) - g_resources.makeDir(context.configDir .. "/" .. dir) - return Config.exist(dir) -end - -Config.list = function(dir) - if not Config.exist(dir) then - if not Config.create(dir) then - return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir) - end - end - local list = g_resources.listDirectoryFiles(context.configDir .. "/" .. dir) - local correctList = {} - for k,v in ipairs(list) do -- filter files - local nv = v:gsub(".json", ""):gsub(".cfg", "") - if nv ~= v then - table.insert(correctList, nv) - end - end - return correctList -end - --- load config from string insteaf of file -Config.parse = function(data) - local status, result = pcall(function() - if data:len() < 2 then return {} end - return json.decode(data) - end) - if status and type(result) == 'table' then - return result - end - local status, result = pcall(function() - return table.decodeStringPairList(data) - end) - if status and type(result) == 'table' then - return result - end - return context.error("Invalid config format") -end - -Config.load = function(dir, name) - local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" - if g_resources.fileExists(file) then -- load json - local status, result = pcall(function() - local data = g_resources.readFileContents(file) - if data:len() < 2 then return {} end - return json.decode(data) - end) - if not status then - context.error("Invalid json config (" .. name .. "): " .. result) - return {} - end - return result - end - file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" - if g_resources.fileExists(file) then -- load cfg - local status, result = pcall(function() - return table.decodeStringPairList(g_resources.readFileContents(file)) - end) - if not status then - context.error("Invalid cfg config (" .. name .. "): " .. result) - return {} - end - return result - end - return context.error("Config " .. file .. " doesn't exist") -end - -Config.loadRaw = function(dir, name) - local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" - if g_resources.fileExists(file) then -- load json - return g_resources.readFileContents(file) - end - file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" - if g_resources.fileExists(file) then -- load cfg - return g_resources.readFileContents(file) - end - return context.error("Config " .. file .. " doesn't exist") -end - -Config.save = function(dir, name, value, forcedExtension) - if not Config.exist(dir) then - if not Config.create(dir) then - return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir) - end - end - if type(value) ~= 'table' then - return context.error("Invalid config value type: " .. type(value) .. ", should be table") - end - local file = context.configDir .. "/" .. dir .. "/" .. name - if (table.isStringPairList(value) and forcedExtension ~= "json") or forcedExtension == "cfg" then -- cfg - g_resources.writeFileContents(file .. ".cfg", table.encodeStringPairList(value)) - else - g_resources.writeFileContents(file .. ".json", json.encode(value, 2)) - end - return true -end - -Config.remove = function(dir, name) - local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" - local ret = false - if g_resources.fileExists(file) then - g_resources.deleteFile(file) - ret = true - end - file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" - if g_resources.fileExists(file) then - g_resources.deleteFile(file) - ret = true - end - return ret -end - --- setup is used for BotConfig widget --- not done yet -Config.setup = function(dir, widget, configExtension, callback) - if type(dir) ~= 'string' or dir:len() == 0 then - return context.error("Invalid config dir") - end - if not Config.exist(dir) and not Config.create(dir) then - return context.error("Can't create config dir: " .. dir) - end - if type(context.storage._configs) ~= "table" then - context.storage._configs = {} - end - if type(context.storage._configs[dir]) ~= "table" then - context.storage._configs[dir] = { - enabled = false, - selected = "" - } - else - widget.switch:setOn(context.storage._configs[dir].enabled) - end - - local isRefreshing = false - local refresh = function() - isRefreshing = true - local configs = Config.list(dir) - local configIndex = 1 - widget.list:clear() - for v,k in ipairs(configs) do - widget.list:addOption(k) - if k == context.storage._configs[dir].selected then - configIndex = v - end - end - local data = nil - if #configs > 0 then - widget.list:setCurrentIndex(configIndex) - context.storage._configs[dir].selected = widget.list:getCurrentOption().text - data = Config.load(dir, configs[configIndex]) - else - context.storage._configs[dir].selected = nil - end - context.storage._configs[dir].enabled = widget.switch:isOn() - isRefreshing = false - callback(context.storage._configs[dir].selected, widget.switch:isOn(), data) - end - - widget.list.onOptionChange = function(widget) - if not isRefreshing then - context.storage._configs[dir].selected = widget:getCurrentOption().text - refresh() - end - end - - widget.switch.onClick = function() - widget.switch:setOn(not widget.switch:isOn()) - refresh() - end - - widget.add.onClick = function() - context.UI.SinglelineEditorWindow("config_name", {title="Enter config name"}, function(name) - name = name:gsub("%s+", "_") - if name:len() == 0 or name:len() >= 30 or name:find("/") or name:find("\\") then - return context.error("Invalid config name") - end - local file = context.configDir .. "/" .. dir .. "/" .. name .. "." .. configExtension - if g_resources.fileExists(file) then - return context.error("Config " .. name .. " already exist") - end - if configExtension == "json" then - g_resources.writeFileContents(file, json.encode({})) - else - g_resources.writeFileContents(file, "") - end - context.storage._configs[dir].selected = name - widget.switch:setOn(false) - refresh() - end) - end - - widget.edit.onClick = function() - local name = context.storage._configs[dir].selected - if not name then return end - context.UI.MultilineEditorWindow(Config.loadRaw(dir, name), {title="Config editor - " .. name .. " in " .. dir}, function(newValue) - local data = Config.parse(newValue) - Config.save(dir, name, data, configExtension) - refresh() - end) - end - - widget.remove.onClick = function() - local name = context.storage._configs[dir].selected - if not name then return end - context.UI.ConfirmationWindow("Config removal", "Do you want to remove config " .. name .. " from " .. dir .. "?", function() - Config.remove(dir, name) - widget.switch:setOn(false) - refresh() - end) - end - - refresh() - - return { - isOn = function() - return widget.switch:isOn() - end, - isOff = function() - return not widget.switch:isOn() - end, - setOn = function(val) - if val == false then - if widget.switch:isOn() then - widget.switch:onClick() - end - return - end - if not widget.switch:isOn() then - widget.switch:onClick() - end - end, - setOff = function(val) - if val == false then - if not widget.switch:isOn() then - widget.switch:onClick() - end - return - end - if widget.switch:isOn() then - widget.switch:onClick() - end - end, - save = function(data) - Config.save(dir, context.storage._configs[dir].selected, data, configExtension) - end, - refresh = refresh, - reload = refresh, - getActiveConfigName = function() - return context.storage._configs[dir].selected - end - } +--[[ +Config - create, load and save config file (.json / .cfg) +Used by cavebot and other things +]]-- + +local context = G.botContext +context.Config = {} +local Config = context.Config + +Config.exist = function(dir) + return g_resources.directoryExists(context.configDir .. "/" .. dir) +end + +Config.create = function(dir) + g_resources.makeDir(context.configDir .. "/" .. dir) + return Config.exist(dir) +end + +Config.list = function(dir) + if not Config.exist(dir) then + if not Config.create(dir) then + return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir) + end + end + local list = g_resources.listDirectoryFiles(context.configDir .. "/" .. dir) + local correctList = {} + for k,v in ipairs(list) do -- filter files + local nv = v:gsub(".json", ""):gsub(".cfg", "") + if nv ~= v then + table.insert(correctList, nv) + end + end + return correctList +end + +-- load config from string insteaf of file +Config.parse = function(data) + local status, result = pcall(function() + if data:len() < 2 then return {} end + return json.decode(data) + end) + if status and type(result) == 'table' then + return result + end + local status, result = pcall(function() + return table.decodeStringPairList(data) + end) + if status and type(result) == 'table' then + return result + end + return context.error("Invalid config format") +end + +Config.load = function(dir, name) + local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + if g_resources.fileExists(file) then -- load json + local status, result = pcall(function() + local data = g_resources.readFileContents(file) + if data:len() < 2 then return {} end + return json.decode(data) + end) + if not status then + context.error("Invalid json config (" .. name .. "): " .. result) + return {} + end + return result + end + file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" + if g_resources.fileExists(file) then -- load cfg + local status, result = pcall(function() + return table.decodeStringPairList(g_resources.readFileContents(file)) + end) + if not status then + context.error("Invalid cfg config (" .. name .. "): " .. result) + return {} + end + return result + end + return context.error("Config " .. file .. " doesn't exist") +end + +Config.loadRaw = function(dir, name) + local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + if g_resources.fileExists(file) then -- load json + return g_resources.readFileContents(file) + end + file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" + if g_resources.fileExists(file) then -- load cfg + return g_resources.readFileContents(file) + end + return context.error("Config " .. file .. " doesn't exist") +end + +Config.save = function(dir, name, value, forcedExtension) + if not Config.exist(dir) then + if not Config.create(dir) then + return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir) + end + end + if type(value) ~= 'table' then + return context.error("Invalid config value type: " .. type(value) .. ", should be table") + end + local file = context.configDir .. "/" .. dir .. "/" .. name + if (table.isStringPairList(value) and forcedExtension ~= "json") or forcedExtension == "cfg" then -- cfg + g_resources.writeFileContents(file .. ".cfg", table.encodeStringPairList(value)) + else + g_resources.writeFileContents(file .. ".json", json.encode(value, 2)) + end + return true +end + +Config.remove = function(dir, name) + local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + local ret = false + if g_resources.fileExists(file) then + g_resources.deleteFile(file) + ret = true + end + file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" + if g_resources.fileExists(file) then + g_resources.deleteFile(file) + ret = true + end + return ret +end + +-- setup is used for BotConfig widget +-- not done yet +Config.setup = function(dir, widget, configExtension, callback) + if type(dir) ~= 'string' or dir:len() == 0 then + return context.error("Invalid config dir") + end + if not Config.exist(dir) and not Config.create(dir) then + return context.error("Can't create config dir: " .. dir) + end + if type(context.storage._configs) ~= "table" then + context.storage._configs = {} + end + if type(context.storage._configs[dir]) ~= "table" then + context.storage._configs[dir] = { + enabled = false, + selected = "" + } + else + widget.switch:setOn(context.storage._configs[dir].enabled) + end + + local isRefreshing = false + local refresh = function() + isRefreshing = true + local configs = Config.list(dir) + local configIndex = 1 + widget.list:clear() + for v,k in ipairs(configs) do + widget.list:addOption(k) + if k == context.storage._configs[dir].selected then + configIndex = v + end + end + local data = nil + if #configs > 0 then + widget.list:setCurrentIndex(configIndex) + context.storage._configs[dir].selected = widget.list:getCurrentOption().text + data = Config.load(dir, configs[configIndex]) + else + context.storage._configs[dir].selected = nil + end + context.storage._configs[dir].enabled = widget.switch:isOn() + isRefreshing = false + callback(context.storage._configs[dir].selected, widget.switch:isOn(), data) + end + + widget.list.onOptionChange = function(widget) + if not isRefreshing then + context.storage._configs[dir].selected = widget:getCurrentOption().text + refresh() + end + end + + widget.switch.onClick = function() + widget.switch:setOn(not widget.switch:isOn()) + refresh() + end + + widget.add.onClick = function() + context.UI.SinglelineEditorWindow("config_name", {title="Enter config name"}, function(name) + name = name:gsub("%s+", "_") + if name:len() == 0 or name:len() >= 30 or name:find("/") or name:find("\\") then + return context.error("Invalid config name") + end + local file = context.configDir .. "/" .. dir .. "/" .. name .. "." .. configExtension + if g_resources.fileExists(file) then + return context.error("Config " .. name .. " already exist") + end + if configExtension == "json" then + g_resources.writeFileContents(file, json.encode({})) + else + g_resources.writeFileContents(file, "") + end + context.storage._configs[dir].selected = name + widget.switch:setOn(false) + refresh() + end) + end + + widget.edit.onClick = function() + local name = context.storage._configs[dir].selected + if not name then return end + context.UI.MultilineEditorWindow(Config.loadRaw(dir, name), {title="Config editor - " .. name .. " in " .. dir}, function(newValue) + local data = Config.parse(newValue) + Config.save(dir, name, data, configExtension) + refresh() + end) + end + + widget.remove.onClick = function() + local name = context.storage._configs[dir].selected + if not name then return end + context.UI.ConfirmationWindow("Config removal", "Do you want to remove config " .. name .. " from " .. dir .. "?", function() + Config.remove(dir, name) + widget.switch:setOn(false) + refresh() + end) + end + + refresh() + + return { + isOn = function() + return widget.switch:isOn() + end, + isOff = function() + return not widget.switch:isOn() + end, + setOn = function(val) + if val == false then + if widget.switch:isOn() then + widget.switch:onClick() + end + return + end + if not widget.switch:isOn() then + widget.switch:onClick() + end + end, + setOff = function(val) + if val == false then + if not widget.switch:isOn() then + widget.switch:onClick() + end + return + end + if widget.switch:isOn() then + widget.switch:onClick() + end + end, + save = function(data) + Config.save(dir, context.storage._configs[dir].selected, data, configExtension) + end, + refresh = refresh, + reload = refresh, + getActiveConfigName = function() + return context.storage._configs[dir].selected + end + } end \ No newline at end of file diff --git a/modules/game_bot/functions/const.lua b/modules/game_bot/functions/const.lua index 637bb34..c43757d 100644 --- a/modules/game_bot/functions/const.lua +++ b/modules/game_bot/functions/const.lua @@ -1,25 +1,25 @@ -local context = G.botContext - -context.North = 0 -context.East = 1 -context.South = 2 -context.West = 3 -context.NorthEast = 4 -context.SouthEast = 5 -context.SouthWest = 6 -context.NorthWest = 7 - -context.InventorySlotOther = 0 -context.InventorySlotHead = 1 -context.InventorySlotNeck = 2 -context.InventorySlotBack = 3 -context.InventorySlotBody = 4 -context.InventorySlotRight = 5 -context.InventorySlotLeft = 6 -context.InventorySlotLeg = 7 -context.InventorySlotFeet = 8 -context.InventorySlotFinger = 9 -context.InventorySlotAmmo = 10 -context.InventorySlotPurse = 11 -context.InventorySlotFirst = 1 -context.InventorySlotLast = 10 +local context = G.botContext + +context.North = 0 +context.East = 1 +context.South = 2 +context.West = 3 +context.NorthEast = 4 +context.SouthEast = 5 +context.SouthWest = 6 +context.NorthWest = 7 + +context.InventorySlotOther = 0 +context.InventorySlotHead = 1 +context.InventorySlotNeck = 2 +context.InventorySlotBack = 3 +context.InventorySlotBody = 4 +context.InventorySlotRight = 5 +context.InventorySlotLeft = 6 +context.InventorySlotLeg = 7 +context.InventorySlotFeet = 8 +context.InventorySlotFinger = 9 +context.InventorySlotAmmo = 10 +context.InventorySlotPurse = 11 +context.InventorySlotFirst = 1 +context.InventorySlotLast = 10 diff --git a/modules/game_bot/functions/icon.lua b/modules/game_bot/functions/icon.lua index 81fcc16..dcd48a2 100644 --- a/modules/game_bot/functions/icon.lua +++ b/modules/game_bot/functions/icon.lua @@ -1,176 +1,176 @@ -local context = G.botContext - -local iconsWithoutPosition = 0 - -context.addIcon = function(id, options, callback) ---[[ - Available options: - item: {id=2160, count=100} - outfit: outfit table ({}) - text: string - x: float (0.0 - 1.0) - y: float (0.0 - 1.0) - hotkey: string - switchable: true / false [default: true] - movable: true / false [default: true] - phantom: true / false [defaule: false] -]]-- - local panel = modules.game_interface.gameMapPanel - if type(id) ~= "string" or id:len() < 1 then - return context.error("Invalid id for addIcon") - end - if options.switchable == false and type(callback) ~= 'function' then - return context.error("Invalid callback for addIcon") - end - if type(context.storage._icons) ~= "table" then - context.storage._icons = {} - end - if type(context.storage._icons[id]) ~= "table" then - context.storage._icons[id] = {} - end - local config = context.storage._icons[id] - local widget = g_ui.createWidget("BotIcon", panel) - widget.botWidget = true - widget.botIcon = true - - if type(config.x) ~= 'number' and type(config.y) ~= 'number' then - if type(options.x) == 'number' and type(options.y) == 'number' then - config.x = math.min(1.0, math.max(0.0, options.x)) - config.y = math.min(1.0, math.max(0.0, options.y)) - else - config.x = 0.01 + math.floor(iconsWithoutPosition / 5) / 10 - config.y = 0.05 + (iconsWithoutPosition % 5) / 5 - iconsWithoutPosition = iconsWithoutPosition + 1 - end - end - - if options.item then - if type(options.item) == 'number' then - widget.item:setItemId(options.item) - else - widget.item:setItemId(options.item.id) - widget.item:setItemCount(options.item.count or 1) - widget.item:setShowCount(false) - end - end - - if options.outfit then - widget.creature:setOutfit(options.outfit) - end - - if options.switchable == false then - widget.status:hide() - widget.status:setOn(true) - else - if config.enabled ~= true then - config.enabled = false - end - widget.status:setOn(config.enabled) - end - - if options.text then - if options.switchable ~= false then - widget.status:hide() - if widget.status:isOn() then - widget.text:setColor('green') - else - widget.text:setColor('red') - end - end - widget.text:setText(options.text) - end - - widget.setOn = function(val) - widget.status:setOn(val) - if widget.status:isOn() then - widget.text:setColor('green') - else - widget.text:setColor('red') - end - config.enabled = widget.status:isOn() - end - - widget.onClick = function(widget) - if options.switchable ~= false then - widget.setOn(not widget.status:isOn()) - if type(callback) == 'table' then - callback.setOn(config.enabled) - return - end - end - - callback(widget, widget.status:isOn()) - end - - if options.hotkey then - widget.hotkey:setText(options.hotkey) - context.hotkey(options.hotkey, "", function() - widget:onClick() - end, nil, options.switchable ~= false) - else - widget.hotkey:hide() - end - - if options.movable ~= false then - widget.onDragEnter = function(widget, mousePos) - if not g_keyboard.isCtrlPressed() then - return false - end - widget:breakAnchors() - widget.movingReference = { x = mousePos.x - widget:getX(), y = mousePos.y - widget:getY() } - return true - end - - widget.onDragMove = function(widget, mousePos, moved) - local parentRect = widget:getParent():getRect() - local x = math.min(math.max(parentRect.x, mousePos.x - widget.movingReference.x), parentRect.x + parentRect.width - widget:getWidth()) - local y = math.min(math.max(parentRect.y - widget:getParent():getMarginTop(), mousePos.y - widget.movingReference.y), parentRect.y + parentRect.height - widget:getHeight()) - widget:move(x, y) - return true - end - - widget.onDragLeave = function(widget, pos) - local parent = widget:getParent() - local parentRect = parent:getRect() - local x = widget:getX() - parentRect.x - local y = widget:getY() - parentRect.y - local width = parentRect.width - widget:getWidth() - local height = parentRect.height - widget:getHeight() - - config.x = math.min(1, math.max(0, x / width)) - config.y = math.min(1, math.max(0, y / height)) - - widget:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) - widget:addAnchor(AnchorVerticalCenter, 'parent', AnchorVerticalCenter) - widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y))) - widget:setMarginLeft(width * (-0.5 + config.x)) - return true - end - end - - widget.onGeometryChange = function(widget) - if widget:isDragging() then return end - local parent = widget:getParent() - local parentRect = parent:getRect() - local width = parentRect.width - widget:getWidth() - local height = parentRect.height - widget:getHeight() - widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y))) - widget:setMarginLeft(width * (-0.5 + config.x)) - end - - if options.phantom ~= true then - widget.onMouseRelease = function() - return true - end - end - - if options.switchable ~= false then - if type(callback) == 'table' then - callback.setOn(config.enabled) - callback.icon = widget - else - callback(widget, widget.status:isOn()) - end - end - return widget +local context = G.botContext + +local iconsWithoutPosition = 0 + +context.addIcon = function(id, options, callback) +--[[ + Available options: + item: {id=2160, count=100} + outfit: outfit table ({}) + text: string + x: float (0.0 - 1.0) + y: float (0.0 - 1.0) + hotkey: string + switchable: true / false [default: true] + movable: true / false [default: true] + phantom: true / false [defaule: false] +]]-- + local panel = modules.game_interface.gameMapPanel + if type(id) ~= "string" or id:len() < 1 then + return context.error("Invalid id for addIcon") + end + if options.switchable == false and type(callback) ~= 'function' then + return context.error("Invalid callback for addIcon") + end + if type(context.storage._icons) ~= "table" then + context.storage._icons = {} + end + if type(context.storage._icons[id]) ~= "table" then + context.storage._icons[id] = {} + end + local config = context.storage._icons[id] + local widget = g_ui.createWidget("BotIcon", panel) + widget.botWidget = true + widget.botIcon = true + + if type(config.x) ~= 'number' and type(config.y) ~= 'number' then + if type(options.x) == 'number' and type(options.y) == 'number' then + config.x = math.min(1.0, math.max(0.0, options.x)) + config.y = math.min(1.0, math.max(0.0, options.y)) + else + config.x = 0.01 + math.floor(iconsWithoutPosition / 5) / 10 + config.y = 0.05 + (iconsWithoutPosition % 5) / 5 + iconsWithoutPosition = iconsWithoutPosition + 1 + end + end + + if options.item then + if type(options.item) == 'number' then + widget.item:setItemId(options.item) + else + widget.item:setItemId(options.item.id) + widget.item:setItemCount(options.item.count or 1) + widget.item:setShowCount(false) + end + end + + if options.outfit then + widget.creature:setOutfit(options.outfit) + end + + if options.switchable == false then + widget.status:hide() + widget.status:setOn(true) + else + if config.enabled ~= true then + config.enabled = false + end + widget.status:setOn(config.enabled) + end + + if options.text then + if options.switchable ~= false then + widget.status:hide() + if widget.status:isOn() then + widget.text:setColor('green') + else + widget.text:setColor('red') + end + end + widget.text:setText(options.text) + end + + widget.setOn = function(val) + widget.status:setOn(val) + if widget.status:isOn() then + widget.text:setColor('green') + else + widget.text:setColor('red') + end + config.enabled = widget.status:isOn() + end + + widget.onClick = function(widget) + if options.switchable ~= false then + widget.setOn(not widget.status:isOn()) + if type(callback) == 'table' then + callback.setOn(config.enabled) + return + end + end + + callback(widget, widget.status:isOn()) + end + + if options.hotkey then + widget.hotkey:setText(options.hotkey) + context.hotkey(options.hotkey, "", function() + widget:onClick() + end, nil, options.switchable ~= false) + else + widget.hotkey:hide() + end + + if options.movable ~= false then + widget.onDragEnter = function(widget, mousePos) + if not g_keyboard.isCtrlPressed() then + return false + end + widget:breakAnchors() + widget.movingReference = { x = mousePos.x - widget:getX(), y = mousePos.y - widget:getY() } + return true + end + + widget.onDragMove = function(widget, mousePos, moved) + local parentRect = widget:getParent():getRect() + local x = math.min(math.max(parentRect.x, mousePos.x - widget.movingReference.x), parentRect.x + parentRect.width - widget:getWidth()) + local y = math.min(math.max(parentRect.y - widget:getParent():getMarginTop(), mousePos.y - widget.movingReference.y), parentRect.y + parentRect.height - widget:getHeight()) + widget:move(x, y) + return true + end + + widget.onDragLeave = function(widget, pos) + local parent = widget:getParent() + local parentRect = parent:getRect() + local x = widget:getX() - parentRect.x + local y = widget:getY() - parentRect.y + local width = parentRect.width - widget:getWidth() + local height = parentRect.height - widget:getHeight() + + config.x = math.min(1, math.max(0, x / width)) + config.y = math.min(1, math.max(0, y / height)) + + widget:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) + widget:addAnchor(AnchorVerticalCenter, 'parent', AnchorVerticalCenter) + widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y))) + widget:setMarginLeft(width * (-0.5 + config.x)) + return true + end + end + + widget.onGeometryChange = function(widget) + if widget:isDragging() then return end + local parent = widget:getParent() + local parentRect = parent:getRect() + local width = parentRect.width - widget:getWidth() + local height = parentRect.height - widget:getHeight() + widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y))) + widget:setMarginLeft(width * (-0.5 + config.x)) + end + + if options.phantom ~= true then + widget.onMouseRelease = function() + return true + end + end + + if options.switchable ~= false then + if type(callback) == 'table' then + callback.setOn(config.enabled) + callback.icon = widget + else + callback(widget, widget.status:isOn()) + end + end + return widget end \ No newline at end of file diff --git a/modules/game_bot/functions/main.lua b/modules/game_bot/functions/main.lua index 1308eb0..211ee0c 100644 --- a/modules/game_bot/functions/main.lua +++ b/modules/game_bot/functions/main.lua @@ -1,211 +1,211 @@ -local context = G.botContext - --- MAIN BOT FUNCTION --- macro(timeout, callback) --- macro(timeout, name, callback) --- macro(timeout, name, callback, parent) --- macro(timeout, name, hotkey, callback) --- macro(timeout, name, hotkey, callback, parent) -context.macro = function(timeout, name, hotkey, callback, parent) - if type(timeout) ~= 'number' or timeout < 1 then - error("Invalid timeout for macro: " .. tostring(timeout)) - end - if type(name) == 'function' then - callback = name - name = "" - hotkey = "" - elseif type(hotkey) == 'function' then - parent = callback - callback = hotkey - hotkey = "" - elseif type(callback) ~= 'function' then - error("Invalid callback for macro: " .. tostring(callback)) - end - if hotkey == nil then - hotkey = "" - end - if type(name) ~= 'string' or type(hotkey) ~= 'string' then - error("Invalid name or hotkey for macro") - end - if not parent then - parent = context.panel - end - if hotkey:len() > 0 then - hotkey = retranslateKeyComboDesc(hotkey) - end - - -- min timeout is 50, to avoid lags - if timeout < 50 then - timeout = 50 - end - - table.insert(context._macros, { - enabled = false, - name = name, - timeout = timeout, - lastExecution = context.now + math.random(0, 100), - hotkey = hotkey, - }) - local macro = context._macros[#context._macros] - - macro.isOn = function() - return macro.enabled - end - macro.isOff = function() - return not macro.enabled - end - macro.toggle = function(widget) - if macro.isOn() then - macro.setOff() - else - macro.setOn() - end - end - macro.setOn = function(val) - if val == false then - return macro.setOff() - end - macro.enabled = true - context.storage._macros[name] = true - if macro.switch then - macro.switch:setOn(true) - end - if macro.icon then - macro.icon.setOn(true) - end - end - macro.setOff = function(val) - if val == false then - return macro.setOn() - end - macro.enabled = false - context.storage._macros[name] = false - if macro.switch then - macro.switch:setOn(false) - end - if macro.icon then - macro.icon.setOn(false) - end - end - - if name:len() > 0 then - -- creature switch - local text = name - if hotkey:len() > 0 then - text = name .. " [" .. hotkey .. "]" - end - macro.switch = context.addSwitch("macro_" .. (#context._macros + 1), text, macro.toggle, parent) - - -- load state - if context.storage._macros[name] == true then - macro.setOn() - end - else - macro.enabled = true -- unnamed macros are enabled by default - end - - local desc = "lua" - local info = debug.getinfo(2, "Sl") - if info then - desc = info.short_src .. ":" .. info.currentline - end - - macro.callback = function(macro) - if not macro.delay or macro.delay < context.now then - context._currentExecution = macro - local start = g_clock.realMillis() - callback(macro) - local executionTime = g_clock.realMillis() - start - if executionTime > 100 then - context.warning("Slow macro (" .. executionTime .. "ms): " .. macro.name .. " - " .. desc) - end - context._currentExecution = nil - return true - end - end - return macro -end - --- hotkey(keys, callback) --- hotkey(keys, name, callback) --- hotkey(keys, name, callback, parent) -context.hotkey = function(keys, name, callback, parent, single) - if type(name) == 'function' then - callback = name - name = "" - end - if not parent then - parent = context.panel - end - keys = retranslateKeyComboDesc(keys) - if not keys or #keys == 0 then - return context.error("Invalid hotkey keys " .. tostring(name)) - end - if context._hotkeys[keys] then - return context.error("Duplicated hotkey: " .. keys) - end - - local switch = nil - if name:len() > 0 then - switch = context._addHotkeySwitch(name, keys, parent) - end - - context._hotkeys[keys] = { - name = name, - lastExecution = context.now, - switch = switch, - single = single - } - - local desc = "lua" - local info = debug.getinfo(2, "Sl") - if info then - desc = info.short_src .. ":" .. info.currentline - end - - local hotkeyData = context._hotkeys[keys] - hotkeyData.callback = function() - if not hotkeyData.delay or hotkeyData.delay < context.now then - context._currentExecution = hotkeyData - local start = g_clock.realMillis() - callback() - local executionTime = g_clock.realMillis() - start - if executionTime > 100 then - context.warning("Slow hotkey (" .. executionTime .. "ms): " .. hotkeyData.name .. " - " .. desc) - end - context._currentExecution = nil - return true - end - end - - return hotkeyData -end - --- singlehotkey(keys, callback) --- singlehotkey(keys, name, callback) --- singlehotkey(keys, name, callback, parent) -context.singlehotkey = function(keys, name, callback, parent) - if type(name) == 'function' then - callback = name - name = "" - end - return context.hotkey(keys, name, callback, parent, true) -end - --- schedule(timeout, callback) -context.schedule = function(timeout, callback) - local extecute_time = g_clock.millis() + timeout - table.insert(context._scheduler, { - execution = extecute_time, - callback = callback - }) - table.sort(context._scheduler, function(a, b) return a.execution < b.execution end) -end - --- delay(duration) -- block execution of current macro/hotkey/callback for x milliseconds -context.delay = function(duration) - if not context._currentExecution then - return context.error("Invalid usage of delay function, it should be used inside callbacks") - end - context._currentExecution.delay = context.now + duration +local context = G.botContext + +-- MAIN BOT FUNCTION +-- macro(timeout, callback) +-- macro(timeout, name, callback) +-- macro(timeout, name, callback, parent) +-- macro(timeout, name, hotkey, callback) +-- macro(timeout, name, hotkey, callback, parent) +context.macro = function(timeout, name, hotkey, callback, parent) + if type(timeout) ~= 'number' or timeout < 1 then + error("Invalid timeout for macro: " .. tostring(timeout)) + end + if type(name) == 'function' then + callback = name + name = "" + hotkey = "" + elseif type(hotkey) == 'function' then + parent = callback + callback = hotkey + hotkey = "" + elseif type(callback) ~= 'function' then + error("Invalid callback for macro: " .. tostring(callback)) + end + if hotkey == nil then + hotkey = "" + end + if type(name) ~= 'string' or type(hotkey) ~= 'string' then + error("Invalid name or hotkey for macro") + end + if not parent then + parent = context.panel + end + if hotkey:len() > 0 then + hotkey = retranslateKeyComboDesc(hotkey) + end + + -- min timeout is 50, to avoid lags + if timeout < 50 then + timeout = 50 + end + + table.insert(context._macros, { + enabled = false, + name = name, + timeout = timeout, + lastExecution = context.now + math.random(0, 100), + hotkey = hotkey, + }) + local macro = context._macros[#context._macros] + + macro.isOn = function() + return macro.enabled + end + macro.isOff = function() + return not macro.enabled + end + macro.toggle = function(widget) + if macro.isOn() then + macro.setOff() + else + macro.setOn() + end + end + macro.setOn = function(val) + if val == false then + return macro.setOff() + end + macro.enabled = true + context.storage._macros[name] = true + if macro.switch then + macro.switch:setOn(true) + end + if macro.icon then + macro.icon.setOn(true) + end + end + macro.setOff = function(val) + if val == false then + return macro.setOn() + end + macro.enabled = false + context.storage._macros[name] = false + if macro.switch then + macro.switch:setOn(false) + end + if macro.icon then + macro.icon.setOn(false) + end + end + + if name:len() > 0 then + -- creature switch + local text = name + if hotkey:len() > 0 then + text = name .. " [" .. hotkey .. "]" + end + macro.switch = context.addSwitch("macro_" .. (#context._macros + 1), text, macro.toggle, parent) + + -- load state + if context.storage._macros[name] == true then + macro.setOn() + end + else + macro.enabled = true -- unnamed macros are enabled by default + end + + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + + macro.callback = function(macro) + if not macro.delay or macro.delay < context.now then + context._currentExecution = macro + local start = g_clock.realMillis() + callback(macro) + local executionTime = g_clock.realMillis() - start + if executionTime > 100 then + context.warning("Slow macro (" .. executionTime .. "ms): " .. macro.name .. " - " .. desc) + end + context._currentExecution = nil + return true + end + end + return macro +end + +-- hotkey(keys, callback) +-- hotkey(keys, name, callback) +-- hotkey(keys, name, callback, parent) +context.hotkey = function(keys, name, callback, parent, single) + if type(name) == 'function' then + callback = name + name = "" + end + if not parent then + parent = context.panel + end + keys = retranslateKeyComboDesc(keys) + if not keys or #keys == 0 then + return context.error("Invalid hotkey keys " .. tostring(name)) + end + if context._hotkeys[keys] then + return context.error("Duplicated hotkey: " .. keys) + end + + local switch = nil + if name:len() > 0 then + switch = context._addHotkeySwitch(name, keys, parent) + end + + context._hotkeys[keys] = { + name = name, + lastExecution = context.now, + switch = switch, + single = single + } + + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + + local hotkeyData = context._hotkeys[keys] + hotkeyData.callback = function() + if not hotkeyData.delay or hotkeyData.delay < context.now then + context._currentExecution = hotkeyData + local start = g_clock.realMillis() + callback() + local executionTime = g_clock.realMillis() - start + if executionTime > 100 then + context.warning("Slow hotkey (" .. executionTime .. "ms): " .. hotkeyData.name .. " - " .. desc) + end + context._currentExecution = nil + return true + end + end + + return hotkeyData +end + +-- singlehotkey(keys, callback) +-- singlehotkey(keys, name, callback) +-- singlehotkey(keys, name, callback, parent) +context.singlehotkey = function(keys, name, callback, parent) + if type(name) == 'function' then + callback = name + name = "" + end + return context.hotkey(keys, name, callback, parent, true) +end + +-- schedule(timeout, callback) +context.schedule = function(timeout, callback) + local extecute_time = g_clock.millis() + timeout + table.insert(context._scheduler, { + execution = extecute_time, + callback = callback + }) + table.sort(context._scheduler, function(a, b) return a.execution < b.execution end) +end + +-- delay(duration) -- block execution of current macro/hotkey/callback for x milliseconds +context.delay = function(duration) + if not context._currentExecution then + return context.error("Invalid usage of delay function, it should be used inside callbacks") + end + context._currentExecution.delay = context.now + duration end \ No newline at end of file diff --git a/modules/game_bot/functions/map.lua b/modules/game_bot/functions/map.lua index 01a6df0..d4e6292 100644 --- a/modules/game_bot/functions/map.lua +++ b/modules/game_bot/functions/map.lua @@ -1,256 +1,256 @@ -local context = G.botContext - -context.getMapView = function() return modules.game_interface.getMapPanel() end -context.getMapPanel = context.getMapView -context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end -context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end - -context.getSpectators = function(param1, param2) ---[[ - if param1 is table (position) then it's used for central position, then param2 is used as param1 - if param1 is creature, then creature position and direction of creature is used, then param2 is used as param1 - if param1 is true/false then it's used for multifloor, example: getSpectators(true) - if param1 is string then it's used for getSpectatorsByPattern -]]-- - local pos = context.player:getPosition() - local direction = context.player:getDirection() - if type(param1) == 'table' then - pos = param1 - direction = 8 -- invalid direction - param1 = param2 - end - if type(param1) == 'userdata' then - pos = param1:getPosition() - direction = param1:getDirection() - param1 = param2 - end - - if type(param1) == 'string' then - return g_map.getSpectatorsByPattern(pos, param1, direction) - end - - local multifloor = false - if type(param1) == 'boolean' and param1 == true then - multifloor = true - end - return g_map.getSpectators(pos, multifloor) -end - -context.getCreatureById = function(id, multifloor) - if type(id) ~= 'number' then return nil end - if multifloor ~= true then - multifloor = false - end - for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do - if spec:getId() == id then - return spec - end - end - return nil -end - -context.getCreatureByName = function(name, multifloor) - if not name then return nil end - name = name:lower() - if multifloor ~= true then - multifloor = false - end - for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do - if spec:getName():lower() == name then - return spec - end - end - return nil -end - -context.getPlayerByName = function(name, multifloor) - if not name then return nil end - name = name:lower() - if multifloor ~= true then - multifloor = false - end - for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do - if spec:isPlayer() and spec:getName():lower() == name then - return spec - end - end - return nil -end - -context.findAllPaths = function(start, maxDist, params) - --[[ - Available params: - ignoreLastCreature - ignoreCreatures - ignoreNonPathable - ignoreNonWalkable - ignoreStairs - ignoreCost - allowUnseen - allowOnlyVisibleTiles - ]]-- - if type(params) ~= 'table' then - params = {} - end - for key, value in pairs(params) do - if value == nil or value == false then - params[key] = 0 - elseif value == true then - params[key] = 1 - end - end - return g_map.findEveryPath(start, maxDist, params) -end -context.findEveryPath = context.findAllPaths - -context.translateAllPathsToPath = function(paths, destPos) - local predirections = {} - local directions = {} - local destPosStr = destPos - if type(destPos) ~= 'string' then - destPosStr = destPos.x .. "," .. destPos.y .. "," .. destPos.z - end - - while destPosStr:len() > 0 do - local node = paths[destPosStr] - if not node then - break - end - if node[3] < 0 then - break - end - table.insert(predirections, node[3]) - destPosStr = node[4] - end - -- reverse - for i=#predirections,1,-1 do - table.insert(directions, predirections[i]) - end - return directions -end -context.translateEveryPathToPath = context.translateAllPathsToPath - - -context.findPath = function(startPos, destPos, maxDist, params) - --[[ - Available params: - ignoreLastCreature - ignoreCreatures - ignoreNonPathable - ignoreNonWalkable - ignoreStairs - ignoreCost - allowUnseen - allowOnlyVisibleTiles - precision - marginMin - marginMax - ]]-- - if not destPos or startPos.z ~= destPos.z then - return - end - if type(maxDist) ~= 'number' then - maxDist = 100 - end - if type(params) ~= 'table' then - params = {} - end - local destPosStr = destPos.x .. "," .. destPos.y .. "," .. destPos.z - params["destination"] = destPosStr - local paths = context.findAllPaths(startPos, maxDist, params) - local marginMin = params.marginMin or params.minMargin - local marginMax = params.marginMax or params.maxMargin - if type(marginMin) == 'number' and type(marginMax) == 'number' then - local bestCandidate = nil - local bestCandidatePos = nil - for x = -marginMax, marginMax do - for y = -marginMax, marginMax do - if math.abs(x) >= marginMin or math.abs(y) >= marginMin then - local dest = (destPos.x + x) .. "," .. (destPos.y + y) .. "," .. destPos.z - local node = paths[dest] - if node and (not bestCandidate or bestCandidate[1] > node[1]) then - bestCandidate = node - bestCandidatePos = dest - end - end - end - end - if bestCandidate then - return context.translateAllPathsToPath(paths, bestCandidatePos) - end - return - end - - if not paths[destPosStr] then - local precision = params.precision - if type(precision) == 'number' then - for p = 1, precision do - local bestCandidate = nil - local bestCandidatePos = nil - for x = -p, p do - for y = -p, p do - local dest = (destPos.x + x) .. "," .. (destPos.y + y) .. "," .. destPos.z - local node = paths[dest] - if node and (not bestCandidate or bestCandidate[1] > node[1]) then - bestCandidate = node - bestCandidatePos = dest - end - end - end - if bestCandidate then - return context.translateAllPathsToPath(paths, bestCandidatePos) - end - end - end - return nil - end - - return context.translateAllPathsToPath(paths, destPos) -end -context.getPath = context.findPath - --- also works as autoWalk(dirs) where dirs is a list eg.: {1,2,3,0,1,1,2,} -context.autoWalk = function(destination, maxDist, params) - if type(destination) == "table" and table.isList(destination) and not maxDist and not params then - g_game.autoWalk(destination, {x=0,y=0,z=0}) - return true - end - - -- Available params same as for findPath - local path = context.findPath(context.player:getPosition(), destination, maxDist, params) - if not path then - return false - end - -- autowalk without prewalk animation - g_game.autoWalk(path, {x=0,y=0,z=0}) - return true -end - -context.getTileUnderCursor = function() - if not modules.game_interface.gameMapPanel.mousePos then return end - return modules.game_interface.gameMapPanel:getTile(modules.game_interface.gameMapPanel.mousePos) -end - -context.canShoot = function(pos, distance) - if not distance then distance = 5 end - local tile = g_map.getTile(pos, distance) - if tile then - return tile:canShoot(distance) - end - return false -end - -context.isTrapped = function(creature) - if not creature then - creature = context.player - end - local pos = creature:getPosition() - local dirs = {{-1,1}, {0,1}, {1,1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1}} - for i=1,#dirs do - local tile = g_map.getTile({x=pos.x-dirs[i][1],y=pos.y-dirs[i][2],z=pos.z}) - if tile and tile:isWalkable(false) then - return false - end - end - return true -end +local context = G.botContext + +context.getMapView = function() return modules.game_interface.getMapPanel() end +context.getMapPanel = context.getMapView +context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end +context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end + +context.getSpectators = function(param1, param2) +--[[ + if param1 is table (position) then it's used for central position, then param2 is used as param1 + if param1 is creature, then creature position and direction of creature is used, then param2 is used as param1 + if param1 is true/false then it's used for multifloor, example: getSpectators(true) + if param1 is string then it's used for getSpectatorsByPattern +]]-- + local pos = context.player:getPosition() + local direction = context.player:getDirection() + if type(param1) == 'table' then + pos = param1 + direction = 8 -- invalid direction + param1 = param2 + end + if type(param1) == 'userdata' then + pos = param1:getPosition() + direction = param1:getDirection() + param1 = param2 + end + + if type(param1) == 'string' then + return g_map.getSpectatorsByPattern(pos, param1, direction) + end + + local multifloor = false + if type(param1) == 'boolean' and param1 == true then + multifloor = true + end + return g_map.getSpectators(pos, multifloor) +end + +context.getCreatureById = function(id, multifloor) + if type(id) ~= 'number' then return nil end + if multifloor ~= true then + multifloor = false + end + for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do + if spec:getId() == id then + return spec + end + end + return nil +end + +context.getCreatureByName = function(name, multifloor) + if not name then return nil end + name = name:lower() + if multifloor ~= true then + multifloor = false + end + for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do + if spec:getName():lower() == name then + return spec + end + end + return nil +end + +context.getPlayerByName = function(name, multifloor) + if not name then return nil end + name = name:lower() + if multifloor ~= true then + multifloor = false + end + for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do + if spec:isPlayer() and spec:getName():lower() == name then + return spec + end + end + return nil +end + +context.findAllPaths = function(start, maxDist, params) + --[[ + Available params: + ignoreLastCreature + ignoreCreatures + ignoreNonPathable + ignoreNonWalkable + ignoreStairs + ignoreCost + allowUnseen + allowOnlyVisibleTiles + ]]-- + if type(params) ~= 'table' then + params = {} + end + for key, value in pairs(params) do + if value == nil or value == false then + params[key] = 0 + elseif value == true then + params[key] = 1 + end + end + return g_map.findEveryPath(start, maxDist, params) +end +context.findEveryPath = context.findAllPaths + +context.translateAllPathsToPath = function(paths, destPos) + local predirections = {} + local directions = {} + local destPosStr = destPos + if type(destPos) ~= 'string' then + destPosStr = destPos.x .. "," .. destPos.y .. "," .. destPos.z + end + + while destPosStr:len() > 0 do + local node = paths[destPosStr] + if not node then + break + end + if node[3] < 0 then + break + end + table.insert(predirections, node[3]) + destPosStr = node[4] + end + -- reverse + for i=#predirections,1,-1 do + table.insert(directions, predirections[i]) + end + return directions +end +context.translateEveryPathToPath = context.translateAllPathsToPath + + +context.findPath = function(startPos, destPos, maxDist, params) + --[[ + Available params: + ignoreLastCreature + ignoreCreatures + ignoreNonPathable + ignoreNonWalkable + ignoreStairs + ignoreCost + allowUnseen + allowOnlyVisibleTiles + precision + marginMin + marginMax + ]]-- + if not destPos or startPos.z ~= destPos.z then + return + end + if type(maxDist) ~= 'number' then + maxDist = 100 + end + if type(params) ~= 'table' then + params = {} + end + local destPosStr = destPos.x .. "," .. destPos.y .. "," .. destPos.z + params["destination"] = destPosStr + local paths = context.findAllPaths(startPos, maxDist, params) + local marginMin = params.marginMin or params.minMargin + local marginMax = params.marginMax or params.maxMargin + if type(marginMin) == 'number' and type(marginMax) == 'number' then + local bestCandidate = nil + local bestCandidatePos = nil + for x = -marginMax, marginMax do + for y = -marginMax, marginMax do + if math.abs(x) >= marginMin or math.abs(y) >= marginMin then + local dest = (destPos.x + x) .. "," .. (destPos.y + y) .. "," .. destPos.z + local node = paths[dest] + if node and (not bestCandidate or bestCandidate[1] > node[1]) then + bestCandidate = node + bestCandidatePos = dest + end + end + end + end + if bestCandidate then + return context.translateAllPathsToPath(paths, bestCandidatePos) + end + return + end + + if not paths[destPosStr] then + local precision = params.precision + if type(precision) == 'number' then + for p = 1, precision do + local bestCandidate = nil + local bestCandidatePos = nil + for x = -p, p do + for y = -p, p do + local dest = (destPos.x + x) .. "," .. (destPos.y + y) .. "," .. destPos.z + local node = paths[dest] + if node and (not bestCandidate or bestCandidate[1] > node[1]) then + bestCandidate = node + bestCandidatePos = dest + end + end + end + if bestCandidate then + return context.translateAllPathsToPath(paths, bestCandidatePos) + end + end + end + return nil + end + + return context.translateAllPathsToPath(paths, destPos) +end +context.getPath = context.findPath + +-- also works as autoWalk(dirs) where dirs is a list eg.: {1,2,3,0,1,1,2,} +context.autoWalk = function(destination, maxDist, params) + if type(destination) == "table" and table.isList(destination) and not maxDist and not params then + g_game.autoWalk(destination, {x=0,y=0,z=0}) + return true + end + + -- Available params same as for findPath + local path = context.findPath(context.player:getPosition(), destination, maxDist, params) + if not path then + return false + end + -- autowalk without prewalk animation + g_game.autoWalk(path, {x=0,y=0,z=0}) + return true +end + +context.getTileUnderCursor = function() + if not modules.game_interface.gameMapPanel.mousePos then return end + return modules.game_interface.gameMapPanel:getTile(modules.game_interface.gameMapPanel.mousePos) +end + +context.canShoot = function(pos, distance) + if not distance then distance = 5 end + local tile = g_map.getTile(pos, distance) + if tile then + return tile:canShoot(distance) + end + return false +end + +context.isTrapped = function(creature) + if not creature then + creature = context.player + end + local pos = creature:getPosition() + local dirs = {{-1,1}, {0,1}, {1,1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1}} + for i=1,#dirs do + local tile = g_map.getTile({x=pos.x-dirs[i][1],y=pos.y-dirs[i][2],z=pos.z}) + if tile and tile:isWalkable(false) then + return false + end + end + return true +end diff --git a/modules/game_bot/functions/npc.lua b/modules/game_bot/functions/npc.lua index 585a2d4..b650f4c 100644 --- a/modules/game_bot/functions/npc.lua +++ b/modules/game_bot/functions/npc.lua @@ -1,130 +1,130 @@ -local context = G.botContext - -context.NPC = {} - -context.NPC.talk = function(text) - if g_game.getClientVersion() >= 810 then - g_game.talkChannel(11, 0, text) - else - return context.say(text) - end -end -context.NPC.say = context.NPC.talk - -context.NPC.isTrading = function() - return modules.game_npctrade.npcWindow and modules.game_npctrade.npcWindow:isVisible() -end -context.NPC.hasTrade = context.NPC.isTrading -context.NPC.hasTradeWindow = context.NPC.isTrading -context.NPC.isTradeOpen = context.NPC.isTrading - -context.NPC.getSellItems = function() - if not context.NPC.isTrading() then return {} end - local items = {} - for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.SELL]) do - table.insert(items, { - item = item.ptr, - id = item.ptr:getId(), - count = item.ptr:getCount(), - name = item.name, - subType = item.ptr:getSubType(), - weight = item.weight / 100, - price = item.price - }) - end - return items -end - -context.NPC.getBuyItems = function() - if not context.NPC.isTrading() then return {} end - local items = {} - for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.BUY]) do - table.insert(items, { - item = item.ptr, - id = item.ptr:getId(), - count = item.ptr:getCount(), - name = item.name, - subType = item.ptr:getSubType(), - weight = item.weight / 100, - price = item.price - }) - end - return items -end - -context.NPC.getSellQuantity = function(item) - if not context.NPC.isTrading() then return 0 end - if type(item) == 'number' then - item = Item.create(item) - end - return modules.game_npctrade.getSellQuantity(item) -end - -context.NPC.canTradeItem = function(item) - if not context.NPC.isTrading() then return false end - if type(item) == 'number' then - item = Item.create(item) - end - return modules.game_npctrade.canTradeItem(item) -end - -context.NPC.sell = function(item, count, ignoreEquipped) - if type(item) == 'number' then - for i, entry in ipairs(context.NPC.getSellItems()) do - if entry.id == item then - item = entry.item - break - end - end - if type(item) == 'number' then - item = Item.create(item) - end - end - if count == 0 then - count = 1 - end - if count == nil or count == -1 then - count = context.NPC.getSellQuantity(item) - end - if ignoreEquipped == nil then - ignoreEquipped = true - end - g_game.sellItem(item, count, ignoreEquipped) -end - -context.NPC.buy = function(item, count, ignoreCapacity, withBackpack) - if type(item) == 'number' then - for i, entry in ipairs(context.NPC.getBuyItems()) do - if entry.id == item then - item = entry.item - break - end - end - if type(item) == 'number' then - item = Item.create(item) - end - end - if count == nil or count <= 0 then - count = 1 - end - if ignoreCapacity == nil then - ignoreCapacity = false - end - if withBackpack == nil then - withBackpack = false - end - g_game.buyItem(item, count, ignoreCapacity, withBackpack) -end - -context.NPC.sellAll = function() - if not context.NPC.isTrading() then return false end - modules.game_npctrade.sellAll() -end - -context.NPC.closeTrade = function() - modules.game_npctrade.closeNpcTrade() -end -context.NPC.close = context.NPC.closeTrade -context.NPC.finish = context.NPC.closeTrade -context.NPC.endTrade = context.NPC.closeTrade +local context = G.botContext + +context.NPC = {} + +context.NPC.talk = function(text) + if g_game.getClientVersion() >= 810 then + g_game.talkChannel(11, 0, text) + else + return context.say(text) + end +end +context.NPC.say = context.NPC.talk + +context.NPC.isTrading = function() + return modules.game_npctrade.npcWindow and modules.game_npctrade.npcWindow:isVisible() +end +context.NPC.hasTrade = context.NPC.isTrading +context.NPC.hasTradeWindow = context.NPC.isTrading +context.NPC.isTradeOpen = context.NPC.isTrading + +context.NPC.getSellItems = function() + if not context.NPC.isTrading() then return {} end + local items = {} + for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.SELL]) do + table.insert(items, { + item = item.ptr, + id = item.ptr:getId(), + count = item.ptr:getCount(), + name = item.name, + subType = item.ptr:getSubType(), + weight = item.weight / 100, + price = item.price + }) + end + return items +end + +context.NPC.getBuyItems = function() + if not context.NPC.isTrading() then return {} end + local items = {} + for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.BUY]) do + table.insert(items, { + item = item.ptr, + id = item.ptr:getId(), + count = item.ptr:getCount(), + name = item.name, + subType = item.ptr:getSubType(), + weight = item.weight / 100, + price = item.price + }) + end + return items +end + +context.NPC.getSellQuantity = function(item) + if not context.NPC.isTrading() then return 0 end + if type(item) == 'number' then + item = Item.create(item) + end + return modules.game_npctrade.getSellQuantity(item) +end + +context.NPC.canTradeItem = function(item) + if not context.NPC.isTrading() then return false end + if type(item) == 'number' then + item = Item.create(item) + end + return modules.game_npctrade.canTradeItem(item) +end + +context.NPC.sell = function(item, count, ignoreEquipped) + if type(item) == 'number' then + for i, entry in ipairs(context.NPC.getSellItems()) do + if entry.id == item then + item = entry.item + break + end + end + if type(item) == 'number' then + item = Item.create(item) + end + end + if count == 0 then + count = 1 + end + if count == nil or count == -1 then + count = context.NPC.getSellQuantity(item) + end + if ignoreEquipped == nil then + ignoreEquipped = true + end + g_game.sellItem(item, count, ignoreEquipped) +end + +context.NPC.buy = function(item, count, ignoreCapacity, withBackpack) + if type(item) == 'number' then + for i, entry in ipairs(context.NPC.getBuyItems()) do + if entry.id == item then + item = entry.item + break + end + end + if type(item) == 'number' then + item = Item.create(item) + end + end + if count == nil or count <= 0 then + count = 1 + end + if ignoreCapacity == nil then + ignoreCapacity = false + end + if withBackpack == nil then + withBackpack = false + end + g_game.buyItem(item, count, ignoreCapacity, withBackpack) +end + +context.NPC.sellAll = function() + if not context.NPC.isTrading() then return false end + modules.game_npctrade.sellAll() +end + +context.NPC.closeTrade = function() + modules.game_npctrade.closeNpcTrade() +end +context.NPC.close = context.NPC.closeTrade +context.NPC.finish = context.NPC.closeTrade +context.NPC.endTrade = context.NPC.closeTrade context.NPC.finishTrade = context.NPC.closeTrade \ No newline at end of file diff --git a/modules/game_bot/functions/player.lua b/modules/game_bot/functions/player.lua index fdb4a71..3541280 100644 --- a/modules/game_bot/functions/player.lua +++ b/modules/game_bot/functions/player.lua @@ -1,167 +1,167 @@ -local context = G.botContext - -context.name = function() return context.player:getName() end - -context.hp = function() return context.player:getHealth() end -context.mana = function() return context.player:getMana() end -context.hppercent = function() return context.player:getHealthPercent() end -context.manapercent = function() if context.player:getMaxMana() <= 1 then return 100 else return math.floor(context.player:getMana() * 100 / context.player:getMaxMana()) end end -context.maxhp = function() return context.player:getMaxHealth() end -context.maxmana = function() return context.player:getMaxMana() end -context.hpmax = function() return context.player:getMaxHealth() end -context.manamax = function() return context.player:getMaxMana() end - -context.cap = function() return context.player:getCapacity() end -context.freecap = function() return context.player:getFreeCapacity() end -context.maxcap = function() return context.player:getTotalCapacity() end -context.capmax = function() return context.player:getTotalCapacity() end - -context.exp = function() return context.player:getExperience() end -context.lvl = function() return context.player:getLevel() end -context.level = function() return context.player:getLevel() end - -context.mlev = function() return context.player:getMagicLevel() end -context.magic = function() return context.player:getMagicLevel() end -context.mlevel = function() return context.player:getMagicLevel() end - -context.soul = function() return context.player:getSoul() end -context.stamina = function() return context.player:getStamina() end -context.voc = function() return context.player:getVocation() end -context.vocation = function() return context.player:getVocation() end - -context.bless = function() return context.player:getBlessings() end -context.blesses = function() return context.player:getBlessings() end -context.blessings = function() return context.player:getBlessings() end - - -context.pos = function() return context.player:getPosition() end -context.posx = function() return context.player:getPosition().x end -context.posy = function() return context.player:getPosition().y end -context.posz = function() return context.player:getPosition().z end - -context.direction = function() return context.player:getDirection() end -context.speed = function() return context.player:getSpeed() end -context.skull = function() return context.player:getSkull() end -context.outfit = function() return context.player:getOutfit() end - -context.setOutfit = function(outfit) - modules.game_outfit.ignoreNextOutfitWindow = g_clock.millis() - g_game.requestOutfit() - context.schedule(100, function() - g_game.changeOutfit(outfit) - end) -end -context.changeOutfit = context.setOutfit -context.setSpeed = function(value) context.player:setSpeed(value) end - -context.walk = function(dir) return modules.game_walking.walk(dir) end -context.turn = function(dir) return g_game.turn(dir) end - --- game releated -context.getChannels = function() - -- return { channelId = channelName } - return modules.game_console.channels -end -context.getChannelId = function(name) - for id, channel in pairs(context.getChannels()) do - if name:lower() == channel:lower() then - return id - end - end - return nil -end -context.getChannel = context.getChannelId - -context.say = g_game.talk -context.talk = g_game.talk -context.yell = function(text) g_game.talkChannel(3, 0, text) end -context.talkChannel = function(channel, text) g_game.talkChannel(7, channel, text) end -context.sayChannel = context.talkChannel -context.talkPrivate = function(receiver, text) g_game.talkPrivate(5, receiver, text) end -context.sayPrivate = context.talkPrivate - -context.talkNpc = function(text) - if g_game.getClientVersion() >= 810 then - g_game.talkChannel(11, 0, text) - else - return context.say(text) - end -end -context.sayNpc = context.talkNpc -context.sayNPC = context.talkNpc -context.talkNPC = context.talkNpc - -context.saySpell = function(text, lastSpellTimeout) - if not text or text:len() < 1 then - return - end - if context.lastSpell == nil then - context.lastSpell = 0 - end - if not lastSpellTimeout then - lastSpellTimeout = 1000 - end - if context.lastSpell + lastSpellTimeout > context.now then - return false - end - context.say(text) - context.lastSpell = context.now - return true -end - -context.setSpellTimeout = function() - context.lastSpell = context.now -end - -context.use = function(thing, subtype) - if type(thing) == 'number' then - return g_game.useInventoryItem(thing, subtype) - else - return g_game.use(thing) - end -end -context.usewith = function(thing, target, subtype) - if type(thing) == 'number' then - return g_game.useInventoryItemWith(thing, target, subtype) - else - return g_game.useWith(item, target, subtype) - end -end -context.useWith = context.usewith - -context.useRune = function(itemid, target, lastSpellTimeout) - if context.lastRuneUse == nil then - context.lastRuneUse = 0 - end - if not lastRuneTimeout then - lastRuneTimeout = 1000 - end - if context.lastRuneUse + lastRuneTimeout > context.now then - return false - end - context.usewith(itemid, target) - context.lastRuneUse = context.now - return true -end -context.userune = context.useRune - -context.findItem = function(itemId, subType) - if subType == nil then - subType = -1 - end - return g_game.findItemInContainers(itemId, subType) -end - -context.attack = g_game.attack -context.cancelAttack = g_game.cancelAttack -context.follow = g_game.follow -context.cancelFollow = g_game.cancelFollow -context.cancelAttackAndFollow = g_game.cancelAttackAndFollow - -context.logout = g_game.forceLogout -context.safeLogout = g_game.safeLogout -context.ping = g_game.getPing - -modules.game_cooldown.isGroupCooldownIconActive(id) -modules.game_cooldown.isCooldownIconActive(id) - +local context = G.botContext + +context.name = function() return context.player:getName() end + +context.hp = function() return context.player:getHealth() end +context.mana = function() return context.player:getMana() end +context.hppercent = function() return context.player:getHealthPercent() end +context.manapercent = function() if context.player:getMaxMana() <= 1 then return 100 else return math.floor(context.player:getMana() * 100 / context.player:getMaxMana()) end end +context.maxhp = function() return context.player:getMaxHealth() end +context.maxmana = function() return context.player:getMaxMana() end +context.hpmax = function() return context.player:getMaxHealth() end +context.manamax = function() return context.player:getMaxMana() end + +context.cap = function() return context.player:getCapacity() end +context.freecap = function() return context.player:getFreeCapacity() end +context.maxcap = function() return context.player:getTotalCapacity() end +context.capmax = function() return context.player:getTotalCapacity() end + +context.exp = function() return context.player:getExperience() end +context.lvl = function() return context.player:getLevel() end +context.level = function() return context.player:getLevel() end + +context.mlev = function() return context.player:getMagicLevel() end +context.magic = function() return context.player:getMagicLevel() end +context.mlevel = function() return context.player:getMagicLevel() end + +context.soul = function() return context.player:getSoul() end +context.stamina = function() return context.player:getStamina() end +context.voc = function() return context.player:getVocation() end +context.vocation = function() return context.player:getVocation() end + +context.bless = function() return context.player:getBlessings() end +context.blesses = function() return context.player:getBlessings() end +context.blessings = function() return context.player:getBlessings() end + + +context.pos = function() return context.player:getPosition() end +context.posx = function() return context.player:getPosition().x end +context.posy = function() return context.player:getPosition().y end +context.posz = function() return context.player:getPosition().z end + +context.direction = function() return context.player:getDirection() end +context.speed = function() return context.player:getSpeed() end +context.skull = function() return context.player:getSkull() end +context.outfit = function() return context.player:getOutfit() end + +context.setOutfit = function(outfit) + modules.game_outfit.ignoreNextOutfitWindow = g_clock.millis() + g_game.requestOutfit() + context.schedule(100, function() + g_game.changeOutfit(outfit) + end) +end +context.changeOutfit = context.setOutfit +context.setSpeed = function(value) context.player:setSpeed(value) end + +context.walk = function(dir) return modules.game_walking.walk(dir) end +context.turn = function(dir) return g_game.turn(dir) end + +-- game releated +context.getChannels = function() + -- return { channelId = channelName } + return modules.game_console.channels +end +context.getChannelId = function(name) + for id, channel in pairs(context.getChannels()) do + if name:lower() == channel:lower() then + return id + end + end + return nil +end +context.getChannel = context.getChannelId + +context.say = g_game.talk +context.talk = g_game.talk +context.yell = function(text) g_game.talkChannel(3, 0, text) end +context.talkChannel = function(channel, text) g_game.talkChannel(7, channel, text) end +context.sayChannel = context.talkChannel +context.talkPrivate = function(receiver, text) g_game.talkPrivate(5, receiver, text) end +context.sayPrivate = context.talkPrivate + +context.talkNpc = function(text) + if g_game.getClientVersion() >= 810 then + g_game.talkChannel(11, 0, text) + else + return context.say(text) + end +end +context.sayNpc = context.talkNpc +context.sayNPC = context.talkNpc +context.talkNPC = context.talkNpc + +context.saySpell = function(text, lastSpellTimeout) + if not text or text:len() < 1 then + return + end + if context.lastSpell == nil then + context.lastSpell = 0 + end + if not lastSpellTimeout then + lastSpellTimeout = 1000 + end + if context.lastSpell + lastSpellTimeout > context.now then + return false + end + context.say(text) + context.lastSpell = context.now + return true +end + +context.setSpellTimeout = function() + context.lastSpell = context.now +end + +context.use = function(thing, subtype) + if type(thing) == 'number' then + return g_game.useInventoryItem(thing, subtype) + else + return g_game.use(thing) + end +end +context.usewith = function(thing, target, subtype) + if type(thing) == 'number' then + return g_game.useInventoryItemWith(thing, target, subtype) + else + return g_game.useWith(item, target, subtype) + end +end +context.useWith = context.usewith + +context.useRune = function(itemid, target, lastSpellTimeout) + if context.lastRuneUse == nil then + context.lastRuneUse = 0 + end + if not lastRuneTimeout then + lastRuneTimeout = 1000 + end + if context.lastRuneUse + lastRuneTimeout > context.now then + return false + end + context.usewith(itemid, target) + context.lastRuneUse = context.now + return true +end +context.userune = context.useRune + +context.findItem = function(itemId, subType) + if subType == nil then + subType = -1 + end + return g_game.findItemInContainers(itemId, subType) +end + +context.attack = g_game.attack +context.cancelAttack = g_game.cancelAttack +context.follow = g_game.follow +context.cancelFollow = g_game.cancelFollow +context.cancelAttackAndFollow = g_game.cancelAttackAndFollow + +context.logout = g_game.forceLogout +context.safeLogout = g_game.safeLogout +context.ping = g_game.getPing + +modules.game_cooldown.isGroupCooldownIconActive(id) +modules.game_cooldown.isCooldownIconActive(id) + diff --git a/modules/game_bot/functions/player_conditions.lua b/modules/game_bot/functions/player_conditions.lua index 0ae0ef8..b699d09 100644 --- a/modules/game_bot/functions/player_conditions.lua +++ b/modules/game_bot/functions/player_conditions.lua @@ -1,32 +1,32 @@ -local context = G.botContext - -for i, state in ipairs(PlayerStates) do - context[state] = state -end - -context.hasCondition = function(condition) return bit.band(context.player:getStates(), condition) > 0 end - -context.isPoisioned = function() return context.hasCondition(PlayerStates.Poison) end -context.isBurnining = function() return context.hasCondition(PlayerStates.Burn) end -context.isEnergized = function() return context.hasCondition(PlayerStates.Energy) end -context.isDrunk = function() return context.hasCondition(PlayerStates.Drunk) end -context.hasManaShield = function() return context.hasCondition(PlayerStates.ManaShield) end -context.isParalyzed = function() return context.hasCondition(PlayerStates.Paralyze) end -context.hasHaste = function() return context.hasCondition(PlayerStates.Haste) end -context.hasSwords = function() return context.hasCondition(PlayerStates.Swords) end -context.isInFight = function() return context.hasCondition(PlayerStates.Swords) end -context.canLogout = function() return not context.hasCondition(PlayerStates.Swords) end -context.isDrowning = function() return context.hasCondition(PlayerStates.Drowning) end -context.isFreezing = function() return context.hasCondition(PlayerStates.Freezing) end -context.isDazzled = function() return context.hasCondition(PlayerStates.Dazzled) end -context.isCursed = function() return context.hasCondition(PlayerStates.Cursed) end -context.hasPartyBuff = function() return context.hasCondition(PlayerStates.PartyBuff) end -context.hasPzLock = function() return context.hasCondition(PlayerStates.PzBlock) end -context.hasPzBlock = function() return context.hasCondition(PlayerStates.PzBlock) end -context.isPzLocked = function() return context.hasCondition(PlayerStates.PzBlock) end -context.isPzBlocked = function() return context.hasCondition(PlayerStates.PzBlock) end -context.isInProtectionZone = function() return context.hasCondition(PlayerStates.Pz) end -context.hasPz = function() return context.hasCondition(PlayerStates.Pz) end -context.isInPz = function() return context.hasCondition(PlayerStates.Pz) end -context.isBleeding = function() return context.hasCondition(PlayerStates.Bleeding) end -context.isHungry = function() return context.hasCondition(PlayerStates.Hungry) end +local context = G.botContext + +for i, state in ipairs(PlayerStates) do + context[state] = state +end + +context.hasCondition = function(condition) return bit.band(context.player:getStates(), condition) > 0 end + +context.isPoisioned = function() return context.hasCondition(PlayerStates.Poison) end +context.isBurnining = function() return context.hasCondition(PlayerStates.Burn) end +context.isEnergized = function() return context.hasCondition(PlayerStates.Energy) end +context.isDrunk = function() return context.hasCondition(PlayerStates.Drunk) end +context.hasManaShield = function() return context.hasCondition(PlayerStates.ManaShield) end +context.isParalyzed = function() return context.hasCondition(PlayerStates.Paralyze) end +context.hasHaste = function() return context.hasCondition(PlayerStates.Haste) end +context.hasSwords = function() return context.hasCondition(PlayerStates.Swords) end +context.isInFight = function() return context.hasCondition(PlayerStates.Swords) end +context.canLogout = function() return not context.hasCondition(PlayerStates.Swords) end +context.isDrowning = function() return context.hasCondition(PlayerStates.Drowning) end +context.isFreezing = function() return context.hasCondition(PlayerStates.Freezing) end +context.isDazzled = function() return context.hasCondition(PlayerStates.Dazzled) end +context.isCursed = function() return context.hasCondition(PlayerStates.Cursed) end +context.hasPartyBuff = function() return context.hasCondition(PlayerStates.PartyBuff) end +context.hasPzLock = function() return context.hasCondition(PlayerStates.PzBlock) end +context.hasPzBlock = function() return context.hasCondition(PlayerStates.PzBlock) end +context.isPzLocked = function() return context.hasCondition(PlayerStates.PzBlock) end +context.isPzBlocked = function() return context.hasCondition(PlayerStates.PzBlock) end +context.isInProtectionZone = function() return context.hasCondition(PlayerStates.Pz) end +context.hasPz = function() return context.hasCondition(PlayerStates.Pz) end +context.isInPz = function() return context.hasCondition(PlayerStates.Pz) end +context.isBleeding = function() return context.hasCondition(PlayerStates.Bleeding) end +context.isHungry = function() return context.hasCondition(PlayerStates.Hungry) end diff --git a/modules/game_bot/functions/player_inventory.lua b/modules/game_bot/functions/player_inventory.lua index c7997c1..9ea8c4c 100644 --- a/modules/game_bot/functions/player_inventory.lua +++ b/modules/game_bot/functions/player_inventory.lua @@ -1,45 +1,45 @@ -local context = G.botContext - -context.SlotOther = InventorySlotOther -context.SlotHead = InventorySlotHead -context.SlotNeck = InventorySlotNeck -context.SlotBack = InventorySlotBack -context.SlotBody = InventorySlotBody -context.SlotRight = InventorySlotRight -context.SlotLeft = InventorySlotLeft -context.SlotLeg = InventorySlotLeg -context.SlotFeet = InventorySlotFeet -context.SlotFinger = InventorySlotFinger -context.SlotAmmo = InventorySlotAmmo -context.SlotPurse = InventorySlotPurse - -context.getInventoryItem = function(slot) return context.player:getInventoryItem(slot) end -context.getSlot = context.getInventoryItem - -context.getHead = function() return context.getInventoryItem(context.SlotHead) end -context.getNeck = function() return context.getInventoryItem(context.SlotNeck) end -context.getBack = function() return context.getInventoryItem(context.SlotBack) end -context.getBody = function() return context.getInventoryItem(context.SlotBody) end -context.getRight = function() return context.getInventoryItem(context.SlotRight) end -context.getLeft = function() return context.getInventoryItem(context.SlotLeft) end -context.getLeg = function() return context.getInventoryItem(context.SlotLeg) end -context.getFeet = function() return context.getInventoryItem(context.SlotFeet) end -context.getFinger = function() return context.getInventoryItem(context.SlotFinger) end -context.getAmmo = function() return context.getInventoryItem(context.SlotAmmo) end -context.getPurse = function() return context.getInventoryItem(context.SlotPurse) end - -context.getContainers = function() return g_game.getContainers() end -context.getContainer = function(index) return g_game.getContainer(index) end - -context.moveToSlot = function(item, slot, count) - if type(item) == 'number' then - item = context.findItem(item) - end - if not item then - return - end - if count == nil then - count = item:getCount() - end - return g_game.move(item, {x=65535, y=slot, z=0}, count) +local context = G.botContext + +context.SlotOther = InventorySlotOther +context.SlotHead = InventorySlotHead +context.SlotNeck = InventorySlotNeck +context.SlotBack = InventorySlotBack +context.SlotBody = InventorySlotBody +context.SlotRight = InventorySlotRight +context.SlotLeft = InventorySlotLeft +context.SlotLeg = InventorySlotLeg +context.SlotFeet = InventorySlotFeet +context.SlotFinger = InventorySlotFinger +context.SlotAmmo = InventorySlotAmmo +context.SlotPurse = InventorySlotPurse + +context.getInventoryItem = function(slot) return context.player:getInventoryItem(slot) end +context.getSlot = context.getInventoryItem + +context.getHead = function() return context.getInventoryItem(context.SlotHead) end +context.getNeck = function() return context.getInventoryItem(context.SlotNeck) end +context.getBack = function() return context.getInventoryItem(context.SlotBack) end +context.getBody = function() return context.getInventoryItem(context.SlotBody) end +context.getRight = function() return context.getInventoryItem(context.SlotRight) end +context.getLeft = function() return context.getInventoryItem(context.SlotLeft) end +context.getLeg = function() return context.getInventoryItem(context.SlotLeg) end +context.getFeet = function() return context.getInventoryItem(context.SlotFeet) end +context.getFinger = function() return context.getInventoryItem(context.SlotFinger) end +context.getAmmo = function() return context.getInventoryItem(context.SlotAmmo) end +context.getPurse = function() return context.getInventoryItem(context.SlotPurse) end + +context.getContainers = function() return g_game.getContainers() end +context.getContainer = function(index) return g_game.getContainer(index) end + +context.moveToSlot = function(item, slot, count) + if type(item) == 'number' then + item = context.findItem(item) + end + if not item then + return + end + if count == nil then + count = item:getCount() + end + return g_game.move(item, {x=65535, y=slot, z=0}, count) end \ No newline at end of file diff --git a/modules/game_bot/functions/script_loader.lua b/modules/game_bot/functions/script_loader.lua index 346b948..edac0dc 100644 --- a/modules/game_bot/functions/script_loader.lua +++ b/modules/game_bot/functions/script_loader.lua @@ -1,59 +1,59 @@ -local context = G.botContext - -context.loadScript = function(path, onLoadCallback) - if type(path) ~= 'string' then - return context.error("Invalid path for loadScript: " .. tostring(path)) - end - if path:lower():find("http") == 1 then - return context.loadRemoteScript(path) - end - if not g_resources.fileExists(path) then - return context.error("File " .. path .. " doesn't exist") - end - - local status, result = pcall(function() - assert(load(g_resources.readFileContents(path), path, nil, context))() - end) - if not status then - return context.error("Error while loading script from: " .. path .. ":\n" .. result) - end - if onLoadCallback then - onLoadCallback() - end -end - -context.loadRemoteScript = function(url, onLoadCallback) - if type(url) ~= 'string' or url:lower():find("http") ~= 1 then - return context.error("Invalid url for loadRemoteScript: " .. tostring(url)) - end - - HTTP.get(url, function(data, err) - if err or data:len() == 0 then - -- try to load from cache - if type(context.storage.scriptsCache) ~= 'table' then - context.storage.scriptsCache = {} - end - local cache = context.storage.scriptsCache[url] - if cache and type(cache) == 'string' and cache:len() > 0 then - data = cache - else - return context.error("Can't load script from: " .. url .. ", error: " .. err) - end - end - - local status, result = pcall(function() - assert(load(data, url, nil, context))() - end) - if not status then - return context.error("Error while loading script from: " .. url .. ":\n" .. result) - end - -- cache script - if type(context.storage.scriptsCache) ~= 'table' then - context.storage.scriptsCache = {} - end - context.storage.scriptsCache[url] = data - if onLoadCallback then - onLoadCallback() - end - end) -end +local context = G.botContext + +context.loadScript = function(path, onLoadCallback) + if type(path) ~= 'string' then + return context.error("Invalid path for loadScript: " .. tostring(path)) + end + if path:lower():find("http") == 1 then + return context.loadRemoteScript(path) + end + if not g_resources.fileExists(path) then + return context.error("File " .. path .. " doesn't exist") + end + + local status, result = pcall(function() + assert(load(g_resources.readFileContents(path), path, nil, context))() + end) + if not status then + return context.error("Error while loading script from: " .. path .. ":\n" .. result) + end + if onLoadCallback then + onLoadCallback() + end +end + +context.loadRemoteScript = function(url, onLoadCallback) + if type(url) ~= 'string' or url:lower():find("http") ~= 1 then + return context.error("Invalid url for loadRemoteScript: " .. tostring(url)) + end + + HTTP.get(url, function(data, err) + if err or data:len() == 0 then + -- try to load from cache + if type(context.storage.scriptsCache) ~= 'table' then + context.storage.scriptsCache = {} + end + local cache = context.storage.scriptsCache[url] + if cache and type(cache) == 'string' and cache:len() > 0 then + data = cache + else + return context.error("Can't load script from: " .. url .. ", error: " .. err) + end + end + + local status, result = pcall(function() + assert(load(data, url, nil, context))() + end) + if not status then + return context.error("Error while loading script from: " .. url .. ":\n" .. result) + end + -- cache script + if type(context.storage.scriptsCache) ~= 'table' then + context.storage.scriptsCache = {} + end + context.storage.scriptsCache[url] = data + if onLoadCallback then + onLoadCallback() + end + end) +end diff --git a/modules/game_bot/functions/server.lua b/modules/game_bot/functions/server.lua index 6495e8b..f620fc5 100644 --- a/modules/game_bot/functions/server.lua +++ b/modules/game_bot/functions/server.lua @@ -1,91 +1,91 @@ -local context = G.botContext - -context.BotServer = {} -context.BotServer.url = "ws://bot.otclient.ovh:8000/" -context.BotServer.timeout = 3 -context.BotServer.ping = 0 -context.BotServer._callbacks = {} -context.BotServer._lastMessageId = 0 -context.BotServer._wasConnected = true -- show first warning - -context.BotServer.init = function(name, channel) - if not channel or not name or channel:len() < 1 or name:len() < 1 then - return context.error("Invalid params for BotServer.init") - end - if context.BotServer._websocket then - return context.error("BotServer is already initialized") - end - context.BotServer._websocket = HTTP.WebSocketJSON(context.BotServer.url, { - onMessage = function(message, socketId) - if not context._websockets[socketId] then - return g_http.cancel(socketId) - end - if not context.BotServer._websocket or context.BotServer._websocket.id ~= socketId then - return g_http.cancel(socketId) - end - context.BotServer._wasConnected = true - if message["type"] == "ping" then - context.BotServer.ping = message["ping"] - return context.BotServer._websocket.send({type="ping"}) - end - if message["type"] == "message" then - context.BotServer._lastMessageId = message["id"] - local topics = context.BotServer._callbacks[message["topic"]] - if topics then - for i=1,#topics do - topics[i](message["name"], message["message"], message["topic"]) - end - end - topics = context.BotServer._callbacks["*"] - if topics then - for i=1,#topics do - topics[i](message["name"], message["message"], message["topic"]) - end - end - return - end - end, - onClose = function(message, socketId) - if not context._websockets[socketId] then - return - end - context._websockets[socketId] = nil - if not context.BotServer._websocket or context.BotServer._websocket.id ~= socketId then - return - end - if context.BotServer._wasConnected then - context.warn("BotServer disconnected") - end - context.BotServer._wasConnected = false - context.BotServer._websocket = nil - context.BotServer.ping = 0 - context.BotServer.init(name, channel) - end - }, context.BotServer.timeout) - context._websockets[context.BotServer._websocket.id] = 1 - context.BotServer._websocket.send({type="init", name=name, channel=channel, lastMessage=context.BotServer._lastMessageId}) -end - -context.BotServer.terminate = function() - if context.BotServer._websocket then - context.BotServer._websocket:close() - context.BotServer._websocket = nil - end -end - -context.BotServer.listen = function(topic, callback) -- callback = function(name, message, topic) -- message is parsed json = table - if not context.BotServer._websocket then - return context.error("BotServer is not initialized") - end - if not context.BotServer._callbacks[topic] then - context.BotServer._callbacks[topic] = {} - end - table.insert(context.BotServer._callbacks[topic], callback) -end - -context.BotServer.send = function(topic, message) - if not context.BotServer._websocket then - return context.error("BotServer is not initialized") - end - context.BotServer._websocket.send({type="message", topic=topic, message=message}) -end +local context = G.botContext + +context.BotServer = {} +context.BotServer.url = "ws://bot.otclient.ovh:8000/" +context.BotServer.timeout = 3 +context.BotServer.ping = 0 +context.BotServer._callbacks = {} +context.BotServer._lastMessageId = 0 +context.BotServer._wasConnected = true -- show first warning + +context.BotServer.init = function(name, channel) + if not channel or not name or channel:len() < 1 or name:len() < 1 then + return context.error("Invalid params for BotServer.init") + end + if context.BotServer._websocket then + return context.error("BotServer is already initialized") + end + context.BotServer._websocket = HTTP.WebSocketJSON(context.BotServer.url, { + onMessage = function(message, socketId) + if not context._websockets[socketId] then + return g_http.cancel(socketId) + end + if not context.BotServer._websocket or context.BotServer._websocket.id ~= socketId then + return g_http.cancel(socketId) + end + context.BotServer._wasConnected = true + if message["type"] == "ping" then + context.BotServer.ping = message["ping"] + return context.BotServer._websocket.send({type="ping"}) + end + if message["type"] == "message" then + context.BotServer._lastMessageId = message["id"] + local topics = context.BotServer._callbacks[message["topic"]] + if topics then + for i=1,#topics do + topics[i](message["name"], message["message"], message["topic"]) + end + end + topics = context.BotServer._callbacks["*"] + if topics then + for i=1,#topics do + topics[i](message["name"], message["message"], message["topic"]) + end + end + return + end + end, + onClose = function(message, socketId) + if not context._websockets[socketId] then + return + end + context._websockets[socketId] = nil + if not context.BotServer._websocket or context.BotServer._websocket.id ~= socketId then + return + end + if context.BotServer._wasConnected then + context.warn("BotServer disconnected") + end + context.BotServer._wasConnected = false + context.BotServer._websocket = nil + context.BotServer.ping = 0 + context.BotServer.init(name, channel) + end + }, context.BotServer.timeout) + context._websockets[context.BotServer._websocket.id] = 1 + context.BotServer._websocket.send({type="init", name=name, channel=channel, lastMessage=context.BotServer._lastMessageId}) +end + +context.BotServer.terminate = function() + if context.BotServer._websocket then + context.BotServer._websocket:close() + context.BotServer._websocket = nil + end +end + +context.BotServer.listen = function(topic, callback) -- callback = function(name, message, topic) -- message is parsed json = table + if not context.BotServer._websocket then + return context.error("BotServer is not initialized") + end + if not context.BotServer._callbacks[topic] then + context.BotServer._callbacks[topic] = {} + end + table.insert(context.BotServer._callbacks[topic], callback) +end + +context.BotServer.send = function(topic, message) + if not context.BotServer._websocket then + return context.error("BotServer is not initialized") + end + context.BotServer._websocket.send({type="message", topic=topic, message=message}) +end diff --git a/modules/game_bot/functions/sound.lua b/modules/game_bot/functions/sound.lua index 1281028..c7fffee 100644 --- a/modules/game_bot/functions/sound.lua +++ b/modules/game_bot/functions/sound.lua @@ -1,31 +1,31 @@ -local context = G.botContext - -context.getSoundChannel = function() - if not g_sounds then - return - end - return g_sounds.getChannel(SoundChannels.Bot) -end - -context.playSound = function(file) - local botSoundChannel = context.getSoundChannel() - if not botSoundChannel then - return - end - botSoundChannel:setEnabled(true) - botSoundChannel:stop(0) - botSoundChannel:play(file, 0, 1.0) - return botSoundChannel -end - -context.stopSound = function() - local botSoundChannel = context.getSoundChannel() - if not botSoundChannel then - return - end - botSoundChannel:stop() -end - -context.playAlarm = function() - return context.playSound("/sounds/alarm.ogg") -end +local context = G.botContext + +context.getSoundChannel = function() + if not g_sounds then + return + end + return g_sounds.getChannel(SoundChannels.Bot) +end + +context.playSound = function(file) + local botSoundChannel = context.getSoundChannel() + if not botSoundChannel then + return + end + botSoundChannel:setEnabled(true) + botSoundChannel:stop(0) + botSoundChannel:play(file, 0, 1.0) + return botSoundChannel +end + +context.stopSound = function() + local botSoundChannel = context.getSoundChannel() + if not botSoundChannel then + return + end + botSoundChannel:stop() +end + +context.playAlarm = function() + return context.playSound("/sounds/alarm.ogg") +end diff --git a/modules/game_bot/functions/test.lua b/modules/game_bot/functions/test.lua index dc8ce0e..9d6afcf 100644 --- a/modules/game_bot/functions/test.lua +++ b/modules/game_bot/functions/test.lua @@ -1,3 +1,3 @@ -local context = G.botContext - +local context = G.botContext + context.test = function() return context.info("test") end \ No newline at end of file diff --git a/modules/game_bot/functions/tools.lua b/modules/game_bot/functions/tools.lua index 9a5e344..bebae76 100644 --- a/modules/game_bot/functions/tools.lua +++ b/modules/game_bot/functions/tools.lua @@ -1,19 +1,19 @@ -local context = G.botContext - -context.encode = function(data, indent) return json.encode(data, indent or 2) end -context.decode = function(text) local status, result = pcall(function() return json.decode(text) end) if status then return result end return {} end - -context.displayGeneralBox = function(title, message, buttons, onEnterCallback, onEscapeCallback) - local box = displayGeneralBox(title, message, buttons, onEnterCallback, onEscapeCallback) - box.botWidget = true - return box -end - -context.doScreenshot = function(filename) - g_app.doScreenshot(filename) -end -context.screenshot = context.doScreenshot - -context.getVersion = function() - return g_app.getVersion() +local context = G.botContext + +context.encode = function(data, indent) return json.encode(data, indent or 2) end +context.decode = function(text) local status, result = pcall(function() return json.decode(text) end) if status then return result end return {} end + +context.displayGeneralBox = function(title, message, buttons, onEnterCallback, onEscapeCallback) + local box = displayGeneralBox(title, message, buttons, onEnterCallback, onEscapeCallback) + box.botWidget = true + return box +end + +context.doScreenshot = function(filename) + g_app.doScreenshot(filename) +end +context.screenshot = context.doScreenshot + +context.getVersion = function() + return g_app.getVersion() end \ No newline at end of file diff --git a/modules/game_bot/functions/ui.lua b/modules/game_bot/functions/ui.lua index 46d70c0..4cc781f 100644 --- a/modules/game_bot/functions/ui.lua +++ b/modules/game_bot/functions/ui.lua @@ -1,24 +1,24 @@ -local context = G.botContext -if type(context.UI) ~= "table" then - context.UI = {} -end -local UI = context.UI - -UI.createWidget = function(name, parent) - if parent == nil then - parent = context.panel - end - local widget = g_ui.createWidget(name, parent) - widget.botWidget = true - return widget -end - -UI.createWindow = function(name) - local widget = g_ui.createWidget(name, g_ui.getRootWidget()) - widget.botWidget = true - widget:show() - widget:raise() - widget:focus() - return widget -end - +local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI + +UI.createWidget = function(name, parent) + if parent == nil then + parent = context.panel + end + local widget = g_ui.createWidget(name, parent) + widget.botWidget = true + return widget +end + +UI.createWindow = function(name) + local widget = g_ui.createWidget(name, g_ui.getRootWidget()) + widget.botWidget = true + widget:show() + widget:raise() + widget:focus() + return widget +end + diff --git a/modules/game_bot/functions/ui_elements.lua b/modules/game_bot/functions/ui_elements.lua index 985e1b4..84e2dd9 100644 --- a/modules/game_bot/functions/ui_elements.lua +++ b/modules/game_bot/functions/ui_elements.lua @@ -1,273 +1,273 @@ -local context = G.botContext -if type(context.UI) ~= "table" then - context.UI = {} -end -local UI = context.UI - -UI.Button = function(text, callback, parent) - local widget = UI.createWidget("BotButton", parent) - widget:setText(text) - widget.onClick = callback - return widget -end - - -UI.Config = function(parent) - return UI.createWidget("BotConfig", parent) -end - --- call :setItems(table) to set items, call :getItems() to get them --- unique if true, won't allow duplicates --- callback (can be nil) gets table with new item list, eg: {{id=2160, count=1}, {id=268, count=100}, {id=269, count=20}} -UI.Container = function(callback, unique, parent, widget) - if not widget then - widget = UI.createWidget("BotContainer", parent) - end - - local oldItems = {} - - local updateItems = function() - local items = widget:getItems() - - -- callback part - local somethingNew = (#items ~= #oldItems) - for i, item in ipairs(items) do - if type(oldItems[i]) ~= "table" then - somethingNew = true - break - end - if oldItems[i].id ~= item.id or oldItems[i].count ~= item.count then - somethingNew = true - break - end - end - - if somethingNew then - oldItems = items - callback(widget, items) - end - - widget:setItems(items) - end - - widget.setItems = function(self, items) - if type(self) == 'table' then - items = self - end - local itemsToShow = math.max(10, #items + 2) - if itemsToShow % 5 ~= 0 then - itemsToShow = itemsToShow + 5 - itemsToShow % 5 - end - widget.items:destroyChildren() - for i = 1, itemsToShow do - local widget = g_ui.createWidget("BotItem", widget.items) - if type(items[i]) == 'number' then - items[i] = {id=items[i], count=1} - end - if type(items[i]) == 'table' then - widget:setItem(Item.create(items[i].id, items[i].count)) - end - end - oldItems = items - for i, child in ipairs(widget.items:getChildren()) do - child.onItemChange = updateItems - end - end - - widget.getItems = function() - local items = {} - local duplicates = {} - for i, child in ipairs(widget.items:getChildren()) do - if child:getItemId() >= 100 then - if not duplicates[child:getItemId()] or not unique then - table.insert(items, {id=child:getItemId(), count=child:getItemCountOrSubType()}) - duplicates[child:getItemId()] = true - end - end - end - return items - end - - widget:setItems({}) - - return widget -end - -UI.DualScrollPanel = function(params, callback, parent) -- callback = function(widget, newParams) - --[[ params: - on - bool, - text - string, - title - string, - min - number, - max - number, - ]] - params.title = params.title or "title" - params.text = params.text or "" - params.min = params.min or 20 - params.max = params.max or 80 - - local widget = UI.createWidget('DualScrollPanel', parent) - - widget.title:setOn(params.on) - widget.title.onClick = function() - params.on = not params.on - widget.title:setOn(params.on) - if callback then - callback(widget, params) - end - end - - widget.text:setText(params.text or "") - widget.text.onTextChange = function(widget, text) - params.text = text - if callback then - callback(widget, params) - end - end - - local update = function(dontSignal) - widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%") - if callback and not dontSignal then - callback(widget, params) - end - end - - widget.scroll1:setValue(params.min) - widget.scroll2:setValue(params.max) - - widget.scroll1.onValueChange = function(scroll, value) - params.min = value - update() - end - widget.scroll2.onValueChange = function(scroll, value) - params.max = value - update() - end - update(true) -end - -UI.DualScrollItemPanel = function(params, callback, parent) -- callback = function(widget, newParams) - --[[ params: - on - bool, - item - number, - subType - number, - title - string, - min - number, - max - number, - ]] - params.title = params.title or "title" - params.item = params.item or 0 - params.subType = params.subType or 0 - params.min = params.min or 20 - params.max = params.max or 80 - - local widget = UI.createWidget('DualScrollItemPanel', parent) - - widget.title:setOn(params.on) - widget.title.onClick = function() - params.on = not params.on - widget.title:setOn(params.on) - if callback then - callback(widget, params) - end - end - - widget.item:setItem(Item.create(params.item, params.subType)) - widget.item.onItemChange = function() - params.item = widget.item:getItemId() - params.subType = widget.item:getItemSubType() - if callback then - callback(widget, params) - end - end - - local update = function(dontSignal) - widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%") - if callback and not dontSignal then - callback(widget, params) - end - end - - widget.scroll1:setValue(params.min) - widget.scroll2:setValue(params.max) - - widget.scroll1.onValueChange = function(scroll, value) - params.min = value - update() - end - widget.scroll2.onValueChange = function(scroll, value) - params.max = value - update() - end - update(true) -end - -UI.Label = function(text, parent) - local label = UI.createWidget('BotLabel', parent) - label:setText(text) - return label -end - -UI.Separator = function(parent) - local separator = UI.createWidget('BotSeparator', parent) - return separator -end - -UI.TextEdit = function(text, callback, parent) - local widget = UI.createWidget('BotTextEdit', parent) - widget.onTextChange = callback - widget:setText(text) - return widget -end - -UI.TwoItemsAndSlotPanel = function(params, callback, parent) - --[[ params: - on - bool, - title - string, - item1 - number, - item2 - number, - slot - number, - ]] - params.title = params.title or "title" - params.item1 = params.item1 or 0 - params.item2 = params.item2 or 0 - params.slot = params.slot or 1 - - local widget = UI.createWidget("TwoItemsAndSlotPanel", parent) - - widget.title:setText(params.title) - widget.title:setOn(params.on) - widget.title.onClick = function() - params.on = not params.on - widget.title:setOn(params.on) - if callback then - callback(widget, params) - end - end - - widget.slot:setCurrentIndex(params.slot) - widget.slot.onOptionChange = function() - params.slot = widget.slot.currentIndex - if callback then - callback(widget, params) - end - end - - widget.item1:setItemId(params.item1) - widget.item1.onItemChange = function() - params.item1 = widget.item1:getItemId() - if callback then - callback(widget, params) - end - end - - widget.item2:setItemId(params.item2) - widget.item2.onItemChange = function() - params.item2 = widget.item2:getItemId() - if callback then - callback(widget, params) - end - end - - return widget -end +local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI + +UI.Button = function(text, callback, parent) + local widget = UI.createWidget("BotButton", parent) + widget:setText(text) + widget.onClick = callback + return widget +end + + +UI.Config = function(parent) + return UI.createWidget("BotConfig", parent) +end + +-- call :setItems(table) to set items, call :getItems() to get them +-- unique if true, won't allow duplicates +-- callback (can be nil) gets table with new item list, eg: {{id=2160, count=1}, {id=268, count=100}, {id=269, count=20}} +UI.Container = function(callback, unique, parent, widget) + if not widget then + widget = UI.createWidget("BotContainer", parent) + end + + local oldItems = {} + + local updateItems = function() + local items = widget:getItems() + + -- callback part + local somethingNew = (#items ~= #oldItems) + for i, item in ipairs(items) do + if type(oldItems[i]) ~= "table" then + somethingNew = true + break + end + if oldItems[i].id ~= item.id or oldItems[i].count ~= item.count then + somethingNew = true + break + end + end + + if somethingNew then + oldItems = items + callback(widget, items) + end + + widget:setItems(items) + end + + widget.setItems = function(self, items) + if type(self) == 'table' then + items = self + end + local itemsToShow = math.max(10, #items + 2) + if itemsToShow % 5 ~= 0 then + itemsToShow = itemsToShow + 5 - itemsToShow % 5 + end + widget.items:destroyChildren() + for i = 1, itemsToShow do + local widget = g_ui.createWidget("BotItem", widget.items) + if type(items[i]) == 'number' then + items[i] = {id=items[i], count=1} + end + if type(items[i]) == 'table' then + widget:setItem(Item.create(items[i].id, items[i].count)) + end + end + oldItems = items + for i, child in ipairs(widget.items:getChildren()) do + child.onItemChange = updateItems + end + end + + widget.getItems = function() + local items = {} + local duplicates = {} + for i, child in ipairs(widget.items:getChildren()) do + if child:getItemId() >= 100 then + if not duplicates[child:getItemId()] or not unique then + table.insert(items, {id=child:getItemId(), count=child:getItemCountOrSubType()}) + duplicates[child:getItemId()] = true + end + end + end + return items + end + + widget:setItems({}) + + return widget +end + +UI.DualScrollPanel = function(params, callback, parent) -- callback = function(widget, newParams) + --[[ params: + on - bool, + text - string, + title - string, + min - number, + max - number, + ]] + params.title = params.title or "title" + params.text = params.text or "" + params.min = params.min or 20 + params.max = params.max or 80 + + local widget = UI.createWidget('DualScrollPanel', parent) + + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.text:setText(params.text or "") + widget.text.onTextChange = function(widget, text) + params.text = text + if callback then + callback(widget, params) + end + end + + local update = function(dontSignal) + widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%") + if callback and not dontSignal then + callback(widget, params) + end + end + + widget.scroll1:setValue(params.min) + widget.scroll2:setValue(params.max) + + widget.scroll1.onValueChange = function(scroll, value) + params.min = value + update() + end + widget.scroll2.onValueChange = function(scroll, value) + params.max = value + update() + end + update(true) +end + +UI.DualScrollItemPanel = function(params, callback, parent) -- callback = function(widget, newParams) + --[[ params: + on - bool, + item - number, + subType - number, + title - string, + min - number, + max - number, + ]] + params.title = params.title or "title" + params.item = params.item or 0 + params.subType = params.subType or 0 + params.min = params.min or 20 + params.max = params.max or 80 + + local widget = UI.createWidget('DualScrollItemPanel', parent) + + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.item:setItem(Item.create(params.item, params.subType)) + widget.item.onItemChange = function() + params.item = widget.item:getItemId() + params.subType = widget.item:getItemSubType() + if callback then + callback(widget, params) + end + end + + local update = function(dontSignal) + widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%") + if callback and not dontSignal then + callback(widget, params) + end + end + + widget.scroll1:setValue(params.min) + widget.scroll2:setValue(params.max) + + widget.scroll1.onValueChange = function(scroll, value) + params.min = value + update() + end + widget.scroll2.onValueChange = function(scroll, value) + params.max = value + update() + end + update(true) +end + +UI.Label = function(text, parent) + local label = UI.createWidget('BotLabel', parent) + label:setText(text) + return label +end + +UI.Separator = function(parent) + local separator = UI.createWidget('BotSeparator', parent) + return separator +end + +UI.TextEdit = function(text, callback, parent) + local widget = UI.createWidget('BotTextEdit', parent) + widget.onTextChange = callback + widget:setText(text) + return widget +end + +UI.TwoItemsAndSlotPanel = function(params, callback, parent) + --[[ params: + on - bool, + title - string, + item1 - number, + item2 - number, + slot - number, + ]] + params.title = params.title or "title" + params.item1 = params.item1 or 0 + params.item2 = params.item2 or 0 + params.slot = params.slot or 1 + + local widget = UI.createWidget("TwoItemsAndSlotPanel", parent) + + widget.title:setText(params.title) + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.slot:setCurrentIndex(params.slot) + widget.slot.onOptionChange = function() + params.slot = widget.slot.currentIndex + if callback then + callback(widget, params) + end + end + + widget.item1:setItemId(params.item1) + widget.item1.onItemChange = function() + params.item1 = widget.item1:getItemId() + if callback then + callback(widget, params) + end + end + + widget.item2:setItemId(params.item2) + widget.item2.onItemChange = function() + params.item2 = widget.item2:getItemId() + if callback then + callback(widget, params) + end + end + + return widget +end diff --git a/modules/game_bot/functions/ui_legacy.lua b/modules/game_bot/functions/ui_legacy.lua index 4dbaccf..2fece4c 100644 --- a/modules/game_bot/functions/ui_legacy.lua +++ b/modules/game_bot/functions/ui_legacy.lua @@ -1,135 +1,135 @@ -local context = G.botContext - --- DO NOT USE THIS CODE. --- IT'S ONLY HERE FOR BACKWARD COMPATIBILITY, MAY BE REMOVED IN THE FUTURE - -context.createWidget = function(name, parent) - if parent == nil then - parent = context.panel - end - g_ui.createWidget(name, parent) -end - -context.setupUI = function(otml, parent) - if parent == nil then - parent = context.panel - end - local widget = g_ui.loadUIFromString(otml, parent) - widget.botWidget = true - return widget -end - -context.importStyle = function(otml) - if type(otml) ~= "string" then - return error("Invalid parameter for importStyle, should be string") - end - if otml:find(".otui") and not otml:find("\n") then - return g_ui.importStyle(context.configDir .. "/" .. otml) - end - return g_ui.importStyleFromString(otml) -end - -context.addTab = function(name) - local tab = context.tabs:getTab(name) - if tab then -- return existing tab - return tab.tabPanel.content - end - - local smallTabs = #(context.tabs.tabs) >= 5 - local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content - context.tabs:setOn(true) - if smallTabs then - for k,tab in pairs(context.tabs.tabs) do - tab:setFont('small-9px') - end - end - - return newTab -end -context.getTab = context.addTab - -context.setDefaultTab = function(name) - local tab = context.addTab(name) - context.panel = tab -end - -context.addSwitch = function(id, text, onClickCallback, parent) - if not parent then - parent = context.panel - end - local switch = g_ui.createWidget('BotSwitch', parent) - switch:setId(id) - switch:setText(text) - switch.onClick = onClickCallback - return switch -end - -context.addButton = function(id, text, onClickCallback, parent) - if not parent then - parent = context.panel - end - local button = g_ui.createWidget('BotButton', parent) - button:setId(id) - button:setText(text) - button.onClick = onClickCallback - return button -end - -context.addLabel = function(id, text, parent) - if not parent then - parent = context.panel - end - local label = g_ui.createWidget('BotLabel', parent) - label:setId(id) - label:setText(text) - return label -end - -context.addTextEdit = function(id, text, onTextChangeCallback, parent) - if not parent then - parent = context.panel - end - local widget = g_ui.createWidget('BotTextEdit', parent) - widget:setId(id) - widget.onTextChange = onTextChangeCallback - widget:setText(text) - return widget -end - -context.addSeparator = function(id, parent) - if not parent then - parent = context.panel - end - local separator = g_ui.createWidget('BotSeparator', parent) - separator:setId(id) - return separator -end - -context._addMacroSwitch = function(name, keys, parent) - if not parent then - parent = context.panel - end - local text = name - if keys:len() > 0 then - text = name .. " [" .. keys .. "]" - end - local switch = context.addSwitch("macro_" .. #context._macros, text, function(widget) - context.storage._macros[name] = not context.storage._macros[name] - widget:setOn(context.storage._macros[name]) - end, parent) - switch:setOn(context.storage._macros[name]) - return switch -end - -context._addHotkeySwitch = function(name, keys, parent) - if not parent then - parent = context.panel - end - local text = name - if keys:len() > 0 then - text = name .. " [" .. keys .. "]" - end - local switch = context.addSwitch("hotkey_" .. #context._hotkeys, text, nil, parent) - switch:setOn(false) - return switch +local context = G.botContext + +-- DO NOT USE THIS CODE. +-- IT'S ONLY HERE FOR BACKWARD COMPATIBILITY, MAY BE REMOVED IN THE FUTURE + +context.createWidget = function(name, parent) + if parent == nil then + parent = context.panel + end + g_ui.createWidget(name, parent) +end + +context.setupUI = function(otml, parent) + if parent == nil then + parent = context.panel + end + local widget = g_ui.loadUIFromString(otml, parent) + widget.botWidget = true + return widget +end + +context.importStyle = function(otml) + if type(otml) ~= "string" then + return error("Invalid parameter for importStyle, should be string") + end + if otml:find(".otui") and not otml:find("\n") then + return g_ui.importStyle(context.configDir .. "/" .. otml) + end + return g_ui.importStyleFromString(otml) +end + +context.addTab = function(name) + local tab = context.tabs:getTab(name) + if tab then -- return existing tab + return tab.tabPanel.content + end + + local smallTabs = #(context.tabs.tabs) >= 5 + local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content + context.tabs:setOn(true) + if smallTabs then + for k,tab in pairs(context.tabs.tabs) do + tab:setFont('small-9px') + end + end + + return newTab +end +context.getTab = context.addTab + +context.setDefaultTab = function(name) + local tab = context.addTab(name) + context.panel = tab +end + +context.addSwitch = function(id, text, onClickCallback, parent) + if not parent then + parent = context.panel + end + local switch = g_ui.createWidget('BotSwitch', parent) + switch:setId(id) + switch:setText(text) + switch.onClick = onClickCallback + return switch +end + +context.addButton = function(id, text, onClickCallback, parent) + if not parent then + parent = context.panel + end + local button = g_ui.createWidget('BotButton', parent) + button:setId(id) + button:setText(text) + button.onClick = onClickCallback + return button +end + +context.addLabel = function(id, text, parent) + if not parent then + parent = context.panel + end + local label = g_ui.createWidget('BotLabel', parent) + label:setId(id) + label:setText(text) + return label +end + +context.addTextEdit = function(id, text, onTextChangeCallback, parent) + if not parent then + parent = context.panel + end + local widget = g_ui.createWidget('BotTextEdit', parent) + widget:setId(id) + widget.onTextChange = onTextChangeCallback + widget:setText(text) + return widget +end + +context.addSeparator = function(id, parent) + if not parent then + parent = context.panel + end + local separator = g_ui.createWidget('BotSeparator', parent) + separator:setId(id) + return separator +end + +context._addMacroSwitch = function(name, keys, parent) + if not parent then + parent = context.panel + end + local text = name + if keys:len() > 0 then + text = name .. " [" .. keys .. "]" + end + local switch = context.addSwitch("macro_" .. #context._macros, text, function(widget) + context.storage._macros[name] = not context.storage._macros[name] + widget:setOn(context.storage._macros[name]) + end, parent) + switch:setOn(context.storage._macros[name]) + return switch +end + +context._addHotkeySwitch = function(name, keys, parent) + if not parent then + parent = context.panel + end + local text = name + if keys:len() > 0 then + text = name .. " [" .. keys .. "]" + end + local switch = context.addSwitch("hotkey_" .. #context._hotkeys, text, nil, parent) + switch:setOn(false) + return switch end \ No newline at end of file diff --git a/modules/game_bot/functions/ui_windows.lua b/modules/game_bot/functions/ui_windows.lua index d066181..a0c0a4c 100644 --- a/modules/game_bot/functions/ui_windows.lua +++ b/modules/game_bot/functions/ui_windows.lua @@ -1,49 +1,49 @@ -local context = G.botContext -if type(context.UI) ~= "table" then - context.UI = {} -end -local UI = context.UI - -UI.EditorWindow = function(text, options, callback) - --[[ - Available options: - title = text - description = text - multiline = true / false - width = number - validation = text (regex) - examples = {{name, text}, {name, text}} - ]]-- - local window = modules.client_textedit.edit(text, options, callback) - window.botWidget = true - return window -end - -UI.SinglelineEditorWindow = function(text, options, callback) - options = options or {} - options.multiline = false - return UI.EditorWindow(text, options, callback) -end - -UI.MultilineEditorWindow = function(text, options, callback) - options = options or {} - options.multiline = true - return UI.EditorWindow(text, options, callback) -end - -UI.ConfirmationWindow = function(title, question, callback) - local window = nil - local onConfirm = function() - window:destroy() - callback() - end - local closeWindow = function() - window:destroy() - end - window = context.displayGeneralBox(title, question, { - { text=tr('Yes'), callback=onConfirm }, - { text=tr('No'), callback=closeWindow }, - anchor=AnchorHorizontalCenter}, onConfirm, closeWindow) - window.botWidget = true - return window +local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI + +UI.EditorWindow = function(text, options, callback) + --[[ + Available options: + title = text + description = text + multiline = true / false + width = number + validation = text (regex) + examples = {{name, text}, {name, text}} + ]]-- + local window = modules.client_textedit.edit(text, options, callback) + window.botWidget = true + return window +end + +UI.SinglelineEditorWindow = function(text, options, callback) + options = options or {} + options.multiline = false + return UI.EditorWindow(text, options, callback) +end + +UI.MultilineEditorWindow = function(text, options, callback) + options = options or {} + options.multiline = true + return UI.EditorWindow(text, options, callback) +end + +UI.ConfirmationWindow = function(title, question, callback) + local window = nil + local onConfirm = function() + window:destroy() + callback() + end + local closeWindow = function() + window:destroy() + end + window = context.displayGeneralBox(title, question, { + { text=tr('Yes'), callback=onConfirm }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, onConfirm, closeWindow) + window.botWidget = true + return window end \ No newline at end of file diff --git a/modules/game_bot/panels/attacking.lua b/modules/game_bot/panels/attacking.lua index ad98548..1ffd37e 100644 --- a/modules/game_bot/panels/attacking.lua +++ b/modules/game_bot/panels/attacking.lua @@ -1,1186 +1,1186 @@ -local context = G.botContext -local Panels = context.Panels - -Panels.MonsterEditor = function(monster, config, callback, parent) - local otherWindow = g_ui.getRootWidget():getChildById('monsterEditor') - if otherWindow then - otherWindow:destory() - end - - local window = context.setupUI([[ -MainWindow - id: monsterEditor - size: 450 450 - !text: tr("Edit monster") - - Label - id: info - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - text-align: center - text: Use monster name * for any other monster not on the list - - Label - id: info2 - anchors.left: parent.left - anchors.right: parent.right - anchors.top: prev.bottom - text-align: center - text: Add number (1-5) at the end of the name to create multiple configs - - TextEdit - id: name - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - margin-left: 100 - margin-top: 5 - multiline: false - - Label - anchors.verticalCenter: prev.verticalCenter - anchors.left: parent.left - text: Target name: - - Label - id: priorityText - anchors.left: parent.left - anchors.right: parent.horizontalCenter - anchors.top: prev.bottom - margin-right: 10 - margin-top: 10 - text: Priority - text-align: center - - HorizontalScrollBar - id: priority - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 5 - minimum: 0 - maximum: 10 - step: 1 - - Label - id: dangerText - anchors.left: parent.left - anchors.right: parent.horizontalCenter - anchors.top: prev.bottom - margin-right: 10 - margin-top: 10 - text: Danger - text-align: center - - HorizontalScrollBar - id: danger - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 5 - minimum: 0 - maximum: 10 - step: 1 - - Label - id: maxDistanceText - anchors.left: parent.left - anchors.right: parent.horizontalCenter - anchors.top: prev.bottom - margin-right: 10 - margin-top: 10 - text: Max distance to target - text-align: center - - HorizontalScrollBar - id: maxDistance - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 5 - minimum: 1 - maximum: 10 - step: 1 - - Label - id: distanceText - anchors.left: parent.left - anchors.right: parent.horizontalCenter - anchors.top: prev.bottom - margin-right: 10 - margin-top: 10 - text: Keep distance - text-align: center - - HorizontalScrollBar - id: distance - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 5 - minimum: 0 - maximum: 5 - step: 1 - - Label - id: minHealthText - anchors.left: parent.left - anchors.right: parent.horizontalCenter - anchors.top: prev.bottom - margin-right: 10 - margin-top: 10 - text: Minimum Health - text-align: center - - HorizontalScrollBar - id: minHealth - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 5 - minimum: 0 - maximum: 100 - step: 1 - - Label - id: maxHealthText - anchors.left: parent.left - anchors.right: parent.horizontalCenter - anchors.top: prev.bottom - margin-right: 10 - margin-top: 10 - text: Maximum Health - text-align: center - - HorizontalScrollBar - id: maxHealth - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 5 - minimum: 0 - maximum: 100 - step: 1 - - Label - id: dangerText - anchors.left: parent.left - anchors.right: parent.horizontalCenter - anchors.top: prev.bottom - margin-right: 5 - margin-top: 10 - text: If total danger is high (>8) bot won't auto loot until it's low again and will be trying to minimize it - text-align: center - text-wrap: true - text-auto-resize: true - - Label - id: attackSpellText - anchors.left: parent.left - anchors.right: parent.horizontalCenter - anchors.top: prev.bottom - margin-right: 5 - margin-top: 10 - text: Attack spell and attack rune are only used when you have more than 30% health - text-align: center - text-wrap: true - text-auto-resize: true - - BotSwitch - id: attack - anchors.left: parent.horizontalCenter - anchors.top: name.bottom - margin-left: 10 - margin-top: 10 - width: 55 - text: Attack - - BotSwitch - id: ignore - anchors.left: prev.right - anchors.top: name.bottom - margin-left: 18 - margin-top: 10 - width: 55 - text: Ignore - - BotSwitch - id: avoid - anchors.left: prev.right - anchors.top: name.bottom - margin-left: 18 - margin-top: 10 - width: 55 - text: Avoid - - BotSwitch - id: keepDistance - anchors.left: parent.horizontalCenter - anchors.right: parent.right - anchors.top: prev.bottom - margin-left: 10 - margin-top: 10 - text: Keep distance - - BotSwitch - id: avoidAttacks - anchors.left: parent.horizontalCenter - anchors.right: parent.right - anchors.top: prev.bottom - margin-left: 10 - margin-top: 10 - text: Avoid monster attacks - - BotSwitch - id: chase - anchors.left: parent.horizontalCenter - anchors.right: parent.right - anchors.top: prev.bottom - margin-left: 10 - margin-top: 10 - text: Chase when has low health - - BotSwitch - id: loot - anchors.left: parent.horizontalCenter - anchors.right: parent.right - anchors.top: prev.bottom - margin-left: 10 - margin-top: 10 - text: Loot corpse - - BotSwitch - id: monstersOnly - anchors.left: parent.horizontalCenter - anchors.right: parent.right - anchors.top: prev.bottom - margin-left: 10 - margin-top: 10 - text: Only for monsters - - BotSwitch - id: dontWalk - anchors.left: parent.horizontalCenter - anchors.right: parent.right - anchors.top: prev.bottom - margin-left: 10 - margin-top: 10 - text: Don't walk to target - - Label - id: attackSpellText - anchors.left: parent.horizontalCenter - anchors.right: parent.right - anchors.top: prev.bottom - margin-left: 10 - margin-top: 10 - text: Attack Spell: - text-align: center - - TextEdit - id: attackSpell - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 2 - - Label - id: attackItemText - anchors.left: parent.horizontalCenter - anchors.top: prev.bottom - margin-top: 20 - margin-left: 20 - text: Attack rune: - text-align: left - - BotItem - id: attackItem - anchors.right: parent.right - anchors.verticalCenter: prev.verticalCenter - margin-right: 30 - - Button - id: okButton - !text: tr('Ok') - anchors.bottom: parent.bottom - anchors.right: next.left - margin-right: 10 - width: 60 - - Button - id: cancelButton - !text: tr('Cancel') - anchors.bottom: parent.bottom - anchors.right: parent.right - width: 60 -]], g_ui.getRootWidget()) - - local destroy = function() - window:destroy() - end - local doneFunc = function() - local monster = window.name:getText() - local config = { - priority = window.priority:getValue(), - danger = window.danger:getValue(), - maxDistance = window.maxDistance:getValue(), - distance = window.distance:getValue(), - minHealth = window.minHealth:getValue(), - maxHealth = window.maxHealth:getValue(), - attack = window.attack:isOn(), - ignore = window.ignore:isOn(), - avoid = window.avoid:isOn(), - keepDistance = window.keepDistance:isOn(), - avoidAttacks = window.avoidAttacks:isOn(), - chase = window.chase:isOn(), - loot = window.loot:isOn(), - monstersOnly = window.monstersOnly:isOn(), - dontWalk = window.dontWalk:isOn(), - attackItem = window.attackItem:getItemId(), - attackSpell = window.attackSpell:getText() - } - destroy() - callback(monster, config) - end - - window.okButton.onClick = doneFunc - window.cancelButton.onClick = destroy - window.onEnter = doneFunc - window.onEscape = destroy - - - window.priority.onValueChange = function(scroll, value) - window.priorityText:setText("Priority: " .. value) - end - window.danger.onValueChange = function(scroll, value) - window.dangerText:setText("Danger: " .. value) - end - window.maxDistance.onValueChange = function(scroll, value) - window.maxDistanceText:setText("Max distance to target: " .. value) - end - window.distance.onValueChange = function(scroll, value) - window.distanceText:setText("Keep distance: " .. value) - end - window.minHealth.onValueChange = function(scroll, value) - window.minHealthText:setText("Minimum health: " .. value .. "%") - end - window.maxHealth.onValueChange = function(scroll, value) - window.maxHealthText:setText("Maximum health: " .. value .. "%") - end - - window.priority:setValue(config.priority or 1) - window.danger:setValue(config.danger or 1) - window.maxDistance:setValue(config.maxDistance or 6) - window.distance:setValue(config.distance or 1) - window.minHealth:setValue(1) -- to force onValueChange update - window.maxHealth:setValue(1) -- to force onValueChange update - window.minHealth:setValue(config.minHealth or 0) - window.maxHealth:setValue(config.maxHealth or 100) - - window.attackSpell:setText(config.attackSpell or "") - window.attackItem:setItemId(config.attackItem or 0) - - window.attack.onClick = function(widget) - if widget:isOn() then - return - end - widget:setOn(true) - window.ignore:setOn(false) - window.avoid:setOn(false) - end - window.ignore.onClick = function(widget) - if widget:isOn() then - return - end - widget:setOn(true) - window.attack:setOn(false) - window.avoid:setOn(false) - end - window.avoid.onClick = function(widget) - if widget:isOn() then - return - end - widget:setOn(true) - window.attack:setOn(false) - window.ignore:setOn(false) - end - - window.attack:setOn(config.attack) - window.ignore:setOn(config.ignore) - window.avoid:setOn(config.avoid) - if not window.attack:isOn() and not window.ignore:isOn() and not window.avoid:isOn() then - window.attack:setOn(true) - end - - window.keepDistance.onClick = function(widget) - widget:setOn(not widget:isOn()) - end - window.avoidAttacks.onClick = function(widget) - widget:setOn(not widget:isOn()) - end - window.chase.onClick = function(widget) - widget:setOn(not widget:isOn()) - end - window.loot.onClick = function(widget) - widget:setOn(not widget:isOn()) - end - window.monstersOnly.onClick = function(widget) - widget:setOn(not widget:isOn()) - end - window.dontWalk.onClick = function(widget) - widget:setOn(not widget:isOn()) - end - - window.keepDistance:setOn(config.keepDistance) - window.avoidAttacks:setOn(config.avoidAttacks) - window.chase:setOn(config.chase) - window.loot:setOn(config.loot) - if config.loot == nil then - window.loot:setOn(true) - end - window.monstersOnly:setOn(config.monstersOnly) - if config.monstersOnly == nil then - window.monstersOnly:setOn(true) - end - window.dontWalk:setOn(config.dontWalk) - - window.name:setText(monster) - - window:show() - window:raise() - window:focus() -end - -Panels.Attacking = function(parent) - local ui = context.setupUI([[ -Panel - id: attacking - height: 140 - - BotLabel - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - text: Attacking - - ComboBox - id: config - anchors.top: prev.bottom - anchors.left: parent.left - margin-top: 5 - text-offset: 3 0 - width: 130 - - Button - id: enableButton - anchors.top: prev.top - anchors.left: prev.right - anchors.right: parent.right - margin-left: 5 - - Button - margin-top: 1 - id: add - anchors.top: prev.bottom - anchors.left: parent.left - text: Add - width: 60 - height: 17 - - Button - id: edit - anchors.top: prev.top - anchors.horizontalCenter: parent.horizontalCenter - text: Edit - width: 60 - height: 17 - - Button - id: remove - anchors.top: prev.top - anchors.right: parent.right - text: Remove - width: 60 - height: 17 - - TextList - id: list - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - vertical-scrollbar: listScrollbar - margin-right: 15 - margin-top: 2 - height: 60 - focusable: false - auto-focus: first - - VerticalScrollBar - id: listScrollbar - anchors.top: prev.top - anchors.bottom: prev.bottom - anchors.right: parent.right - pixels-scroll: true - step: 5 - - Button - margin-top: 2 - id: mAdd - anchors.top: prev.bottom - anchors.left: parent.left - text: Add - width: 60 - height: 17 - - Button - id: mEdit - anchors.top: prev.top - anchors.horizontalCenter: parent.horizontalCenter - text: Edit - width: 60 - height: 17 - - Button - id: mRemove - anchors.top: prev.top - anchors.right: parent.right - text: Remove - width: 60 - height: 17 - -]], parent) - - if type(context.storage.attacking) ~= "table" then - context.storage.attacking = {} - end - if type(context.storage.attacking.configs) ~= "table" then - context.storage.attacking.configs = {} - end - - local getConfigName = function(config) - local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) - if matches[1] and matches[1][2] then - return matches[1][2]:trim() - end - return nil - end - - local commands = {} - local monsters = {} - local configName = nil - local refreshConfig = nil -- declared later - - local createNewConfig = function() - if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then - return - end - - local newConfig = "" - if configName ~= nil then - newConfig = "name:" .. configName .. "\n" - end - for monster, config in pairs(monsters) do - newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config, 2) .. "\n" - end - - context.storage.attacking.configs[context.storage.attacking.activeConfig] = newConfig - refreshConfig() - end - - local parseConfig = function(config) - commands = {} - monsters = {} - configName = nil - - local matches = regexMatch(config, [[([^:^\n]+)(:?)([^\n]*)]]) - for i=1,#matches do - local command = matches[i][2] - local validation = (matches[i][3] == ":") - local text = matches[i][4] - if validation then - table.insert(commands, {command=command:lower(), text=text}) - elseif #commands > 0 then - commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1] - end - end - local labels = {} - for i, command in ipairs(commands) do - if commands[i].command == "name" then - configName = commands[i].text - else - local status, result = pcall(function() return json.decode(command.text) end) - if not status then - context.error("Invalid monster config: " .. commands[i].command .. ", error: " .. result) - else - monsters[commands[i].command] = result - table.insert(labels, commands[i].command) - end - end - end - table.sort(labels) - for i, text in ipairs(labels) do - local label = g_ui.createWidget("CaveBotLabel", ui.list) - label:setText(text) - end - end - - local ignoreOnOptionChange = true - refreshConfig = function(scrollDown) - ignoreOnOptionChange = true - if context.storage.attacking.enabled then - ui.enableButton:setText("On") - ui.enableButton:setColor('#00AA00FF') - else - ui.enableButton:setText("Off") - ui.enableButton:setColor('#FF0000FF') - end - - ui.config:clear() - for i, config in ipairs(context.storage.attacking.configs) do - local name = getConfigName(config) - if not name then - name = "Unnamed config" - end - ui.config:addOption(name) - end - - if (not context.storage.attacking.activeConfig or context.storage.attacking.activeConfig == 0) and #context.storage.attacking.configs > 0 then - context.storage.attacking.activeConfig = 1 - end - - ui.list:destroyChildren() - - if context.storage.attacking.activeConfig and context.storage.attacking.configs[context.storage.attacking.activeConfig] then - ui.config:setCurrentIndex(context.storage.attacking.activeConfig) - parseConfig(context.storage.attacking.configs[context.storage.attacking.activeConfig]) - end - - context.saveConfig() - if scrollDown and ui.list:getLastChild() then - ui.list:focusChild(ui.list:getLastChild()) - end - - ignoreOnOptionChange = false - end - - - ui.config.onOptionChange = function(widget) - if not ignoreOnOptionChange then - context.storage.attacking.activeConfig = widget.currentIndex - refreshConfig() - end - end - ui.enableButton.onClick = function() - if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then - return - end - context.storage.attacking.enabled = not context.storage.attacking.enabled - refreshConfig() - end - ui.add.onClick = function() - modules.client_textedit.multilineEditor("Target list editor", "name:Config name", function(newText) - table.insert(context.storage.attacking.configs, newText) - context.storage.attacking.activeConfig = #context.storage.attacking.configs - refreshConfig() - end) - end - ui.edit.onClick = function() - if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then - return - end - modules.client_textedit.multilineEditor("Target list editor", context.storage.attacking.configs[context.storage.attacking.activeConfig], function(newText) - context.storage.attacking.configs[context.storage.attacking.activeConfig] = newText - refreshConfig() - end) - end - ui.remove.onClick = function() - if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then - return - end - local questionWindow = nil - local closeWindow = function() - questionWindow:destroy() - end - local removeConfig = function() - closeWindow() - if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then - return - end - context.storage.attacking.enabled = false - table.remove(context.storage.attacking.configs, context.storage.attacking.activeConfig) - context.storage.attacking.activeConfig = 0 - refreshConfig() - end - questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current attacking config?'), { - { text=tr('Yes'), callback=removeConfig }, - { text=tr('No'), callback=closeWindow }, - anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) - end - - - ui.mAdd.onClick = function() - if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then - return - end - Panels.MonsterEditor("", {}, function(name, config) - if name:len() > 0 then - monsters[name] = config - end - createNewConfig() - end, parent) - end - ui.mEdit.onClick = function() - if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then - return - end - local monsterWidget = ui.list:getFocusedChild() - if not monsterWidget or not monsters[monsterWidget:getText()] then - return - end - Panels.MonsterEditor(monsterWidget:getText(), monsters[monsterWidget:getText()], function(name, config) - monsters[monsterWidget:getText()] = nil - if name:len() > 0 then - monsters[name] = config - end - createNewConfig() - end, parent) - end - ui.mRemove.onClick = function() - if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then - return - end - local monsterWidget = ui.list:getFocusedChild() - if not monsterWidget or not monsters[monsterWidget:getText()] then - return - end - monsters[monsterWidget:getText()] = nil - createNewConfig() - end - - refreshConfig() - - -- processing - local isConfigPassingConditions = function(monster, config) - if not config or type(config.priority) ~= 'number' or type(config.danger) ~= 'number' then - return false - end - - if not config.attack then - return false - end - - if monster:isPlayer() and (config.monstersOnly == true or config.monstersOnly == nil) then - return false - end - - local pos = context.player:getPosition() - local mpos = monster:getPosition() - local hp = monster:getHealthPercent() - - if config.minHealth > hp or config.maxHealth < hp then - return false - end - - local maxDistance = 5 - if type(config.maxDistance) == 'number' then - maxDistance = config.maxDistance - end - if config.chase and hp < 25 then - maxDistance = maxDistance + 2 - end - - local distance = math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y)) - if distance > maxDistance then - return false - end - - local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, maxDistance + 2, { ignoreNonPathable = true, precision=1, allowOnlyVisibleTiles = true, ignoreCost = true }) - if not pathTo or #pathTo > maxDistance + 1 then - return false - end - return true - end - - local getMonsterConfig = function(monster) - local name = monster:getName():lower() - local hasConfig = false - hasConfig = hasConfig or (monsters[name] ~= nil) - if isConfigPassingConditions(monster, monsters[name]) then - return monsters[name] - end - for i=1, 5 do - hasConfig = hasConfig or (monsters[name .. i] ~= nil) - if isConfigPassingConditions(monster, monsters[name .. i]) then - return monsters[name .. i] - end - end - if not hasConfig and isConfigPassingConditions(monster, monsters["*"]) then - return monsters["*"] - end - return nil - end - - local calculatePriority = function(monster) - local priority = 0 - local config = getMonsterConfig(monster) - if not config then - return -1 - end - - local pos = context.player:getPosition() - local mpos = monster:getPosition() - local hp = monster:getHealthPercent() - local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, 10, { ignoreNonPathable = true, ignoreLastCreature = true, precision=0, allowOnlyVisibleTiles = true }) - if not pathTo then - pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, 10, { ignoreNonPathable = true, precision=1, allowOnlyVisibleTiles = true }) - if not pathTo then - return -1 - end - end - local distance = #pathTo - - if monster == g_game.getAttackingCreature() then - priority = priority + 10 - end - - if distance <= 4 then - priority = priority + 10 - end - if distance <= 2 then - priority = priority + 10 - end - if distance <= 1 then - priority = priority + 10 - end - - if hp <= 25 and config.chase then - priority = priority + 30 - end - - if hp <= 10 then - priority = priority + 10 - end - if hp <= 25 then - priority = priority + 10 - end - if hp <= 50 then - priority = priority + 10 - end - if hp <= 75 then - priority = priority + 10 - end - - priority = priority + config.priority * 10 - return priority - end - - local calculateMonsterDanger = function(monster) - local danger = 0 - local config = getMonsterConfig(monster) - if not config or type(config.danger) ~= 'number' then - return danger - end - danger = danger + config.danger - return danger - end - - local lastAttack = context.now - local lootContainers = {} - local lootTries = 0 - local openContainerRequest = 0 - local waitForLooting = 0 - local lastAttackSpell = 0 - local lastAttackRune = 0 - - local goForLoot = function() - if #lootContainers == 0 or not context.storage.looting.enabled then - return false - end - if modules.game_walking.lastManualWalk + 500 > context.now then - return true - end - - local pos = context.player:getPosition() - table.sort(lootContainers, function(pos1, pos2) - local dist1 = math.max(math.abs(pos.x-pos1.x), math.abs(pos.y-pos1.y)) - local dist2 = math.max(math.abs(pos.x-pos2.x), math.abs(pos.y-pos2.y)) - return dist1 < dist2 - end) - - local cpos = lootContainers[1] - if cpos.z ~= pos.z then - table.remove(lootContainers, 1) - return true - end - - if lootTries >= 5 then - lootTries = 0 - table.remove(lootContainers, 1) - return true - end - local dist = math.max(math.abs(pos.x-cpos.x), math.abs(pos.y-cpos.y)) - if dist <= 5 then - local tile = g_map.getTile(cpos) - if not tile then - table.remove(lootContainers, 1) - return true - end - - local topItem = tile:getTopUseThing() - if not topItem or not topItem:isContainer() then - table.remove(lootContainers, 1) - return true - end - topItem:setMarked('orange') - - if dist <= 1 then - lootTries = lootTries + 1 - openContainerRequest = context.now - g_game.open(topItem) - waitForLooting = math.max(waitForLooting, context.now + 500) - return true - end - end - - if dist <= 25 then - if context.player:isWalking() then - return true - end - - lootTries = lootTries + 1 - if context.autoWalk(cpos, 20, { precision = 1}) then - return true - end - - if context.autoWalk(cpos, 20, { ignoreNonPathable = true, precision = 1}) then - return true - end - - if context.autoWalk(cpos, 20, { ignoreNonPathable = true, precision = 2}) then - return true - end - - if context.autoWalk(cpos, 20, { ignoreNonPathable = true, ignoreCreatures = true, precision = 2}) then - return true - end - else - table.remove(lootContainers, 1) - return false - end - return true - end - - context.onCreatureDisappear(function(creature) - if not creature:isMonster() then - return - end - local pos = context.player:getPosition() - local tpos = creature:getPosition() - if tpos.z ~= pos.z then - return - end - - local config = getMonsterConfig(creature) - if not config or not config.loot then - return - end - local distance = math.max(math.abs(pos.x-tpos.x), math.abs(pos.y-tpos.y)) - if distance > 6 then - return - end - - local tile = g_map.getTile(tpos) - if not tile then - return - end - - local topItem = tile:getTopUseThing() - if not topItem or not topItem:isContainer() then - return - end - - topItem:setMarked('blue') - table.insert(lootContainers, tpos) - end) - - context.onContainerOpen(function(container, prevContainer) - lootTries = 0 - if not context.storage.attacking.enabled then - return - end - - if openContainerRequest + 500 > context.now and #lootContainers > 0 then - waitForLooting = math.max(waitForLooting, context.now + 1000 + container:getItemsCount() * 100) - table.remove(lootContainers, 1) - end - if prevContainer then - container.autoLooting = prevContainer.autoLooting - else - container.autoLooting = (openContainerRequest + 3000 > context.now) - end - end) - - context.macro(200, function() - if not context.storage.attacking.enabled then - return - end - - local attacking = nil - local following = nil - local attackingCandidate = g_game.getAttackingCreature() - local followingCandidate = g_game.getFollowingCreature() - local spectators = context.getSpectators() - local monsters = {} - local danger = 0 - - for i, spec in ipairs(spectators) do - if attackingCandidate and attackingCandidate:getId() == spec:getId() then - attacking = spec - end - if followingCandidate and followingCandidate:getId() == spec:getId() then - following = spec - end - if spec:isMonster() or (spec:isPlayer() and not spec:isLocalPlayer()) then - danger = danger + calculateMonsterDanger(spec) - spec.attackingPriority = calculatePriority(spec) - table.insert(monsters, spec) - end - end - - if following then - return - end - - if waitForLooting > context.now then - return - end - - if #monsters == 0 or context.isInProtectionZone() then - goForLoot() - return - end - - table.sort(monsters, function(a, b) - return a.attackingPriority > b.attackingPriority - end) - - local target = monsters[1] - if target.attackingPriority < 0 then - return - end - - local pos = context.player:getPosition() - local tpos = target:getPosition() - local config = getMonsterConfig(target) - local offsetX = pos.x - tpos.x - local offsetY = pos.y - tpos.y - - local justStartedAttack = false - if target ~= attacking then - g_game.attack(target) - attacking = target - lastAttack = context.now - justStartedAttack = true - end - - -- proceed attack - if not target:isPlayer() and lastAttack + 15000 < context.now then - -- stop and attack again, just in case - g_game.cancelAttack() - g_game.attack(target) - lastAttack = context.now - return - end - - if not justStartedAttack and config.attackSpell and config.attackSpell:len() > 0 then - if context.now > lastAttackSpell + 1000 and context.player:getHealthPercent() > 30 then - if context.saySpell(config.attackSpell, 1500) then - lastAttackRune = context.now - end - end - end - - if not justStartedAttack and config.attackItem and config.attackItem >= 100 then - if context.now > lastAttackRune + 1000 and context.player:getHealthPercent() > 30 then - if context.useRune(config.attackItem, target, 1500) then - lastAttackRune = context.now - end - end - end - - if modules.game_walking.lastManualWalk + 500 > context.now then - return - end - - if danger < 8 then - -- low danger, go for loot first - if goForLoot() then - return - end - end - - target.ignoreByWaypoints = config.dontWalk - if config.dontWalk then - if goForLoot() then - return - end - return - end - - local distance = math.max(math.abs(offsetX), math.abs(offsetY)) - if config.keepDistance then - local minDistance = config.distance - if target:getHealthPercent() <= 25 and config.chase and danger < 10 then - minDistance = 1 - end - if (distance == minDistance or distance == minDistance + 1) then - return - else - local bestDist = 10 - local bestPos = pos - if not context.autoWalk(tpos, 10, { minMargin=minDistance, maxMargin=minDistance + 1, allowOnlyVisibleTiles = true}) then - if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, minMargin=minDistance, maxMargin=minDistance + 1, allowOnlyVisibleTiles = true}) then - if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, ignoreCreatures = true, minMargin=minDistance, maxMargin=minDistance + 2, allowOnlyVisibleTiles = true}) then - return - end - end - end - if not target:isPlayer() then - context.delay(300) - end - end - return - end - - if config.avoidAttacks and distance <= 1 then - if (offsetX == 0 and offsetY ~= 0) then - if context.player:canWalk(Directions.East) then - g_game.walk(Directions.East) - elseif context.player:canWalk(Directions.West) then - g_game.walk(Directions.West) - end - elseif (offsetX ~= 0 and offsetY == 0) then - if context.player:canWalk(Directions.North) then - g_game.walk(Directions.North) - elseif context.player:canWalk(Directions.South) then - g_game.walk(Directions.South) - end - end - end - - if distance > 1 then - if not context.autoWalk(tpos, 10, { precision = 1, allowOnlyVisibleTiles = true}) then - if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, precision = 1, allowOnlyVisibleTiles = true}) then - if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, precision = 2, allowOnlyVisibleTiles = true}) then - return - end - end - end - if not target:isPlayer() then - context.delay(300) - end - end - end) -end - +local context = G.botContext +local Panels = context.Panels + +Panels.MonsterEditor = function(monster, config, callback, parent) + local otherWindow = g_ui.getRootWidget():getChildById('monsterEditor') + if otherWindow then + otherWindow:destory() + end + + local window = context.setupUI([[ +MainWindow + id: monsterEditor + size: 450 450 + !text: tr("Edit monster") + + Label + id: info + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + text: Use monster name * for any other monster not on the list + + Label + id: info2 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + text-align: center + text: Add number (1-5) at the end of the name to create multiple configs + + TextEdit + id: name + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 100 + margin-top: 5 + multiline: false + + Label + anchors.verticalCenter: prev.verticalCenter + anchors.left: parent.left + text: Target name: + + Label + id: priorityText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Priority + text-align: center + + HorizontalScrollBar + id: priority + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + + Label + id: dangerText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Danger + text-align: center + + HorizontalScrollBar + id: danger + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + + Label + id: maxDistanceText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Max distance to target + text-align: center + + HorizontalScrollBar + id: maxDistance + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 1 + maximum: 10 + step: 1 + + Label + id: distanceText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Keep distance + text-align: center + + HorizontalScrollBar + id: distance + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 5 + step: 1 + + Label + id: minHealthText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Minimum Health + text-align: center + + HorizontalScrollBar + id: minHealth + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 100 + step: 1 + + Label + id: maxHealthText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Maximum Health + text-align: center + + HorizontalScrollBar + id: maxHealth + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 100 + step: 1 + + Label + id: dangerText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 5 + margin-top: 10 + text: If total danger is high (>8) bot won't auto loot until it's low again and will be trying to minimize it + text-align: center + text-wrap: true + text-auto-resize: true + + Label + id: attackSpellText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 5 + margin-top: 10 + text: Attack spell and attack rune are only used when you have more than 30% health + text-align: center + text-wrap: true + text-auto-resize: true + + BotSwitch + id: attack + anchors.left: parent.horizontalCenter + anchors.top: name.bottom + margin-left: 10 + margin-top: 10 + width: 55 + text: Attack + + BotSwitch + id: ignore + anchors.left: prev.right + anchors.top: name.bottom + margin-left: 18 + margin-top: 10 + width: 55 + text: Ignore + + BotSwitch + id: avoid + anchors.left: prev.right + anchors.top: name.bottom + margin-left: 18 + margin-top: 10 + width: 55 + text: Avoid + + BotSwitch + id: keepDistance + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Keep distance + + BotSwitch + id: avoidAttacks + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Avoid monster attacks + + BotSwitch + id: chase + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Chase when has low health + + BotSwitch + id: loot + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Loot corpse + + BotSwitch + id: monstersOnly + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Only for monsters + + BotSwitch + id: dontWalk + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Don't walk to target + + Label + id: attackSpellText + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Attack Spell: + text-align: center + + TextEdit + id: attackSpell + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 2 + + Label + id: attackItemText + anchors.left: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 20 + margin-left: 20 + text: Attack rune: + text-align: left + + BotItem + id: attackItem + anchors.right: parent.right + anchors.verticalCenter: prev.verticalCenter + margin-right: 30 + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 +]], g_ui.getRootWidget()) + + local destroy = function() + window:destroy() + end + local doneFunc = function() + local monster = window.name:getText() + local config = { + priority = window.priority:getValue(), + danger = window.danger:getValue(), + maxDistance = window.maxDistance:getValue(), + distance = window.distance:getValue(), + minHealth = window.minHealth:getValue(), + maxHealth = window.maxHealth:getValue(), + attack = window.attack:isOn(), + ignore = window.ignore:isOn(), + avoid = window.avoid:isOn(), + keepDistance = window.keepDistance:isOn(), + avoidAttacks = window.avoidAttacks:isOn(), + chase = window.chase:isOn(), + loot = window.loot:isOn(), + monstersOnly = window.monstersOnly:isOn(), + dontWalk = window.dontWalk:isOn(), + attackItem = window.attackItem:getItemId(), + attackSpell = window.attackSpell:getText() + } + destroy() + callback(monster, config) + end + + window.okButton.onClick = doneFunc + window.cancelButton.onClick = destroy + window.onEnter = doneFunc + window.onEscape = destroy + + + window.priority.onValueChange = function(scroll, value) + window.priorityText:setText("Priority: " .. value) + end + window.danger.onValueChange = function(scroll, value) + window.dangerText:setText("Danger: " .. value) + end + window.maxDistance.onValueChange = function(scroll, value) + window.maxDistanceText:setText("Max distance to target: " .. value) + end + window.distance.onValueChange = function(scroll, value) + window.distanceText:setText("Keep distance: " .. value) + end + window.minHealth.onValueChange = function(scroll, value) + window.minHealthText:setText("Minimum health: " .. value .. "%") + end + window.maxHealth.onValueChange = function(scroll, value) + window.maxHealthText:setText("Maximum health: " .. value .. "%") + end + + window.priority:setValue(config.priority or 1) + window.danger:setValue(config.danger or 1) + window.maxDistance:setValue(config.maxDistance or 6) + window.distance:setValue(config.distance or 1) + window.minHealth:setValue(1) -- to force onValueChange update + window.maxHealth:setValue(1) -- to force onValueChange update + window.minHealth:setValue(config.minHealth or 0) + window.maxHealth:setValue(config.maxHealth or 100) + + window.attackSpell:setText(config.attackSpell or "") + window.attackItem:setItemId(config.attackItem or 0) + + window.attack.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.ignore:setOn(false) + window.avoid:setOn(false) + end + window.ignore.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.attack:setOn(false) + window.avoid:setOn(false) + end + window.avoid.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.attack:setOn(false) + window.ignore:setOn(false) + end + + window.attack:setOn(config.attack) + window.ignore:setOn(config.ignore) + window.avoid:setOn(config.avoid) + if not window.attack:isOn() and not window.ignore:isOn() and not window.avoid:isOn() then + window.attack:setOn(true) + end + + window.keepDistance.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.avoidAttacks.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.chase.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.loot.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.monstersOnly.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.dontWalk.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + + window.keepDistance:setOn(config.keepDistance) + window.avoidAttacks:setOn(config.avoidAttacks) + window.chase:setOn(config.chase) + window.loot:setOn(config.loot) + if config.loot == nil then + window.loot:setOn(true) + end + window.monstersOnly:setOn(config.monstersOnly) + if config.monstersOnly == nil then + window.monstersOnly:setOn(true) + end + window.dontWalk:setOn(config.dontWalk) + + window.name:setText(monster) + + window:show() + window:raise() + window:focus() +end + +Panels.Attacking = function(parent) + local ui = context.setupUI([[ +Panel + id: attacking + height: 140 + + BotLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: Attacking + + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 5 + text-offset: 3 0 + width: 130 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Button + margin-top: 1 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 17 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 17 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 17 + + TextList + id: list + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + vertical-scrollbar: listScrollbar + margin-right: 15 + margin-top: 2 + height: 60 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + pixels-scroll: true + step: 5 + + Button + margin-top: 2 + id: mAdd + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 17 + + Button + id: mEdit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 17 + + Button + id: mRemove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 17 + +]], parent) + + if type(context.storage.attacking) ~= "table" then + context.storage.attacking = {} + end + if type(context.storage.attacking.configs) ~= "table" then + context.storage.attacking.configs = {} + end + + local getConfigName = function(config) + local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) + if matches[1] and matches[1][2] then + return matches[1][2]:trim() + end + return nil + end + + local commands = {} + local monsters = {} + local configName = nil + local refreshConfig = nil -- declared later + + local createNewConfig = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + + local newConfig = "" + if configName ~= nil then + newConfig = "name:" .. configName .. "\n" + end + for monster, config in pairs(monsters) do + newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config, 2) .. "\n" + end + + context.storage.attacking.configs[context.storage.attacking.activeConfig] = newConfig + refreshConfig() + end + + local parseConfig = function(config) + commands = {} + monsters = {} + configName = nil + + local matches = regexMatch(config, [[([^:^\n]+)(:?)([^\n]*)]]) + for i=1,#matches do + local command = matches[i][2] + local validation = (matches[i][3] == ":") + local text = matches[i][4] + if validation then + table.insert(commands, {command=command:lower(), text=text}) + elseif #commands > 0 then + commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1] + end + end + local labels = {} + for i, command in ipairs(commands) do + if commands[i].command == "name" then + configName = commands[i].text + else + local status, result = pcall(function() return json.decode(command.text) end) + if not status then + context.error("Invalid monster config: " .. commands[i].command .. ", error: " .. result) + else + monsters[commands[i].command] = result + table.insert(labels, commands[i].command) + end + end + end + table.sort(labels) + for i, text in ipairs(labels) do + local label = g_ui.createWidget("CaveBotLabel", ui.list) + label:setText(text) + end + end + + local ignoreOnOptionChange = true + refreshConfig = function(scrollDown) + ignoreOnOptionChange = true + if context.storage.attacking.enabled then + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + end + + ui.config:clear() + for i, config in ipairs(context.storage.attacking.configs) do + local name = getConfigName(config) + if not name then + name = "Unnamed config" + end + ui.config:addOption(name) + end + + if (not context.storage.attacking.activeConfig or context.storage.attacking.activeConfig == 0) and #context.storage.attacking.configs > 0 then + context.storage.attacking.activeConfig = 1 + end + + ui.list:destroyChildren() + + if context.storage.attacking.activeConfig and context.storage.attacking.configs[context.storage.attacking.activeConfig] then + ui.config:setCurrentIndex(context.storage.attacking.activeConfig) + parseConfig(context.storage.attacking.configs[context.storage.attacking.activeConfig]) + end + + context.saveConfig() + if scrollDown and ui.list:getLastChild() then + ui.list:focusChild(ui.list:getLastChild()) + end + + ignoreOnOptionChange = false + end + + + ui.config.onOptionChange = function(widget) + if not ignoreOnOptionChange then + context.storage.attacking.activeConfig = widget.currentIndex + refreshConfig() + end + end + ui.enableButton.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + context.storage.attacking.enabled = not context.storage.attacking.enabled + refreshConfig() + end + ui.add.onClick = function() + modules.client_textedit.multilineEditor("Target list editor", "name:Config name", function(newText) + table.insert(context.storage.attacking.configs, newText) + context.storage.attacking.activeConfig = #context.storage.attacking.configs + refreshConfig() + end) + end + ui.edit.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + modules.client_textedit.multilineEditor("Target list editor", context.storage.attacking.configs[context.storage.attacking.activeConfig], function(newText) + context.storage.attacking.configs[context.storage.attacking.activeConfig] = newText + refreshConfig() + end) + end + ui.remove.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + local questionWindow = nil + local closeWindow = function() + questionWindow:destroy() + end + local removeConfig = function() + closeWindow() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + context.storage.attacking.enabled = false + table.remove(context.storage.attacking.configs, context.storage.attacking.activeConfig) + context.storage.attacking.activeConfig = 0 + refreshConfig() + end + questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current attacking config?'), { + { text=tr('Yes'), callback=removeConfig }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) + end + + + ui.mAdd.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + Panels.MonsterEditor("", {}, function(name, config) + if name:len() > 0 then + monsters[name] = config + end + createNewConfig() + end, parent) + end + ui.mEdit.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + local monsterWidget = ui.list:getFocusedChild() + if not monsterWidget or not monsters[monsterWidget:getText()] then + return + end + Panels.MonsterEditor(monsterWidget:getText(), monsters[monsterWidget:getText()], function(name, config) + monsters[monsterWidget:getText()] = nil + if name:len() > 0 then + monsters[name] = config + end + createNewConfig() + end, parent) + end + ui.mRemove.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + local monsterWidget = ui.list:getFocusedChild() + if not monsterWidget or not monsters[monsterWidget:getText()] then + return + end + monsters[monsterWidget:getText()] = nil + createNewConfig() + end + + refreshConfig() + + -- processing + local isConfigPassingConditions = function(monster, config) + if not config or type(config.priority) ~= 'number' or type(config.danger) ~= 'number' then + return false + end + + if not config.attack then + return false + end + + if monster:isPlayer() and (config.monstersOnly == true or config.monstersOnly == nil) then + return false + end + + local pos = context.player:getPosition() + local mpos = monster:getPosition() + local hp = monster:getHealthPercent() + + if config.minHealth > hp or config.maxHealth < hp then + return false + end + + local maxDistance = 5 + if type(config.maxDistance) == 'number' then + maxDistance = config.maxDistance + end + if config.chase and hp < 25 then + maxDistance = maxDistance + 2 + end + + local distance = math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y)) + if distance > maxDistance then + return false + end + + local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, maxDistance + 2, { ignoreNonPathable = true, precision=1, allowOnlyVisibleTiles = true, ignoreCost = true }) + if not pathTo or #pathTo > maxDistance + 1 then + return false + end + return true + end + + local getMonsterConfig = function(monster) + local name = monster:getName():lower() + local hasConfig = false + hasConfig = hasConfig or (monsters[name] ~= nil) + if isConfigPassingConditions(monster, monsters[name]) then + return monsters[name] + end + for i=1, 5 do + hasConfig = hasConfig or (monsters[name .. i] ~= nil) + if isConfigPassingConditions(monster, monsters[name .. i]) then + return monsters[name .. i] + end + end + if not hasConfig and isConfigPassingConditions(monster, monsters["*"]) then + return monsters["*"] + end + return nil + end + + local calculatePriority = function(monster) + local priority = 0 + local config = getMonsterConfig(monster) + if not config then + return -1 + end + + local pos = context.player:getPosition() + local mpos = monster:getPosition() + local hp = monster:getHealthPercent() + local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, 10, { ignoreNonPathable = true, ignoreLastCreature = true, precision=0, allowOnlyVisibleTiles = true }) + if not pathTo then + pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, 10, { ignoreNonPathable = true, precision=1, allowOnlyVisibleTiles = true }) + if not pathTo then + return -1 + end + end + local distance = #pathTo + + if monster == g_game.getAttackingCreature() then + priority = priority + 10 + end + + if distance <= 4 then + priority = priority + 10 + end + if distance <= 2 then + priority = priority + 10 + end + if distance <= 1 then + priority = priority + 10 + end + + if hp <= 25 and config.chase then + priority = priority + 30 + end + + if hp <= 10 then + priority = priority + 10 + end + if hp <= 25 then + priority = priority + 10 + end + if hp <= 50 then + priority = priority + 10 + end + if hp <= 75 then + priority = priority + 10 + end + + priority = priority + config.priority * 10 + return priority + end + + local calculateMonsterDanger = function(monster) + local danger = 0 + local config = getMonsterConfig(monster) + if not config or type(config.danger) ~= 'number' then + return danger + end + danger = danger + config.danger + return danger + end + + local lastAttack = context.now + local lootContainers = {} + local lootTries = 0 + local openContainerRequest = 0 + local waitForLooting = 0 + local lastAttackSpell = 0 + local lastAttackRune = 0 + + local goForLoot = function() + if #lootContainers == 0 or not context.storage.looting.enabled then + return false + end + if modules.game_walking.lastManualWalk + 500 > context.now then + return true + end + + local pos = context.player:getPosition() + table.sort(lootContainers, function(pos1, pos2) + local dist1 = math.max(math.abs(pos.x-pos1.x), math.abs(pos.y-pos1.y)) + local dist2 = math.max(math.abs(pos.x-pos2.x), math.abs(pos.y-pos2.y)) + return dist1 < dist2 + end) + + local cpos = lootContainers[1] + if cpos.z ~= pos.z then + table.remove(lootContainers, 1) + return true + end + + if lootTries >= 5 then + lootTries = 0 + table.remove(lootContainers, 1) + return true + end + local dist = math.max(math.abs(pos.x-cpos.x), math.abs(pos.y-cpos.y)) + if dist <= 5 then + local tile = g_map.getTile(cpos) + if not tile then + table.remove(lootContainers, 1) + return true + end + + local topItem = tile:getTopUseThing() + if not topItem or not topItem:isContainer() then + table.remove(lootContainers, 1) + return true + end + topItem:setMarked('orange') + + if dist <= 1 then + lootTries = lootTries + 1 + openContainerRequest = context.now + g_game.open(topItem) + waitForLooting = math.max(waitForLooting, context.now + 500) + return true + end + end + + if dist <= 25 then + if context.player:isWalking() then + return true + end + + lootTries = lootTries + 1 + if context.autoWalk(cpos, 20, { precision = 1}) then + return true + end + + if context.autoWalk(cpos, 20, { ignoreNonPathable = true, precision = 1}) then + return true + end + + if context.autoWalk(cpos, 20, { ignoreNonPathable = true, precision = 2}) then + return true + end + + if context.autoWalk(cpos, 20, { ignoreNonPathable = true, ignoreCreatures = true, precision = 2}) then + return true + end + else + table.remove(lootContainers, 1) + return false + end + return true + end + + context.onCreatureDisappear(function(creature) + if not creature:isMonster() then + return + end + local pos = context.player:getPosition() + local tpos = creature:getPosition() + if tpos.z ~= pos.z then + return + end + + local config = getMonsterConfig(creature) + if not config or not config.loot then + return + end + local distance = math.max(math.abs(pos.x-tpos.x), math.abs(pos.y-tpos.y)) + if distance > 6 then + return + end + + local tile = g_map.getTile(tpos) + if not tile then + return + end + + local topItem = tile:getTopUseThing() + if not topItem or not topItem:isContainer() then + return + end + + topItem:setMarked('blue') + table.insert(lootContainers, tpos) + end) + + context.onContainerOpen(function(container, prevContainer) + lootTries = 0 + if not context.storage.attacking.enabled then + return + end + + if openContainerRequest + 500 > context.now and #lootContainers > 0 then + waitForLooting = math.max(waitForLooting, context.now + 1000 + container:getItemsCount() * 100) + table.remove(lootContainers, 1) + end + if prevContainer then + container.autoLooting = prevContainer.autoLooting + else + container.autoLooting = (openContainerRequest + 3000 > context.now) + end + end) + + context.macro(200, function() + if not context.storage.attacking.enabled then + return + end + + local attacking = nil + local following = nil + local attackingCandidate = g_game.getAttackingCreature() + local followingCandidate = g_game.getFollowingCreature() + local spectators = context.getSpectators() + local monsters = {} + local danger = 0 + + for i, spec in ipairs(spectators) do + if attackingCandidate and attackingCandidate:getId() == spec:getId() then + attacking = spec + end + if followingCandidate and followingCandidate:getId() == spec:getId() then + following = spec + end + if spec:isMonster() or (spec:isPlayer() and not spec:isLocalPlayer()) then + danger = danger + calculateMonsterDanger(spec) + spec.attackingPriority = calculatePriority(spec) + table.insert(monsters, spec) + end + end + + if following then + return + end + + if waitForLooting > context.now then + return + end + + if #monsters == 0 or context.isInProtectionZone() then + goForLoot() + return + end + + table.sort(monsters, function(a, b) + return a.attackingPriority > b.attackingPriority + end) + + local target = monsters[1] + if target.attackingPriority < 0 then + return + end + + local pos = context.player:getPosition() + local tpos = target:getPosition() + local config = getMonsterConfig(target) + local offsetX = pos.x - tpos.x + local offsetY = pos.y - tpos.y + + local justStartedAttack = false + if target ~= attacking then + g_game.attack(target) + attacking = target + lastAttack = context.now + justStartedAttack = true + end + + -- proceed attack + if not target:isPlayer() and lastAttack + 15000 < context.now then + -- stop and attack again, just in case + g_game.cancelAttack() + g_game.attack(target) + lastAttack = context.now + return + end + + if not justStartedAttack and config.attackSpell and config.attackSpell:len() > 0 then + if context.now > lastAttackSpell + 1000 and context.player:getHealthPercent() > 30 then + if context.saySpell(config.attackSpell, 1500) then + lastAttackRune = context.now + end + end + end + + if not justStartedAttack and config.attackItem and config.attackItem >= 100 then + if context.now > lastAttackRune + 1000 and context.player:getHealthPercent() > 30 then + if context.useRune(config.attackItem, target, 1500) then + lastAttackRune = context.now + end + end + end + + if modules.game_walking.lastManualWalk + 500 > context.now then + return + end + + if danger < 8 then + -- low danger, go for loot first + if goForLoot() then + return + end + end + + target.ignoreByWaypoints = config.dontWalk + if config.dontWalk then + if goForLoot() then + return + end + return + end + + local distance = math.max(math.abs(offsetX), math.abs(offsetY)) + if config.keepDistance then + local minDistance = config.distance + if target:getHealthPercent() <= 25 and config.chase and danger < 10 then + minDistance = 1 + end + if (distance == minDistance or distance == minDistance + 1) then + return + else + local bestDist = 10 + local bestPos = pos + if not context.autoWalk(tpos, 10, { minMargin=minDistance, maxMargin=minDistance + 1, allowOnlyVisibleTiles = true}) then + if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, minMargin=minDistance, maxMargin=minDistance + 1, allowOnlyVisibleTiles = true}) then + if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, ignoreCreatures = true, minMargin=minDistance, maxMargin=minDistance + 2, allowOnlyVisibleTiles = true}) then + return + end + end + end + if not target:isPlayer() then + context.delay(300) + end + end + return + end + + if config.avoidAttacks and distance <= 1 then + if (offsetX == 0 and offsetY ~= 0) then + if context.player:canWalk(Directions.East) then + g_game.walk(Directions.East) + elseif context.player:canWalk(Directions.West) then + g_game.walk(Directions.West) + end + elseif (offsetX ~= 0 and offsetY == 0) then + if context.player:canWalk(Directions.North) then + g_game.walk(Directions.North) + elseif context.player:canWalk(Directions.South) then + g_game.walk(Directions.South) + end + end + end + + if distance > 1 then + if not context.autoWalk(tpos, 10, { precision = 1, allowOnlyVisibleTiles = true}) then + if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, precision = 1, allowOnlyVisibleTiles = true}) then + if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, precision = 2, allowOnlyVisibleTiles = true}) then + return + end + end + end + if not target:isPlayer() then + context.delay(300) + end + end + end) +end + diff --git a/modules/game_bot/panels/basic.lua b/modules/game_bot/panels/basic.lua index bd948b2..dfa7d87 100644 --- a/modules/game_bot/panels/basic.lua +++ b/modules/game_bot/panels/basic.lua @@ -1,57 +1,57 @@ -local context = G.botContext -local Panels = context.Panels - -Panels.Turning = function(parent) - context.macro(1000, "Turning / AntiIdle", nil, function() - context.turn(math.random(1, 4)) - end, parent) -end -Panels.AntiIdle = Panels.Turning - -Panels.AttackSpell = function(parent) - context.macro(500, "Auto attack spell", nil, function() - local target = g_game.getAttackingCreature() - if target and context.getCreatureById(target:getId()) and context.storage.autoAttackText:len() > 0 then - if context.saySpell(context.storage.autoAttackText, 1000) then - context.delay(1000) - end - end - end, parent) - context.addTextEdit("autoAttackText", context.storage.autoAttackText or "exori vis", function(widget, text) - context.storage.autoAttackText = text - end, parent) -end - -Panels.AttackItem = function(parent) - if not parent then - parent = context.panel - end - - local panelName = "attackItem" - local ui = g_ui.createWidget("ItemAndButtonPanel", parent) - ui:setId(panelName) - - ui.title:setText("Auto attack item") - - if not context.storage.attackItem then - context.storage.attackItem = {} - end - - ui.title:setOn(context.storage.attackItem.enabled) - ui.title.onClick = function(widget) - context.storage.attackItem.enabled = not context.storage.attackItem.enabled - widget:setOn(context.storage.attackItem.enabled) - end - - ui.item.onItemChange = function(widget) - context.storage.attackItem.item = widget:getItemId() - end - ui.item:setItemId(context.storage.attackItem.item or 3155) - - context.macro(500, function() - local target = g_game.getAttackingCreature() - if context.storage.attackItem.enabled and target and context.getCreatureById(target:getId()) and context.storage.attackItem.item and context.storage.attackItem.item >= 100 then - context.useWith(context.storage.attackItem.item, target) - end - end) -end +local context = G.botContext +local Panels = context.Panels + +Panels.Turning = function(parent) + context.macro(1000, "Turning / AntiIdle", nil, function() + context.turn(math.random(1, 4)) + end, parent) +end +Panels.AntiIdle = Panels.Turning + +Panels.AttackSpell = function(parent) + context.macro(500, "Auto attack spell", nil, function() + local target = g_game.getAttackingCreature() + if target and context.getCreatureById(target:getId()) and context.storage.autoAttackText:len() > 0 then + if context.saySpell(context.storage.autoAttackText, 1000) then + context.delay(1000) + end + end + end, parent) + context.addTextEdit("autoAttackText", context.storage.autoAttackText or "exori vis", function(widget, text) + context.storage.autoAttackText = text + end, parent) +end + +Panels.AttackItem = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "attackItem" + local ui = g_ui.createWidget("ItemAndButtonPanel", parent) + ui:setId(panelName) + + ui.title:setText("Auto attack item") + + if not context.storage.attackItem then + context.storage.attackItem = {} + end + + ui.title:setOn(context.storage.attackItem.enabled) + ui.title.onClick = function(widget) + context.storage.attackItem.enabled = not context.storage.attackItem.enabled + widget:setOn(context.storage.attackItem.enabled) + end + + ui.item.onItemChange = function(widget) + context.storage.attackItem.item = widget:getItemId() + end + ui.item:setItemId(context.storage.attackItem.item or 3155) + + context.macro(500, function() + local target = g_game.getAttackingCreature() + if context.storage.attackItem.enabled and target and context.getCreatureById(target:getId()) and context.storage.attackItem.item and context.storage.attackItem.item >= 100 then + context.useWith(context.storage.attackItem.item, target) + end + end) +end diff --git a/modules/game_bot/panels/healing.lua b/modules/game_bot/panels/healing.lua index fd83993..fe4171d 100644 --- a/modules/game_bot/panels/healing.lua +++ b/modules/game_bot/panels/healing.lua @@ -1,346 +1,346 @@ -local context = G.botContext -local Panels = context.Panels - -Panels.Haste = function(parent) - context.macro(500, "Auto Haste", nil, function() - if not context.hasHaste() and context.storage.autoHasteText:len() > 0 then - if context.saySpell(context.storage.autoHasteText, 2500) then - context.delay(5000) - end - end - end, parent) - context.addTextEdit("autoHasteText", context.storage.autoHasteText or "utani hur", function(widget, text) - context.storage.autoHasteText = text - end, parent) -end - -Panels.ManaShield = function(parent) - local lastManaShield = 0 - context.macro(100, "Auto Mana Shield", nil, function() - if not context.hasManaShield() or context.now > lastManaShield + 90000 then - if context.saySpell("utamo vita", 200) then - lastManaShield = context.now - end - end - end, parent) -end - -Panels.AntiParalyze = function(parent) - context.macro(100, "Anti Paralyze", nil, function() - if context.isParalyzed() and context.storage.autoAntiParalyzeText:len() > 0 then - context.saySpell(context.storage.autoAntiParalyzeText, 750) - end - end, parent) - context.addTextEdit("autoAntiParalyzeText", context.storage.autoAntiParalyzeText or "utani hur", function(widget, text) - context.storage.autoAntiParalyzeText = text - end, parent) -end - - -Panels.Health = function(parent) - if not parent then - parent = context.panel - end - - local panelName = "autoHealthPanel" - local panelId = 1 - while parent:getChildById(panelName .. panelId) do - panelId = panelId + 1 - end - panelName = panelName .. panelId - - local ui = g_ui.createWidget("DualScrollPanel", parent) - ui:setId(panelName) - - if not context.storage[panelName] then - context.storage[panelName] = { - item = 266, - min = 20, - max = 80, - text = "exura" - } - end - - ui.title:setOn(context.storage[panelName].enabled) - ui.title.onClick = function(widget) - context.storage[panelName].enabled = not context.storage[panelName].enabled - widget:setOn(context.storage[panelName].enabled) - end - - ui.text.onTextChange = function(widget, text) - context.storage[panelName].text = text - end - ui.text:setText(context.storage[panelName].text or "exura") - - local updateText = function() - ui.title:setText("" .. context.storage[panelName].min .. "% <= hp <= " .. context.storage[panelName].max .. "%") - end - - ui.scroll1.onValueChange = function(scroll, value) - context.storage[panelName].min = value - updateText() - end - ui.scroll2.onValueChange = function(scroll, value) - context.storage[panelName].max = value - updateText() - end - - ui.scroll1:setValue(context.storage[panelName].min) - ui.scroll2:setValue(context.storage[panelName].max) - - context.macro(25, function() - if context.storage[panelName].enabled and context.storage[panelName].text:len() > 0 and context.storage[panelName].min <= context.hppercent() and context.hppercent() <= context.storage[panelName].max then - if context.saySpell(context.storage[panelName].text, 500) then - context.delay(200) - end - end - end) -end - -Panels.HealthItem = function(parent) - if not parent then - parent = context.panel - end - - local panelName = "autoHealthItemPanel" - local panelId = 1 - while parent:getChildById(panelName .. panelId) do - panelId = panelId + 1 - end - panelName = panelName .. panelId - - local ui = g_ui.createWidget("DualScrollItemPanel", parent) - ui:setId(panelName) - - if not context.storage[panelName] then - context.storage[panelName] = { - item = 266, - min = 0, - max = 60 - } - end - - ui.title:setOn(context.storage[panelName].enabled) - ui.title.onClick = function(widget) - context.storage[panelName].enabled = not context.storage[panelName].enabled - widget:setOn(context.storage[panelName].enabled) - end - - ui.item.onItemChange = function(widget) - context.storage[panelName].item = widget:getItemId() - end - ui.item:setItemId(context.storage[panelName].item) - - local updateText = function() - ui.title:setText("" .. (context.storage[panelName].min or "") .. "% <= hp <= " .. (context.storage[panelName].max or "") .. "%") - end - - ui.scroll1.onValueChange = function(scroll, value) - context.storage[panelName].min = value - updateText() - end - ui.scroll2.onValueChange = function(scroll, value) - context.storage[panelName].max = value - updateText() - end - - ui.scroll1:setValue(context.storage[panelName].min) - ui.scroll2:setValue(context.storage[panelName].max) - - context.macro(25, function() - if context.storage[panelName].enabled and context.storage[panelName].item >= 100 and context.storage[panelName].min <= context.hppercent() and context.hppercent() <= context.storage[panelName].max then - if context.useRune(context.storage[panelName].item, context.player, 500) then - context.delay(300) - end - end - end) -end - -Panels.Mana = function(parent) - if not parent then - parent = context.panel - end - - local panelName = "autoManaItemPanel" - local panelId = 1 - while parent:getChildById(panelName .. panelId) do - panelId = panelId + 1 - end - panelName = panelName .. panelId - - local ui = g_ui.createWidget("DualScrollItemPanel", parent) - ui:setId(panelName) - - if not context.storage[panelName] then - context.storage[panelName] = { - item = 268, - min = 0, - max = 60 - } - end - - ui.title:setOn(context.storage[panelName].enabled) - ui.title.onClick = function(widget) - context.storage[panelName].enabled = not context.storage[panelName].enabled - widget:setOn(context.storage[panelName].enabled) - end - - ui.item.onItemChange = function(widget) - context.storage[panelName].item = widget:getItemId() - end - ui.item:setItemId(context.storage[panelName].item) - - local updateText = function() - ui.title:setText("" .. (context.storage[panelName].min or "") .. "% <= mana <= " .. (context.storage[panelName].max or "") .. "%") - end - - ui.scroll1.onValueChange = function(scroll, value) - context.storage[panelName].min = value - updateText() - end - ui.scroll2.onValueChange = function(scroll, value) - context.storage[panelName].max = value - updateText() - end - - ui.scroll1:setValue(context.storage[panelName].min) - ui.scroll2:setValue(context.storage[panelName].max) - - context.macro(25, function() - if context.storage[panelName].enabled and context.storage[panelName].item >= 100 and context.storage[panelName].min <= context.manapercent() and context.manapercent() <= context.storage[panelName].max then - if context.useRune(context.storage[panelName].item, context.player, 500) then - context.delay(300) - end - end - end) -end -Panels.ManaItem = Panels.Mana - -Panels.Equip = function(parent) - if not parent then - parent = context.panel - end - - local panelName = "autoEquipItem" - local panelId = 1 - while parent:getChildById(panelName .. panelId) do - panelId = panelId + 1 - end - panelName = panelName .. panelId - - local ui = g_ui.createWidget("TwoItemsAndSlotPanel", parent) - ui:setId(panelName) - - if not context.storage[panelName] then - context.storage[panelName] = {} - if panelId == 1 then - context.storage[panelName].item1 = 3052 - context.storage[panelName].item2 = 3089 - context.storage[panelName].slot = 9 - end - end - - ui.title:setText("Auto equip") - ui.title:setOn(context.storage[panelName].enabled) - ui.title.onClick = function(widget) - context.storage[panelName].enabled = not context.storage[panelName].enabled - widget:setOn(context.storage[panelName].enabled) - end - - ui.item1:setItemId(context.storage[panelName].item1 or 0) - ui.item1.onItemChange = function(widget) - context.storage[panelName].item1 = widget:getItemId() - end - - ui.item2:setItemId(context.storage[panelName].item2 or 0) - ui.item2.onItemChange = function(widget) - context.storage[panelName].item2 = widget:getItemId() - end - - if not context.storage[panelName].slot then - context.storage[panelName].slot = 1 - end - ui.slot:setCurrentIndex(context.storage[panelName].slot) - ui.slot.onOptionChange = function(widget) - context.storage[panelName].slot = widget.currentIndex - end - - context.macro(250, function() - if context.storage[panelName].enabled and context.storage[panelName].slot > 0 then - local item1 = context.storage[panelName].item1 or 0 - local item2 = context.storage[panelName].item2 or 0 - if item1 < 100 and item2 < 100 then - return - end - local slotItem = context.getSlot(context.storage[panelName].slot) - if slotItem and (slotItem:getId() == item1 or slotItem:getId() == item2) then - return - end - local newItem = context.findItem(context.storage[panelName].item1) - if not newItem then - newItem = context.findItem(context.storage[panelName].item2) - if not newItem then - return - end - end - g_game.move(newItem, {x=65535, y=context.storage[panelName].slot, z=0}) - context.delay(1000) - end - end) -end -Panels.AutoEquip = Panels.Equip - -Panels.Eating = function(parent) - if not parent then - parent = context.panel - end - - local panelName = "autoEatingPanel" - local panelId = 1 - while parent:getChildById(panelName .. panelId) do - panelId = panelId + 1 - end - panelName = panelName .. panelId - - local ui = g_ui.createWidget("ItemsPanel", parent) - ui:setId(panelName) - - if not context.storage[panelName] then - context.storage[panelName] = {} - end - - ui.title:setText("Auto eating") - ui.title:setOn(context.storage[panelName].enabled) - ui.title.onClick = function(widget) - context.storage[panelName].enabled = not context.storage[panelName].enabled - widget:setOn(context.storage[panelName].enabled) - end - - if type(context.storage[panelName].items) ~= 'table' then - context.storage[panelName].items = {3725, 0, 0, 0, 0} - end - - for i=1,5 do - ui.items:getChildByIndex(i).onItemChange = function(widget) - context.storage[panelName].items[i] = widget:getItemId() - end - ui.items:getChildByIndex(i):setItemId(context.storage[panelName].items[i]) - end - - context.macro(15000, function() - if not context.storage[panelName].enabled then - return - end - local candidates = {} - for i, item in pairs(context.storage[panelName].items) do - if item >= 100 then - table.insert(candidates, item) - end - end - if #candidates == 0 then - return - end - context.use(candidates[math.random(1, #candidates)]) - end) -end - +local context = G.botContext +local Panels = context.Panels + +Panels.Haste = function(parent) + context.macro(500, "Auto Haste", nil, function() + if not context.hasHaste() and context.storage.autoHasteText:len() > 0 then + if context.saySpell(context.storage.autoHasteText, 2500) then + context.delay(5000) + end + end + end, parent) + context.addTextEdit("autoHasteText", context.storage.autoHasteText or "utani hur", function(widget, text) + context.storage.autoHasteText = text + end, parent) +end + +Panels.ManaShield = function(parent) + local lastManaShield = 0 + context.macro(100, "Auto Mana Shield", nil, function() + if not context.hasManaShield() or context.now > lastManaShield + 90000 then + if context.saySpell("utamo vita", 200) then + lastManaShield = context.now + end + end + end, parent) +end + +Panels.AntiParalyze = function(parent) + context.macro(100, "Anti Paralyze", nil, function() + if context.isParalyzed() and context.storage.autoAntiParalyzeText:len() > 0 then + context.saySpell(context.storage.autoAntiParalyzeText, 750) + end + end, parent) + context.addTextEdit("autoAntiParalyzeText", context.storage.autoAntiParalyzeText or "utani hur", function(widget, text) + context.storage.autoAntiParalyzeText = text + end, parent) +end + + +Panels.Health = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoHealthPanel" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("DualScrollPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = { + item = 266, + min = 20, + max = 80, + text = "exura" + } + end + + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + ui.text.onTextChange = function(widget, text) + context.storage[panelName].text = text + end + ui.text:setText(context.storage[panelName].text or "exura") + + local updateText = function() + ui.title:setText("" .. context.storage[panelName].min .. "% <= hp <= " .. context.storage[panelName].max .. "%") + end + + ui.scroll1.onValueChange = function(scroll, value) + context.storage[panelName].min = value + updateText() + end + ui.scroll2.onValueChange = function(scroll, value) + context.storage[panelName].max = value + updateText() + end + + ui.scroll1:setValue(context.storage[panelName].min) + ui.scroll2:setValue(context.storage[panelName].max) + + context.macro(25, function() + if context.storage[panelName].enabled and context.storage[panelName].text:len() > 0 and context.storage[panelName].min <= context.hppercent() and context.hppercent() <= context.storage[panelName].max then + if context.saySpell(context.storage[panelName].text, 500) then + context.delay(200) + end + end + end) +end + +Panels.HealthItem = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoHealthItemPanel" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("DualScrollItemPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = { + item = 266, + min = 0, + max = 60 + } + end + + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + ui.item.onItemChange = function(widget) + context.storage[panelName].item = widget:getItemId() + end + ui.item:setItemId(context.storage[panelName].item) + + local updateText = function() + ui.title:setText("" .. (context.storage[panelName].min or "") .. "% <= hp <= " .. (context.storage[panelName].max or "") .. "%") + end + + ui.scroll1.onValueChange = function(scroll, value) + context.storage[panelName].min = value + updateText() + end + ui.scroll2.onValueChange = function(scroll, value) + context.storage[panelName].max = value + updateText() + end + + ui.scroll1:setValue(context.storage[panelName].min) + ui.scroll2:setValue(context.storage[panelName].max) + + context.macro(25, function() + if context.storage[panelName].enabled and context.storage[panelName].item >= 100 and context.storage[panelName].min <= context.hppercent() and context.hppercent() <= context.storage[panelName].max then + if context.useRune(context.storage[panelName].item, context.player, 500) then + context.delay(300) + end + end + end) +end + +Panels.Mana = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoManaItemPanel" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("DualScrollItemPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = { + item = 268, + min = 0, + max = 60 + } + end + + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + ui.item.onItemChange = function(widget) + context.storage[panelName].item = widget:getItemId() + end + ui.item:setItemId(context.storage[panelName].item) + + local updateText = function() + ui.title:setText("" .. (context.storage[panelName].min or "") .. "% <= mana <= " .. (context.storage[panelName].max or "") .. "%") + end + + ui.scroll1.onValueChange = function(scroll, value) + context.storage[panelName].min = value + updateText() + end + ui.scroll2.onValueChange = function(scroll, value) + context.storage[panelName].max = value + updateText() + end + + ui.scroll1:setValue(context.storage[panelName].min) + ui.scroll2:setValue(context.storage[panelName].max) + + context.macro(25, function() + if context.storage[panelName].enabled and context.storage[panelName].item >= 100 and context.storage[panelName].min <= context.manapercent() and context.manapercent() <= context.storage[panelName].max then + if context.useRune(context.storage[panelName].item, context.player, 500) then + context.delay(300) + end + end + end) +end +Panels.ManaItem = Panels.Mana + +Panels.Equip = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoEquipItem" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("TwoItemsAndSlotPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = {} + if panelId == 1 then + context.storage[panelName].item1 = 3052 + context.storage[panelName].item2 = 3089 + context.storage[panelName].slot = 9 + end + end + + ui.title:setText("Auto equip") + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + ui.item1:setItemId(context.storage[panelName].item1 or 0) + ui.item1.onItemChange = function(widget) + context.storage[panelName].item1 = widget:getItemId() + end + + ui.item2:setItemId(context.storage[panelName].item2 or 0) + ui.item2.onItemChange = function(widget) + context.storage[panelName].item2 = widget:getItemId() + end + + if not context.storage[panelName].slot then + context.storage[panelName].slot = 1 + end + ui.slot:setCurrentIndex(context.storage[panelName].slot) + ui.slot.onOptionChange = function(widget) + context.storage[panelName].slot = widget.currentIndex + end + + context.macro(250, function() + if context.storage[panelName].enabled and context.storage[panelName].slot > 0 then + local item1 = context.storage[panelName].item1 or 0 + local item2 = context.storage[panelName].item2 or 0 + if item1 < 100 and item2 < 100 then + return + end + local slotItem = context.getSlot(context.storage[panelName].slot) + if slotItem and (slotItem:getId() == item1 or slotItem:getId() == item2) then + return + end + local newItem = context.findItem(context.storage[panelName].item1) + if not newItem then + newItem = context.findItem(context.storage[panelName].item2) + if not newItem then + return + end + end + g_game.move(newItem, {x=65535, y=context.storage[panelName].slot, z=0}) + context.delay(1000) + end + end) +end +Panels.AutoEquip = Panels.Equip + +Panels.Eating = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoEatingPanel" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("ItemsPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = {} + end + + ui.title:setText("Auto eating") + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + if type(context.storage[panelName].items) ~= 'table' then + context.storage[panelName].items = {3725, 0, 0, 0, 0} + end + + for i=1,5 do + ui.items:getChildByIndex(i).onItemChange = function(widget) + context.storage[panelName].items[i] = widget:getItemId() + end + ui.items:getChildByIndex(i):setItemId(context.storage[panelName].items[i]) + end + + context.macro(15000, function() + if not context.storage[panelName].enabled then + return + end + local candidates = {} + for i, item in pairs(context.storage[panelName].items) do + if item >= 100 then + table.insert(candidates, item) + end + end + if #candidates == 0 then + return + end + context.use(candidates[math.random(1, #candidates)]) + end) +end + diff --git a/modules/game_bot/panels/looting.lua b/modules/game_bot/panels/looting.lua index d6ecd05..cf2785a 100644 --- a/modules/game_bot/panels/looting.lua +++ b/modules/game_bot/panels/looting.lua @@ -1,431 +1,431 @@ -local context = G.botContext -local Panels = context.Panels - -Panels.Looting = function(parent) - local ui = context.setupUI([[ -Panel - id: looting - height: 180 - - BotLabel - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - text: Looting - - ComboBox - id: config - anchors.top: prev.bottom - anchors.left: parent.left - margin-top: 5 - text-offset: 3 0 - width: 130 - - Button - id: enableButton - anchors.top: prev.top - anchors.left: prev.right - anchors.right: parent.right - margin-left: 5 - - Button - margin-top: 1 - id: add - anchors.top: prev.bottom - anchors.left: parent.left - text: Add - width: 60 - height: 17 - - Button - id: edit - anchors.top: prev.top - anchors.horizontalCenter: parent.horizontalCenter - text: Edit - width: 60 - height: 17 - - Button - id: remove - anchors.top: prev.top - anchors.right: parent.right - text: Remove - width: 60 - height: 17 - - ScrollablePanel - id: items - anchors.top: prev.bottom - anchors.right: parent.right - anchors.left: parent.left - vertical-scrollbar: scrollBar - margin-right: 5 - margin-top: 2 - height: 70 - layout: - type: grid - cell-size: 34 34 - flow: true - - BotSmallScrollBar - id: scrollBar - anchors.top: prev.top - anchors.bottom: prev.bottom - anchors.right: parent.right - step: 10 - pixels-scroll: true - - BotLabel - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - margin-top: 4 - text: Loot Containers - - ItemsRow - id: containers - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - height: 33 - margin-top: 2 - -]], parent) - - local lootContainers = { ui.containers.item1, ui.containers.item2, ui.containers.item3, ui.containers.item4, ui.containers.item5 } - - if type(context.storage.looting) ~= "table" then - context.storage.looting = {} - end - if type(context.storage.looting.configs) ~= "table" then - context.storage.looting.configs = {} - end - - local getConfigName = function(config) - local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) - if matches[1] and matches[1][2] then - return matches[1][2]:trim() - end - return nil - end - - local items = {} - local itemsByKey = {} - local containers = {} - local commands = {} - local refreshConfig = nil -- declared later - - local createNewConfig = function(focusedWidget) - if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then - return - end - - local tmpItems = {} - local tmpContainers = {} - local focusIndex = 0 - - local newConfig = "" - for i, text in ipairs(commands) do - newConfig = newConfig .. text .. "\n" - end - for i=1,ui.items:getChildCount() do - local widget = ui.items:getChildByIndex(i) - if widget and widget:getItemId() >= 100 then - if tmpItems[widget:getItemId()] == nil then - tmpItems[widget:getItemId()] = 1 - newConfig = newConfig .. "\n" .. widget:getItemId() - end - end - if widget == focusedWidget then - focusIndex = i - end - end - for i, widget in ipairs(lootContainers) do - if widget:getItemId() >= 100 then - if tmpContainers[widget:getItemId()] == nil then - tmpContainers[widget:getItemId()] = 1 -- remove duplicates - newConfig = newConfig .. "\ncontainer:" .. widget:getItemId() - end - end - end - - context.storage.looting.configs[context.storage.looting.activeConfig] = newConfig - refreshConfig(focusIndex) - end - - local parseConfig = function(config) - items = {} - itemsByKey = {} - containers = {} - commands = {} - local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]]) - for i=1,#matches do - local command = matches[i][2] - local validation = (matches[i][3] == ":") - local text = matches[i][4] - local commandAsNumber = tonumber(command) - local textAsNumber = tonumber(text) - if commandAsNumber and commandAsNumber >= 100 then - table.insert(items, commandAsNumber) - itemsByKey[commandAsNumber] = 1 - elseif command == "container" and validation and textAsNumber and textAsNumber >= 100 then - containers[textAsNumber] = 1 - elseif validation then - table.insert(commands, command .. ":" .. text) - end - end - - local itemsToShow = #items + 2 - if itemsToShow % 5 ~= 0 then - itemsToShow = itemsToShow + 5 - itemsToShow % 5 - end - if itemsToShow < 10 then - itemsToShow = 10 - end - - for i=1,itemsToShow do - local widget = g_ui.createWidget("BotItem", ui.items) - local itemId = 0 - if i <= #items then - itemId = items[i] - end - widget:setItemId(itemId) - widget.onItemChange = createNewConfig - end - - for i, widget in ipairs(lootContainers) do - widget:setItemId(0) - end - local containerIndex = 1 - for containerId, i in pairs(containers) do - if lootContainers[containerIndex] then - lootContainers[containerIndex]:setItemId(containerId) - end - containerIndex = containerIndex + 1 - end - for i, widget in ipairs(lootContainers) do - widget.onItemChange = createNewConfig - end - end - - local ignoreOnOptionChange = true - refreshConfig = function(focusIndex) - ignoreOnOptionChange = true - if context.storage.looting.enabled then - ui.enableButton:setText("On") - ui.enableButton:setColor('#00AA00FF') - else - ui.enableButton:setText("Off") - ui.enableButton:setColor('#FF0000FF') - end - - ui.config:clear() - for i, config in ipairs(context.storage.looting.configs) do - local name = getConfigName(config) - if not name then - name = "Unnamed config" - end - ui.config:addOption(name) - end - - if (not context.storage.looting.activeConfig or context.storage.looting.activeConfig == 0) and #context.storage.looting.configs > 0 then - context.storage.looting.activeConfig = 1 - end - - ui.items:destroyChildren() - for i, widget in ipairs(lootContainers) do - widget.onItemChange = nil - widget:setItemId(0) - widget:setItemCount(0) - end - - if context.storage.looting.activeConfig and context.storage.looting.configs[context.storage.looting.activeConfig] then - ui.config:setCurrentIndex(context.storage.looting.activeConfig) - parseConfig(context.storage.looting.configs[context.storage.looting.activeConfig]) - end - - context.saveConfig() - if focusIndex and focusIndex > 0 and ui.items:getChildByIndex(focusIndex) then - ui.items:focusChild(ui.items:getChildByIndex(focusIndex)) - end - - ignoreOnOptionChange = false - end - - ui.config.onOptionChange = function(widget) - if not ignoreOnOptionChange then - context.storage.looting.activeConfig = widget.currentIndex - refreshConfig() - end - end - ui.enableButton.onClick = function() - if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then - return - end - context.storage.looting.enabled = not context.storage.looting.enabled - refreshConfig() - end - ui.add.onClick = function() - modules.client_textedit.multilineEditor("Looting editor", "name:Config name", function(newText) - table.insert(context.storage.looting.configs, newText) - context.storage.looting.activeConfig = #context.storage.looting.configs - refreshConfig() - end) - end - ui.edit.onClick = function() - if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then - return - end - modules.client_textedit.multilineEditor("Looting editor", context.storage.looting.configs[context.storage.looting.activeConfig], function(newText) - context.storage.looting.configs[context.storage.looting.activeConfig] = newText - refreshConfig() - end) - end - ui.remove.onClick = function() - if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then - return - end - local questionWindow = nil - local closeWindow = function() - questionWindow:destroy() - end - local removeConfig = function() - closeWindow() - if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then - return - end - context.storage.looting.enabled = false - table.remove(context.storage.looting.configs, context.storage.looting.activeConfig) - context.storage.looting.activeConfig = 0 - refreshConfig() - end - questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current looting config?'), { - { text=tr('Yes'), callback=removeConfig }, - { text=tr('No'), callback=closeWindow }, - anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) - end - refreshConfig() - - context.onContainerOpen(function(container, prevContainer) - if context.storage.attacking.enabled then - return - end - if prevContainer then - container.autoLooting = prevContainer.autoLooting - else - container.autoLooting = true - end - end) - - context.macro(200, function() - if not context.storage.looting.enabled then - return - end - local candidates = {} - local lootContainersCandidates = {} - for containerId, container in pairs(g_game.getContainers()) do - local containerItem = container:getContainerItem() - if container.autoLooting and container:getItemsCount() > 0 and (not containerItem or containers[containerItem:getId()] == nil) then - table.insert(candidates, container) - elseif containerItem and containers[containerItem:getId()] ~= nil then - table.insert(lootContainersCandidates, container) - end - end - if #lootContainersCandidates == 0 then - for slot = InventorySlotFirst, InventorySlotLast do - local item = context.getInventoryItem(slot) - if item and item:isContainer() and containers[item:getId()] ~= nil then - table.insert(lootContainersCandidates, item) - end - end - if #lootContainersCandidates > 0 then - -- try to open inventory backpack - local target = lootContainersCandidates[math.random(1,#lootContainersCandidates)] - g_game.open(target, nil) - context.delay(200) - end - return - end - - if #candidates == 0 then - return - end - - local container = candidates[math.random(1,#candidates)] - local nextContainers = {} - local foundItem = nil - for i, item in ipairs(container:getItems()) do - if item:isContainer() then - table.insert(nextContainers, item) - elseif itemsByKey[item:getId()] ~= nil then - foundItem = item - break - end - end - - -- found item to loot - if foundItem then - -- find backpack for it, first backpack with same items - for i, container in ipairs(lootContainersCandidates) do - if container:getItemsCount() < container:getCapacity() or foundItem:isStackable() then -- has space - for j, item in ipairs(container:getItems()) do - if item:getId() == foundItem:getId() then - if foundItem:isStackable() then - if item:getCount() ~= 100 then - g_game.move(foundItem, container:getSlotPosition(j - 1), foundItem:getCount()) - return - end - else - g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount()) - return - end - end - end - end - end - -- now any backpack with empty slot - for i, container in ipairs(lootContainersCandidates) do - if container:getItemsCount() < container:getCapacity() then -- has space - g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount()) - return - end - end - - -- can't find backpack, try to open new - for i, container in ipairs(lootContainersCandidates) do - local candidates = {} - for j, item in ipairs(container:getItems()) do - if item:isContainer() and containers[item:getId()] ~= nil then - table.insert(candidates, item) - end - end - if #candidates > 0 then - g_game.open(candidates[math.random(1,#candidates)], container) - return - end - -- full, close it - g_game.close(container) - return - end - return - end - - -- open remaining containers - if #nextContainers == 0 then - return - end - local delay = 1 - for i=2,#nextContainers do - -- if more than 1 container, open them in new window - context.schedule(delay, function() - g_game.open(nextContainers[i], nil) - end) - delay = delay + 250 - end - context.schedule(delay, function() - g_game.open(nextContainers[1], container) - end) - context.delay(150 + delay) - end) -end - +local context = G.botContext +local Panels = context.Panels + +Panels.Looting = function(parent) + local ui = context.setupUI([[ +Panel + id: looting + height: 180 + + BotLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: Looting + + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 5 + text-offset: 3 0 + width: 130 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Button + margin-top: 1 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 17 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 17 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 17 + + ScrollablePanel + id: items + anchors.top: prev.bottom + anchors.right: parent.right + anchors.left: parent.left + vertical-scrollbar: scrollBar + margin-right: 5 + margin-top: 2 + height: 70 + layout: + type: grid + cell-size: 34 34 + flow: true + + BotSmallScrollBar + id: scrollBar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + step: 10 + pixels-scroll: true + + BotLabel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 4 + text: Loot Containers + + ItemsRow + id: containers + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 33 + margin-top: 2 + +]], parent) + + local lootContainers = { ui.containers.item1, ui.containers.item2, ui.containers.item3, ui.containers.item4, ui.containers.item5 } + + if type(context.storage.looting) ~= "table" then + context.storage.looting = {} + end + if type(context.storage.looting.configs) ~= "table" then + context.storage.looting.configs = {} + end + + local getConfigName = function(config) + local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) + if matches[1] and matches[1][2] then + return matches[1][2]:trim() + end + return nil + end + + local items = {} + local itemsByKey = {} + local containers = {} + local commands = {} + local refreshConfig = nil -- declared later + + local createNewConfig = function(focusedWidget) + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + + local tmpItems = {} + local tmpContainers = {} + local focusIndex = 0 + + local newConfig = "" + for i, text in ipairs(commands) do + newConfig = newConfig .. text .. "\n" + end + for i=1,ui.items:getChildCount() do + local widget = ui.items:getChildByIndex(i) + if widget and widget:getItemId() >= 100 then + if tmpItems[widget:getItemId()] == nil then + tmpItems[widget:getItemId()] = 1 + newConfig = newConfig .. "\n" .. widget:getItemId() + end + end + if widget == focusedWidget then + focusIndex = i + end + end + for i, widget in ipairs(lootContainers) do + if widget:getItemId() >= 100 then + if tmpContainers[widget:getItemId()] == nil then + tmpContainers[widget:getItemId()] = 1 -- remove duplicates + newConfig = newConfig .. "\ncontainer:" .. widget:getItemId() + end + end + end + + context.storage.looting.configs[context.storage.looting.activeConfig] = newConfig + refreshConfig(focusIndex) + end + + local parseConfig = function(config) + items = {} + itemsByKey = {} + containers = {} + commands = {} + local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]]) + for i=1,#matches do + local command = matches[i][2] + local validation = (matches[i][3] == ":") + local text = matches[i][4] + local commandAsNumber = tonumber(command) + local textAsNumber = tonumber(text) + if commandAsNumber and commandAsNumber >= 100 then + table.insert(items, commandAsNumber) + itemsByKey[commandAsNumber] = 1 + elseif command == "container" and validation and textAsNumber and textAsNumber >= 100 then + containers[textAsNumber] = 1 + elseif validation then + table.insert(commands, command .. ":" .. text) + end + end + + local itemsToShow = #items + 2 + if itemsToShow % 5 ~= 0 then + itemsToShow = itemsToShow + 5 - itemsToShow % 5 + end + if itemsToShow < 10 then + itemsToShow = 10 + end + + for i=1,itemsToShow do + local widget = g_ui.createWidget("BotItem", ui.items) + local itemId = 0 + if i <= #items then + itemId = items[i] + end + widget:setItemId(itemId) + widget.onItemChange = createNewConfig + end + + for i, widget in ipairs(lootContainers) do + widget:setItemId(0) + end + local containerIndex = 1 + for containerId, i in pairs(containers) do + if lootContainers[containerIndex] then + lootContainers[containerIndex]:setItemId(containerId) + end + containerIndex = containerIndex + 1 + end + for i, widget in ipairs(lootContainers) do + widget.onItemChange = createNewConfig + end + end + + local ignoreOnOptionChange = true + refreshConfig = function(focusIndex) + ignoreOnOptionChange = true + if context.storage.looting.enabled then + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + end + + ui.config:clear() + for i, config in ipairs(context.storage.looting.configs) do + local name = getConfigName(config) + if not name then + name = "Unnamed config" + end + ui.config:addOption(name) + end + + if (not context.storage.looting.activeConfig or context.storage.looting.activeConfig == 0) and #context.storage.looting.configs > 0 then + context.storage.looting.activeConfig = 1 + end + + ui.items:destroyChildren() + for i, widget in ipairs(lootContainers) do + widget.onItemChange = nil + widget:setItemId(0) + widget:setItemCount(0) + end + + if context.storage.looting.activeConfig and context.storage.looting.configs[context.storage.looting.activeConfig] then + ui.config:setCurrentIndex(context.storage.looting.activeConfig) + parseConfig(context.storage.looting.configs[context.storage.looting.activeConfig]) + end + + context.saveConfig() + if focusIndex and focusIndex > 0 and ui.items:getChildByIndex(focusIndex) then + ui.items:focusChild(ui.items:getChildByIndex(focusIndex)) + end + + ignoreOnOptionChange = false + end + + ui.config.onOptionChange = function(widget) + if not ignoreOnOptionChange then + context.storage.looting.activeConfig = widget.currentIndex + refreshConfig() + end + end + ui.enableButton.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + context.storage.looting.enabled = not context.storage.looting.enabled + refreshConfig() + end + ui.add.onClick = function() + modules.client_textedit.multilineEditor("Looting editor", "name:Config name", function(newText) + table.insert(context.storage.looting.configs, newText) + context.storage.looting.activeConfig = #context.storage.looting.configs + refreshConfig() + end) + end + ui.edit.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + modules.client_textedit.multilineEditor("Looting editor", context.storage.looting.configs[context.storage.looting.activeConfig], function(newText) + context.storage.looting.configs[context.storage.looting.activeConfig] = newText + refreshConfig() + end) + end + ui.remove.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + local questionWindow = nil + local closeWindow = function() + questionWindow:destroy() + end + local removeConfig = function() + closeWindow() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + context.storage.looting.enabled = false + table.remove(context.storage.looting.configs, context.storage.looting.activeConfig) + context.storage.looting.activeConfig = 0 + refreshConfig() + end + questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current looting config?'), { + { text=tr('Yes'), callback=removeConfig }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) + end + refreshConfig() + + context.onContainerOpen(function(container, prevContainer) + if context.storage.attacking.enabled then + return + end + if prevContainer then + container.autoLooting = prevContainer.autoLooting + else + container.autoLooting = true + end + end) + + context.macro(200, function() + if not context.storage.looting.enabled then + return + end + local candidates = {} + local lootContainersCandidates = {} + for containerId, container in pairs(g_game.getContainers()) do + local containerItem = container:getContainerItem() + if container.autoLooting and container:getItemsCount() > 0 and (not containerItem or containers[containerItem:getId()] == nil) then + table.insert(candidates, container) + elseif containerItem and containers[containerItem:getId()] ~= nil then + table.insert(lootContainersCandidates, container) + end + end + if #lootContainersCandidates == 0 then + for slot = InventorySlotFirst, InventorySlotLast do + local item = context.getInventoryItem(slot) + if item and item:isContainer() and containers[item:getId()] ~= nil then + table.insert(lootContainersCandidates, item) + end + end + if #lootContainersCandidates > 0 then + -- try to open inventory backpack + local target = lootContainersCandidates[math.random(1,#lootContainersCandidates)] + g_game.open(target, nil) + context.delay(200) + end + return + end + + if #candidates == 0 then + return + end + + local container = candidates[math.random(1,#candidates)] + local nextContainers = {} + local foundItem = nil + for i, item in ipairs(container:getItems()) do + if item:isContainer() then + table.insert(nextContainers, item) + elseif itemsByKey[item:getId()] ~= nil then + foundItem = item + break + end + end + + -- found item to loot + if foundItem then + -- find backpack for it, first backpack with same items + for i, container in ipairs(lootContainersCandidates) do + if container:getItemsCount() < container:getCapacity() or foundItem:isStackable() then -- has space + for j, item in ipairs(container:getItems()) do + if item:getId() == foundItem:getId() then + if foundItem:isStackable() then + if item:getCount() ~= 100 then + g_game.move(foundItem, container:getSlotPosition(j - 1), foundItem:getCount()) + return + end + else + g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount()) + return + end + end + end + end + end + -- now any backpack with empty slot + for i, container in ipairs(lootContainersCandidates) do + if container:getItemsCount() < container:getCapacity() then -- has space + g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount()) + return + end + end + + -- can't find backpack, try to open new + for i, container in ipairs(lootContainersCandidates) do + local candidates = {} + for j, item in ipairs(container:getItems()) do + if item:isContainer() and containers[item:getId()] ~= nil then + table.insert(candidates, item) + end + end + if #candidates > 0 then + g_game.open(candidates[math.random(1,#candidates)], container) + return + end + -- full, close it + g_game.close(container) + return + end + return + end + + -- open remaining containers + if #nextContainers == 0 then + return + end + local delay = 1 + for i=2,#nextContainers do + -- if more than 1 container, open them in new window + context.schedule(delay, function() + g_game.open(nextContainers[i], nil) + end) + delay = delay + 250 + end + context.schedule(delay, function() + g_game.open(nextContainers[1], container) + end) + context.delay(150 + delay) + end) +end + diff --git a/modules/game_bot/panels/tools.lua b/modules/game_bot/panels/tools.lua index 3425a12..1094b6e 100644 --- a/modules/game_bot/panels/tools.lua +++ b/modules/game_bot/panels/tools.lua @@ -1,36 +1,36 @@ -local context = G.botContext -local Panels = context.Panels - -Panels.TradeMessage = function(parent) - context.macro(60000, "Send message on trade", nil, function() - local trade = context.getChannelId("advertising") - if not trade then - trade = context.getChannelId("trade") - end - if context.storage.autoTradeMessage:len() > 0 and trade then - context.sayChannel(trade, context.storage.autoTradeMessage) - end - end, parent) - context.addTextEdit("autoTradeMessage", context.storage.autoTradeMessage or "I'm using OTClientV8 - https://github.com/OTCv8/otclientv8", function(widget, text) - context.storage.autoTradeMessage = text - end, parent) -end - -Panels.AutoStackItems = function(parent) - context.macro(500, "Auto stacking items", nil, function() - local containers = context.getContainers() - for i, container in pairs(containers) do - local toStack = {} - for j, item in ipairs(container:getItems()) do - if item:isStackable() and item:getCount() ~= 100 then - local otherItem = toStack[item:getId()] - if otherItem then - g_game.move(item, otherItem, item:getCount()) - return - end - toStack[item:getId()] = container:getSlotPosition(j - 1) - end - end - end - end, parent) +local context = G.botContext +local Panels = context.Panels + +Panels.TradeMessage = function(parent) + context.macro(60000, "Send message on trade", nil, function() + local trade = context.getChannelId("advertising") + if not trade then + trade = context.getChannelId("trade") + end + if context.storage.autoTradeMessage:len() > 0 and trade then + context.sayChannel(trade, context.storage.autoTradeMessage) + end + end, parent) + context.addTextEdit("autoTradeMessage", context.storage.autoTradeMessage or "I'm using OTClientV8 - https://github.com/OTCv8/otclientv8", function(widget, text) + context.storage.autoTradeMessage = text + end, parent) +end + +Panels.AutoStackItems = function(parent) + context.macro(500, "Auto stacking items", nil, function() + local containers = context.getContainers() + for i, container in pairs(containers) do + local toStack = {} + for j, item in ipairs(container:getItems()) do + if item:isStackable() and item:getCount() ~= 100 then + local otherItem = toStack[item:getId()] + if otherItem then + g_game.move(item, otherItem, item:getCount()) + return + end + toStack[item:getId()] = container:getSlotPosition(j - 1) + end + end + end + end, parent) end \ No newline at end of file diff --git a/modules/game_bot/panels/war.lua b/modules/game_bot/panels/war.lua index ed9a41b..44cf767 100644 --- a/modules/game_bot/panels/war.lua +++ b/modules/game_bot/panels/war.lua @@ -1,127 +1,127 @@ -local context = G.botContext -local Panels = context.Panels - -Panels.AttackLeaderTarget = function(parent) - local toAttack = nil - context.onMissle(function(missle) - if not context.storage.attackLeader or context.storage.attackLeader:len() == 0 then - return - end - local src = missle:getSource() - if src.z ~= context.posz() then - return - end - local from = g_map.getTile(src) - local to = g_map.getTile(missle:getDestination()) - if not from or not to then - return - end - local fromCreatures = from:getCreatures() - local toCreatures = to:getCreatures() - if #fromCreatures ~= 1 or #toCreatures ~= 1 then - return - end - local c1 = fromCreatures[1] - if c1:getName():lower() == context.storage.attackLeader:lower() then - toAttack = toCreatures[1] - end - end) - context.macro(50, "Attack leader's target", nil, function() - if toAttack and context.storage.attackLeader:len() > 0 and toAttack ~= g_game.getAttackingCreature() then - g_game.attack(toAttack) - toAttack = nil - end - end, parent) - context.addTextEdit("attackLeader", context.storage.attackLeader or "player name", function(widget, text) - context.storage.attackLeader = text - end, parent) -end - - -Panels.LimitFloor = function(parent) - context.onPlayerPositionChange(function(pos) - if context.storage.limitFloor then - local gameMapPanel = modules.game_interface.getMapPanel() - if gameMapPanel then - gameMapPanel:lockVisibleFloor(pos.z) - end - end - end) - - local switch = context.addSwitch("limitFloor", "Don't show higher floors", function(widget) - widget:setOn(not widget:isOn()) - context.storage.limitFloor = widget:isOn() - local gameMapPanel = modules.game_interface.getMapPanel() - if gameMapPanel then - if context.storage.limitFloor then - gameMapPanel:lockVisibleFloor(context.posz()) - else - gameMapPanel:unlockVisibleFloor() - end - end - end, parent) - switch:setOn(context.storage.limitFloor) -end - -Panels.AntiPush = function(parent) - if not parent then - parent = context.panel - end - - local panelName = "antiPushPanel" - local ui = g_ui.createWidget("ItemsPanel", parent) - ui:setId(panelName) - - if not context.storage[panelName] then - context.storage[panelName] = {} - end - - ui.title:setText("Anti push") - ui.title:setOn(context.storage[panelName].enabled) - ui.title.onClick = function(widget) - context.storage[panelName].enabled = not context.storage[panelName].enabled - widget:setOn(context.storage[panelName].enabled) - end - - if type(context.storage[panelName].items) ~= 'table' then - context.storage[panelName].items = {3031, 3035, 0, 0, 0} - end - - for i=1,5 do - ui.items:getChildByIndex(i).onItemChange = function(widget) - context.storage[panelName].items[i] = widget:getItemId() - end - ui.items:getChildByIndex(i):setItemId(context.storage[panelName].items[i]) - end - - context.macro(100, function() - if not context.storage[panelName].enabled then - return - end - local tile = g_map.getTile(context.player:getPosition()) - if not tile then - return - end - local topItem = tile:getTopUseThing() - if topItem and topItem:isStackable() then - topItem = topItem:getId() - else - topItem = 0 - end - local candidates = {} - for i, item in pairs(context.storage[panelName].items) do - if item >= 100 and item ~= topItem and context.findItem(item) then - table.insert(candidates, item) - end - end - if #candidates == 0 then - return - end - if type(context.storage[panelName].lastItem) ~= 'number' or context.storage[panelName].lastItem > #candidates then - context.storage[panelName].lastItem = 1 - end - local item = context.findItem(candidates[context.storage[panelName].lastItem]) - g_game.move(item, context.player:getPosition(), 1) - context.storage[panelName].lastItem = context.storage[panelName].lastItem + 1 - end) -end +local context = G.botContext +local Panels = context.Panels + +Panels.AttackLeaderTarget = function(parent) + local toAttack = nil + context.onMissle(function(missle) + if not context.storage.attackLeader or context.storage.attackLeader:len() == 0 then + return + end + local src = missle:getSource() + if src.z ~= context.posz() then + return + end + local from = g_map.getTile(src) + local to = g_map.getTile(missle:getDestination()) + if not from or not to then + return + end + local fromCreatures = from:getCreatures() + local toCreatures = to:getCreatures() + if #fromCreatures ~= 1 or #toCreatures ~= 1 then + return + end + local c1 = fromCreatures[1] + if c1:getName():lower() == context.storage.attackLeader:lower() then + toAttack = toCreatures[1] + end + end) + context.macro(50, "Attack leader's target", nil, function() + if toAttack and context.storage.attackLeader:len() > 0 and toAttack ~= g_game.getAttackingCreature() then + g_game.attack(toAttack) + toAttack = nil + end + end, parent) + context.addTextEdit("attackLeader", context.storage.attackLeader or "player name", function(widget, text) + context.storage.attackLeader = text + end, parent) +end + + +Panels.LimitFloor = function(parent) + context.onPlayerPositionChange(function(pos) + if context.storage.limitFloor then + local gameMapPanel = modules.game_interface.getMapPanel() + if gameMapPanel then + gameMapPanel:lockVisibleFloor(pos.z) + end + end + end) + + local switch = context.addSwitch("limitFloor", "Don't show higher floors", function(widget) + widget:setOn(not widget:isOn()) + context.storage.limitFloor = widget:isOn() + local gameMapPanel = modules.game_interface.getMapPanel() + if gameMapPanel then + if context.storage.limitFloor then + gameMapPanel:lockVisibleFloor(context.posz()) + else + gameMapPanel:unlockVisibleFloor() + end + end + end, parent) + switch:setOn(context.storage.limitFloor) +end + +Panels.AntiPush = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "antiPushPanel" + local ui = g_ui.createWidget("ItemsPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = {} + end + + ui.title:setText("Anti push") + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + if type(context.storage[panelName].items) ~= 'table' then + context.storage[panelName].items = {3031, 3035, 0, 0, 0} + end + + for i=1,5 do + ui.items:getChildByIndex(i).onItemChange = function(widget) + context.storage[panelName].items[i] = widget:getItemId() + end + ui.items:getChildByIndex(i):setItemId(context.storage[panelName].items[i]) + end + + context.macro(100, function() + if not context.storage[panelName].enabled then + return + end + local tile = g_map.getTile(context.player:getPosition()) + if not tile then + return + end + local topItem = tile:getTopUseThing() + if topItem and topItem:isStackable() then + topItem = topItem:getId() + else + topItem = 0 + end + local candidates = {} + for i, item in pairs(context.storage[panelName].items) do + if item >= 100 and item ~= topItem and context.findItem(item) then + table.insert(candidates, item) + end + end + if #candidates == 0 then + return + end + if type(context.storage[panelName].lastItem) ~= 'number' or context.storage[panelName].lastItem > #candidates then + context.storage[panelName].lastItem = 1 + end + local item = context.findItem(candidates[context.storage[panelName].lastItem]) + g_game.move(item, context.player:getPosition(), 1) + context.storage[panelName].lastItem = context.storage[panelName].lastItem + 1 + end) +end diff --git a/modules/game_bot/panels/waypoints.lua b/modules/game_bot/panels/waypoints.lua index bf3190d..55f508f 100644 --- a/modules/game_bot/panels/waypoints.lua +++ b/modules/game_bot/panels/waypoints.lua @@ -1,754 +1,754 @@ -local context = G.botContext -local Panels = context.Panels - -Panels.Waypoints = function(parent) - local ui = context.setupUI([[ -Panel - id: waypoints - height: 206 - - BotLabel - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - text: Waypoints - - ComboBox - id: config - anchors.top: prev.bottom - anchors.left: parent.left - margin-top: 5 - text-offset: 3 0 - width: 130 - - Button - id: enableButton - anchors.top: prev.top - anchors.left: prev.right - anchors.right: parent.right - margin-left: 5 - - Button - margin-top: 1 - id: add - anchors.top: prev.bottom - anchors.left: parent.left - text: Add - width: 60 - height: 17 - - Button - id: edit - anchors.top: prev.top - anchors.horizontalCenter: parent.horizontalCenter - text: Edit - width: 60 - height: 17 - - Button - id: remove - anchors.top: prev.top - anchors.right: parent.right - text: Remove - width: 60 - height: 17 - - TextList - id: list - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - vertical-scrollbar: listScrollbar - margin-right: 15 - margin-top: 2 - height: 60 - focusable: false - auto-focus: first - - VerticalScrollBar - id: listScrollbar - anchors.top: prev.top - anchors.bottom: prev.bottom - anchors.right: parent.right - pixels-scroll: true - step: 5 - - Label - id: pos - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - text-align: center - margin-top: 2 - - Button - id: wGoto - anchors.top: prev.bottom - anchors.left: parent.left - text: Goto - width: 61 - margin-top: 1 - height: 17 - - Button - id: wUse - anchors.top: prev.top - anchors.left: prev.right - text: Use - width: 61 - height: 17 - - Button - id: wUseWith - anchors.top: prev.top - anchors.left: prev.right - text: UseWith - width: 61 - height: 17 - - Button - id: wWait - anchors.top: prev.bottom - anchors.left: parent.left - text: Wait - width: 61 - margin-top: 1 - height: 17 - - Button - id: wSay - anchors.top: prev.top - anchors.left: prev.right - text: Say - width: 61 - height: 17 - - Button - id: wNpc - anchors.top: prev.top - anchors.left: prev.right - text: Say NPC - width: 61 - height: 17 - - Button - id: wLabel - anchors.top: prev.bottom - anchors.left: parent.left - text: Label - width: 61 - margin-top: 1 - height: 17 - - Button - id: wFollow - anchors.top: prev.top - anchors.left: prev.right - text: Follow - width: 61 - height: 17 - - Button - id: wFunction - anchors.top: prev.top - anchors.left: prev.right - text: Function - width: 61 - height: 17 - - BotSwitch - id: recording - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - text: Auto Recording - height: 17 - -]], parent) - - if type(context.storage.cavebot) ~= "table" then - context.storage.cavebot = {} - end - if type(context.storage.cavebot.configs) ~= "table" then - context.storage.cavebot.configs = {} - end - - local getConfigName = function(config) - local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) - if matches[1] and matches[1][2] then - return matches[1][2]:trim() - end - return nil - end - - local isValidCommand = function(command) - if command == "goto" then - return true - elseif command == "use" then - return true - elseif command == "usewith" then - return true - elseif command == "wait" then - return true - elseif command == "say" then - return true - elseif command == "npc" then - return true - elseif command == "follow" then - return true - elseif command == "label" then - return true - elseif command == "gotolabel" then - return true - elseif command == "comment" then - return true - elseif command == "function" then - return true - end - return false - end - - local commands = {} - local waitTo = 0 - local autoRecording = false - - local parseConfig = function(config) - commands = {} - local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]]) - for i=1,#matches do - local command = matches[i][2] - local validation = (matches[i][3] == ":") - if not validation or isValidCommand(command) then - local text = matches[i][4] - if validation then - table.insert(commands, {command=command:lower(), text=text}) - elseif #commands > 0 then - commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1] - end - end - end - - for i=1,#commands do - local label = g_ui.createWidget("CaveBotLabel", ui.list) - label:setText(commands[i].command .. ":" .. commands[i].text) - if commands[i].command == "goto" then - label:setColor("green") - elseif commands[i].command == "label" then - label:setColor("yellow") - elseif commands[i].command == "comment" then - label:setText(commands[i].text) - label:setColor("white") - elseif commands[i].command == "use" or commands[i].command == "usewith" then - label:setColor("orange") - elseif commands[i].command == "gotolabel" then - label:setColor("red") - end - end - end - - local ignoreOnOptionChange = true - local refreshConfig = function(scrollDown) - ignoreOnOptionChange = true - if context.storage.cavebot.enabled then - autoRecording = false - ui.recording:setOn(false) - ui.enableButton:setText("On") - ui.enableButton:setColor('#00AA00FF') - else - ui.enableButton:setText("Off") - ui.enableButton:setColor('#FF0000FF') - ui.recording:setOn(autoRecording) - end - - ui.config:clear() - for i, config in ipairs(context.storage.cavebot.configs) do - local name = getConfigName(config) - if not name then - name = "Unnamed config" - end - ui.config:addOption(name) - end - - if (not context.storage.cavebot.activeConfig or context.storage.cavebot.activeConfig == 0) and #context.storage.cavebot.configs > 0 then - context.storage.cavebot.activeConfig = 1 - end - - ui.list:destroyChildren() - - if context.storage.cavebot.activeConfig and context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - ui.config:setCurrentIndex(context.storage.cavebot.activeConfig) - parseConfig(context.storage.cavebot.configs[context.storage.cavebot.activeConfig]) - end - - context.saveConfig() - if scrollDown and ui.list:getLastChild() then - ui.list:focusChild(ui.list:getLastChild()) - end - - waitTo = 0 - ignoreOnOptionChange = false - end - - - ui.config.onOptionChange = function(widget) - if not ignoreOnOptionChange then - context.storage.cavebot.activeConfig = widget.currentIndex - refreshConfig() - end - end - ui.enableButton.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - context.storage.cavebot.enabled = not context.storage.cavebot.enabled - if autoRecording then - refreshConfig() - elseif context.storage.cavebot.enabled then - ui.enableButton:setText("On") - ui.enableButton:setColor('#00AA00FF') - else - ui.enableButton:setText("Off") - ui.enableButton:setColor('#FF0000FF') - end - end - ui.add.onClick = function() - modules.client_textedit.multilineEditor("Waypoints editor", "name:Config name\nlabel:start\n", function(newText) - table.insert(context.storage.cavebot.configs, newText) - context.storage.cavebot.activeConfig = #context.storage.cavebot.configs - refreshConfig() - end) - end - ui.edit.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - modules.client_textedit.multilineEditor("Waypoints editor", context.storage.cavebot.configs[context.storage.cavebot.activeConfig], function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = newText - refreshConfig() - end) - end - ui.remove.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - local questionWindow = nil - local closeWindow = function() - questionWindow:destroy() - end - local removeConfig = function() - closeWindow() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - context.storage.cavebot.enabled = false - table.remove(context.storage.cavebot.configs, context.storage.cavebot.activeConfig) - context.storage.cavebot.activeConfig = 0 - refreshConfig() - end - questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current waypoints config?'), { - { text=tr('Yes'), callback=removeConfig }, - { text=tr('No'), callback=closeWindow }, - anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) - end - - -- waypoint editor - -- auto recording - local stepsSincleLastPos = 0 - - context.onPlayerPositionChange(function(newPos, oldPos) - ui.pos:setText("Position: " .. newPos.x .. ", " .. newPos.y .. ", " .. newPos.z) - if not autoRecording then - return - end - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - local newText = "" - if newPos.z ~= oldPos.z then - newText = "goto:" .. oldPos.x .. "," .. oldPos.y .. "," .. oldPos.z - newText = newText .. "\ngoto:" .. newPos.x .. "," .. newPos.y .. "," .. newPos.z - stepsSincleLastPos = 0 - else - stepsSincleLastPos = stepsSincleLastPos + 1 - if stepsSincleLastPos > 10 then - newText = "goto:" .. oldPos.x .. "," .. oldPos.y .. "," .. oldPos.z - stepsSincleLastPos = 0 - end - end - - if newText:len() > 0 then - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText - refreshConfig(true) - end - end) - - context.onUse(function(pos, itemId, stackPos, subType) - if not autoRecording then - return - end - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - if pos.x == 0xFFFF then - return - end - stepsSincleLastPos = 0 - local playerPos = context.player:getPosition() - newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nuse:" .. pos.x .. "," .. pos.y .. "," .. pos.z - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText - refreshConfig(true) - end) - context.onUseWith(function(pos, itemId, target, subType) - if not autoRecording then - return - end - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - if not target:isItem() then - return - end - local targetPos = target:getPosition() - if targetPos.x == 0xFFFF then - return - end - stepsSincleLastPos = 0 - local playerPos = context.player:getPosition() - newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nusewith:" .. itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText - refreshConfig(true) - end) - - -- ui - local pos = context.player:getPosition() - ui.pos:setText("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z) - - ui.wGoto.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - local pos = context.player:getPosition() - modules.client_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\ngoto:" .. newText - refreshConfig(true) - end) - end - - ui.wUse.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - local pos = context.player:getPosition() - modules.client_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nuse:" .. newText - refreshConfig(true) - end) - end - - ui.wUseWith.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - local pos = context.player:getPosition() - modules.client_textedit.singlelineEditor("ITEMID," .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nusewith:" .. newText - refreshConfig(true) - end) - end - - ui.wWait.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - modules.client_textedit.singlelineEditor("1000", function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nwait:" .. newText - refreshConfig(true) - end) - end - - ui.wSay.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - modules.client_textedit.singlelineEditor("text", function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nsay:" .. newText - refreshConfig(true) - end) - end - - ui.wNpc.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - modules.client_textedit.singlelineEditor("text", function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nnpc:" .. newText - refreshConfig(true) - end) - end - - ui.wLabel.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - modules.client_textedit.singlelineEditor("label name", function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nlabel:" .. newText - refreshConfig(true) - end) - end - - ui.wFollow.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - modules.client_textedit.singlelineEditor("creature name", function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfollow:" .. newText - refreshConfig(true) - end) - end - - ui.wFunction.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - modules.client_textedit.multilineEditor("Add function", "function(waypoints)\n -- your lua code, function is executed if previous goto was successful or is just after label\n\n -- must return true to execute next command, otherwise will run in loop till correct return\n return true\nend", function(newText) - context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfunction:" .. newText - refreshConfig(true) - end) - end - - ui.recording.onClick = function() - if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then - return - end - autoRecording = not autoRecording - if autoRecording then - context.storage.cavebot.enabled = false - stepsSincleLastPos = 10 - end - refreshConfig(true) - end - - refreshConfig() - - local usedGotoLabel = false - local executeNextMacroCall = false - local commandExecutionNo = 0 - local lastGotoSuccesful = true - local lastOpenedContainer = 0 - - local functions = { - enable = function() - context.storage.cavebot.enabled = true - refreshConfig() - end, - disable = function() - context.storage.cavebot.enabled = false - refreshConfig() - end, - refresh = function() - refreshConfig() - end, - wait = function(peroid) - waitTo = context.now + peroid - end, - waitTo = function(timepoint) - waitTo = timepoint - end, - gotoLabel = function(name) - for i=1,ui.list:getChildCount() do - local command = commands[i] - if command and command.command == "label" and command.text == name then - ui.list:focusChild(ui.list:getChildByIndex(i)) - usedGotoLabel = true - lastGotoSuccesful = true - return true - end - end - end - } - - context.onContainerOpen(function(container) - if container:getItemsCount() > 0 then - lastOpenedContainer = context.now + container:getItemsCount() * 100 - end - end) - - - context.macro(250, function() - if not context.storage.cavebot.enabled then - return - end - - if modules.game_walking.lastManualWalk + 500 > context.now then - return - end - - -- wait if walked or opened container recently - if context.player:isWalking() or lastOpenedContainer + 1000 > context.now then - executeNextMacroCall = false - return - end - - -- wait if attacking/following creature - local attacking = g_game.getAttackingCreature() - local following = g_game.getFollowingCreature() - if (attacking and context.getCreatureById(attacking:getId()) and not attacking.ignoreByWaypoints) or (following and context.getCreatureById(following:getId())) then - executeNextMacroCall = false - return - end - - if not executeNextMacroCall then - executeNextMacroCall = true - return - end - executeNextMacroCall = false - - local commandWidget = ui.list:getFocusedChild() - if not commandWidget then - if ui.list:getFirstChild() then - ui.list:focusChild(ui.list:getFirstChild()) - end - return - end - - local commandIndex = ui.list:getChildIndex(commandWidget) - local command = commands[commandIndex] - if not command then - if ui.list:getFirstChild() then - ui.list:focusChild(ui.list:getFirstChild()) - end - return - end - - if commandIndex == 1 then - lastGotoSuccesful = true - end - - if command.command == "goto" or command.command == "follow" then - local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) - if (#matches == 1 and #matches[1] == 4) or command.command == "follow" then - local position = nil - if command.command == "follow" then - local creature = context.getCreatureByName(command.text) - if creature then - position = creature:getPosition() - end - else - position = {x=tonumber(matches[1][2]), y=tonumber(matches[1][3]), z=tonumber(matches[1][4])} - end - local distance = 0 - if position then - distance = context.getDistanceBetween(position, context.player:getPosition()) - end - if distance > 100 or not position or position.z ~= context.player:getPosition().z then - lastGotoSuccesful = false - elseif distance > 0 then - if not context.findPath(context.player:getPosition(), position, 100, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true }) then - lastGotoSuccesful = false - executeNextMacroCall = true - else - commandExecutionNo = commandExecutionNo + 1 - lastGotoSuccesful = false - if commandExecutionNo <= 3 then -- try max 3 times - if not context.autoWalk(position, distance * 2, { ignoreNonPathable = false }) then - if commandExecutionNo > 1 then - if context.autoWalk(position, distance * 2, { ignoreNonPathable = true, precision = 1 }) then - context.delay(500) - end - end - return - end - return - elseif commandExecutionNo == 4 then -- try last time, location close to destination - if context.autoWalk(position, distance * 2, { ignoreNonPathable = true, ignoreLastCreature = true, precision = 2, allowUnseen = true }) then - context.delay(500) - return - end - elseif distance <= 2 then - lastGotoSuccesful = true - executeNextMacroCall = true - end - end - else - lastGotoSuccesful = true - executeNextMacroCall = true - end - else - context.error("Waypoints: invalid use of goto function") - end - elseif command.command == "use" then - local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) - if #matches == 1 and #matches[1] == 4 then - local position = {x=tonumber(matches[1][2]), y=tonumber(matches[1][3]), z=tonumber(matches[1][4])} - if context.player:getPosition().z == position.z then - local tile = g_map.getTile(position) - if tile then - local topThing = tile:getTopUseThing() - if topThing then - g_game.use(topThing) - context.delay(500) - end - end - end - else - context.error("Waypoints: invalid use of use function") - end - elseif command.command == "usewith" then - local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) - if #matches == 1 and #matches[1] == 5 then - local itemId = tonumber(matches[1][2]) - local position = {x=tonumber(matches[1][3]), y=tonumber(matches[1][4]), z=tonumber(matches[1][5])} - if context.player:getPosition().z == position.z then - local tile = g_map.getTile(position) - if tile then - local topThing = tile:getTopUseThing() - if topThing then - context.useWith(itemId, topThing) - context.delay(500) - end - end - end - else - context.error("Waypoints: invalid use of usewith function") - end - elseif command.command == "wait" and lastGotoSuccesful then - if not waitTo or waitTo == 0 then - waitTo = context.now + tonumber(command.text) - end - if context.now < waitTo then - return - end - waitTo = 0 - elseif command.command == "say" and lastGotoSuccesful then - context.say(command.text) - elseif command.command == "npc" and lastGotoSuccesful then - context.sayNpc(command.text) - elseif command.command == "function" and lastGotoSuccesful then - usedGotoLabel = false - local status, result = pcall(function() - return assert(load("return " .. command.text, nil, nil, context))()(functions) - end) - if not status then - context.error("Waypoints function execution error:\n" .. result) - context.delay(2500) - end - if not result or usedGotoLabel then - return - end - elseif command.command == "gotolabel" then - if functions.gotoLabel(command.text) then - return - end - end - - local nextIndex = 1 + commandIndex % #commands - local nextChild = ui.list:getChildByIndex(nextIndex) - if nextChild then - ui.list:focusChild(nextChild) - commandExecutionNo = 0 - end - end) - - return functions -end - +local context = G.botContext +local Panels = context.Panels + +Panels.Waypoints = function(parent) + local ui = context.setupUI([[ +Panel + id: waypoints + height: 206 + + BotLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: Waypoints + + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 5 + text-offset: 3 0 + width: 130 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Button + margin-top: 1 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 17 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 17 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 17 + + TextList + id: list + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + vertical-scrollbar: listScrollbar + margin-right: 15 + margin-top: 2 + height: 60 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + pixels-scroll: true + step: 5 + + Label + id: pos + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-align: center + margin-top: 2 + + Button + id: wGoto + anchors.top: prev.bottom + anchors.left: parent.left + text: Goto + width: 61 + margin-top: 1 + height: 17 + + Button + id: wUse + anchors.top: prev.top + anchors.left: prev.right + text: Use + width: 61 + height: 17 + + Button + id: wUseWith + anchors.top: prev.top + anchors.left: prev.right + text: UseWith + width: 61 + height: 17 + + Button + id: wWait + anchors.top: prev.bottom + anchors.left: parent.left + text: Wait + width: 61 + margin-top: 1 + height: 17 + + Button + id: wSay + anchors.top: prev.top + anchors.left: prev.right + text: Say + width: 61 + height: 17 + + Button + id: wNpc + anchors.top: prev.top + anchors.left: prev.right + text: Say NPC + width: 61 + height: 17 + + Button + id: wLabel + anchors.top: prev.bottom + anchors.left: parent.left + text: Label + width: 61 + margin-top: 1 + height: 17 + + Button + id: wFollow + anchors.top: prev.top + anchors.left: prev.right + text: Follow + width: 61 + height: 17 + + Button + id: wFunction + anchors.top: prev.top + anchors.left: prev.right + text: Function + width: 61 + height: 17 + + BotSwitch + id: recording + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text: Auto Recording + height: 17 + +]], parent) + + if type(context.storage.cavebot) ~= "table" then + context.storage.cavebot = {} + end + if type(context.storage.cavebot.configs) ~= "table" then + context.storage.cavebot.configs = {} + end + + local getConfigName = function(config) + local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) + if matches[1] and matches[1][2] then + return matches[1][2]:trim() + end + return nil + end + + local isValidCommand = function(command) + if command == "goto" then + return true + elseif command == "use" then + return true + elseif command == "usewith" then + return true + elseif command == "wait" then + return true + elseif command == "say" then + return true + elseif command == "npc" then + return true + elseif command == "follow" then + return true + elseif command == "label" then + return true + elseif command == "gotolabel" then + return true + elseif command == "comment" then + return true + elseif command == "function" then + return true + end + return false + end + + local commands = {} + local waitTo = 0 + local autoRecording = false + + local parseConfig = function(config) + commands = {} + local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]]) + for i=1,#matches do + local command = matches[i][2] + local validation = (matches[i][3] == ":") + if not validation or isValidCommand(command) then + local text = matches[i][4] + if validation then + table.insert(commands, {command=command:lower(), text=text}) + elseif #commands > 0 then + commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1] + end + end + end + + for i=1,#commands do + local label = g_ui.createWidget("CaveBotLabel", ui.list) + label:setText(commands[i].command .. ":" .. commands[i].text) + if commands[i].command == "goto" then + label:setColor("green") + elseif commands[i].command == "label" then + label:setColor("yellow") + elseif commands[i].command == "comment" then + label:setText(commands[i].text) + label:setColor("white") + elseif commands[i].command == "use" or commands[i].command == "usewith" then + label:setColor("orange") + elseif commands[i].command == "gotolabel" then + label:setColor("red") + end + end + end + + local ignoreOnOptionChange = true + local refreshConfig = function(scrollDown) + ignoreOnOptionChange = true + if context.storage.cavebot.enabled then + autoRecording = false + ui.recording:setOn(false) + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + ui.recording:setOn(autoRecording) + end + + ui.config:clear() + for i, config in ipairs(context.storage.cavebot.configs) do + local name = getConfigName(config) + if not name then + name = "Unnamed config" + end + ui.config:addOption(name) + end + + if (not context.storage.cavebot.activeConfig or context.storage.cavebot.activeConfig == 0) and #context.storage.cavebot.configs > 0 then + context.storage.cavebot.activeConfig = 1 + end + + ui.list:destroyChildren() + + if context.storage.cavebot.activeConfig and context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + ui.config:setCurrentIndex(context.storage.cavebot.activeConfig) + parseConfig(context.storage.cavebot.configs[context.storage.cavebot.activeConfig]) + end + + context.saveConfig() + if scrollDown and ui.list:getLastChild() then + ui.list:focusChild(ui.list:getLastChild()) + end + + waitTo = 0 + ignoreOnOptionChange = false + end + + + ui.config.onOptionChange = function(widget) + if not ignoreOnOptionChange then + context.storage.cavebot.activeConfig = widget.currentIndex + refreshConfig() + end + end + ui.enableButton.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + context.storage.cavebot.enabled = not context.storage.cavebot.enabled + if autoRecording then + refreshConfig() + elseif context.storage.cavebot.enabled then + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + end + end + ui.add.onClick = function() + modules.client_textedit.multilineEditor("Waypoints editor", "name:Config name\nlabel:start\n", function(newText) + table.insert(context.storage.cavebot.configs, newText) + context.storage.cavebot.activeConfig = #context.storage.cavebot.configs + refreshConfig() + end) + end + ui.edit.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.multilineEditor("Waypoints editor", context.storage.cavebot.configs[context.storage.cavebot.activeConfig], function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = newText + refreshConfig() + end) + end + ui.remove.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local questionWindow = nil + local closeWindow = function() + questionWindow:destroy() + end + local removeConfig = function() + closeWindow() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + context.storage.cavebot.enabled = false + table.remove(context.storage.cavebot.configs, context.storage.cavebot.activeConfig) + context.storage.cavebot.activeConfig = 0 + refreshConfig() + end + questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current waypoints config?'), { + { text=tr('Yes'), callback=removeConfig }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) + end + + -- waypoint editor + -- auto recording + local stepsSincleLastPos = 0 + + context.onPlayerPositionChange(function(newPos, oldPos) + ui.pos:setText("Position: " .. newPos.x .. ", " .. newPos.y .. ", " .. newPos.z) + if not autoRecording then + return + end + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local newText = "" + if newPos.z ~= oldPos.z then + newText = "goto:" .. oldPos.x .. "," .. oldPos.y .. "," .. oldPos.z + newText = newText .. "\ngoto:" .. newPos.x .. "," .. newPos.y .. "," .. newPos.z + stepsSincleLastPos = 0 + else + stepsSincleLastPos = stepsSincleLastPos + 1 + if stepsSincleLastPos > 10 then + newText = "goto:" .. oldPos.x .. "," .. oldPos.y .. "," .. oldPos.z + stepsSincleLastPos = 0 + end + end + + if newText:len() > 0 then + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText + refreshConfig(true) + end + end) + + context.onUse(function(pos, itemId, stackPos, subType) + if not autoRecording then + return + end + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + if pos.x == 0xFFFF then + return + end + stepsSincleLastPos = 0 + local playerPos = context.player:getPosition() + newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nuse:" .. pos.x .. "," .. pos.y .. "," .. pos.z + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText + refreshConfig(true) + end) + context.onUseWith(function(pos, itemId, target, subType) + if not autoRecording then + return + end + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + if not target:isItem() then + return + end + local targetPos = target:getPosition() + if targetPos.x == 0xFFFF then + return + end + stepsSincleLastPos = 0 + local playerPos = context.player:getPosition() + newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nusewith:" .. itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText + refreshConfig(true) + end) + + -- ui + local pos = context.player:getPosition() + ui.pos:setText("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z) + + ui.wGoto.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local pos = context.player:getPosition() + modules.client_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\ngoto:" .. newText + refreshConfig(true) + end) + end + + ui.wUse.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local pos = context.player:getPosition() + modules.client_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nuse:" .. newText + refreshConfig(true) + end) + end + + ui.wUseWith.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local pos = context.player:getPosition() + modules.client_textedit.singlelineEditor("ITEMID," .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nusewith:" .. newText + refreshConfig(true) + end) + end + + ui.wWait.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("1000", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nwait:" .. newText + refreshConfig(true) + end) + end + + ui.wSay.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("text", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nsay:" .. newText + refreshConfig(true) + end) + end + + ui.wNpc.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("text", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nnpc:" .. newText + refreshConfig(true) + end) + end + + ui.wLabel.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("label name", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nlabel:" .. newText + refreshConfig(true) + end) + end + + ui.wFollow.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("creature name", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfollow:" .. newText + refreshConfig(true) + end) + end + + ui.wFunction.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.multilineEditor("Add function", "function(waypoints)\n -- your lua code, function is executed if previous goto was successful or is just after label\n\n -- must return true to execute next command, otherwise will run in loop till correct return\n return true\nend", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfunction:" .. newText + refreshConfig(true) + end) + end + + ui.recording.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + autoRecording = not autoRecording + if autoRecording then + context.storage.cavebot.enabled = false + stepsSincleLastPos = 10 + end + refreshConfig(true) + end + + refreshConfig() + + local usedGotoLabel = false + local executeNextMacroCall = false + local commandExecutionNo = 0 + local lastGotoSuccesful = true + local lastOpenedContainer = 0 + + local functions = { + enable = function() + context.storage.cavebot.enabled = true + refreshConfig() + end, + disable = function() + context.storage.cavebot.enabled = false + refreshConfig() + end, + refresh = function() + refreshConfig() + end, + wait = function(peroid) + waitTo = context.now + peroid + end, + waitTo = function(timepoint) + waitTo = timepoint + end, + gotoLabel = function(name) + for i=1,ui.list:getChildCount() do + local command = commands[i] + if command and command.command == "label" and command.text == name then + ui.list:focusChild(ui.list:getChildByIndex(i)) + usedGotoLabel = true + lastGotoSuccesful = true + return true + end + end + end + } + + context.onContainerOpen(function(container) + if container:getItemsCount() > 0 then + lastOpenedContainer = context.now + container:getItemsCount() * 100 + end + end) + + + context.macro(250, function() + if not context.storage.cavebot.enabled then + return + end + + if modules.game_walking.lastManualWalk + 500 > context.now then + return + end + + -- wait if walked or opened container recently + if context.player:isWalking() or lastOpenedContainer + 1000 > context.now then + executeNextMacroCall = false + return + end + + -- wait if attacking/following creature + local attacking = g_game.getAttackingCreature() + local following = g_game.getFollowingCreature() + if (attacking and context.getCreatureById(attacking:getId()) and not attacking.ignoreByWaypoints) or (following and context.getCreatureById(following:getId())) then + executeNextMacroCall = false + return + end + + if not executeNextMacroCall then + executeNextMacroCall = true + return + end + executeNextMacroCall = false + + local commandWidget = ui.list:getFocusedChild() + if not commandWidget then + if ui.list:getFirstChild() then + ui.list:focusChild(ui.list:getFirstChild()) + end + return + end + + local commandIndex = ui.list:getChildIndex(commandWidget) + local command = commands[commandIndex] + if not command then + if ui.list:getFirstChild() then + ui.list:focusChild(ui.list:getFirstChild()) + end + return + end + + if commandIndex == 1 then + lastGotoSuccesful = true + end + + if command.command == "goto" or command.command == "follow" then + local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) + if (#matches == 1 and #matches[1] == 4) or command.command == "follow" then + local position = nil + if command.command == "follow" then + local creature = context.getCreatureByName(command.text) + if creature then + position = creature:getPosition() + end + else + position = {x=tonumber(matches[1][2]), y=tonumber(matches[1][3]), z=tonumber(matches[1][4])} + end + local distance = 0 + if position then + distance = context.getDistanceBetween(position, context.player:getPosition()) + end + if distance > 100 or not position or position.z ~= context.player:getPosition().z then + lastGotoSuccesful = false + elseif distance > 0 then + if not context.findPath(context.player:getPosition(), position, 100, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true }) then + lastGotoSuccesful = false + executeNextMacroCall = true + else + commandExecutionNo = commandExecutionNo + 1 + lastGotoSuccesful = false + if commandExecutionNo <= 3 then -- try max 3 times + if not context.autoWalk(position, distance * 2, { ignoreNonPathable = false }) then + if commandExecutionNo > 1 then + if context.autoWalk(position, distance * 2, { ignoreNonPathable = true, precision = 1 }) then + context.delay(500) + end + end + return + end + return + elseif commandExecutionNo == 4 then -- try last time, location close to destination + if context.autoWalk(position, distance * 2, { ignoreNonPathable = true, ignoreLastCreature = true, precision = 2, allowUnseen = true }) then + context.delay(500) + return + end + elseif distance <= 2 then + lastGotoSuccesful = true + executeNextMacroCall = true + end + end + else + lastGotoSuccesful = true + executeNextMacroCall = true + end + else + context.error("Waypoints: invalid use of goto function") + end + elseif command.command == "use" then + local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) + if #matches == 1 and #matches[1] == 4 then + local position = {x=tonumber(matches[1][2]), y=tonumber(matches[1][3]), z=tonumber(matches[1][4])} + if context.player:getPosition().z == position.z then + local tile = g_map.getTile(position) + if tile then + local topThing = tile:getTopUseThing() + if topThing then + g_game.use(topThing) + context.delay(500) + end + end + end + else + context.error("Waypoints: invalid use of use function") + end + elseif command.command == "usewith" then + local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) + if #matches == 1 and #matches[1] == 5 then + local itemId = tonumber(matches[1][2]) + local position = {x=tonumber(matches[1][3]), y=tonumber(matches[1][4]), z=tonumber(matches[1][5])} + if context.player:getPosition().z == position.z then + local tile = g_map.getTile(position) + if tile then + local topThing = tile:getTopUseThing() + if topThing then + context.useWith(itemId, topThing) + context.delay(500) + end + end + end + else + context.error("Waypoints: invalid use of usewith function") + end + elseif command.command == "wait" and lastGotoSuccesful then + if not waitTo or waitTo == 0 then + waitTo = context.now + tonumber(command.text) + end + if context.now < waitTo then + return + end + waitTo = 0 + elseif command.command == "say" and lastGotoSuccesful then + context.say(command.text) + elseif command.command == "npc" and lastGotoSuccesful then + context.sayNpc(command.text) + elseif command.command == "function" and lastGotoSuccesful then + usedGotoLabel = false + local status, result = pcall(function() + return assert(load("return " .. command.text, nil, nil, context))()(functions) + end) + if not status then + context.error("Waypoints function execution error:\n" .. result) + context.delay(2500) + end + if not result or usedGotoLabel then + return + end + elseif command.command == "gotolabel" then + if functions.gotoLabel(command.text) then + return + end + end + + local nextIndex = 1 + commandIndex % #commands + local nextChild = ui.list:getChildByIndex(nextIndex) + if nextChild then + ui.list:focusChild(nextChild) + commandExecutionNo = 0 + end + end) + + return functions +end + diff --git a/modules/game_bot/ui/basic.otui b/modules/game_bot/ui/basic.otui index a1a40fd..56d9e03 100644 --- a/modules/game_bot/ui/basic.otui +++ b/modules/game_bot/ui/basic.otui @@ -1,88 +1,88 @@ -BotButton < Button - height: 17 - margin-top: 2 - -BotSwitch < Button - margin-top: 2 - height: 17 - image-color: green - $!on: - image-color: red - -SmallBotSwitch < Button - margin-top: 2 - height: 15 - image-color: green - $!on: - image-color: red - -BotLabel < Label - margin-top: 2 - height: 15 - text-auto-resize: true - text-align: center - text-wrap: true - -BotItem < Item - virtual: true - &selectable: true - &editable: true - -BotTextEdit < TextEdit - @onClick: modules.client_textedit.show(self) - text-align: center - multiline: false - focusable: false - height: 20 - -BotSeparator < HorizontalSeparator - margin-top: 5 - margin-bottom: 3 - -BotSmallScrollBar < SmallScrollBar - -BotPanel < Panel - margin-top: 1 - ScrollablePanel - id: content - anchors.fill: parent - margin-right: 8 - margin-left: 1 - margin-bottom: 5 - vertical-scrollbar: botPanelScroll - layout: - type: verticalBox - $mobile: - margin-right: 16 - - BotSmallScrollBar - id: botPanelScroll - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - -CaveBotLabel < Label - background-color: alpha - text-offset: 2 0 - focusable: true - - $focus: - background-color: #00000055 - -SlotComboBoxPopupMenu < ComboBoxPopupMenu -SlotComboBoxPopupMenuButton < ComboBoxPopupMenuButton -SlotComboBox < ComboBox - @onSetup: | - self:addOption("Head") - self:addOption("Neck") - self:addOption("Back") - self:addOption("Body") - self:addOption("Right") - self:addOption("Left") - self:addOption("Leg") - self:addOption("Feet") - self:addOption("Finger") - self:addOption("Ammo") - self:addOption("Purse") - - +BotButton < Button + height: 17 + margin-top: 2 + +BotSwitch < Button + margin-top: 2 + height: 17 + image-color: green + $!on: + image-color: red + +SmallBotSwitch < Button + margin-top: 2 + height: 15 + image-color: green + $!on: + image-color: red + +BotLabel < Label + margin-top: 2 + height: 15 + text-auto-resize: true + text-align: center + text-wrap: true + +BotItem < Item + virtual: true + &selectable: true + &editable: true + +BotTextEdit < TextEdit + @onClick: modules.client_textedit.show(self) + text-align: center + multiline: false + focusable: false + height: 20 + +BotSeparator < HorizontalSeparator + margin-top: 5 + margin-bottom: 3 + +BotSmallScrollBar < SmallScrollBar + +BotPanel < Panel + margin-top: 1 + ScrollablePanel + id: content + anchors.fill: parent + margin-right: 8 + margin-left: 1 + margin-bottom: 5 + vertical-scrollbar: botPanelScroll + layout: + type: verticalBox + $mobile: + margin-right: 16 + + BotSmallScrollBar + id: botPanelScroll + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + +CaveBotLabel < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + +SlotComboBoxPopupMenu < ComboBoxPopupMenu +SlotComboBoxPopupMenuButton < ComboBoxPopupMenuButton +SlotComboBox < ComboBox + @onSetup: | + self:addOption("Head") + self:addOption("Neck") + self:addOption("Back") + self:addOption("Body") + self:addOption("Right") + self:addOption("Left") + self:addOption("Leg") + self:addOption("Feet") + self:addOption("Finger") + self:addOption("Ammo") + self:addOption("Purse") + + diff --git a/modules/game_bot/ui/config.otui b/modules/game_bot/ui/config.otui index eecea23..c99d741 100644 --- a/modules/game_bot/ui/config.otui +++ b/modules/game_bot/ui/config.otui @@ -1,54 +1,54 @@ -BotConfig < Panel - id: botConfig - height: 45 - margin-left: 2 - margin-right: 2 - - ComboBox - id: list - anchors.top: parent.top - anchors.left: parent.left - text-offset: 3 0 - width: 130 - - Button - id: switch - anchors.top: prev.top - anchors.left: prev.right - anchors.right: parent.right - margin-left: 5 - $on: - text: On - color: #00AA00 - - $!on: - text: Off - color: #FF0000 - - Button - margin-top: 2 - id: add - anchors.top: prev.bottom - anchors.left: parent.left - text: Add - width: 56 - height: 18 - text-offet: 0 2 - - Button - id: edit - anchors.top: prev.top - anchors.horizontalCenter: parent.horizontalCenter - text: Edit - width: 56 - height: 18 - text-offet: 0 2 - - Button - id: remove - anchors.top: prev.top - anchors.right: parent.right - text: Remove - width: 56 - height: 18 +BotConfig < Panel + id: botConfig + height: 45 + margin-left: 2 + margin-right: 2 + + ComboBox + id: list + anchors.top: parent.top + anchors.left: parent.left + text-offset: 3 0 + width: 130 + + Button + id: switch + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + $on: + text: On + color: #00AA00 + + $!on: + text: Off + color: #FF0000 + + Button + margin-top: 2 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 56 + height: 18 + text-offet: 0 2 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 56 + height: 18 + text-offet: 0 2 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 56 + height: 18 text-offet: 0 2 \ No newline at end of file diff --git a/modules/game_bot/ui/container.otui b/modules/game_bot/ui/container.otui index b7ec078..acb04a1 100644 --- a/modules/game_bot/ui/container.otui +++ b/modules/game_bot/ui/container.otui @@ -1,19 +1,19 @@ -BotContainer < Panel - height: 68 - - ScrollablePanel - id: items - anchors.fill: parent - vertical-scrollbar: scroll - layout: - type: grid - cell-size: 34 34 - flow: true - - BotSmallScrollBar - id: scroll - anchors.top: prev.top - anchors.bottom: prev.bottom - anchors.right: parent.right - step: 10 - pixels-scroll: true +BotContainer < Panel + height: 68 + + ScrollablePanel + id: items + anchors.fill: parent + vertical-scrollbar: scroll + layout: + type: grid + cell-size: 34 34 + flow: true + + BotSmallScrollBar + id: scroll + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + step: 10 + pixels-scroll: true diff --git a/modules/game_bot/ui/icons.otui b/modules/game_bot/ui/icons.otui index 5de4524..655c66c 100644 --- a/modules/game_bot/ui/icons.otui +++ b/modules/game_bot/ui/icons.otui @@ -1,60 +1,60 @@ -BotIcon < UIWidget - size: 50 50 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - focusable: false - phantom: false - draggable: true - - UIItem - id: item - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - margin-top: 6 - virtual: true - phantom: true - size: 32 32 - - UICreature - id: creature - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - margin-top: 0 - size: 48 48 - phantom: true - - UIWidget - id: status - anchors.top: parent.top - anchors.left: parent.left - size: 18 10 - color: black - font: terminus-10px - phantom: true - - $on: - text: ON - background: green - - $!on: - text: OFF - background: red - - UIWidget - id: hotkey - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - size: 18 10 - color: white - phantom: true - text-align: right - - UIWidget - id: text - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - text-wrap: true - text-auto-resize: true - phantom: true +BotIcon < UIWidget + size: 50 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + focusable: false + phantom: false + draggable: true + + UIItem + id: item + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 6 + virtual: true + phantom: true + size: 32 32 + + UICreature + id: creature + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 0 + size: 48 48 + phantom: true + + UIWidget + id: status + anchors.top: parent.top + anchors.left: parent.left + size: 18 10 + color: black + font: terminus-10px + phantom: true + + $on: + text: ON + background: green + + $!on: + text: OFF + background: red + + UIWidget + id: hotkey + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + size: 18 10 + color: white + phantom: true + text-align: right + + UIWidget + id: text + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + text-wrap: true + text-auto-resize: true + phantom: true diff --git a/modules/game_bot/ui/panels.otui b/modules/game_bot/ui/panels.otui index 82115e6..43b39ec 100644 --- a/modules/game_bot/ui/panels.otui +++ b/modules/game_bot/ui/panels.otui @@ -1,240 +1,240 @@ -DualScrollPanel < Panel - height: 51 - margin-top: 3 - - SmallBotSwitch - id: title - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - text-align: center - - HorizontalScrollBar - id: scroll1 - anchors.left: title.left - anchors.right: title.horizontalCenter - anchors.top: title.bottom - margin-right: 2 - margin-top: 2 - minimum: 0 - maximum: 100 - step: 1 - &disableScroll: true - - HorizontalScrollBar - id: scroll2 - anchors.left: title.horizontalCenter - anchors.right: title.right - anchors.top: prev.top - margin-left: 2 - minimum: 0 - maximum: 100 - step: 1 - &disableScroll: true - - BotTextEdit - id: text - anchors.left: parent.left - anchors.right: parent.right - anchors.top: scroll1.bottom - margin-top: 3 - margin-left: 2 - margin-right: 1 - -SingleScrollItemPanel < Panel - height: 45 - margin-top: 2 - - BotItem - id: item - anchors.left: parent.left - anchors.top: prev.bottom - margin-top: 3 - - SmallBotSwitch - id: title - anchors.left: prev.right - anchors.right: parent.right - anchors.top: prev.top - margin-left: 2 - text-align: center - - HorizontalScrollBar - id: scroll - anchors.left: title.left - anchors.right: title.right - anchors.top: title.bottom - margin-top: 2 - minimum: 0 - maximum: 100 - step: 1 - &disableScroll: true - -DualScrollItemPanel < Panel - height: 33 - margin-top: 3 - - BotItem - id: item - anchors.left: parent.left - anchors.top: prev.bottom - margin-top: 3 - - SmallBotSwitch - id: title - anchors.left: prev.right - anchors.right: parent.right - anchors.top: prev.top - margin-left: 2 - text-align: center - - HorizontalScrollBar - id: scroll1 - anchors.left: title.left - anchors.right: title.horizontalCenter - anchors.top: title.bottom - margin-top: 2 - margin-right: 2 - minimum: 0 - maximum: 100 - step: 1 - &disableScroll: true - - HorizontalScrollBar - id: scroll2 - anchors.left: title.horizontalCenter - anchors.right: title.right - anchors.top: prev.top - margin-left: 2 - minimum: 0 - maximum: 100 - step: 1 - &disableScroll: true - -ItemsRow < Panel - height: 33 - margin-top: 2 - - BotItem - id: item1 - anchors.top: parent.top - anchors.left: parent.left - - BotItem - id: item2 - anchors.top: prev.top - anchors.left: prev.right - margin-left: 2 - - BotItem - id: item3 - anchors.top: prev.top - anchors.left: prev.right - margin-left: 2 - - BotItem - id: item4 - anchors.top: prev.top - anchors.left: prev.right - margin-left: 2 - - BotItem - id: item5 - anchors.top: prev.top - anchors.left: prev.right - margin-left: 2 - -ItemsPanel < Panel - height: 55 - - SmallBotSwitch - id: title - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - text-align: center - - ItemsRow - id: items - anchors.left: parent.left - anchors.right: parent.right - anchors.top: prev.bottom - - -ItemAndButtonPanel < Panel - height: 40 - - BotItem - id: item - anchors.left: parent.left - anchors.top: parent.top - - BotSwitch - id: title - anchors.left: prev.right - anchors.right: parent.right - anchors.verticalCenter: prev.verticalCenter - text-align: center - margin-left: 2 - margin-top: 0 - -ItemAndSlotPanel < Panel - height: 40 - - BotItem - id: item - anchors.left: parent.left - anchors.top: parent.top - - SmallBotSwitch - id: title - anchors.left: prev.right - anchors.right: parent.right - anchors.top: prev.top - text-align: center - margin-left: 2 - margin-top: 0 - - SlotComboBox - id: slot - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 2 - height: 20 - &disableScroll: true - -TwoItemsAndSlotPanel < Panel - height: 35 - margin-top: 4 - - BotItem - id: item1 - anchors.left: parent.left - anchors.top: parent.top - margin-top: 1 - - BotItem - id: item2 - anchors.left: prev.right - anchors.top: prev.top - margin-left: 1 - - SmallBotSwitch - id: title - anchors.left: prev.right - anchors.right: parent.right - anchors.top: parent.top - text-align: center - margin-left: 2 - margin-top: 0 - - SlotComboBox - id: slot - anchors.left: prev.left - anchors.right: prev.right - anchors.top: prev.bottom - margin-top: 2 - height: 20 - &disableScroll: true +DualScrollPanel < Panel + height: 51 + margin-top: 3 + + SmallBotSwitch + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + HorizontalScrollBar + id: scroll1 + anchors.left: title.left + anchors.right: title.horizontalCenter + anchors.top: title.bottom + margin-right: 2 + margin-top: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + + HorizontalScrollBar + id: scroll2 + anchors.left: title.horizontalCenter + anchors.right: title.right + anchors.top: prev.top + margin-left: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + + BotTextEdit + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: scroll1.bottom + margin-top: 3 + margin-left: 2 + margin-right: 1 + +SingleScrollItemPanel < Panel + height: 45 + margin-top: 2 + + BotItem + id: item + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + + SmallBotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 2 + text-align: center + + HorizontalScrollBar + id: scroll + anchors.left: title.left + anchors.right: title.right + anchors.top: title.bottom + margin-top: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + +DualScrollItemPanel < Panel + height: 33 + margin-top: 3 + + BotItem + id: item + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + + SmallBotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 2 + text-align: center + + HorizontalScrollBar + id: scroll1 + anchors.left: title.left + anchors.right: title.horizontalCenter + anchors.top: title.bottom + margin-top: 2 + margin-right: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + + HorizontalScrollBar + id: scroll2 + anchors.left: title.horizontalCenter + anchors.right: title.right + anchors.top: prev.top + margin-left: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + +ItemsRow < Panel + height: 33 + margin-top: 2 + + BotItem + id: item1 + anchors.top: parent.top + anchors.left: parent.left + + BotItem + id: item2 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item3 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item4 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item5 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + +ItemsPanel < Panel + height: 55 + + SmallBotSwitch + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + ItemsRow + id: items + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + + +ItemAndButtonPanel < Panel + height: 40 + + BotItem + id: item + anchors.left: parent.left + anchors.top: parent.top + + BotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.verticalCenter: prev.verticalCenter + text-align: center + margin-left: 2 + margin-top: 0 + +ItemAndSlotPanel < Panel + height: 40 + + BotItem + id: item + anchors.left: parent.left + anchors.top: parent.top + + SmallBotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + text-align: center + margin-left: 2 + margin-top: 0 + + SlotComboBox + id: slot + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 2 + height: 20 + &disableScroll: true + +TwoItemsAndSlotPanel < Panel + height: 35 + margin-top: 4 + + BotItem + id: item1 + anchors.left: parent.left + anchors.top: parent.top + margin-top: 1 + + BotItem + id: item2 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 1 + + SmallBotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.top: parent.top + text-align: center + margin-left: 2 + margin-top: 0 + + SlotComboBox + id: slot + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 2 + height: 20 + &disableScroll: true \ No newline at end of file diff --git a/modules/game_hotkeys/hotkeys_extra.lua b/modules/game_hotkeys/hotkeys_extra.lua index 6669e5c..ac066de 100644 --- a/modules/game_hotkeys/hotkeys_extra.lua +++ b/modules/game_hotkeys/hotkeys_extra.lua @@ -1,119 +1,119 @@ -extraHotkeys = {} - -function addExtraHotkey(name, description, callback) - table.insert(extraHotkeys, { - name = name:lower(), - description = tr(description), - callback = callback - }) - -end - -function setupExtraHotkeys(combobox) - addExtraHotkey("none", "None", nil) - addExtraHotkey("cancelAttack", "Stop attacking", function(repeated) - if not repeated then - g_game.attack(nil) - end - end) - addExtraHotkey("attackNext", "Attack next target from battle list", function(repeated) - if repeated or not modules.game_battle then - return - end - local battlePanel = modules.game_battle.battlePanel - local attackedCreature = g_game.getAttackingCreature() - local nextChild = nil - local breakNext = false - for i, child in ipairs(battlePanel:getChildren()) do - if not child.creature or not child:isOn() then - break - end - nextChild = child - if breakNext then - break - end - if child.creature == attackedCreature then - breakNext = true - nextChild = battlePanel:getFirstChild() - end - end - if not breakNext then - nextChild = battlePanel:getFirstChild() - end - if nextChild and nextChild.creature ~= attackedCreature then - g_game.attack(nextChild.creature) - end - end) - - addExtraHotkey("attackPrevious", "Attack previous target from battle list", function(repeated) - if repeated or not modules.game_battle then - return - end - local battlePanel = modules.game_battle.battlePanel - local attackedCreature = g_game.getAttackingCreature() - local prevChild = nil - for i, child in ipairs(battlePanel:getChildren()) do - if not child.creature or not child:isOn() then - break - end - if child.creature == attackedCreature then - break - end - prevChild = child - end - if prevChild and prevChild.creature ~= attackedCreature then - g_game.attack(prevChild.creature) - end - end) - - addExtraHotkey("toogleWsad", "Enable/disable wsad walking", function(repeated) - if repeated or not modules.game_console then - return - end - if not modules.game_console.consoleToggleChat:isChecked() then - modules.game_console.disableChat(true) - else - modules.game_console.enableChat(true) - end - end) - - for index, actionDetails in ipairs(extraHotkeys) do - combobox:addOption(actionDetails.description) - end -end - -function executeExtraHotkey(action, repeated) - action = action:lower() - for index, actionDetails in ipairs(extraHotkeys) do - if actionDetails.name == action and actionDetails.callback then - actionDetails.callback(repeated) - end - end -end - -function translateActionToActionComboboxIndex(action) - action = action:lower() - for index, actionDetails in ipairs(extraHotkeys) do - if actionDetails.name == action then - return index - end - end - return 1 -end - -function translateActionComboboxIndexToAction(index) - if index > 1 and index <= #extraHotkeys then - return extraHotkeys[index].name - end - return nil -end - -function getActionDescription(action) - action = action:lower() - for index, actionDetails in ipairs(extraHotkeys) do - if actionDetails.name == action then - return actionDetails.description - end - end - return "invalid action" +extraHotkeys = {} + +function addExtraHotkey(name, description, callback) + table.insert(extraHotkeys, { + name = name:lower(), + description = tr(description), + callback = callback + }) + +end + +function setupExtraHotkeys(combobox) + addExtraHotkey("none", "None", nil) + addExtraHotkey("cancelAttack", "Stop attacking", function(repeated) + if not repeated then + g_game.attack(nil) + end + end) + addExtraHotkey("attackNext", "Attack next target from battle list", function(repeated) + if repeated or not modules.game_battle then + return + end + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local nextChild = nil + local breakNext = false + for i, child in ipairs(battlePanel:getChildren()) do + if not child.creature or not child:isOn() then + break + end + nextChild = child + if breakNext then + break + end + if child.creature == attackedCreature then + breakNext = true + nextChild = battlePanel:getFirstChild() + end + end + if not breakNext then + nextChild = battlePanel:getFirstChild() + end + if nextChild and nextChild.creature ~= attackedCreature then + g_game.attack(nextChild.creature) + end + end) + + addExtraHotkey("attackPrevious", "Attack previous target from battle list", function(repeated) + if repeated or not modules.game_battle then + return + end + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local prevChild = nil + for i, child in ipairs(battlePanel:getChildren()) do + if not child.creature or not child:isOn() then + break + end + if child.creature == attackedCreature then + break + end + prevChild = child + end + if prevChild and prevChild.creature ~= attackedCreature then + g_game.attack(prevChild.creature) + end + end) + + addExtraHotkey("toogleWsad", "Enable/disable wsad walking", function(repeated) + if repeated or not modules.game_console then + return + end + if not modules.game_console.consoleToggleChat:isChecked() then + modules.game_console.disableChat(true) + else + modules.game_console.enableChat(true) + end + end) + + for index, actionDetails in ipairs(extraHotkeys) do + combobox:addOption(actionDetails.description) + end +end + +function executeExtraHotkey(action, repeated) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action and actionDetails.callback then + actionDetails.callback(repeated) + end + end +end + +function translateActionToActionComboboxIndex(action) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action then + return index + end + end + return 1 +end + +function translateActionComboboxIndexToAction(index) + if index > 1 and index <= #extraHotkeys then + return extraHotkeys[index].name + end + return nil +end + +function getActionDescription(action) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action then + return actionDetails.description + end + end + return "invalid action" end \ No newline at end of file diff --git a/modules/game_interface/gameinterface.lua b/modules/game_interface/gameinterface.lua index e4a5900..267523b 100644 --- a/modules/game_interface/gameinterface.lua +++ b/modules/game_interface/gameinterface.lua @@ -161,6 +161,7 @@ function hide() countWindow = nil end gameRootPanel:hide() + gameMapPanel:setShader("") modules.client_background.show() end diff --git a/modules/game_market/market.lua b/modules/game_market/market.lua index a361294..5e55f76 100644 --- a/modules/game_market/market.lua +++ b/modules/game_market/market.lua @@ -1384,7 +1384,7 @@ function Market.onMarketEnter(depotItems, offers, balance, vocation, items) for i = 1, #marketItems[MarketCategory.TibiaCoins] do local item = marketItems[MarketCategory.TibiaCoins][i].displayItem - information.depotItems[item:getId()] = tibiaCoins + depotItems[item:getId()] = tibiaCoins end -- update the items widget to match depot items @@ -1425,10 +1425,9 @@ end function Market.onCoinBalance(coins, transferableCoins) tibiaCoins = coins - if not information or type(information.depotItems) ~= "table" then return end if not marketItems[MarketCategory.TibiaCoins] then return end for i = 1, #marketItems[MarketCategory.TibiaCoins] do local item = marketItems[MarketCategory.TibiaCoins][i].displayItem - information.depotItems[item:getId()] = tibiaCoins + depotItems[item:getId()] = tibiaCoins end end diff --git a/modules/game_shaders/shaders.lua b/modules/game_shaders/shaders.lua index c24a3c9..180a862 100644 --- a/modules/game_shaders/shaders.lua +++ b/modules/game_shaders/shaders.lua @@ -1,6 +1,22 @@ function init() -- add manually your shaders from /data/shaders - g_shaders.createOutfitShader("default", "/shaders/outfit_default_vertex", "/shaders/outfit_default_fragment") + + -- map shaders + g_shaders.createShader("map_default", "/shaders/map_default_vertex", "/shaders/map_default_fragment") + + g_shaders.createShader("map_rainbow", "/shaders/map_rainbow_vertex", "/shaders/map_rainbow_fragment") + g_shaders.addTexture("map_rainbow", "/images/shaders/rainbow.png") + + -- use modules.game_interface.gameMapPanel:setShader("map_rainbow") to set shader + + -- outfit shaders + g_shaders.createOutfitShader("outfit_default", "/shaders/outfit_default_vertex", "/shaders/outfit_default_fragment") + + g_shaders.createOutfitShader("outfit_rainbow", "/shaders/outfit_rainbow_vertex", "/shaders/outfit_rainbow_fragment") + g_shaders.addTexture("outfit_rainbow", "/images/shaders/rainbow.png") + + -- you can use creature:setOutfitShader("outfit_rainbow") to set shader + end function terminate() diff --git a/modules/game_skills/skills.lua b/modules/game_skills/skills.lua index 1fec5db..c68b53b 100644 --- a/modules/game_skills/skills.lua +++ b/modules/game_skills/skills.lua @@ -282,13 +282,24 @@ function onSkillButtonClick(button) end function onExperienceChange(localPlayer, value) - setSkillValue('experience', comma_value(value)) + local postFix = "" + if value > 1e15 then + postFix = "B" + value = math.floor(value / 1e9) + elseif value > 1e12 then + postFix = "M" + value = math.floor(value / 1e6) + elseif value > 1e9 then + postFix = "K" + value = math.floor(value / 1e3) + end + setSkillValue('experience', comma_value(value) .. postFix) end function onLevelChange(localPlayer, value, percent) setSkillValue('level', value) local text = tr('You have %s percent to go', 100 - percent) .. '\n' .. - tr('%s of experience left', comma_value(expToAdvance(localPlayer:getLevel(), localPlayer:getExperience()))) + tr('%s of experience left', expToAdvance(localPlayer:getLevel(), localPlayer:getExperience())) if localPlayer.expSpeed ~= nil then local expPerHour = math.floor(localPlayer.expSpeed * 3600) @@ -297,7 +308,7 @@ function onLevelChange(localPlayer, value, percent) local hoursLeft = (nextLevelExp - localPlayer:getExperience()) / expPerHour local minutesLeft = math.floor((hoursLeft - math.floor(hoursLeft))*60) hoursLeft = math.floor(hoursLeft) - text = text .. '\n' .. comma_value(expPerHour) .. tr(' of experience per hour') + text = text .. '\n' .. tr('%d of experience per hour', expPerHour) text = text .. '\n' .. tr('Next level in %d hours and %d minutes', hoursLeft, minutesLeft) end end diff --git a/modules/game_things/things.lua b/modules/game_things/things.lua index 4145971..648bc10 100644 --- a/modules/game_things/things.lua +++ b/modules/game_things/things.lua @@ -14,32 +14,16 @@ function load() local things = g_settings.getNode('things') local datPath, sprPath - if things["data"] ~= nil and things["sprites"] ~= nil then + if things and things["data"] ~= nil and things["sprites"] ~= nil then datPath = '/things/' .. things["data"] - if G.hdSprites and things["sprites_hd"] then - sprPath = '/things/' .. things["sprites_hd"] - else - sprPath = '/things/' .. things["sprites"] - end + sprPath = '/things/' .. things["sprites"] else if filename then datPath = resolvepath('/things/' .. filename) sprPath = resolvepath('/things/' .. filename) - if G.hdSprites then - local hdsprPath = resolvepath('/things/' .. filename .. '_hd') - if g_resources.fileExists(hdsprPath) then - sprPath = hdsprPath - end - end else datPath = resolvepath('/things/' .. version .. '/Tibia') sprPath = resolvepath('/things/' .. version .. '/Tibia') - if G.hdSprites then - local hdsprPath = resolvepath('/things/' .. version .. '/Tibia_hd') - if g_resources.fileExists(hdsprPath) then - sprPath = hdsprPath - end - end end end @@ -54,7 +38,7 @@ function load() errorMessage = errorMessage .. tr("Unable to load dat file, please place a valid dat in '%s'", datPath) .. '\n' end end - if not g_sprites.loadSpr(sprPath, G.hdSprites or false) then + if not g_sprites.loadSpr(sprPath, false) then errorMessage = errorMessage .. tr("Unable to load spr file, please place a valid spr in '%s'", sprPath) end diff --git a/modules/gamelib/const.lua b/modules/gamelib/const.lua index 21455d7..ca59285 100644 --- a/modules/gamelib/const.lua +++ b/modules/gamelib/const.lua @@ -196,6 +196,8 @@ GameDrawAuraOnTop = 109 GamePacketSizeU32 = 110 GamePacketCompression = 111 +GameOldInformationBar = 112 + LastGameFeature = 130 TextColors = { diff --git a/otclient_dx.exe b/otclient_dx.exe index 689d736..fe46c63 100644 Binary files a/otclient_dx.exe and b/otclient_dx.exe differ diff --git a/otclient_gl.exe b/otclient_gl.exe index 92a004e..b06093b 100644 Binary files a/otclient_gl.exe and b/otclient_gl.exe differ diff --git a/otclientv8.apk b/otclientv8.apk index 47cfd0b..fb003b1 100644 Binary files a/otclientv8.apk and b/otclientv8.apk differ