smartWalkDirs = {} smartWalkDir = nil wsadWalking = false nextWalkDir = nil lastWalkDir = nil lastFinishedStep = 0 autoWalkEvent = nil firstStep = true walkLock = 0 walkEvent = nil lastWalk = 0 lastTurn = 0 lastTurnDirection = 0 lastStop = 0 lastManualWalk = 0 autoFinishNextServerWalk = 0 turnKeys = {} function init() connect(g_game, { onTeleport = onTeleport }) connect(LocalPlayer, { onPositionChange = onPositionChange, onWalk = onWalk, onWalkFinish = onWalkFinish, onCancelWalk = onCancelWalk }) modules.game_interface.getRootPanel().onFocusChange = stopSmartWalk bindKeys() end function terminate() disconnect(g_game, { onTeleport = onTeleport }) disconnect(LocalPlayer, { onPositionChange = onPositionChange, onWalk = onWalk, onWalkFinish = onWalkFinish }) removeEvent(autoWalkEvent) stopSmartWalk() unbindKeys() disableWSAD() end function bindKeys() bindWalkKey('Up', North) bindWalkKey('Right', East) bindWalkKey('Down', South) bindWalkKey('Left', West) bindWalkKey('Numpad8', North) bindWalkKey('Numpad9', NorthEast) bindWalkKey('Numpad6', East) bindWalkKey('Numpad3', SouthEast) bindWalkKey('Numpad2', South) bindWalkKey('Numpad1', SouthWest) bindWalkKey('Numpad4', West) bindWalkKey('Numpad7', NorthWest) bindTurnKey('Ctrl+Up', North) bindTurnKey('Ctrl+Right', East) bindTurnKey('Ctrl+Down', South) bindTurnKey('Ctrl+Left', West) bindTurnKey('Ctrl+Numpad8', North) bindTurnKey('Ctrl+Numpad6', East) bindTurnKey('Ctrl+Numpad2', South) bindTurnKey('Ctrl+Numpad4', West) end function unbindKeys() unbindWalkKey('Up', North) unbindWalkKey('Right', East) unbindWalkKey('Down', South) unbindWalkKey('Left', West) unbindWalkKey('Numpad8', North) unbindWalkKey('Numpad9', NorthEast) unbindWalkKey('Numpad6', East) unbindWalkKey('Numpad3', SouthEast) unbindWalkKey('Numpad2', South) unbindWalkKey('Numpad1', SouthWest) unbindWalkKey('Numpad4', West) unbindWalkKey('Numpad7', NorthWest) unbindTurnKey('Ctrl+Up', North) unbindTurnKey('Ctrl+Right', East) unbindTurnKey('Ctrl+Down', South) unbindTurnKey('Ctrl+Left', West) unbindTurnKey('Ctrl+Numpad8', North) unbindTurnKey('Ctrl+Numpad6', East) unbindTurnKey('Ctrl+Numpad2', South) unbindTurnKey('Ctrl+Numpad4', West) end function enableWSAD() if wsadWalking then return end wsadWalking = true local player = g_game.getLocalPlayer() if player then player:lockWalk(100) -- 100 ms walk lock for all directions end bindWalkKey("W", North) bindWalkKey("D", East) bindWalkKey("S", South) bindWalkKey("A", West) bindTurnKey("Ctrl+W", North) bindTurnKey("Ctrl+D", East) bindTurnKey("Ctrl+S", South) bindTurnKey("Ctrl+A", West) bindWalkKey("E", NorthEast) bindWalkKey("Q", NorthWest) bindWalkKey("C", SouthEast) bindWalkKey("Z", SouthWest) end function disableWSAD() if not wsadWalking then return end wsadWalking = false unbindWalkKey("W") unbindWalkKey("D") unbindWalkKey("S") unbindWalkKey("A") unbindTurnKey("Ctrl+W") unbindTurnKey("Ctrl+D") unbindTurnKey("Ctrl+S") unbindTurnKey("Ctrl+A") unbindWalkKey("E") unbindWalkKey("Q") unbindWalkKey("C") unbindWalkKey("Z") end function bindWalkKey(key, dir) local gameRootPanel = modules.game_interface.getRootPanel() g_keyboard.bindKeyDown(key, function() changeWalkDir(dir) end, gameRootPanel, true) g_keyboard.bindKeyUp(key, function() changeWalkDir(dir, true) end, gameRootPanel, true) g_keyboard.bindKeyPress(key, function(c, k, ticks) smartWalk(dir, ticks) end, gameRootPanel) end function unbindWalkKey(key) local gameRootPanel = modules.game_interface.getRootPanel() g_keyboard.unbindKeyDown(key, gameRootPanel) g_keyboard.unbindKeyUp(key, gameRootPanel) g_keyboard.unbindKeyPress(key, gameRootPanel) end function bindTurnKey(key, dir) turnKeys[key] = dir local gameRootPanel = modules.game_interface.getRootPanel() g_keyboard.bindKeyDown(key, function() turn(dir, false) end, gameRootPanel) g_keyboard.bindKeyPress(key, function() turn(dir, true) end, gameRootPanel) g_keyboard.bindKeyUp(key, function() local player = g_game.getLocalPlayer() if player then player:lockWalk(200) end end, gameRootPanel) end function unbindTurnKey(key) turnKeys[key] = nil local gameRootPanel = modules.game_interface.getRootPanel() g_keyboard.unbindKeyDown(key, gameRootPanel) g_keyboard.unbindKeyPress(key, gameRootPanel) g_keyboard.unbindKeyUp(key, gameRootPanel) end function stopSmartWalk() smartWalkDirs = {} smartWalkDir = nil end function changeWalkDir(dir, pop) while table.removevalue(smartWalkDirs, dir) do end if pop then if #smartWalkDirs == 0 then stopSmartWalk() return end else table.insert(smartWalkDirs, 1, dir) end smartWalkDir = smartWalkDirs[1] if modules.client_options.getOption('smartWalk') and #smartWalkDirs > 1 then for _,d in pairs(smartWalkDirs) do if (smartWalkDir == North and d == West) or (smartWalkDir == West and d == North) then smartWalkDir = NorthWest break elseif (smartWalkDir == North and d == East) or (smartWalkDir == East and d == North) then smartWalkDir = NorthEast break elseif (smartWalkDir == South and d == West) or (smartWalkDir == West and d == South) then smartWalkDir = SouthWest break elseif (smartWalkDir == South and d == East) or (smartWalkDir == East and d == South) then smartWalkDir = SouthEast break end end end end function smartWalk(dir, ticks) walkEvent = scheduleEvent(function() if g_keyboard.getModifiers() == KeyboardNoModifier then local direction = smartWalkDir or dir walk(direction, ticks) return true end return false end, 20) end function canChangeFloorDown(pos) pos.z = pos.z + 1 toTile = g_map.getTile(pos) return toTile and toTile:hasElevation(3) end function canChangeFloorUp(pos) pos.z = pos.z - 1 toTile = g_map.getTile(pos) return toTile and toTile:isWalkable() end function onPositionChange(player, newPos, oldPos) end function onWalk(player, newPos, oldPos) if autoFinishNextServerWalk + 200 > g_clock.millis() then player:finishServerWalking() end end function onTeleport(player, newPos, oldPos) if not newPos or not oldPos then return end -- floor change is also teleport if math.abs(newPos.x - oldPos.x) >= 3 or math.abs(newPos.y - oldPos.y) >= 3 or math.abs(newPos.z - oldPos.z) >= 2 then -- far teleport, lock walk for 100ms walkLock = g_clock.millis() + g_settings.getNumber('walkTeleportDelay') else walkLock = g_clock.millis() + g_settings.getNumber('walkStairsDelay') end nextWalkDir = nil -- cancel autowalk end function onWalkFinish(player) lastFinishedStep = g_clock.millis() if nextWalkDir ~= nil then removeEvent(autoWalkEvent) autoWalkEvent = addEvent(function() if nextWalkDir ~= nil then walk(nextWalkDir, 0) end end, false) end end function onCancelWalk(player) player:lockWalk(50) end function walk(dir, ticks) lastManualWalk = g_clock.millis() local player = g_game.getLocalPlayer() if not player or g_game.isDead() or player:isDead() then return end if player:isWalkLocked() then nextWalkDir = nil return end if g_game.isFollowing() then g_game.cancelFollow() end if player:isAutoWalking() then if lastStop + 100 < g_clock.millis() then lastStop = g_clock.millis() player:stopAutoWalk() g_game.stop() end end local dash = false local ignoredCanWalk = false if not g_game.getFeature(GameNewWalking) then dash = g_settings.getBoolean("dash", false) end local ticksToNextWalk = player:getStepTicksLeft() if not player:canWalk(dir) then -- canWalk return false when previous walk is not finished or not confirmed by server if dash then ignoredCanWalk = true else if ticksToNextWalk < 500 and (lastWalkDir ~= dir or ticks == 0) then nextWalkDir = dir end if ticksToNextWalk < 30 and lastFinishedStep + 400 > g_clock.millis() and nextWalkDir == nil then -- clicked walk 20 ms too early, try to execute again as soon possible to keep smooth walking nextWalkDir = dir end return end end --if nextWalkDir ~= nil and lastFinishedStep + 200 < g_clock.millis() then -- print("Cancel " .. nextWalkDir) -- nextWalkDir = nil --end if nextWalkDir ~= nil and nextWalkDir ~= lastWalkDir then dir = nextWalkDir end local toPos = player:getPrewalkingPosition(true) if dir == North then toPos.y = toPos.y - 1 elseif dir == East then toPos.x = toPos.x + 1 elseif dir == South then toPos.y = toPos.y + 1 elseif dir == West then toPos.x = toPos.x - 1 elseif dir == NorthEast then toPos.x = toPos.x + 1 toPos.y = toPos.y - 1 elseif dir == SouthEast then toPos.x = toPos.x + 1 toPos.y = toPos.y + 1 elseif dir == SouthWest then toPos.x = toPos.x - 1 toPos.y = toPos.y + 1 elseif dir == NorthWest then toPos.x = toPos.x - 1 toPos.y = toPos.y - 1 end local toTile = g_map.getTile(toPos) if walkLock >= g_clock.millis() and lastWalkDir == dir then nextWalkDir = nil return end if firstStep and lastWalkDir == dir and lastWalk + g_settings.getNumber('walkFirstStepDelay') > g_clock.millis() then firstStep = false walkLock = lastWalk + g_settings.getNumber('walkFirstStepDelay') return end if dash and lastWalkDir == dir and lastWalk + 50 > g_clock.millis() then return end firstStep = (not player:isWalking() and lastFinishedStep + 100 < g_clock.millis() and walkLock + 100 < g_clock.millis()) if player:isServerWalking() and not dash then walkLock = walkLock + math.max(g_settings.getNumber('walkFirstStepDelay'), 100) end nextWalkDir = nil removeEvent(autoWalkEvent) autoWalkEvent = nil local preWalked = false if toTile and toTile:isWalkable() then if not player:isServerWalking() and not ignoredCanWalk then player:preWalk(dir) preWalked = true end else local playerTile = player:getTile() if (playerTile and playerTile:hasElevation(3) and canChangeFloorUp(toPos)) or canChangeFloorDown(toPos) or (toTile and toTile:isEmpty() and not toTile:isBlocking()) then player:lockWalk(100) elseif player:isServerWalking() then g_game.stop() return elseif not toTile then player:lockWalk(100) -- bug fix for missing stairs down on map else if g_app.isMobile() and dir <= Directions.West then turn(dir, ticks > 0) end return -- not walkable tile end end if player:isServerWalking() and not dash then g_game.stop() player:finishServerWalking() autoFinishNextServerWalk = g_clock.millis() + 200 end g_game.walk(dir, preWalked) if not firstStep and lastWalkDir ~= dir then walkLock = g_clock.millis() + g_settings.getNumber('walkTurnDelay') end lastWalkDir = dir lastWalk = g_clock.millis() return true end function turn(dir, repeated) local player = g_game.getLocalPlayer() if player:isWalking() and player:getWalkDirection() == dir and not player:isServerWalking() then return end removeEvent(walkEvent) if not repeated or (lastTurn + 100 < g_clock.millis()) then g_game.turn(dir) changeWalkDir(dir) lastTurn = g_clock.millis() if not repeated then lastTurn = g_clock.millis() + 50 end lastTurnDirection = dir nextWalkDir = nil player:lockWalk(g_settings.getNumber('walkCtrlTurnDelay')) end end function checkTurn() for keys, direction in pairs(turnKeys) do if g_keyboard.areKeysPressed(keys) then turn(direction, false) end end end