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 finalAlert (new Addon) #2191

Open
wants to merge 2 commits 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
7 changes: 7 additions & 0 deletions addons/addons.xml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,13 @@
<bugtracker>https://github.com/cairface/Lua/issues</bugtracker>
<support>https://discord.gg/aUrHCvk</support>
</addon>
<addon>
<name>finalAlert</name>
<author>Godchain (Asura)</author>
<description>Displays Final Fantasy 7 style alerts when enemies use skills or magic. Allows you to emphasize skills by name (useful for interrupts).</description>
<bugtracker>https://github.com/Windower/Lua/issues</bugtracker>
<support>https://discord.gg/aUrHCvk</support>
</addon>
<addon>
<name>FFOColor</name>
<author>Nitrous (Shiva)</author>
Expand Down
40 changes: 40 additions & 0 deletions addons/finalAlert/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# finalAlert
Displays Final Fantasy 7 style alerts when enemies use skills or magic. Allows you to emphasize skills by name (useful for interrupts).

## Commands:

### Test window
Copy link
Member

Choose a reason for hiding this comment

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

I'm honestly not sure if this command should be even present in the finished addon, feel even less enthused about it being documented. But up to you. It seems like a debug command and those should not be user facing imo.

```
//fa test ws
```
Shows a test alert (accepts 'ws' for TP moves, 'ma' for magic, 'int' for interrupts).

### Emphasize a WS or spell
```
//fa emphasize Firaga VI
```
Toggles emphasis for "Firaga VI" (plays a different sound).

### Change position
```
//fa pos 960 200
```
Moves the display to 960 X (horizontal) and 200 Y (vertical).

### Change size
```
//fa size small
```
Sets the display size to small (accepts 'regular' and 'small').

### Change duration
```
//fa duration 5
```
Sets the display duration to 5 seconds.

### Turn sounds on/off
```
//fa sounds off
```
Turns off sounds except for emphasized abilities (accepts 'on' and 'off').
305 changes: 305 additions & 0 deletions addons/finalAlert/finalAlert.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
--[[
Copyright © 2022, Godchain
Copy link
Member

Choose a reason for hiding this comment

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

The entire copyright notice is not wrapped in comments. This way, the addon won't load. Wrap it in --[[ and ]].

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 finalAlert 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 Godchain 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.
]]

_addon.name = "finalAlert"
_addon.author = "Godchain (Asura)"
_addon.version = "1.4"
_addon.commands = {"finalAlert", "fa"}

config = require("config")
texts = require("texts")
res = require("resources")

caption = texts.new({})

background_ability = "background_ability"
background_magic = "background_magic"
background_interrupt = "background_interrupt"
background_emphasize = "background_emphasize"

-- Timing
showing = false
Copy link
Member

Choose a reason for hiding this comment

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

This variable can be removed entirely and be replaced with caption:visible(). It's a very cheap function call.

last_trigger = 0

-- IDs
weapon_skill_category = 7
magic_category = 8
interrupt_id = 28787
Copy link
Member

Choose a reason for hiding this comment

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

How was this determined? If this is a zone resource, it might be different for every zone, and subject to changes after updates.


defaults = {}
defaults.x_position = windower.get_windower_settings().x_res / 2
defaults.y_position = 100
defaults.background_size = "regular"
defaults.emphasize = S {}
defaults.trigger_duration = 3
defaults.sounds = "on"
Copy link
Member

Choose a reason for hiding this comment

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

I would use boolean values instead of on/off, and even rather than binary enum values, like for the background_size above. I'd reword it to large with true/false as possible values or small/compact, with the same (but reversed) values. Users are notoriously clumsy with handling a single set of allowed values, you'll not only get users who misspell the allowed values, but also users who will argue about case insensitivity. The boolean approach saves you from a bunch of possible issues.


settings = config.load(defaults)

windower.register_event(
"load",
Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't couple this to the load FFXI event, but instead use config.register to hook into the settings reload event. This will trigger on load as well, and additionally on relogging to a character with different settings.

function()
create_backgrounds(settings.x_position - 250, settings.y_position)
caption:bg_visible(false)
caption:bold(true)
end
)

windower.register_event(
"postrender",
function()
if showing then
local x, y = caption:extents()
local x_offset = settings.x_position - x / 2
local y_offset =
settings.background_size == "regular" and settings.y_position + 10 or settings.y_position + 3
caption:pos(x_offset, y_offset)
if os.time() - last_trigger > settings.trigger_duration then
hide_caption()
end
else
end
end
)

windower.register_event(
"addon command",
function(cmd, ...)
local args = L {...}

if not cmd or cmd == "help" then
local bullet = windower.to_shift_jis("» ")
print("=== Usage Examples ===")
print(bullet .. "//fa test ws")
Copy link
Member

Choose a reason for hiding this comment

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

See above about the test command.

print("Shows a test alert (accepts 'ws' for TP moves, 'ma' for magic, 'int' for interrupts).")
print(bullet .. "//fa emphasize Firaga VI")
print("Toggles emphasis for "Firaga VI" (plays a different sound).")
print(bullet .. "//fa pos 960 200")
print("Moves the display to 960 X (horizontal) and 200 Y (vertical).")
print(bullet .. "//fa size small")
print("Sets the display size to small (accepts 'regular' and 'small').")
print(bullet .. "//fa duration 5")
print("Sets the display duration to 5 seconds.")
print(bullet .. "//fa sounds off")
print("Turns off sounds except for emphasized abilities (accepts 'on' and 'off').")
elseif cmd == "test" then
if args[1] == "ws" then
show_caption("Self-Destruct", "ws")
elseif args[1] == "ma" then
show_caption("Tornado II", "ma")
elseif args[1] == "int" then
show_caption("Interrupted!", "int")
else
print('Please specify "ws", "ma" or "int".')
end
elseif cmd == "emphasize" then
local estring = args:concat(" "):gsub("%s+", ""):lower()
local verb = settings.emphasize:contains(estring) and "Removed" or "Added"
print("Emphasize: " .. verb .. ' "' .. args:concat(" ") .. '".')

if settings.emphasize:contains(estring) then
settings.emphasize:remove(estring)
else
settings.emphasize:add(estring)
end

settings:save()
elseif cmd == "pos" then
local x = tonumber(args[1])
local y = tonumber(args[2])

if type(x) == "number" and type(y) == "number" then
settings.x_position = x
settings.y_position = y
settings:save()
refresh_backgrounds()
print("Moved display to: " .. args[1] .. ", " .. args[2])
else
print("Please specify x and y coordinates.")
end
elseif cmd == "size" then
local size = args[1]

if size == "small" or size == "regular" then
settings.background_size = size
settings:save()
refresh_backgrounds()
print("Display size set to " .. size .. ".")
else
print('Please specify "small" or "regular" for the size.')
end
elseif cmd == "duration" then
local duration = tonumber(args[1])

if type(duration) == "number" and duration > 0 then
settings.trigger_duration = duration
print("Display duration set to " .. duration .. " secs.")
else
print("Please specify a positive number.")
end
elseif cmd == "sounds" then
local state = args[1]

if state == "on" or state == "off" then
settings.sounds = state
settings:save()
print("Sounds have been turned " .. state .. ".")
else
print('Please specify "on" or "off" for sounds.')
end
else
print("Unrecognized command.")
end
end
)

windower.register_event(
"action",
function(act)
local target
local t = windower.ffxi.get_mob_by_target("t")
local bt = windower.ffxi.get_mob_by_target("bt")

if t and t.is_npc and not t.in_party and not t.in_alliance then
target = t.id
elseif bt then
target = bt.id
else
return
end

if act.category == weapon_skill_category and act.actor_id == target then
local skill_name =
res.monster_abilities[act.targets[1].actions[1].param] and
res.monster_abilities[act.targets[1].actions[1].param].name or
"???"

if act.param == interrupt_id then
skill_name = "Interrupted!"
show_caption(skill_name, "int")
else
show_caption(skill_name, "ws")
end
elseif act.category == magic_category and act.actor_id == target then
local spell_name =
res.spells[act.targets[1].actions[1].param] and res.spells[act.targets[1].actions[1].param].name or
Copy link
Member

Choose a reason for hiding this comment

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

It bothers me a bit that this is broken up into three lines in the WS block, but only two lines in the magic block :\ I'm starting to get the feeling you're using a fixed width and wrap lines exceeding it, in which case I'd vote to just leave them be. Fixed-width line wraps are never a good idea, despite what the Python style guide says :|

"???"

if act.param == interrupt_id then
spell_name = "Interrupted!"
show_caption(spell_name, "int")
else
show_caption(spell_name, "ma")
end
end
end
)

function refresh_backgrounds()
create_backgrounds(settings.x_position - 250, settings.y_position)
end

function create_backgrounds(x, y)
Copy link
Member

Choose a reason for hiding this comment

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

This function uses the windower.prim.* API heavily. I'd recommend looking into the images library, which is essentially to windower.prim.* what the texts library is to windower.text.*. Just abstracts away some of the boilerplate.

windower.prim.create(background_ability)
windower.prim.set_fit_to_texture(background_ability, true)
windower.prim.set_texture(
background_ability,
windower.addon_path .. "images/" .. settings.background_size .. "/background_ability.png"
)
windower.prim.set_position(background_ability, x, y)
windower.prim.set_visibility(background_ability, false)

windower.prim.create(background_magic)
windower.prim.set_fit_to_texture(background_magic, true)
windower.prim.set_texture(
background_magic,
windower.addon_path .. "images/" .. settings.background_size .. "/background_magic.png"
)
windower.prim.set_position(background_magic, x, y)
windower.prim.set_visibility(background_magic, false)

windower.prim.create(background_interrupt)
windower.prim.set_fit_to_texture(background_interrupt, true)
windower.prim.set_texture(
background_interrupt,
windower.addon_path .. "images/" .. settings.background_size .. "/background_interrupt.png"
)
windower.prim.set_position(background_interrupt, x, y)
windower.prim.set_visibility(background_interrupt, false)

windower.prim.create(background_emphasize)
windower.prim.set_fit_to_texture(background_emphasize, true)
windower.prim.set_texture(
background_emphasize,
windower.addon_path .. "images/" .. settings.background_size .. "/background_emphasize.png"
)
windower.prim.set_position(background_emphasize, x, y)
windower.prim.set_visibility(background_emphasize, false)
end

function show_caption(text, type)
local event_type

hide_caption()
Copy link
Member

Choose a reason for hiding this comment

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

Feels weird that you hide it only to show it again. If this is about hiding the currently active box, I'd either maintain which box is currently active in its own variable, or extract the box-hiding code into a new function and call that from both here and hide_caption.

showing = true
caption:text(text)
caption:show()

if (type == "ws") then
Copy link
Member

Choose a reason for hiding this comment

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

No need for the parantheses here, as well as in the following if/elseif sections.

event_type = "ability"
windower.prim.set_visibility(background_ability, true)
elseif (type == "ma") then
event_type = "magic"
windower.prim.set_visibility(background_magic, true)
elseif (type == "int") then
event_type = "interrupt"
windower.prim.set_visibility(background_interrupt, true)
end

if (settings.emphasize:contains(text:gsub("%s+", ""):lower())) then
windower.play_sound(windower.addon_path .. "sounds/emphasize.wav")
windower.prim.set_visibility(background_emphasize, true)
elseif (settings.sounds == "on") then
windower.play_sound(windower.addon_path .. "sounds/" .. event_type .. "_alert.wav")
end

last_trigger = os.time()
end

function hide_caption()
showing = false
caption:hide()
windower.prim.set_visibility(background_ability, false)
windower.prim.set_visibility(background_magic, false)
windower.prim.set_visibility(background_interrupt, false)
windower.prim.set_visibility(background_emphasize, false)
end

function print(str)
windower.add_to_chat(207, str)
end
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added addons/finalAlert/sounds/ability_alert.wav
Binary file not shown.
Binary file added addons/finalAlert/sounds/emphasize.wav
Binary file not shown.
Binary file added addons/finalAlert/sounds/interrupt_alert.wav
Binary file not shown.
Binary file added addons/finalAlert/sounds/magic_alert.wav
Binary file not shown.