Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for items in shortcuts addon. #2076

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 19 additions & 3 deletions addons/shortcuts/helper_functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,33 @@ function bracket_closer(str,opener,closer)
end

-----------------------------------------------------------------------------------
--Name: strip()
--Name: strip_non_alphanumeric_convert_digits_to_roman()
--Args:
---- name (string): Name to be slugged
---- name (string): Name to be stripped
-----------------------------------------------------------------------------------
--Returns:
---- string with a gsubbed version of name that removes non-alphanumeric characters,
-------- forces the string to lower-case, and converts numbers to Roman numerals,
-------- which are upper case.
-----------------------------------------------------------------------------------
function strip(name)
function strip_non_alphanumeric_convert_digits_to_roman(name)
return name:gsub('[^%w]',''):lower():gsub('(%d+)',to_roman)
end

-----------------------------------------------------------------------------------
--Name: strip_non_alphanumeric_keep_plus()
--Args:
---- name (string): Name to be stripped
-----------------------------------------------------------------------------------
--Returns:
---- string with a gsubbed version of name that removes non-alphanumeric characters,
-------- but allows the character '+', and forces the string to lower-case. Does not
-------- convert numbers to roman numerals.
-----------------------------------------------------------------------------------
function strip_non_alphanumeric_keep_plus(name)
return name:gsub('[^%w+]',''):lower()
end


-----------------------------------------------------------------------------------
--Name: to_roman()
Expand Down Expand Up @@ -186,6 +200,8 @@ function check_usability(player,resource,id)
return true
elseif resource == 'mounts' and math.floor((windower.packets.last_incoming(0x0AE):byte(math.floor(id/8)+5)%2^(id%8+1))/2^(id%8)) == 1 then
return true
elseif resource == 'items' then
return true
end
end

Expand Down
124 changes: 90 additions & 34 deletions addons/shortcuts/shortcuts.lua
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,16 @@ default_aliases = {
cw5="Curing Waltz V",
hw="Healing Waltz"
}
default_settings = {
include_items = false,
}

aliases = config.load('data\\aliases.xml',default_aliases)
aliases = config.load('data/aliases.xml', default_aliases)
settings = config.load('data/settings.xml', default_settings)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding another settings file just to allow people to disable compatibility with items seems like a lot of work for no obvious benefit. I would just make it an always-on feature. No one is going to accidentally type //proether+3 or //vileelixir+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this because of vague concerns that this would cause problems. https://discord.com/channels/338590234235371531/501099842097905675/883160617706483712

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds complexity without benefit, so I would eliminate it. Make items always-on and don't add a settings file.

config.save(aliases)
config.save(settings)
setmetatable(aliases,nil)
setmetatable(settings,nil)


require 'statics'
Expand Down Expand Up @@ -166,12 +172,12 @@ windower.register_event('outgoing text',function(original,modified)
if modified:sub(1,1) ~= '/' then return modified end
debug_chat('outgoing_text: '..modified..' '..tostring(windower.ffxi.get_mob_by_target('st')))
temp_org = temp_org:gsub(' <wait %d+>',''):sub(2)

if logging then
logfile:write('\n\n',tostring(os.clock()),'temp_org: ',temp_org,'\nModified: ',modified)
logfile:flush()
end

-- If it's the command that was just sent, blank lastsent and pass it through with only the changes applied by other addons
if modified == lastsent then
lastsent = ''
Expand Down Expand Up @@ -218,11 +224,11 @@ function command_logic(original,modified)
potential_targ = splitline[splitline.n]
end
local a,b,spell = string.find(original,'"(.-)"')

if unhandled_list[command] then
return modified,true
end

if spell then
spell = spell:lower()
elseif splitline.n == 3 then
Expand All @@ -232,17 +238,17 @@ function command_logic(original,modified)
spell = splitline[2]..' '..splitline[3]
end
end

if targ_reps[potential_targ] then
potential_targ = targ_reps[potential_targ]
end

if ignore_list[command] then -- If the command is legitimate and on the blacklist, return it unaltered.
lastsent = ''
return modified,true
elseif command2_list[command] and not valid_target(potential_targ,true) then
-- If the command is legitimate and requires target completion but not ability interpretation

if not command2_list[command].args then -- If there are not any secondary commands
local temptarg = valid_target(potential_targ) or target_make(command2_list[command]) -- Complete the target or make one.
if temptarg ~= '<me>' then -- These commands, like emotes, check, etc., don't need to default to <me>
Expand All @@ -253,7 +259,7 @@ function command_logic(original,modified)

debug_chat('258: input '..lastsent)
if logging then
logfile:write('\n\n',tostring(os.clock()),'Original: ',original,'\n(162) ',lastsent)
logfile:write('\n\n',tostring(os.clock()),'Original: ',original,'\n(162) ',lastsent)
logfile:flush()
end
windower.send_command('@input '..lastsent)
Expand Down Expand Up @@ -339,54 +345,104 @@ end
---- Sends a command if the command needs to be changed.
-----------------------------------------------------------------------------------
function interp_text(splitline,offset,modified)
local temptarg,abil
local no_targ_abil = strip(table.concat(splitline,' ',1+offset,splitline.n))

if validabils[no_targ_abil] then
abil = no_targ_abil
-- Assume there was not a target suffix on the command.
local preliminary_action_name = table.concat(splitline,' ',1+offset,splitline.n)
local preliminary_action_name_normalized_as_item = strip_non_alphanumeric_keep_plus(preliminary_action_name)
local preliminary_action_name_normalized_as_nonitem = strip_non_alphanumeric_convert_digits_to_roman(preliminary_action_name)

-- Note: The normalized 'item' name is almost strictly more specific than
-- the normalized 'nonitem' name, and thus the former must be searched
-- before the latter to avoid falsely matching the wrong entry.
local temporary_target_name, normalized_preliminary_action_name
if validabils[preliminary_action_name_normalized_as_item] then
normalized_preliminary_action_name = preliminary_action_name_normalized_as_item
elseif validabils[preliminary_action_name_normalized_as_nonitem] then
normalized_preliminary_action_name = preliminary_action_name_normalized_as_nonitem
elseif splitline.n > 1 then
temptarg = valid_target(targ_reps[splitline[splitline.n]] or splitline[splitline.n])
temporary_target_name = valid_target(targ_reps[splitline[splitline.n]] or splitline[splitline.n])
end

if temptarg then abil = _raw.table.concat(splitline,' ',1+offset,splitline.n-1)
elseif not abil then abil = _raw.table.concat(splitline,' ',1+offset,splitline.n) end

local strippedabil = strip(abil) -- Slug the ability
-- Compute a better name to look up based on the result of the above.
local finalized_action_name = normalized_preliminary_action_name
if temporary_target_name then
finalized_action_name = _raw.table.concat(splitline,' ',1+offset,splitline.n-1)
elseif not normalized_preliminary_action_name then
finalized_action_name = _raw.table.concat(splitline,' ',1+offset,splitline.n)
end

-- Re-normalize the action name, but using the finalized name
local finalized_action_name_normalized_as_item = strip_non_alphanumeric_keep_plus(finalized_action_name)
local finalized_action_name_normalized_as_nonitem = strip_non_alphanumeric_convert_digits_to_roman(finalized_action_name)

-- Note: The normalized 'item' name is almost strictly more specific than
-- the normalized 'nonitem' name, and thus the former must be searched
-- before the latter to avoid falsely matching the wrong entry.
local actions_by_normalized_name
if validabils[finalized_action_name_normalized_as_item] then
actions_by_normalized_name = validabils[finalized_action_name_normalized_as_item]
else
actions_by_normalized_name = validabils[finalized_action_name_normalized_as_nonitem]
end

if validabils[strippedabil] then
local options,nonoptions,num_opts, r_line = {},{},0
if actions_by_normalized_name then
local options,nonoptions,num_opts = {},{},0
local player = windower.ffxi.get_player()
for v in validabils[strippedabil]:it() do
for v in actions_by_normalized_name:it() do
if check_usability(player,v.res,v.id) then
options[v.res] = v.id
num_opts = num_opts + 1
elseif v.res ~= nil then
nonoptions[v.res] = v.id
end
end
if num_opts > 0 then
-- If there are usable options then prioritize:
-- Prefix, if given -> Spells -> Job Abilities -> Weapon Skills -> Monster Skills
r_line = res[(offset == 1 and options[command_list[splitline[1]]] and command_list[splitline[1]]) or (options.spells and 'spells') or (options.job_abilities and 'job_abilities') or (options.weapon_skills and 'weapon_skills') or (options.monster_skills and 'monster_skills') or (options.mounts and 'mounts')][options[command_list[splitline[1]]] or options.spells or options.job_abilities or options.weapon_skills or options.monster_skills or options.mounts]
elseif num_opts == 0 then
r_line = res[(offset == 1 and nonoptions[command_list[splitline[1]]] and command_list[splitline[1]]) or (nonoptions.spells and 'spells') or (nonoptions.weapon_skills and 'weapon_skills') or (nonoptions.job_abilities and 'job_abilities') or (nonoptions.monster_skills and 'monster_skills') or (nonoptions.mounts and 'mounts')][nonoptions[command_list[splitline[1]]] or nonoptions.spells or nonoptions.weapon_skills or nonoptions.job_abilities or nonoptions.monster_skills or nonoptions.mounts]

-- If there are usable options then prioritize:
-- Prefix, if given -> Spells -> Job Abilities -> Weapon Skills -> Monster Skills
local r_type,r_idx,r_line
local opts_to_use = num_opts > 0 and options or nonoptions
if offset == 1 and opts_to_use[command_list[splitline[1]]] then
r_type = command_list[splitline[1]]
else
r_type = (opts_to_use.spells and 'spells')
or (opts_to_use.job_abilities and 'job_abilities')
or (opts_to_use.weapon_skills and 'weapon_skills')
or (opts_to_use.monster_skills and 'monster_skills')
or (opts_to_use.mounts and 'mounts')
or (opts_to_use.items and 'items')
end
if opts_to_use[command_list[splitline[1]]] then
r_idx = opts_to_use[command_list[splitline[1]]]
else
r_idx = opts_to_use.spells
or opts_to_use.job_abilities
or opts_to_use.weapon_skills
or opts_to_use.monster_skills
or opts_to_use.mounts
or opts_to_use.items
end
r_line = res[r_type][r_idx]

-- Modify r_line to contain 'prefix' for items.
if r_line and not r_line.prefix and r_type == 'items' then
r_line = r_line:copy()
r_line.prefix = '/item'
end

local targets = table.reassign({},r_line.targets)

-- Handling for abilities that change potential targets.
if r_line.skill == 40 and r_line.cast_time == 8 and L(player.buffs):contains(409) then
targets.Party = true -- Pianissimo changes the target list of
targets.Party = true -- Pianissimo changes the target list of
elseif r_line.skill == 44 and r_line.en:find('Indi-') and L(player.buffs):contains(584) then
targets.Party = true -- Indi- spells can be cast on others when Entrust is up
end

local abil_name = r_line.english -- Remove spaces at the end of the ability name.
while abil_name:sub(-1) == ' ' do
abil_name = abil_name:sub(1,-2)
end
local out_tab = {prefix = in_game_res_commands[r_line.prefix:gsub("/","")], name = abil_name, target = temptarg or target_make(targets)}

local out_tab = {prefix = in_game_res_commands[r_line.prefix:gsub("/","")], name = abil_name, target = temporary_target_name or target_make(targets)}
if not out_tab.prefix then print('Could not find prefix',r_line.prefix) end
lastsent = out_tab.prefix..' "'..out_tab.name..'" '..out_tab.target
if logging then
Expand Down
20 changes: 12 additions & 8 deletions addons/shortcuts/statics.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ validabils = {}
-- List of valid prefixes to be interpreted with the resources. The values currently have no use.
command_list = {['ja']='job_abilities',['jobability']='job_abilities',['so']='spells',['song']='spells',['ma']='spells',['magic']='spells',['nin']='spells',['ninjutsu']='spells',
['ra']='Ranged Attack',['range']='Ranged Attack',['throw']='Ranged Attack',['shoot']='Ranged Attack',['monsterskill']='monster_skills',['ms']='monster_skills',
['ws']='weapon_skills',['weaponskill']='weapon_skills',['item']='Ability',['pet']='job_abilities',['mo']='mounts',['mount']='mounts'}
['ws']='weapon_skills',['weaponskill']='weapon_skills',['item']='items',['pet']='job_abilities',['mo']='mounts',['mount']='mounts'}

in_game_res_commands = {['ja']='/ja',['jobability']='/ja',['pet']='/ja',
['so']='/ma',['song']='/ma',['ma']='/ma',['magic']='/ma',['nin']='/ma',['ninjutsu']='/ma',
['monsterskill']='/ms',['ms']='/ms',['ws']='/ws',['weaponskill']='/ws',
['monsterskill']='/ms',['ms']='/ms',['ws']='/ws',['weaponskill']='/ws',['item']='/item',
['ra']='/ra',['range']='/ra',['throw']='/ra',['shoot']='/ra',['mount']='/mo',['mo']='/mo'}

-- List of other commands that might use name completion.
Expand Down Expand Up @@ -161,7 +161,7 @@ command2_list = {
['returnfaith']=new_cmd_entry(Party_targets,{all=No_targets}),
['bstpet']=new_cmd_entry(No_targets,{['1']=BST_targets,['2']=BST_targets,['3']=BST_targets,['4']=BST_targets,['5']=BST_targets,['6']=BST_targets,['7']=BST_targets}),
}

unhandled_list = {['p']=true,['s']=true,['sh']=true,['yell']=true,['echo']=true,['t']=true,['l']=true,['breaklinkshell']=true}

-- List of commands to be ignored
Expand All @@ -176,7 +176,7 @@ st_targs = T{'<st>','<stpc>','<stal>','<stnpc>','<stpt>'}
targ_reps = {t='<t>',me='<me>',ft='<ft>',scan='<scan>',bt='<bt>',lastst='<lastst>',r='<r>',pet='<pet>',p0='<p0>',p1='<p1>',p2='<p2>',p3='<p3>',p4='<p4>',
p5='<p5>',a10='<a10>',a11='<a11>',a12='<a12>',a13='<a13>',a14='<a14>',a15='<a15>',a20='<a20>',a21='<a21>',a22='<a22>',a23='<a23>',a24='<a24>',a25='<a25>',
st='<st>',stpc='<stpc>',stal='<stal>',stnpc='<stnpc>',stpt='<stpt>',focust='<focust>'}

language = 'english' -- windower.ffxi.get_info()['language']:lower()


Expand All @@ -198,9 +198,10 @@ end
-- Iterate through resources and make validabils.
function validabils_it(resource)
for id,v in pairs(res[resource]) do
if (not v.monster_level and v.prefix) or (v.monster_level and v.monster_level ~= -1 and v.ja:sub(1,1) ~= '#' ) then
if (not v.monster_level and v.prefix) or (v.monster_level and v.monster_level ~= -1 and v.ja:sub(1,1) ~= '#' ) or (v.category == 'Usable') then
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limits the items added to ones in the Usable category, which seem to be items like medicines, food, and clusters, but doesn't include usable gear, like Warp Ring. This seems fine to me, but highlighting this change in case someone else has a different opinion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems okay to me. If someone wants to revisit this decision, they can do it in another PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a reason why it couldn't shouldn't check equipped items and make those usable via command.
or, just, checking the flags instead of the category to see if its "use"-able

-- Monster Abilities contains a large number of player-usable moves (but not monstrosity-usable). This excludes them.
make_abil(strip(v.english),resource,id)
local name = resource ~= 'items' and strip_non_alphanumeric_convert_digits_to_roman(v.english) or strip_non_alphanumeric_keep_plus(v.english)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there name collisions if you don't keep the plus and avoid converting digits to roman numerals?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't try, but I felt /rolandaifuku+1 felt more natural than /rolandaifuku1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, but you can use /rolandaifuku+1 either way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main concern here is that this adds complexity (two slug paths) without adding any obvious benefit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. If we only used the original, it would be simply /rolandaifuku for both the normal and +1 versions: the plus gets stripped and the 1 gets omitted when converting to Roman numerals.

make_abil(name,resource,id)
end
end
end
Expand All @@ -209,4 +210,7 @@ validabils_it('spells')
validabils_it('job_abilities')
validabils_it('weapon_skills')
validabils_it('monster_skills')
validabils_it('mounts')
validabils_it('mounts')
if settings.include_items then
validabils_it('items')
end
4 changes: 2 additions & 2 deletions addons/shortcuts/targets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
-----------------------------------------------------------------------------------
function valid_target(targ,flag)
local spell_targ
local san_targ = find_san(strip(targ))
local san_targ = find_san(strip_non_alphanumeric_convert_digits_to_roman(targ))
-- If the target is whitelisted, pass it through.
if pass_through_targs:contains(targ:lower()) or st_targs:contains(targ:lower()) or (tonumber(targ:lower()) and windower.ffxi.get_mob_by_id(tonumber(targ:lower()))) then
return targ:lower()
Expand All @@ -48,7 +48,7 @@ function valid_target(targ,flag)
local current_target = windower.ffxi.get_mob_by_target('t')
local targar = {}
for i,v in pairs(windower.ffxi.get_mob_array()) do
if string.find(strip(v.name),san_targ) and (v.valid_target or v.id == windower.ffxi.get_player().id) then -- Malformed pattern somehow
if string.find(strip_non_alphanumeric_convert_digits_to_roman(v.name),san_targ) and (v.valid_target or v.id == windower.ffxi.get_player().id) then -- Malformed pattern somehow
-- Handling for whether it's a monster or not
if v.is_npc and v.spawn_type ~= 14 and current_target then
if v.id == current_target.id then
Expand Down