commit dce7b5be54eba6a39405f7dd62d735ce70952f67 Author: Sjshovan Date: Tue Jan 22 16:48:47 2019 -0500 initial. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1b2e964 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright © 2018, Sjshovan (Apogee) +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 Mount Muzzle 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 Sjshovan (Apogee) 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a427780 --- /dev/null +++ b/README.md @@ -0,0 +1,196 @@ +**Author:** [Sjshovan (Apogee)](https://github.com/Ap0gee) +**Version:** v0.9.0 + + +# Mount Muzzle + +> An Ashita v3 addon that allows the user to change or remove the default mount music in Final Fantasy 11 Online. + + +### Table of Contents + +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Aliases](#aliases) +- [Usage](#usage) +- [Commands](#commands) +- [Support](#support) +- [Change Log](#change-log) +- [Known Issues](#known-issues) +- [TODOs](#todos) +- [License](#license) + +___ +### Prerequisites +1. [Final Fantasy 11 Online](http://www.playonline.com/ff11us/index.shtml) +2. [Ashita v3](https://www.ashitaxi.com/) + +___ +### Installation + +**Ashita:** +1. Navigate to the `Addons` section on the left. +2. Locate the `MountMuzzle` addon. +3. Click the flashing download button near the upper-right corner. + +**Manual:** +1. Navigate to . +2. Click on `Releases`. +3. Click on the `Source code (zip)` link within the latest release to download. +4. Extract the zipped folder to `Ashita_v3/addons/`. +5. Rename the folder to remove the version tag (`-v0.9.0`). The folder should be named `MountMuzzle`. + +**Autoloading:** + +By default you will need to manually load this addon each time you restart the game. +To autoload MountMuzzle so that it is always ready for use upon entering the game, follow these steps: + +1. Navigate to the `Ashita_v3/scripts/` directory. +2. Open the `Default.txt` file. +3. Locate the `Load Common Addons` section. +4. add the following line: `/addon load mountmuzzle`. + +___ +### Aliases +The following aliases are available to Mount Muzzle commands: + +**mountmuzzle:** muzzle | mm +**list:** l +**set:** s +**get:** g +**default:** d +**unload:** u +**reload:** r +**about:** a +**silent:** s +**mount:** m +**chocobo:** c +**zone:** z +**help:** h + + ___ +### Usage + +Manually load the addon by using the following command: + + /addon load mountmuzzle + +___ +### Commands + +**help** + +Displays available Mount Muzzle commands. Below are the equivalent ways of calling the command: + + /mountmuzzle help + /muzzle help + /mm help + /mountmuzzle h + /muzzle h + /mm h + +**list** + +Displays the available muzzle types. Below are the equivalent ways of calling the command: + + /mountmuzzle list + /muzzle list + /mm list + /mm l + +**set _\_** + +Sets the current muzzle to the given muzzle type. This command takes a single argument represented by ``. Below are the equivalent ways of calling the command: + + /mountmuzzle set + /muzzle set + /mm set + /mm s + +Here are some usage examples for the **set _\_** command: `mm set silent` and `muzzle set zone` etc... + +**get** + +Displays the current muzzle that is set. Below are the equivalent ways of calling the command: + + /mountmuzzle get + /muzzle get + /mm get + /mm g + +**default** + +Sets the current muzzle to the default muzzle type: `Silent`. Below are the equivalent ways of calling the command: + + /mountmuzzle default + /muzzle default + /mm default + /mm d + +**unload** + +Unloads the Mount Muzzle addon. Below are the equivalent ways of calling the command: + + /mountmuzzle unload + /muzzle unload + /mm unload + /mm u + +**reload** + +Reloads the Mount Muzzle addon. Below are the equivalent ways of calling the command: + + /mountmuzzle reload + /muzzle reload + /mm reload + /mm r + +**about** + +Displays information about the Mount Muzzle addon. Below are the equivalent ways of calling the command: + + /mountmuzzle about + /muzzle about + /mm about + /mm a + +___ +### Support +**Having Issues with this addon?** +* Please let me know [here](https://github.com/Ap0gee/Ashita-MountMuzzle/issues/new). + +**Have something to say?** +* Send me some feedback here: + +**Want to stay in the loop with my work?** +* You can follow me at: + +**Want to show your love and help me make more awesome stuff?** +* You can do so here: + +___ +### Change Log + +**v0.9.0** - 12/4/2018 +- Initial release + +___ +### Known Issues + +- **Issue:** If Mount Muzzle is selected to automatically load and the player is mounted upon login, there is a significant delay before the chosen music will begin to play. +- **Issue:** Upon changing zones the default music can be heard for a moment before the chosen music begins to play. +- **Issue:** Unable to correctly set mount music to original if Mount Muzzle is unloaded while mounted. + +___ +### TODOs + +- **TODO:** Investigate alternative methods for music change as packet injection/swap allows the player to hear the default music upon zone change and login, regardless of chosen music. +- **TODO:** Investigate methods for determining which mount type the player is on when loading/unloading Mount Muzzle. +___ + +### License + +Copyright © 2018, [Sjshovan (Apogee)](https://github.com/Ap0gee). +Released under the [BSD License](LICENSE). + +*** \ No newline at end of file diff --git a/constants.lua b/constants.lua new file mode 100644 index 0000000..1266441 --- /dev/null +++ b/constants.lua @@ -0,0 +1,120 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +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 Mount Muzzle 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 Sjshovan (Apogee) 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. +--]] + +packets = { + inbound = { + music_change = { + id = 0x05F, + offsets = { + type = 0x06 + } + }, + zone_update = { + id = 0x00A + } + }, + outbound = { + action = { + id = 0x1A, + categories = { + mount = 0x1A, + unmount = 0x12 + }, + } + }, +} + +player = { + statuses = { + types = { + mount = 1072 + }, + mounted = { + chocobo = 5, + mount = 85 + } + }, + buffs = { + reiveMark = 511, + mounted = 252 + } +} + +music = { + songs = { + silent = 0x5B, + mount = 0x54, + chocobo = 0xD4, + zone = 0x00, + }, + types = { + mount = 84, + idle_day = 0, + idle_night = 1 + } +} + +colors = { + primary = "\31\200%s", + secondary = "\31\207%s", + info = "\31\1%s", + warn = "\31\140%s", + danger = "\31\167%s", + success = "\31\158%s" +} + +muzzles = { + silent = { + name = 'silent', + song = music.songs.silent, + description = 'No Music (Default)' + }, + mount = { + name = 'mount', + song = music.songs.mount, + description = 'Mount Music' + }, + chocobo = { + name = 'chocobo', + song = music.songs.chocobo, + description = 'Chocobo Music' + }, + zone = { + name = 'zone', + song = music.songs.zone, + description = 'Current Zone Music' + } +} + +return { + packets = packets, + player = player, + music = music, + colors = colors, + muzzles = muzzles +} \ No newline at end of file diff --git a/helpers.lua b/helpers.lua new file mode 100644 index 0000000..479e436 --- /dev/null +++ b/helpers.lua @@ -0,0 +1,99 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +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 Mount Muzzle 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 Sjshovan (Apogee) 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 colors = require("constants").colors + +function buildHelpCommandEntry(command, description) + local short_name = strColor("mm", colors.primary) + local command = strColor(command, colors.secondary) + local sep = strColor("=>", colors.primary) + local description = strColor(description, colors.info) + + return string.format("%s %s %s %s", short_name, command, sep, description) +end + +function buildHelpTypeEntry(name, description) + local name = strColor(name, colors.secondary) + local sep = strColor("=>", colors.primary) + local description = strColor(description, colors.info) + + return string.format("%s %s %s", name, sep, description) +end + +function buildHelpTitle(context) + local context = strColor(context, colors.danger) + + return string.format("%s Help: %s", _addon.name, context) +end + +function buildHelpSeperator(character, count) + local sep = '' + + for i = 1, count do + sep = sep .. character + end + + return strColor(sep, colors.warn) +end + +function buildCommandResponse(message, success) + local response_color = colors.success + local response_type = 'Success' + + if not success then + response_type = 'Error' + response_color = colors.danger + end + + return string.format("%s: %s", + strColor(response_type, response_color), strColor(message, colors.info) + ) +end + +function displayResponse(response, color) + color = color or colors.info + print(strColor(response, color)) +end + +function strColor(str, color) + return string.format(color, str) +end + +function ucFirst(str) + return str:gsub("^%l", string.upper) +end + +function tableContains(tab, val) + for index, value in pairs(tab) do + if value == val then + return true + end + end + + return false +end \ No newline at end of file diff --git a/mountmuzzle.lua b/mountmuzzle.lua new file mode 100644 index 0000000..b45ab16 --- /dev/null +++ b/mountmuzzle.lua @@ -0,0 +1,291 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +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 Mount Muzzle 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 Sjshovan (Apogee) 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 = 'Mount Muzzle' +_addon.description = 'Change or remove the default mount music.' +_addon.author = 'Sjshovan (Apogee) sjshovan@gmail.com' +_addon.version = '0.9.0' +_addon.commands = {'/mountmuzzle', '/muzzle', '/mm'} + +require('constants') +require('helpers') + +require 'common' + +local default_settings = { + muzzle = "silent" +} + +local settings = default_settings; + +local defaults = { + muzzle = muzzles.silent.name +} + +local needs_inject = false + +local help = { + commands = { + buildHelpSeperator('=', 26), + buildHelpTitle('Commands'), + buildHelpSeperator('=', 26), + buildHelpCommandEntry('list', 'Display the available muzzle types.'), + buildHelpCommandEntry('set ', 'Set the current muzzle to the given muzzle type.'), + buildHelpCommandEntry('get', 'Display the current muzzle.'), + buildHelpCommandEntry('default', 'Set the current muzzle to the default (Silent).'), + buildHelpCommandEntry('unload', 'Unload Mount Muzzle.'), + buildHelpCommandEntry('reload', 'Reload Mount Muzzle.'), + buildHelpCommandEntry('about', 'Display information about Mount Muzzle.'), + buildHelpCommandEntry('help', 'Display Mount Muzzle commands.'), + buildHelpSeperator('=', 26), + }, + types = { + buildHelpSeperator('=', 23), + buildHelpTitle('Types'), + buildHelpSeperator('=', 23), + buildHelpTypeEntry(muzzles.silent.name, muzzles.silent.description), + buildHelpTypeEntry(muzzles.mount.name, muzzles.mount.description), + buildHelpTypeEntry(muzzles.chocobo.name, muzzles.chocobo.description), + buildHelpTypeEntry(muzzles.zone.name, muzzles.zone.description), + buildHelpSeperator('=', 23), + }, + about = { + buildHelpSeperator('=', 23), + buildHelpTitle('About'), + buildHelpSeperator('=', 23), + buildHelpTypeEntry('Name', _addon.name), + buildHelpTypeEntry('Description', _addon.description), + buildHelpTypeEntry('Author', _addon.author), + buildHelpTypeEntry('Version', _addon.version), + buildHelpSeperator('=', 23), + }, + aliases = { + muzzles = { + s = muzzles.silent.name, + m = muzzles.mount.name, + c = muzzles.chocobo.name, + z = muzzles.zone.name + } + } +} + +function display_help(table_help) + for index, command in pairs(table_help) do + displayResponse(command) + end +end + +function getMuzzle() + return settings.muzzle +end + +function getPlayerBuffs() + return AshitaCore:GetDataManager():GetPlayer():GetBuffs() +end + +function resolveCurrentMuzzle() + local current_muzzle = getMuzzle() + + if not muzzleValid(current_muzzle) then + current_muzzle = muzzles.silent.name + setMuzzle(current_muzzle) + displayResponse( + string.format( + 'Note: Muzzle found in settings was not valid and is now set to the default (%s\30\1).', + strColor('Silent', colors.secondary) + ), + colors.warn + ) + end + + return muzzles[current_muzzle] +end + +function setMuzzle(muzzle) + settings.muzzle = muzzle + ashita.settings.save(_addon.path .. '/settings/settings.json', settings); +end + +function playerInReive() + return tableContains(getPlayerBuffs(), player.buffs.reiveMark) +end + +function playerIsMounted() + local entity = AshitaCore:GetDataManager():GetEntity() + + if entity then + return tableContains( + player.statuses.mounted, entity:GetStatus(player.statuses.types.mount) + ) or tableContains( + getPlayerBuffs(), player.buffs.mounted + ) + end + + return false +end + +function muzzleValid(muzzle) + return muzzles[muzzle] ~= nil +end + +function injectMuzzleMusic() + injectMusic(music.types.mount, resolveCurrentMuzzle().song) +end + +function injectMusic(bgmType, songID) + local bgm_packet = struct.pack("bbbbbbb", + 0x5F, 0x04, 0x00, 0x00, 0x04, 0x00, songID, 0x00 + ):totable(); + AddIncomingPacket(packets.inbound.music_change.id, bgm_packet) +end + +function requestInject() + needs_inject = true +end + +function handleInjectionNeeds() + if needs_inject and playerIsMounted() then + injectMuzzleMusic() + needs_inject = false; + end +end + +function tryInject() + requestInject() + handleInjectionNeeds() +end + +ashita.register_event('load', function() + settings = ashita.settings.load_merged( + _addon.path .. '/settings/settings.json', settings + ) + tryInject(); +end) + +ashita.register_event('unload', function() + injectMusic(music.types.mount, muzzles.zone.song) +end) + +ashita.register_event('command', function(command, ntype) + + local command_args = command:lower():args() + + if not tableContains(_addon.commands, command_args[1]) then + return false + end + + local respond = false + local response_message = '' + local success = true + + if command_args[2] == 'list' or command_args[2] == 'l' then + display_help(help.types) + + elseif command_args[2] == 'set' or command_args[2] == 's' then + respond = true + + local muzzle = tostring(command_args[3]):lower() + local from_alias = help.aliases.muzzles[muzzle] + + if (from_alias ~= nil) then + muzzle = from_alias + end + + if not muzzleValid(muzzle) then + success = false + response_message = 'Muzzle type not recognized.' + else + requestInject() + setMuzzle(muzzle) + response_message = string.format( + 'Updated current muzzle to %s.', + strColor(ucFirst(muzzle), colors.secondary) + ) + end + + elseif command_args[2] == 'get' or command_args[2] == 'g' then + respond = true + response_message = string.format( + 'Current muzzle is %s.', + strColor(ucFirst(getMuzzle()), colors.secondary) + ) + + elseif command_args[2] == 'default' or command_args[2] == 'd' then + respond = true + requestInject() + + setMuzzle(muzzles.silent.name) + response_message = string.format( + 'Updated current muzzle to the default (%s\30\1).', + strColor('Silent', colors.secondary) + ) + + elseif command_args[2] == 'reload' or command_args[2] == 'r' then + AshitaCore:GetChatManager():QueueCommand('/addon reload mountmuzzle', 1) + + elseif command_args[2] == 'unload' or command_args[2] == 'u' then + respond = true + response_message = 'Thank you for using Mount Muzzle. Goodbye.' + AshitaCore:GetChatManager():QueueCommand('/addon unload mountmuzzle', 1) + + elseif command_args[2] == 'about' or command_args[2] == 'a' then + display_help(help.about) + + elseif command_args[2] == 'help' or command_args[2] == 'h' then + display_help(help.commands) + + else + display_help(help.commands) + + end + + if respond then + displayResponse( + buildCommandResponse(response_message, success) + ) + end + + handleInjectionNeeds() + + return false +end) + +ashita.register_event('incoming_packet', function(id, size, packet, modified_packet, blocked_packet) + if id == packets.inbound.music_change.id then + local music_type = struct.unpack('H', packet, packets.inbound.music_change.offsets.type + 1) + + if music_type == music.types.mount then + injectMusic(music.types.mount, resolveCurrentMuzzle().song) + return true + end + + tryInject() + end + + return false +end) \ No newline at end of file