diff --git a/jobchange/README.md b/jobchange/README.md new file mode 100644 index 00000000..62f64a85 --- /dev/null +++ b/jobchange/README.md @@ -0,0 +1,17 @@ +**Author:** Sammeh
+**v5 Migration:** Akaden
+**Version:** 1.0.0.0
+**Date:** April 28, 2020
+ +# Job Change # + +* Change jobs with a single text command +* Reset job ability timers by changing jobs quickly. + +---- + +**Main Command:** `/jc` + +#### Commands: #### +* 1: /jc main/sub - Changes main and sub jobs to the given values. Also accepts "/jc /sub" and even "/jc main/" or "/jc main". (EG: "/jc cor/nin", "/jc /blm", "/jc war") +* 2: /jc reset - Resets job ability timers by changing sub job to another job and then back to your current sub job. diff --git a/jobchange/jobchange.lua b/jobchange/jobchange.lua new file mode 100644 index 00000000..218cdd2b --- /dev/null +++ b/jobchange/jobchange.lua @@ -0,0 +1,215 @@ +--[[ +Copyright © 2020, JobChange +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 JobChange 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 THE AUTHOR 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. +]] + +string = require('string.ext') +table = require('table') +set = require('set') +list = require('list') + +packets = require('packets') +client_data = require ('client_data') + +entities = require('entities') +player = require('player') +world = require('world') + +chat = require('core.chat') +command = require('core.command')settings = require('settings') + +local defaults = { + log_to_chat = false, +} +local options = settings.load(defaults) + +local temp_jobs = list(13, 19, 1, 2, 3, 4, 5) +local mog_zones = set('Selbina', 'Mhaura', 'Tavnazian Safehold', 'Nashmau', 'Rabao', 'Kazham', 'Norg') +local moogles = set('Moogle', 'Nomad Moogle', 'Green Thumb Moogle') + +local log = function(msg) + if options.log_to_chat then + chat.add_text('JobChange: '..msg) + end +end + +local jobchange = function(job_id, is_main) + local field_name = is_main and 'main_job_id' or 'sub_job_id' + packets.outgoing[0x100]:inject({ + [field_name] = job_id, + }) +end + +local find_job = function(job_name) + if job_name == nil or job_name == '' then + return nil + end + + for index, value in pairs(client_data.jobs) do + local job_level = player.job_levels[index] + if value.abbreviation:lower() == job_name:lower() and job_level > 0 then + return index + end + end +end + +local find_job_change_npc = function() + if not (world.mog_house or mog_zones:contains(client_data.zones[world.zone_id].english)) then + log('Not in a zone with a Change NPC') + return + end + + local closest_moogle, closest_distance + for _, npc in pairs(entities.npcs) do + if npc ~= nil and not npc.flags.hidden and npc.distance < 36 and moogles:contains(npc.name) then + if closest_distance == nil or npc.distance < closest_distance then + closest_moogle = npc + closest_distance = npc.distance + end + end + end + return closest_moogle +end + +local find_conflict = function(job_id) + if player.main_job_id == job_id then + return true + end + if player.sub_job_id == job_id then + return true + end +end + +local find_temp_job = function() + for _, job_id in pairs(temp_jobs) do -- check temp jobs (nin, dnc, war, mnk, whm, blm, rdm, thf) + if not find_conflict(job_id) and player.job_levels[job_id] > 0 then + return job_id + end + end +end + +local solve_jobchange = function(main_id, sub_id) + if main_id == nil and sub_id == nil then + log('No change required.') + + return + end + local changes = list() + + if main_id ~= nil and main_id == player.sub_job_id then + if sub_id ~= nil and sub_id == player.main_job_id then + changes:add({job_id=find_temp_job(), is_conflict=true, is_main=false}) + changes:add({job_id=main_id, is_main=true}) + changes:add({job_id=sub_id, is_main=false}) + else + if sub_id ~= nil then + changes:add({job_id=sub_id, is_main=false}) + else + changes:add({job_id=find_temp_job(), is_conflict=true, is_main=false}) + end + changes:add({job_id=main_id, is_main=true}) + end + elseif sub_id ~= nil and sub_id == player.main_job_id then + if main_id ~= nil then + changes:add({job_id=main_id, is_main=true}) + else + changes:add({job_id=find_temp_job(), is_conflict=true, is_main=true}) + end + changes:add({job_id=sub_id, is_main=false}) + else + if main_id ~= nil then + if main_id == player.main_job_id then + changes:add({job_id=find_temp_job(), is_conflict=true, is_main=true}) + end + changes:add({job_id=main_id, is_main=true}) + end + if sub_id ~= nil then + if sub_id == player.sub_job_id then + changes:add({job_id=find_temp_job(), is_conflict=true, is_main=false}) + end + changes:add({job_id=sub_id, is_main=false}) + end + end + + local npc = find_job_change_npc() + if npc then + for _, change in ipairs(changes) do + if change.is_conflict then + log('Conflict with '..(change.is_main and 'main' or 'sub')..' job. Changing to: '..client_data.jobs[change.job_id].abbreviation) + else + log('Changing '..(change.is_main and 'main' or 'sub')..' job to: '..client_data.jobs[change.job_id].abbreviation) + end + jobchange(change.job_id, change.is_main) + + coroutine.sleep(0.5) + end + else + log('Not close enough to a Moogle!') + end +end + +local handle_jobchange = function(main_name, sub_name) + local main_id = find_job(main_name) + local sub_id = find_job(sub_name) + + if main_name ~= '' and main_id == nil then + log('Could not change main job to '..main_name:upper()..' ---Mistype|NotUnlocked') + return + end + local sub_id = find_job(sub_name, p) + if sub_name ~= '' and sub_id == nil then + log('Could not change sub job to '..sub_name:upper()..' ---Mistype|NotUnlocked') + return + end + + if main_id == player.main_job_id then + main_id = nil + end + if sub_id == player.sub_job_id then + sub_id = nil + end + + coroutine.schedule(solve_jobchange, 0, main_id, sub_id) +end + +local handle_reset = function() + coroutine.schedule(solve_jobchange,0, nil, player.sub_job_id) +end + +local handle_command = function(command) + if command:lower() == 'reset' then + handle_reset() + elseif command:contains('/') then + local main_sub = command:split('/') + handle_jobchange(table.unpack(main_sub)) + else + -- assume main job. + handle_jobchange(command, '') + end +end + +local jc = command.new('jc') +jc:register(handle_command, '') \ No newline at end of file diff --git a/jobchange/manifest.xml b/jobchange/manifest.xml new file mode 100644 index 00000000..851b24f1 --- /dev/null +++ b/jobchange/manifest.xml @@ -0,0 +1,16 @@ + + jobchange + 1.0.0.0 + addon + + packets + client_data + entities + player + set + list + world + string + settings + +