Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions addons/Lockstyle/Lockstyle.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
-- Copyright © 2026, Sechs
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
-- * Neither the name of AnnounceTarget nor the
-- names of its contributors may be used to endorse or promote products
-- derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL Sechs BE LIABLE FOR ANY
-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

local config = require('config')
require('logger')

_addon.name = 'Lockstyle'
_addon.author = 'Sechs'
_addon.version = '1.2'
_addon.commands = {'lockstyle', 'ls'}

local job_list = {'WAR','MNK','WHM','BLM','RDM','THF','PLD','DRK','BST','BRD','RNG','SAM','NIN','DRG','SMN','BLU','COR','PUP','DNC','SCH','GEO','RUN'}

local defaults = {}
defaults.delay = 10
for _, job in ipairs(job_list) do
defaults[job] = ''
end
defaults.apply_on_login = true

local settings = config.load(defaults)
local packets = require('packets')

local function init_random()
math.randomseed(os.time())
math.random()
end

local function parse_list(str)
local t = {}
if str == nil then
return t
end
for token in tostring(str):gmatch('[^,%s]+') do
local n = tonumber(token)
if n then
table.insert(t, n)
end
end
return t
end

local function pick_style(job)
if not job or settings[job] == nil then
return nil
end
local list = parse_list(settings[job])
local count = #list
if count == 0 then
return nil
else
return list[math.random(count)]
end
end

local pending_id = 0
local last_style = {}
local last_main_id = nil

local function track_main_job()
local player = windower.ffxi.get_player()
if player then
last_main_id = player.main_job_id
end
end

local function schedule_apply(job, mode, forced_delay)
mode = mode or 'new'
pending_id = pending_id + 1
local my_id = pending_id

local delay = forced_delay
if delay == nil then
delay = tonumber(settings.delay) or 0
end
if delay < 0 then
delay = 0
end

coroutine.schedule(function()
if my_id ~= pending_id then
return
end

local player = windower.ffxi.get_player()
if not player then
return
end
if job ~= nil and player.main_job ~= job then
return
end

local style
if mode == 'same' and last_style[job] ~= nil then
style = last_style[job]
else
style = pick_style(job)
if style then
last_style[job] = style
end
end

if style then
windower.send_command('input /lockstyleset ' .. style)
end
end, delay)
end

windower.register_event('job change', function(main_job_id)
local player = windower.ffxi.get_player()
if not player then
return
end
if main_job_id ~= last_main_id then
last_main_id = main_job_id
schedule_apply(player.main_job, 'new')
end
end)

windower.register_event('load', function()
init_random()
track_main_job()
local player = windower.ffxi.get_player()
if player then
schedule_apply(player.main_job, 'new')
end
end)

windower.register_event('login', function()
track_main_job()
if not settings.apply_on_login then
return
end
local player = windower.ffxi.get_player()
if player then
schedule_apply(player.main_job, 'new')
end
end)

windower.register_event('outgoing chunk', function(id, original, modified)
if id ~= 0x053 then return end
local player = windower.ffxi.get_player()
local packet = packets.parse('outgoing', original)
if packet.Type == 0 and player then
schedule_apply(player.main_job, 'same')
end
end)


-- Manual commands
windower.register_event('addon command', function(...)
local args = {...}
local cmd = args[1] and args[1]:lower() or ''

if cmd == 'random' or cmd == 'reroll' then
local player = windower.ffxi.get_player()
if not player then
return
end
schedule_apply(player.main_job, 'new', 0)
log('new random for ' .. player.main_job .. '.')

elseif cmd == 'status' then
local player = windower.ffxi.get_player()
if not player then
return
end
local job = player.main_job
local list_str = settings[job] or ''
if list_str == '' then
log('[' .. job .. ']: no set defined.')
else
log('[' .. job .. ']: ' .. list_str .. ' (delay: ' .. tostring(settings.delay) .. 's, apply_on_login: ' .. tostring(settings.apply_on_login) .. ')')
end

elseif cmd == 'reload' then
config.reload(settings)
log('config reloaded from the file.')

else
log('commands: //lockstyle random | status | reload | help')
end
end)
32 changes: 32 additions & 0 deletions addons/Lockstyle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
**Authors:** Sechs
**Version:** 1.2
**Date:** 09/09/2026


**Description:**
Lockstyle is a very simple addon that allows you to define a list of lockstyles for each job, customizable for each of your character.
Once you have a list ready for the current job you're on, Lockstyle will pick a random one in the list and will apply that lockstyle, mantaining it if for any reason it gets removed.
Lockstyle won't reapply your lockstyle if you manually change to another one through the in-game commands.
Lockstyle picks a random style each time you swap your main job to a job that has a list defined in the config file.
Alternatively you can use the manual command to force a reroll.
By default the delay is set to 10 seconds but this number can be changed in the config file.
The delay is ignored when you use the reroll manual command.
The apply_on_login setting if set on true (default) also applies the lockstyle when you login or relog coming from a mule. If set to false it only applies it when you change job, subjob, manual command etc.


**Commands:**
* //ls reload
* //ls random
* //ls reroll
* //ls status


**Commands details:**
* full command is //lockstyle but //ls is also accepted
* reload - Reloads the xml file
* random - rerolls the current lockstyle
* reroll - same as above
* status - displays a list of the currently defined sets

**Special Note on Delay:**
If you put a delay under 10 seconds (which is the default value) it will probably work most of the time, but there is at least one specific situation where you will incur into an error. If you remove the lockstyle (with the in-game command /lockstyle off) that counts as a lockstyle command, the same as the one that apply it. As such, it DOES incur into the serverwide 10 seconds cooldown. Which means the addon would try to reapply the just lost lockstyle again with a delay of under 10 seconds, resulting in an in-game error because the cooldown is not over yet. So I warmly suggest to not put the delay under 10.
7 changes: 7 additions & 0 deletions addons/addons.xml
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,13 @@
<bugtracker>https://github.com/Windower/Lua/issues</bugtracker>
<support>https://discord.gg/aUrHCvk</support>
</addon>
<addon>
<name>Lockstyle</name>
<author>Sechs</author>
<description>Randomizes a lockstyle from an user defined list, personalized for each job and each character.</description>
<bugtracker>https://github.com/Windower/Lua/issues</bugtracker>
<support>http://www.ffxiah.com/player/Asura/Sechs</support>
</addon>
<addon>
<name>Logger</name>
<author>Arcon</author>
Expand Down