DiscipleOfEris / Windower4Addons

Addons for FFXI using Windower4 that I (DiscipleOfEris) have written or modified.
MIT License
10 stars 1 forks source link

FastFollow: Error on zonig (POSwarp into new zone) #5

Open SvenBent opened 1 year ago

SvenBent commented 1 year ago

Sometimes i will get the message " cant enter this area" or something along the line, but he mule still enters the zone but is POS hacked to a weird location in the middle of the map.

I have disabled the outgoing packet monitoring to avoid this. Is it possible to have the old zone mechanics back where it just nudges the mule in the right direction instead of injecting packets ?

SvenBent commented 1 year ago

I stripped out all packet functions calls

_addon.name = 'FastFollow'
_addon.author = 'DiscipleOfEri Safety modification by Daneblood'
_addon.version = '1.2.3b'
_addon.commands = {'fastfollow', 'ffo'}

-- TODO: pause on ranged attacks.

require('strings')
require('tables')
require('sets')
require('coroutine')
res = require('resources')
spells = require('spell_cast_times')
items = res.items
config = require('config')
texts = require('texts')
require('logger')
require('strings')

defaults = {}
defaults.show = false
defaults.min = 0.5
defaults.display = {}
defaults.display.pos = {}
defaults.display.pos.x = 0
defaults.display.pos.y = 0
defaults.display.bg = {}
defaults.display.bg.red = 0
defaults.display.bg.green = 0
defaults.display.bg.blue = 0
defaults.display.bg.alpha = 102
defaults.display.text = {}
defaults.display.text.font = 'Consolas'
defaults.display.text.red = 255
defaults.display.text.green = 255
defaults.display.text.blue = 255
defaults.display.text.alpha = 255
defaults.display.text.size = 10

settings = config.load(defaults)
box = texts.new("", settings.display, settings)

follow_me = 0
following = false
target = nil
last_target = nil
min_dist = settings.min^2
max_dist = 50.0^2
spell_dist = 20.4^2
repeated = false
last_self = nil
last_zone = os.clock()
zone_suppress = 3
zone_min_dist = 1.0^2
zoned = false
running = false
cast_time = 0
pause_delay = 0.1
pause_dismount_delay = 0.5
pauseon = S{}
co = nil
tracking = false

track_info = T{}

windower.register_event('unload', function()
  windower.send_command('ffo stop')
  coroutine.sleep(0.25) -- Reduce crash on reload, since Windower seems to crash if IPC messages are received as it's restarting.
end)

windower.register_event('addon command', function(command, ...)
  command = command and command:lower() or nil
  args = T{...}

  if not command then
    log('Provide a name to follow, or "me" to make others follow you.')
    log('Stop following with "stop" on a single character, or "stopall" on all characters.')
    log('Can configure auto-pausing with pauseon|pausedelay commands.')
  elseif command == 'followme' or command == 'me' then
    self = windower.ffxi.get_mob_by_target('me')
    if not self and not repeated then
      repeated = true
      windower.send_command('@wait 1; ffo followme')
      return
    end

    repeated = false
    windower.send_ipc_message('follow '..self.name)
    windower.send_ipc_message('track '..(settings.show and 'on' or 'off'))
  elseif command == 'stop' then
    if following then windower.send_ipc_message('stopfollowing '..following) end
    following = false
    tracking = false
  elseif command == 'stopall' then
    follow_me = 0
    following = false
    tracking = false
    windower.send_ipc_message('stop')
  elseif command == 'follow' then
    if #args == 0 then
      return windower.add_to_chat(0, 'FastFollow: You must provide a player name to follow.')
    end
    following = args[1]:lower()
    windower.send_ipc_message('following '..following)
    windower.ffxi.follow()
  elseif command == 'pauseon' then
    if #args == 0 then
      return windower.add_to_chat(0, 'FastFollow: To change pausing behavior, provide spell|item|any to pauseon.')
    end

    local arg = args[1]:lower()
    if arg == 'spell' or arg == 'any' then
      if pauseon:contains('spell') then pauseon:remove('spell')
      else pauseon:add('spell') end
    end
    if arg == 'item' or arg == 'any' then
      if pauseon:contains('item') then pauseon:remove('item')
      else pauseon:add('item') end
    end
    if arg == 'dismount' or arg == 'any' then
      if pauseon:contains('dismount') then pauseon:remove('dismount')
      else pauseon:add('dismount') end
    end

    windower.add_to_chat(0, 'FastFollow: Pausing on Spell: '..tostring(pauseon:contains('spell'))..', Item: '..tostring(pauseon:contains('item')))
    -- TODO: Save settings.
  elseif command == 'pausedelay' then
    pause_delay = tonumber(args[1])
    windower.add_to_chat(0, 'FastFollow: Setting item/spell pause delay to '..tostring(pause_delay)..' seconds.')
  elseif command == 'info' then
    if not args[1] then
      settings.show = not settings.show
    elseif args[1] == 'on' then
      settings.show = true
    elseif args[2] == 'off' then
      settings.show = false
    end

    windower.send_ipc_message('track '..(settings.show and 'on' or 'off'))

    config.save(settings)
  elseif command == 'min' then
    local dist = tonumber(args[1])
    if not dist then return end

    dist = math.min(math.max(0.2, dist), 50.0)

    settings.min = dist
    min_dist = settings.min^2
    config.save(settings)
  elseif command and #args == 0 then
    windower.send_command('ffo follow '..command)
  end
end)

windower.register_event('ipc message', function(msgStr)
  local args = msgStr:lower():split(' ')
  local command = args:remove(1)

  if command == 'stop' then
    follow_me = 0
    following = false
    tracking = false
    windower.ffxi.run(false)
  elseif command == 'follow' then
    if following then windower.send_ipc_message('stopfollowing '..following) end
    following = args[1]
    target_pos = nil
    last_target_pos = nil
    windower.send_ipc_message('following '..following)
    windower.ffxi.follow()
  elseif command == 'following' then
    self = windower.ffxi.get_player()
    if not self or self.name:lower() ~= args[1] then return end
    follow_me = follow_me + 1
  elseif command == 'stopfollowing' then
    self = windower.ffxi.get_player()
    if not self or self.name:lower() ~= args[1] then return end
    follow_me = math.max(follow_me - 1, 0)
  elseif command == 'update' then
    local pos = {x=tonumber(args[3]), y=tonumber(args[4])}
    track_info[args[1]] = pos

    if not following or args[1] ~= following then return end

    zoned = false
    target = {x=pos.x, y=pos.y, zone=tonumber(args[2])}

    if not last_target then last_target = target end

     if target.zone ~= -1 and (target.x ~= last_target.x or target.y ~= last_target.y or target.zone ~= last_target.zone) then
      last_target = target
    end
  elseif command == 'zone' then
    if not following or args[1] ~= following then return end

    local zone_line = tonumber(args[2])
    local zone_type = tonumber(args[3])

    if zone_line and zone_type then zone(zone_line, zone_type, target.zone, target.x, target.y) end
  elseif command == 'track' then
    tracking = args[1] == 'on' and true or false
  end
end)

windower.register_event('prerender', function()
  updateInfo()

  if not follow_me and not following then return end

  if follow_me > 0 then
    local self = windower.ffxi.get_mob_by_target('me')
    local info = windower.ffxi.get_info()

    if not self or not info then return end

    args = T{'update', self.name , info.zone, self.x, self.y}
    windower.send_ipc_message(args:concat(' '))
  elseif following then
    local self = windower.ffxi.get_mob_by_target('me')
    local info = windower.ffxi.get_info()

    if not self or not info then return end

    if tracking then
      windower.send_ipc_message('update '..self.name..' '..info.zone..' '..self.x..' '..self.y)
    end

    if not target then
      if running then
        windower.ffxi.run(false)
        running = false
      end
      return
    end

    distSq = distanceSquared(target, self)
    len = math.sqrt(distSq)
    if len < 1 then len = 1 end

    if target.zone == info.zone and distSq > min_dist and distSq < max_dist then
      windower.ffxi.run((target.x - self.x)/len, (target.y - self.y)/len)
      running = true
    elseif target.zone == info.zone and distSq <= min_dist then
      windower.ffxi.run(false)
      running = true
    elseif running then
      windower.ffxi.run(false)
      running = false
    end
  end
end)

function updateInfo()
  box:visible(settings.show)

  if not settings.show then return end

  local self = windower.ffxi.get_mob_by_target('me')

  if not self then
    box:visible(false)
    return
  end

  lines = T{}
  for char,pos in pairs(track_info) do
    local dist = math.sqrt(distanceSquared(self, pos))
    lines:insert(string.format('%s %.2f', char, dist))
  end

  local maxWidth = math.max(1, table.reduce(lines, function(a, b) return math.max(a, #b) end, '1'))
  for i,line in ipairs(lines) do lines[i] = lines[i]:lpad(' ', maxWidth) end
  box:text(lines:concat('\n'))
end

function distanceSquared(A, B)
  local dx = B.x-A.x
  local dy = B.y-A.y
  return dx*dx + dy*dy
end