diff --git a/data/images/game/mobile/keypad.png b/data/images/game/mobile/keypad.png new file mode 100644 index 0000000..7198080 Binary files /dev/null and b/data/images/game/mobile/keypad.png differ diff --git a/data/images/game/mobile/keypad_pointer.png b/data/images/game/mobile/keypad_pointer.png new file mode 100644 index 0000000..c8385d3 Binary files /dev/null and b/data/images/game/mobile/keypad_pointer.png differ diff --git a/data/images/topbuttons/keypad.png b/data/images/topbuttons/keypad.png new file mode 100644 index 0000000..ea01039 Binary files /dev/null and b/data/images/topbuttons/keypad.png differ diff --git a/modules/client_mobile/mobile.lua b/modules/client_mobile/mobile.lua index 34ef6ec..b3f47e7 100644 --- a/modules/client_mobile/mobile.lua +++ b/modules/client_mobile/mobile.lua @@ -1,17 +1,33 @@ local overlay +local keypad local touchStart = 0 -local updateCursorEvent = nil +local updateCursorEvent local zoomInButton local zoomOutButton +local keypadButton +local keypadEvent +local keypadMousePos = {x=0.5, y=0.5} +local keypadTicks = 0 -- public functions function init() if not g_app.isMobile() then return end overlay = g_ui.displayUI('mobile') + keypad = overlay.keypad overlay:raise() zoomInButton = modules.client_topmenu.addLeftButton('zoomInButton', 'Zoom In', '/images/topbuttons/zoomin', function() g_app.scaleUp() end) zoomOutButton = modules.client_topmenu.addLeftButton('zoomOutButton', 'Zoom Out', '/images/topbuttons/zoomout', function() g_app.scaleDown() end) + keypadButton = modules.client_topmenu.addLeftGameToggleButton('keypadButton', 'Keypad', '/images/topbuttons/keypad', function() + keypadButton:setChecked(not keypadButton:isChecked()) + if not g_game.isOnline() then + keypad:setVisible(false) + return + end + keypad:setVisible(keypadButton:isChecked()) + end) + keypadButton:setChecked(true) + scheduleEvent(function() g_app.scale(5.0) end, 10) @@ -23,10 +39,25 @@ function init() onTouchRelease = onMouseRelease, onMouseMove = onMouseMove }) + connect(keypad, { + onTouchPress = onKeypadTouchPress, + onTouchRelease = onKeypadTouchRelease, + onMouseMove = onKeypadTouchMove + }) + connect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + if g_game.isOnline() then + online() + end end function terminate() if not g_app.isMobile() then return end + removeEvent(updateCursorEvent) + removeEvent(keypadEvent) + keypadEvent = nil disconnect(overlay, { onMousePress = onMousePress, onMouseRelease = onMouseRelease, @@ -34,8 +65,18 @@ function terminate() onTouchRelease = onMouseRelease, onMouseMove = onMouseMove }) + disconnect(keypad, { + onTouchPress = onKeypadTouchPress, + onTouchRelease = onKeypadTouchRelease, + onMouseMove = onKeypadTouchMove + }) + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline + }) zoomInButton:destroy() zoomOutButton:destroy() + keypadButton:destroy() overlay:destroy() overlay = nil end @@ -48,13 +89,24 @@ function show() overlay:show() end +function online() + if keypadButton:isChecked() then + keypad:raise() + keypad:show() + end +end + +function offline() + keypad:hide() +end + function onMouseMove(widget, pos, offset) end function onMousePress(widget, pos, button) overlay:raise() - if button == 4 then -- touch + if button == MouseTouch then -- touch overlay:raise() overlay.cursor:show() overlay.cursor:setPosition({x=pos.x - 32, y = pos.y - 32}) @@ -67,12 +119,15 @@ function onMousePress(widget, pos, button) end function onMouseRelease(widget, pos, button) - overlay.cursor:hide() - removeEvent(updateCursorEvent) + if button == MouseTouch then + overlay.cursor:hide() + removeEvent(updateCursorEvent) + end end function updateCursor() removeEvent(updateCursorEvent) + if not g_mouse.isPressed(MouseTouch) then return end local percent = 100 - math.max(0, math.min(100, (g_clock.millis() - touchStart) / 5)) -- 500 ms overlay.cursor:setPercent(percent) if percent > 0 then @@ -81,4 +136,81 @@ function updateCursor() else overlay.cursor:setOpacity(0.8) end +end + +function onKeypadTouchMove(widget, pos, offset) + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + return true +end + +function onKeypadTouchPress(widget, pos, button) + if button ~= MouseTouch then return false end + keypadTicks = 0 + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + executeWalk() + return true +end + +function onKeypadTouchRelease(widget, pos, button) + if button ~= MouseTouch then return false end + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + executeWalk() + removeEvent(keypadEvent) + keypad.pointer:setMarginTop(0) + keypad.pointer:setMarginLeft(0) + return true +end + +function executeWalk() + removeEvent(keypadEvent) + keypadEvent = nil + if not modules.game_walking or not g_mouse.isPressed(MouseTouch) then + keypad.pointer:setMarginTop(0) + keypad.pointer:setMarginLeft(0) + return + end + keypadEvent = scheduleEvent(executeWalk, 20) + keypadMousePos.x = math.min(1, math.max(0, keypadMousePos.x)) + keypadMousePos.y = math.min(1, math.max(0, keypadMousePos.y)) + local angle = math.atan2(keypadMousePos.x - 0.5, keypadMousePos.y - 0.5) + local maxTop = math.abs(math.cos(angle)) * 75 + local marginTop = math.max(-maxTop, math.min(maxTop, (keypadMousePos.y - 0.5) * 150)) + local maxLeft = math.abs(math.sin(angle)) * 75 + local marginLeft = math.max(-maxLeft, math.min(maxLeft, (keypadMousePos.x - 0.5) * 150)) + keypad.pointer:setMarginTop(marginTop) + keypad.pointer:setMarginLeft(marginLeft) + local dir + if keypadMousePos.y < 0.3 and keypadMousePos.x < 0.3 then + dir = Directions.NorthWest + elseif keypadMousePos.y < 0.3 and keypadMousePos.x > 0.7 then + dir = Directions.NorthEast + elseif keypadMousePos.y > 0.7 and keypadMousePos.x < 0.3 then + dir = Directions.SouthWest + elseif keypadMousePos.y > 0.7 and keypadMousePos.x > 0.7 then + dir = Directions.SouthEast + end + if not dir and (math.abs(keypadMousePos.y - 0.5) > 0.1 or math.abs(keypadMousePos.x - 0.5) > 0.1) then + if math.abs(keypadMousePos.y - 0.5) > math.abs(keypadMousePos.x - 0.5) then + if keypadMousePos.y < 0.5 then + dir = Directions.North + else + dir = Directions.South + end + else + if keypadMousePos.x < 0.5 then + dir = Directions.West + else + dir = Directions.East + end + end + end + if dir then + modules.game_walking.walk(dir, keypadTicks) + if keypadTicks == 0 then + keypadTicks = 100 + end + end end \ No newline at end of file diff --git a/modules/client_mobile/mobile.otui b/modules/client_mobile/mobile.otui index e5ddb93..c85e83c 100644 --- a/modules/client_mobile/mobile.otui +++ b/modules/client_mobile/mobile.otui @@ -13,3 +13,27 @@ UIWidget y: 0 focusable: false phantom: true + + UIWidget + id: keypad + size: 200 150 + anchors.bottom: parent.bottom + anchors.right: parent.right + phantom: false + focusable: false + visible: false + background: #00000044 + image-source: /images/game/mobile/keypad + image-fixed-ratio: true + image-rect: 25 0 150 150 + + UIWidget + id: pointer + size: 49 49 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/game/mobile/keypad_pointer + image-fixed-ratio: true + phantom: true + focusable: false + \ No newline at end of file diff --git a/modules/client_options/options.lua b/modules/client_options/options.lua index c5905a8..3936f21 100644 --- a/modules/client_options/options.lua +++ b/modules/client_options/options.lua @@ -5,7 +5,7 @@ local defaultOptions = { showPing = true, fullscreen = false, classicView = not g_app.isMobile(), - cacheMap = false, + cacheMap = g_app.isMobile(), classicControl = not g_app.isMobile(), smartWalk = false, dash = false, diff --git a/modules/client_topmenu/topmenu.lua b/modules/client_topmenu/topmenu.lua index 245591d..76f74d9 100644 --- a/modules/client_topmenu/topmenu.lua +++ b/modules/client_topmenu/topmenu.lua @@ -29,11 +29,12 @@ local function addButton(id, description, icon, callback, panel, toggle, front, button:setTooltip(description) button:setIcon(resolvepath(icon, 3)) button.onMouseRelease = function(widget, mousePos, mouseButton) - if widget:containsPoint(mousePos) and mouseButton ~= MouseMidButton then + if widget:containsPoint(mousePos) and mouseButton ~= MouseMidButton and mouseButton ~= MouseTouch then callback() return true end end + button.onTouchRelease = button.onMouseRelease if not button.index and type(index) == 'number' then button.index = index end diff --git a/modules/corelib/const.lua b/modules/corelib/const.lua index 304adb1..ab5c9ce 100644 --- a/modules/corelib/const.lua +++ b/modules/corelib/const.lua @@ -36,6 +36,9 @@ MouseNoButton = 0 MouseLeftButton = 1 MouseRightButton = 2 MouseMidButton = 3 +MouseTouch = 4 +MouseTouch2 = 5 -- multitouch, 2nd finger +MouseTouch3 = 6 -- multitouch, 3th finger MouseNoWheel = 0 MouseWheelUp = 1 diff --git a/modules/game_actionbar/actionbar.lua b/modules/game_actionbar/actionbar.lua index 955ba20..344cb24 100644 --- a/modules/game_actionbar/actionbar.lua +++ b/modules/game_actionbar/actionbar.lua @@ -15,12 +15,12 @@ ActionTypes = { ActionColors = { empty = '#00000033', - text = '#88888866', - itemUse = '#8888FF66', - itemUseSelf = '#00FF0066', - itemUseTarget = '#FF000066', - itemUseWith = '#F5B32566', - itemEquip = '#FFFFFF66' + text = '#00000033', + itemUse = '#8888FF88', + itemUseSelf = '#00FF0088', + itemUseTarget = '#FF000088', + itemUseWith = '#F5B32588', + itemEquip = '#FFFFFF88' } function init() @@ -142,6 +142,7 @@ 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 @@ -165,7 +166,7 @@ function setupAction(action) action.cooldownStart = 0 if type(config.text) == 'string' and config.text:len() > 0 then action.text:setText(config.text) - action:setBorderColor(ActionColors.text) + action.item:setBorderColor(ActionColors.text) action.item:setOn(true) -- removes background action.item:setItemId(0) if Spells then @@ -188,7 +189,7 @@ function setupAction(action) else action.item:setItemId(0) action.item:setOn(false) - action:setBorderColor(ActionColors.empty) + action.item:setBorderColor(ActionColors.empty) end end end @@ -212,15 +213,15 @@ function setupActionType(action, actionType) action.config.actionType = actionType if action.config.actionType == ActionTypes.USE then - action:setBorderColor(ActionColors.itemUse) + action.item:setBorderColor(ActionColors.itemUse) elseif action.config.actionType == ActionTypes.USE_SELF then - action:setBorderColor(ActionColors.itemUseSelf) + action.item:setBorderColor(ActionColors.itemUseSelf) elseif action.config.actionType == ActionTypes.USE_TARGET then - action:setBorderColor(ActionColors.itemUseTarget) + action.item:setBorderColor(ActionColors.itemUseTarget) elseif action.config.actionType == ActionTypes.USE_WITH then - action:setBorderColor(ActionColors.itemUseWith) + action.item:setBorderColor(ActionColors.itemUseWith) elseif action.config.actionType == ActionTypes.EQUIP then - action:setBorderColor(ActionColors.itemEquip) + action.item:setBorderColor(ActionColors.itemEquip) end end @@ -237,6 +238,7 @@ function updateAction(action, newConfig) 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) @@ -292,7 +294,7 @@ function actionOnMouseRelease(action, mousePosition, mouseButton) end) menu:display(mousePosition) return true - elseif mouseButton == MouseLeftButton then + elseif mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3 then action.callback() return true end diff --git a/modules/game_actionbar/actionbar.otui b/modules/game_actionbar/actionbar.otui index a4e510a..fc81d82 100644 --- a/modules/game_actionbar/actionbar.otui +++ b/modules/game_actionbar/actionbar.otui @@ -1,10 +1,10 @@ ActionButton < Panel - size: 36 36 font: cipsoftFont anchors.top: parent.top - margin-left: 3 - border-width: 1 - border-color: #00000022 + anchors.bottom: parent.bottom + width: 40 + padding: 1 1 1 1 + margin-left: 1 $first: anchors.left: parent.left @@ -15,18 +15,19 @@ ActionButton < Panel Item id: item anchors.fill: parent - margin: 1 1 1 1 &selectable: true &editable: false virtual: true + border-width: 1 + + border-color: #00000000 $!on: image-source: /images/game/actionbarslot - + Label id: text anchors.fill: parent - margin: 1 1 1 1 text-auto-resize: true text-wrap: true phantom: true @@ -85,7 +86,6 @@ Panel anchors.left: prev.right anchors.right: next.left margin-right: 3 - margin-top: 2 clipping: true TabButton diff --git a/modules/game_battle/battle.lua b/modules/game_battle/battle.lua index 92b2a3d..a5ce972 100644 --- a/modules/game_battle/battle.lua +++ b/modules/game_battle/battle.lua @@ -126,7 +126,11 @@ end function getSortType() local settings = g_settings.getNode('BattleList') if not settings then - return 'name' + if g_app.isMobile() then + return 'distance' + else + return 'name' + end end return settings['sortType'] end @@ -219,7 +223,7 @@ function updateBattleList() end function checkCreatures() - if not g_game.isOnline() then + if not battlePanel or not g_game.isOnline() then return end @@ -257,11 +261,17 @@ function checkCreatures() local battleButton = battleButtons[i] battleButton:creatureSetup(creature) battleButton:show() + battleButton:setOn(true) + end + + if g_app.isMobile() and #creatures > 0 then + onBattleButtonHoverChange(battleButtons[1], true) end for i=#creatures + 1,maxCreatures do if battleButtons[i]:isHidden() then break end battleButtons[i]:hide() + battleButton:setOn(false) end battlePanel:getLayout():enableUpdates() diff --git a/modules/game_battle/battle.otui b/modules/game_battle/battle.otui index 0d0ef05..c3af2e3 100644 --- a/modules/game_battle/battle.otui +++ b/modules/game_battle/battle.otui @@ -66,11 +66,13 @@ MiniWindow BattlePlayers id: hidePlayers !tooltip: tr('Hide players') + @onSetup: if g_app.isMobile() then self:setChecked(true) end @onCheckChange: modules.game_battle.checkCreatures() - + BattleNPCs id: hideNPCs !tooltip: tr('Hide Npcs') + @onSetup: if g_app.isMobile() then self:setChecked(true) end @onCheckChange: modules.game_battle.checkCreatures() BattleMonsters @@ -86,6 +88,7 @@ MiniWindow BattleParty id: hideParty !tooltip: tr('Hide party members') + @onSetup: if g_app.isMobile() then self:setChecked(true) end @onCheckChange: modules.game_battle.checkCreatures() Panel diff --git a/modules/game_bot/default_configs/cavebot_1.2/main.lua b/modules/game_bot/default_configs/cavebot_1.2/main.lua index fe8aca5..5b21125 100644 --- a/modules/game_bot/default_configs/cavebot_1.2/main.lua +++ b/modules/game_bot/default_configs/cavebot_1.2/main.lua @@ -14,7 +14,7 @@ UI.Button("Discord", function() end) UI.Button("Forum", function() - g_platform.openUrl("http://otclient.net") + g_platform.openUrl("http://otclient.net/") end) UI.Button("Help & Tutorials", function() diff --git a/modules/game_hotkeys/hotkeys_extra.lua b/modules/game_hotkeys/hotkeys_extra.lua index 01b1f8e..6669e5c 100644 --- a/modules/game_hotkeys/hotkeys_extra.lua +++ b/modules/game_hotkeys/hotkeys_extra.lua @@ -25,7 +25,7 @@ function setupExtraHotkeys(combobox) local nextChild = nil local breakNext = false for i, child in ipairs(battlePanel:getChildren()) do - if not child.creature or child:isDisabled() then + if not child.creature or not child:isOn() then break end nextChild = child @@ -53,7 +53,7 @@ function setupExtraHotkeys(combobox) local attackedCreature = g_game.getAttackingCreature() local prevChild = nil for i, child in ipairs(battlePanel:getChildren()) do - if not child.creature or child:isDisabled() or child:isHidden() then + if not child.creature or not child:isOn() then break end if child.creature == attackedCreature then diff --git a/modules/game_interface/gameinterface.lua b/modules/game_interface/gameinterface.lua index 5cd572f..e4a5900 100644 --- a/modules/game_interface/gameinterface.lua +++ b/modules/game_interface/gameinterface.lua @@ -40,6 +40,7 @@ function init() mouseGrabberWidget = gameRootPanel:getChildById('mouseGrabber') mouseGrabberWidget.onMouseRelease = onMouseGrabberRelease + mouseGrabberWidget.onTouchRelease = mouseGrabberWidget.onMouseRelease bottomSplitter = gameRootPanel:getChildById('bottomSplitter') gameMapPanel = gameRootPanel:getChildById('gameMapPanel') @@ -267,6 +268,7 @@ function updateStretchShrink() end function onMouseGrabberRelease(self, mousePosition, mouseButton) + if mouseButton == MouseTouch then return end if selectedThing == nil then return false end if mouseButton == MouseLeftButton then local clickedWidget = gameRootPanel:recursiveGetChildByPos(mousePosition, false) @@ -575,7 +577,7 @@ function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, u createThingMenu(menuPosition, lookThing, useThing, creatureThing) return true end - if mouseButton ~= MouseLeftButton then + if mouseButton ~= MouseLeftButton and mouseButton ~= MouseTouch2 and mouseButton ~= MouseTouch3 then return false end local action = getLeftAction() @@ -707,7 +709,7 @@ function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, u local player = g_game.getLocalPlayer() player:stopAutoWalk() - if autoWalkPos and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseLeftButton then + if autoWalkPos and keyboardModifiers == KeyboardNoModifier and (mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3) then local autoWalkTile = g_map.getTile(autoWalkPos) if autoWalkTile and not autoWalkTile:isWalkable(true) then modules.game_textmessage.displayFailureMessage(tr('Sorry, not possible.')) @@ -1056,15 +1058,85 @@ function setupLeftActions() if not g_app.isMobile() then return end for _, widget in ipairs(gameLeftActions:getChildren()) do widget.image:setChecked(false) + widget.lastClicked = 0 widget.onClick = function() if widget.image:isChecked() then widget.image:setChecked(false) + if widget.doubleClickAction and widget.lastClicked + 200 > g_clock.millis() then + widget.doubleClickAction() + end return end resetLeftActions() widget.image:setChecked(true) + widget.lastClicked = g_clock.millis() end end + if gameLeftActions.use then + gameLeftActions.use.doubleClickAction = function() + local player = g_game.getLocalPlayer() + local dir = player:getDirection() + local usePos = player:getPrewalkingPosition(true) + if dir == North then + usePos.y = usePos.y - 1 + elseif dir == East then + usePos.x = usePos.x + 1 + elseif dir == South then + usePos.y = usePos.y + 1 + elseif dir == West then + usePos.x = usePos.x - 1 + end + local tile = g_map.getTile(usePos) + if not tile then return end + local thing = tile:getTopUseThing() + if thing then + g_game.use(thing) + end + end + end + if gameLeftActions.attack then + gameLeftActions.attack.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or not child:isOn()) then + child = nil + end + if child then + g_game.attack(child.creature) + else + g_game.attack(nil) + end + end + end + if gameLeftActions.follow then + gameLeftActions.follow.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or not child:isOn()) then + child = nil + end + if child then + g_game.follow(child.creature) + else + g_game.follow(nil) + end + end + end + if gameLeftActions.look then + gameLeftActions.look.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or child:isHidden()) then + child = nil + end + if child then + g_game.look(child.creature) + end + end + end if not gameLeftActions.chat then return end gameLeftActions.chat.onClick = function() if gameBottomPanel:getHeight() <= 5 then @@ -1078,6 +1150,7 @@ end function resetLeftActions() for _, widget in ipairs(gameLeftActions:getChildren()) do widget.image:setChecked(false) + widget.lastClicked = 0 end end diff --git a/modules/game_interface/widgets/uigamemap.lua b/modules/game_interface/widgets/uigamemap.lua index c91175d..67d9bf9 100644 --- a/modules/game_interface/widgets/uigamemap.lua +++ b/modules/game_interface/widgets/uigamemap.lua @@ -172,6 +172,12 @@ function UIGameMap:onMouseRelease(mousePosition, mouseButton) return ret end +function UIGameMap:onTouchRelease(mousePosition, mouseButton) + if mouseButton ~= MouseTouch then + return self:onMouseRelease(mousePosition, mouseButton) + end +end + function UIGameMap:canAcceptDrop(widget, mousePos) if not widget or not widget.currentDragThing then return false end diff --git a/modules/game_walking/walking.lua b/modules/game_walking/walking.lua index 15ec37b..95d7506 100644 --- a/modules/game_walking/walking.lua +++ b/modules/game_walking/walking.lua @@ -379,6 +379,9 @@ function walk(dir, ticks) player:lockWalk(100) -- bug fix for missing stairs down on map return else + if g_app.isMobile() and dir <= Directions.West then + turn(dir, ticks > 0) + end return -- not walkable tile end end diff --git a/otclient_dx.exe b/otclient_dx.exe index e7ef2f2..9cfd98f 100644 Binary files a/otclient_dx.exe and b/otclient_dx.exe differ diff --git a/otclient_gl.exe b/otclient_gl.exe index a63aeeb..07a9a76 100644 Binary files a/otclient_gl.exe and b/otclient_gl.exe differ diff --git a/otclient_linux b/otclient_linux index bec8b4c..8e901e9 100644 Binary files a/otclient_linux and b/otclient_linux differ diff --git a/otclientv8.apk b/otclientv8.apk index 97342a9..cf52a13 100644 Binary files a/otclientv8.apk and b/otclientv8.apk differ diff --git a/pdb/pdb.7z b/pdb/pdb.7z index 1a98e53..b518af8 100644 Binary files a/pdb/pdb.7z and b/pdb/pdb.7z differ diff --git a/run_android.bat b/run_android.bat new file mode 100644 index 0000000..b407fa1 --- /dev/null +++ b/run_android.bat @@ -0,0 +1 @@ +adb uninstall com.otclientv8 && adb install otclientv8.apk && adb logcat -c && adb shell am start -n com.otclientv8/com.otclientv8.OTClientV8 && adb logcat | findstr /i otclient \ No newline at end of file