|
Addon: Audible
By Nsane 2024-08-09 11:21:52
An audible queue system for...
Debuffs landing or wearing on you or enemies.
Monster SP's, most are commmented out, important ones are active... feel free to uncomment the one's you want.
Buffs wearing off of you and ones you placed on others, this include's all spells and job abilities.
Sortie call outs for bosses, base around the nuking strat.
Some other random call outs,
When an enemy starts casting Dread Spike.
Immunobreak or Completely Resist.
Out of Ranged for anything.
MP is too low for the current spell.
Treasure Hunter 8+ Procs.
Ran out of Tools or Ammo.
You've completed an Trial.
Luopan was Destroyed.
Trying to use something while medicated.
Multiple dispel, basically for RDM Neck... If you only dispel one buff it will tell you which buff it was.
Some things that are in there that have been blocked commented out because of annoyance,
*** Obviously You can comment out anything you dislike or find annoying ***
The only thing that will trigger an audible call-out is with in your party, not alliance, you can make this alliance wide by changing line 40 from " if g == 1 then" to " if g == 1 or 2 or 3 then"
You will be hearing buffs being removed that you place on others, one person did not like this so there is a blocked comment on line 152, if you would like this option... block comment line 151 and unblock comment line 152.
Download can be found on my Github Updated: 12/26/2024
By zeta 2024-08-09 19:42:17
Been doing something very similar to this with Chatmon plugin for years. Will check this out since chatmon parsing text strings is slow. Least how it was explained to me.
What about adding custom chat lines yourself?
Bahamut.Negan
Serveur: Bahamut
Game: FFXI
Posts: 2213
By Bahamut.Negan 2024-08-09 19:47:26
Incoming tIncoming tIncoming tIncoming tIncoming tIncoming tIncoming tYou've been examinedIncoming tIncoming tIncoming tIncoming tIncoming tIncoming tYou've been examined<CALL><CALL>
On voice chat is the best!
[+]
By Nsane 2024-08-09 20:35:48
Been doing something very similar to this with Chatmon plugin for years. Will check this out since chatmon parsing text strings is slow. Least how it was explained to me.
What about adding custom chat lines yourself?
I used to have this all Chatmon, but I found it would randomly fire off a call out for something I didn't want it to, Buddy said this is how you can read incoming packets, once I understood a little bit about how the incoming packets work I took everything from my chatmon and made it packet based, found it to be much faster and accurate.
As for your question you would have to be more specific in your question, to add some custom lines for what specifically? Everything can be added from in game text dialog to people yelling. They all have different packets to parse though.
[+]
By zeta 2024-08-09 20:57:59
Incoming tIncoming tIncoming tIncoming tIncoming tIncoming tIncoming tYou've been examinedIncoming tIncoming tIncoming tIncoming tIncoming tIncoming tYou've been examined<CALL><CALL>
On voice chat is the best!
I play with headset and push to talk so no one hears. :)
[+]
Bahamut.Negan
Serveur: Bahamut
Game: FFXI
Posts: 2213
By Bahamut.Negan 2024-08-09 21:00:19
Incoming tIncoming tIncoming tIncoming tIncoming tIncoming tIncoming tYou've been examinedIncoming tIncoming tIncoming tIncoming tIncoming tIncoming tYou've been examined<CALL><CALL>
On voice chat is the best!
I play with headset and push to talk so no one hears. :) DRAT! xD
[+]
By zeta 2024-08-09 21:03:00
Been doing something very similar to this with Chatmon plugin for years. Will check this out since chatmon parsing text strings is slow. Least how it was explained to me.
What about adding custom chat lines yourself?
I used to have this all Chatmon, but I found it would randomly fire off a call out for something I didn't want it to, Buddy said this is how you can read incoming packets, once I understood a little bit about how the incoming packets work I took everything from my chatmon and made it packet based, found it to be much faster and accurate.
As for your question you would have to be more specific in your question, to add some custom lines for what specifically? Everything can be added from in game text dialog to people yelling. They all have different packets to parse though. Been doing something very similar to this with Chatmon plugin for years. Will check this out since chatmon parsing text strings is slow. Least how it was explained to me.
What about adding custom chat lines yourself?
I used to have this all Chatmon, but I found it would randomly fire off a call out for something I didn't want it to, Buddy said this is how you can read incoming packets, once I understood a little bit about how the incoming packets work I took everything from my chatmon and made it packet based, found it to be much faster and accurate.
As for your question you would have to be more specific in your question, to add some custom lines for what specifically? Everything can be added from in game text dialog to people yelling. They all have different packets to parse though.
examples: Code <trigger match="*Peach Power*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\eventShout.wav" />
<trigger match="*PLAYERNAME learns*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\Spell.wav" />
<trigger match="*objective* remain*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\ObjectiveCompleted.wav" />
<trigger match="*Rune of Transfer Activated*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\FloorComplete.wav" />
<trigger match="*Time remaining*|*Your visitant status will*|*expelled from dynamis in*|*left in limbus*|*remaining to complete the battle*|*You have * minute* (Earth time)*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\TimeRemaining.wav" />
<trigger match="*too far away*|*out of range*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\TooFar.wav" />
<trigger match="*unable to see*|*you cannot see*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\SeeTarget.wav" />
<trigger match="*skill rises*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\SkillUp.wav" />
<trigger match="*You have completed Trial*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\TrialCompleted.wav" />
<trigger match="*obtained*Bayld!*" from="All" sound="C:\Windower4\plugins\ChatMonAudio\Bayld.wav" />
By Nsane 2024-08-09 21:32:39
I was gonna include these, but I found every time SE decides to add more "Message ID"'s to the game you have to update the the numbers. Which is not hard, just a bit annoying, obviously I still have them, but didn't want to be ask constantly for them to be updated.
Code --These need to be updated once in a while after a maintenance if they add additional "Message ID"'s into the game.
--These need to be updated once in a while after a maintenance if they add additional "Message ID"'s into the game.
--These need to be updated once in a while after a maintenance if they add additional "Message ID"'s into the game.
if id == 0x02A then local resting_message = packets.parse('incoming', data)
local zone = windower.ffxi.get_info().zone
if T{139, 144, 146}:contains (zone) and T{40516, 40747, 40470}:contains (resting_message['Message ID']) then windower.play_sound (windower.addon_path .. "FFaudio/Loud Thud.wav")
elseif T{139, 144, 146}:contains (zone) and T{40517, 40748, 40471}:contains (resting_message['Message ID']) then windower.play_sound (windower.addon_path .. "FFaudio/Thud.wav")
elseif T{139, 144, 146}:contains (zone) and T{40518, 40749, 40472}:contains (resting_message['Message ID']) then windower.play_sound (windower.addon_path .. "FFaudio/Noise.wav")
--Abyssea Vistant
elseif resting_message['Message ID'] == 40107 and resting_message['Param 1'] == 1 then windower.play_sound (windower.addon_path .. "FFaudio/1min.wav")
elseif resting_message['Message ID'] == 40107 and resting_message['Param 1'] == 5 then windower.play_sound (windower.addon_path .. "FFaudio/5min.wav")
elseif resting_message['Message ID'] == 40107 and resting_message['Param 1'] == 10 then windower.play_sound (windower.addon_path .. "FFaudio/10min.wav")
elseif resting_message['Message ID'] == 40107 and resting_message['Param 1'] == 30 then windower.play_sound (windower.addon_path .. "FFaudio/30min.wav")
end
end
if id == 0x027 then local string_message = packets.parse('incoming', data)
--Nyzul Time
if string_message['Message ID'] == 40128 then windower.play_sound (windower.addon_path .. "FFaudio/Activated.wav")
elseif string_message['Message ID'] == 40088 and string_message['Param 1'] == 1 then windower.play_sound (windower.addon_path .. "FFaudio/1min.wav")
elseif string_message['Message ID'] == 40088 and string_message['Param 1'] == 5 then windower.play_sound (windower.addon_path .. "FFaudio/5min.wav")
elseif string_message['Message ID'] == 40088 and string_message['Param 1'] == 10 then windower.play_sound (windower.addon_path .. "FFaudio/10min.wav")
end
end
if id == 0x036 then local npc_chat = packets.parse('incoming', data)
--Nyzul objectives
if npc_chat['Message ID'] == 7372 then windower.play_sound (windower.addon_path .. "FFaudio/Boss.wav")
elseif npc_chat['Message ID'] == 7373 then windower.play_sound (windower.addon_path .. "FFaudio/Leader.wav")
elseif npc_chat['Message ID'] == 7374 then windower.play_sound (windower.addon_path .. "FFaudio/Family.wav")
elseif npc_chat['Message ID'] == 7375 then windower.play_sound (windower.addon_path .. "FFaudio/Lamps.wav")
elseif npc_chat['Message ID'] == 7376 then windower.play_sound (windower.addon_path .. "FFaudio/SpecEnemy.wav")
elseif npc_chat['Message ID'] == 7377 then windower.play_sound (windower.addon_path .. "FFaudio/All.wav")
elseif npc_chat['Message ID'] == 7378 then coroutine.sleep(1.45) windower.play_sound (windower.addon_path .. "FFaudio/Avoid Discovery.wav")
elseif npc_chat['Message ID'] == 7379 then coroutine.sleep(1.45) windower.play_sound (windower.addon_path .. "FFaudio/Dont Destroy.wav")
end
end
Yells can be added like this
Code if id == 0x017 then local incoming_chat = packets.parse('incoming', data)
--Yells
if incoming_chat['Mode'] == 26 then
if windower.convert_auto_trans (incoming_chat['Message']):lower():contains ('mireu') then windower.play_sound (windower.addon_path .. "FFaudio/Mireu.wav")
elseif windower.convert_auto_trans (incoming_chat['Message']):lower():contains ('peach power') then windower.play_sound (windower.addon_path .. "FFaudio/Peach.wav")
end
end
end
In general though you would need to learn how to use Packetviewer addon, looks complicated from the readme, but just follow through the ReadMe and you'll pick it up fairly quick.
[+]
By zeta 2024-08-09 22:11:22
Out of curiosity wouldn't user being able to do text strings allow for more flexibility or no? Since you mention message IDs and they changing sometimes.
By zixxer 2024-08-09 23:20:43
I've been using Nsanes add-on for a couple of months now. And if Nsane uploaded the same thing he gave me, it also calls out what to proc for Aita, Aminon, and Gartell.
Might be echoing what's been said in the OP but it has a lot of useful cues, when someone casted dia on the mob, when food runs out, when mob uses dread spikes, when a buff expires, when you are unstunned. I also like that it tells me when a JA expires like "invincble off" or "yaegesumi off" so I don't need to look at the icons.
Ultimately similar to chatmon system but more comprehensive out of the box. He painstakingly made sure the audio files are pre-loaded, which he tells me, could take some time.
Try it for your next sortie run. :)
[+]
By zeta 2024-08-11 08:48:19
Quote: I was gonna include these, but I found every time SE decides to add more "Message ID"'s to the game you have to update the the numbers. Which is not hard, just a bit annoying, obviously I still have them, but didn't want to be ask constantly for them to be updated.
I wanted to ask about this a bit more if i could. what do you mean exactly? I see a lot of numbers in tables.. are you saying all those could change or just certain events? If just certain events and i wanted to maintain it myself is it fairly easy to determine the new changes(as how do you test/etc)
Thanks.
EDIT: is it this you mean? id == 0x02A
By Nsane 2024-08-11 10:57:24
Again you would have to learn how to use Packetview, 0x02A is an incoming packet, "Message ID" is just something within the packet its self,
You could simply just use both of your chatmon/audible at one time, until you get familiar with reading packets to add your own. What is in audible providing is mostly just all buffs/debuffs in game. around 450 different call outs. Like you said above chatmon feels a bit slow, and someone could just type the trigger to you, and it would cause chatmon to sound off. This does not, and is much faster, you will hear the call out before you physically see the automation.
Edit: Everything within Audible does not have to be maintain, pretty much those elements are static and rarely change.
By zeta 2024-08-11 14:35:49
I will try to learn how to use packetflow in next few weeks.
I think my ultimate goal with this addon would be to completely replace chatmon... I ended up makes some adjustments to the lua so it is easier for me to read through. I tend to get lost sometimes :)
Code
local path = windower.addon_path..'FFaudio/'
local play = windower.play_sound
local tap = 'Target 1 Action 1 Param'
if T{43, 326, 675}:contains(action['Target 1 Action 1 Message']) then
--Aita
if T{3389, 4323, 4355}:contains(action[tap]) then play(path..'Fulminous Smash.wav')
elseif T{3390, 4324, 4356}:contains(action[tap]) then play(path..'Flaming Kick.wav')
elseif T{3391, 4325, 4357}:contains(action[tap]) then play(path..'Icy Grasp.wav')
elseif T{3392, 4326, 4358}:contains(action[tap]) then play(path..'Flashflood.wav')
elseif T{3393, 4327, 4359}:contains(action[tap]) then play(path..'Eroding Flesh.wav')
elseif T{3394, 4328} :contains(action[tap]) then play(path..'Vivisection.wav')
--Aminon
elseif T{3402, 4367} :contains(action[tap]) then play(path..'Demonfire.wav')
elseif T{3403, 4368} :contains(action[tap]) then play(path..'Frozen Bloodh.wav')
elseif T{3405, 4369} :contains(action[tap]) then play(path..'Blast of Reticence.wav')
elseif T{3406, 4370} :contains(action[tap]) then play(path..'Ensepulcher.wav')
elseif T{3407, 4371} :contains(action[tap]) then play(path..'Ceaseless Surge.wav')
elseif T{3408, 4372} :contains(action[tap]) then play(path..'Torrential Pain.wav')
--Dhartok
elseif T{3372, 4296, 4346}:contains(action[tap]) then play(path..'Cesspool.wav')
--Gartell
elseif T{2726, 3367, 4309}:contains(action[tap]) then play(path..'Shrieking Gale.wav')
elseif T{3368, 4310} :contains (action[tap]) then play(path..'Undulating Shockwave.wav')
end
end
EDIT: Me playing/messing the code was me trying to understand it better and such. I am not a coder by trade and skills are not high but try to learn when i can.
Quetzalcoatl.Balthor
Serveur: Quetzalcoatl
Game: FFXI
Posts: 20
By Quetzalcoatl.Balthor 2024-08-11 15:08:46
EDIT: Me playing/messing the code was me trying to understand it better and such. I am not a coder by trade and skills are not high but try to learn when i can.
That is how I learned Gearswap after 9 year break. Spellcast was what I was learning when I left.
By zixxer 2024-08-11 15:19:15
Quetzalcoatl.Balthor said: »EDIT: Me playing/messing the code was me trying to understand it better and such. I am not a coder by trade and skills are not high but try to learn when i can.
That is how I learned Gearswap after 9 year break. Spellcast was what I was learning when I left.
I had the same exact experience. Are you me?
Shiva.Thorny
Serveur: Shiva
Game: FFXI
Posts: 2852
By Shiva.Thorny 2024-08-11 15:55:42
EDIT: Me playing/messing the code was me trying to understand it better and such. I am not a coder by trade and skills are not high but try to learn when i can.
Not trying to demean anyone in this thread, but if you want to simplify it, make a mapping of ability IDs to sound files:
Code
local abilityIdToSoundMap = {
[3389] = 'Fulminous Smash.wav',
[4323] = 'Fulminous Smash.wav',
[4355] = 'Fulminous Smash.wav',
[3390] = 'Flaming Kick.wav',
};
Then pull the matching sound file using the action id as a key: Code
if T{43, 326, 675}:contains(action['Target 1 Action 1 Message']) then
local soundFromMapping = abilityIdToSoundMap[action['Target 1 Action 1 Param']];
if soundFromMapping then
windower.play_sound(windower.addon_path..'FFaudio/'..soundFromMapping);
end
end
I don't know how heavy the T wrapper is in windower, but every single if line there is creating a table and calling a wrapper function on it, then dereferencing a value from the action table, then further iterating the entire table. This is like 1/100 the cpu time(it just dereferences one table and doesn't create anything on the fly), and easier to read.
[+]
By zeta 2024-08-11 16:04:01
EDIT: Me playing/messing the code was me trying to understand it better and such. I am not a coder by trade and skills are not high but try to learn when i can.
Not trying to demean anyone in this thread, but if you want to simplify it, make a mapping of ability IDs to sound files:
Code
local abilityIdToSoundMap = {
[3389] = 'Fulminous Smash.wav',
[4323] = 'Fulminous Smash.wav',
[4355] = 'Fulminous Smash.wav',
[3390] = 'Flaming Kick.wav',
};
Then pull the matching sound file using the action id as a key: Code
if T{43, 326, 675}:contains(action['Target 1 Action 1 Message']) then
local soundFromMapping = abilityIdToSoundMap[action['Target 1 Action 1 Param']];
if soundFromMapping then
windower.play_sound(windower.addon_path..'FFaudio/'..soundFromMapping);
end
end
I don't know how heavy the T wrapper is in windower, but every single if line there is creating a table and calling a wrapper function on it, then dereferencing a value from the action table, then further iterating the entire table. This is like 1/100 the cpu time(it just dereferences one table and doesn't create anything on the fly), and easier to read.
So much i dont know still or dont fully understand. trying to figure out a way to use a table and in my mind would have been a `for` loop(i think that is wrong but kinda understand them)
These seems like what i was imagining wanting. Excited to play with it more. I think Nsane sharing is great and not something i would have thought about myself so the idea is neat. I am also a weirdo with sound files and have about 60 i made with a MS Azure account. Like the female voice more so i am sure will redo all them for my weirdo ways :)
Shiva.Thorny
Serveur: Shiva
Game: FFXI
Posts: 2852
By Shiva.Thorny 2024-08-11 16:06:30
A for loop does something to every element in a table. This is a key lookup, it checks if the table has any key matching the input value and if so gives you the associated value.
IE: The first entry has a key of 3389 and a value of 'Fulminous Smash.wav'. So, soundFromMapping[3389] is the same as 'Fulminous Smash.wav' in this context. If the input key does not exist, the value is nil, and that if check would fail so nothing would play. Those 6 lines handle all of the 'thought', the table is just a dataset connecting ids to ability names. Lemme transform it rq and i'll edit a pastebin into this post.
Edit: didn't realize how many different categories, i'll dig into this tomorrow to clean it up
Second Edit:
Somewhat tested windower version at:
https://github.com/ThornyFFXI/Audible
It's written in a way that the same addon will work for ashita4, but I haven't actually finished that file yet so just windower atm.
Sound files should be placed in:
addons/audible/resources/audio/
Notes:
-Only parsing one action means aoe casts won't trigger the original unless you're the primary target. I've altered it to parse all actions and targets.
-Action ID for buff stolen was listed in self debuff gained. A stolen buff would be lost not gained, so false trigger for debuff. I removed that action ID.
-Skillchain messages don't need to be in the spell damage taken section. They're additional effects, the line for spell damage still exists. I removed those action IDs.
-Keying everything by ID makes it more difficult to edit. I changed most of the keying to use windower's resources and look up by name. Statuses can still be done by either, since in some cases the same names are different statuses(storm 1/2, geo vs standard, etc).
-I bundled spell starts in with MobReadies. This isn't really necessary, but since dread spikes was the only spell start it seemed more practical to have one file for all readies alerts.
-Several of the buff IDs in buff lost section were wrong. I only noticed them through duplicate keys when converting to a table, so it's likely there are still others that are wrong.
-Fixed the major performance sinks by using table derefs instead of massive if statements.
The main change, however, was that I moved all the triggers to their own files and added descriptions of how to edit them at the top of the files. This should make it much easier to edit your own triggers into.
[+]
Necro Bump Detected!
[83 days between previous and next post]
Bismarck.Voight
Serveur: Bismarck
Game: FFXI
Posts: 14
By Bismarck.Voight 2024-11-03 02:41:12
Quetzalcoatl.Balthor said: »EDIT: Me playing/messing the code was me trying to understand it better and such. I am not a coder by trade and skills are not high but try to learn when i can.
That is how I learned Gearswap after 9 year break. Spellcast was what I was learning when I left.
I had the same exact experience. Are you me?
If he is you, then you are both me. Because I did the exact same thing. Even as far as doing Spellcast then taking a break and coming back to finding Gearswap and lua instead of xml
By DaneBlood 2024-11-12 23:38:38
i realyl liek the idea but a few negative points from me
1: audio volume is to low. with the game running with no music and sfx at 50% volume it hard to hear the voice
2: way to much information. i dont need an audible alert on everything wearing it just results in information fattique.
i dont care that much about knowing haste dropped since my whm will ensure im getting it. not a lot ofaction on my side.
but i really like to hear if my food wore so ican do the action if eating a new one, or if my mob put up perfect dodge so ican change to the next one.
I just simpy ltried besiged with it and efore the combat was done i was ignored the message due to the above. hard to hear and just a spam of noises.
Offcause I can delete the unwnated wave files or start modding the code . but i think somekind of interface to narrow down what you need would probably be a nice touch.
Anyway I'm looking forward to next release
By wick 2024-11-13 12:47:34
Took me awhile but I just commented out most of it. But I agree it was a touch complex to setup. I’m also wishing if I commented out could I not make it check at all as it still monitors for the commented out stuff. Worried about resources.
Shiva.Thorny
Serveur: Shiva
Game: FFXI
Posts: 2852
By Shiva.Thorny 2024-11-13 12:49:45
I didn't see a whole lot of interest in this after I worked on it, so probably not likely my version will see many more changes. Definitely don't worry about resources if you are using that version though, the check costs very close to the same regardless of how many entries you have and it is not very resource heavy.
I'm not sure what you mean by monitors for commented out stuff, commented code is never evaluated in either version. The entire point of a comment is that it is not used by the interpreter(in this case)/compiler(in other languages).
By wick 2024-11-13 13:25:38
Oh it’s awesome addon, love it. I recorded my own stuff to play and it works perfect, funny too. What I mean by the commented out stuff is that the debug still shows it as “triggering” but just commenting out the play this file on trigger so it says no file to play? If that makes sense.
That part is the part I was worried about using resources. I use your version Thorny, it’s really nice.
Serveur: Asura
Game: FFXI
Posts: 24
By Asura.Auxtaru 2024-11-13 14:53:42
Just wanted to toss out that this add on has become a part of my init load out so thank you guys for your work on it. I particularly like that Frontier Sodas fall under the Adloquium tag so I get a prompt when it falls off so I can quickly reapply!
By Felgarr 2024-11-13 16:36:51
After the initial post, I didn't know this existed. I think it's great. I love the additional bandwidth that an audio cue provides.
Shiva.Thorny
Serveur: Shiva
Game: FFXI
Posts: 2852
By Shiva.Thorny 2024-11-14 07:23:56
Oh it’s awesome addon, love it. I recorded my own stuff to play and it works perfect, funny too. What I mean by the commented out stuff is that the debug still shows it as “triggering” but just commenting out the play this file on trigger so it says no file to play? If that makes sense.
You can turn off the debug by setting Debug=false on line 10 of the windower4.lua file. But, it does not use any meaningful resources to print that debug line; it's primarily intended for if the user is having trouble getting a specific event to trigger and wants to see what is being processed by the addon.
It is not a resource concern at all, don't worry!
An audible queue system for...
Debuffs landing or wearing on you or enemies.
Monster SP's, most are commmented out, important ones are active... feel free to uncomment the one's you want.
Buffs wearing off of you and ones you placed on others, this include's all spells and job abilities.
Sortie call outs for bosses, base around the nuking strat.
Some other random call outs,
When an enemy starts casting Dread Spike.
Immunobreak or Completely Resist.
Out of Ranged for anything.
MP is too low for the current spell.
Treasure Hunter 8+ Procs.
Ran out of Tools or Ammo.
You've completed an Trial.
Luopan was Destroyed.
Trying to use something while medicated.
Multiple dispel, basically for RDM Neck... If you only dispel one buff it will tell you which buff it was.
Some things that are in there that have been blocked commented out because of annoyance,
*** Obviously You can comment out anything you dislike or find annoying ***
The only thing that will trigger an audible call-out is with in your party, not alliance, you can make this alliance wide by changing line 40 from " if g == 1 then" to " if g == 1 or 2 or 3 then"
You will be hearing buffs being removed that you place on others, one person did not like this so there is a blocked comment on line 152, if you would like this option... block comment line 151 and unblock comment line 152.
Download can be found on my Github Updated: 12/26/2024
|
|