Skip to content

Commit

Permalink
Support commas as decimal markers in floating point numbers
Browse files Browse the repository at this point in the history
FFXIV plugin will put floating point numbers with commas for decimals in locales
where that is appropriate. So we have to parse numbers that way. However js
parseFloat() does not support that, so we have to convert comma to period before
calling parseFloat.

This adds a \y{Float} regex extension to patch floating point numbers across
locales. And a Regexes.ParseLocaleFloat() that can turn those matches into
a floating point number type.

The ParseLocaleFloat() is exposed to triggers on the data object as
data.ParseLocaleFloat() for triggers that want to match numbers, and this
changes all [0-9.] style matchers in the code and in triggers to use \y{Float}.

This should address the problems in bug #137.
  • Loading branch information
danakj committed Nov 12, 2017
1 parent 9e23d6d commit 5269d55
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 57 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ convenience to avoid having to match against all possible unicode characters
or to know the details of how the FFXIV ACT plugin writes things.

The set of extensions are:
- '\y{Float}`: Matches a floating-point number, accounting for locale-specific encodings.
- `\y{Name}`: Matches any character or ability name (including empty strings which the FFXIV ACT plugin can generate when unknown).
- `\y{AbilityCode}`: Matches the FFXIV ACT plugin's format for the number code of a spell or ability.
- `\y{TimeStamp}`: Matches the time stamp at the front of each log event such as `[10:23:34.123]`.
Expand Down
14 changes: 12 additions & 2 deletions resources/regexes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 35 additions & 35 deletions ui/jobs/jobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class ComboTracker {
constructor(me, comboBreakers, callback) {
this.me = me;
this.comboTimer = null;
this.kReEndCombo = new RegExp(':' + me + '( starts using |:' + Regexes.AbilityCode + ':)(' + comboBreakers.join('|') + ')( |:)');
this.kReEndCombo = Regexes.Parse(':' + me + '( starts using |:' + Regexes.AbilityCode + ':)(' + comboBreakers.join('|') + ')( |:)');
this.comboNodes = {}; // { key => { re: string, next: [node keys], last: bool } }
this.startList = [];
this.callback = callback;
Expand Down Expand Up @@ -174,15 +174,15 @@ function setupComboTracker(me, callback) {
}

function setupRegexes(me) {
kReRdmWhiteManaProc = new RegExp(':' + me + ' gains the effect of Verstone Ready from ' + me + ' for ([0-9.]+) Seconds\.');
kReRdmWhiteManaProcEnd = new RegExp('(:' + me + ' loses the effect of Verstone Ready from ' + me + '\.)|(:' + me + ':' + Regexes.AbilityCode + ':Verstone:)')
kReRdmBlackManaProc = new RegExp(':' + me + ' gains the effect of Verfire Ready from ' + me + ' for ([0-9.]+) Seconds\.');
kReRdmBlackManaProcEnd = new RegExp('(:' + me + ' loses the effect of Verfire Ready from ' + me + '\.)|(:' + me + ':' + Regexes.AbilityCode + ':Verfire:)');
kReRdmImpactProc = new RegExp(':' + me + ' gains the effect of Impactful from ' + me + ' for ([0-9.]+) Seconds\.');
kReRdmImpactProcEnd = new RegExp('(:' + me + ' loses the effect of Impactful from ' + me + '.)|(:' + me + ':' + Regexes.AbilityCode + ':Impact:)');
kReSmnRuinProc = new RegExp(':' + me + ' gains the effect of Further Ruin from ' + me + ' for ([0-9.]+) Seconds\.');
kReSmnRuinProcEnd = new RegExp(':' + me + ' loses the effect of Further Ruin from ' + me + '\.');
kReFoodBuff = new RegExp(':' + me + ' gains the effect of Well Fed from ' + me + ' for ([0-9.]+) Seconds\.')
kReRdmWhiteManaProc = Regexes.Parse(':' + me + ' gains the effect of Verstone Ready from ' + me + ' for (\\y{Float}) Seconds\.');
kReRdmWhiteManaProcEnd = Regexes.Parse('(:' + me + ' loses the effect of Verstone Ready from ' + me + '\.)|(:' + me + ':' + Regexes.AbilityCode + ':Verstone:)')
kReRdmBlackManaProc = Regexes.Parse(':' + me + ' gains the effect of Verfire Ready from ' + me + ' for (\\y{Float}) Seconds\.');
kReRdmBlackManaProcEnd = Regexes.Parse('(:' + me + ' loses the effect of Verfire Ready from ' + me + '\.)|(:' + me + ':' + Regexes.AbilityCode + ':Verfire:)');
kReRdmImpactProc = Regexes.Parse(':' + me + ' gains the effect of Impactful from ' + me + ' for (\\y{Float}) Seconds\.');
kReRdmImpactProcEnd = Regexes.Parse('(:' + me + ' loses the effect of Impactful from ' + me + '.)|(:' + me + ':' + Regexes.AbilityCode + ':Impact:)');
kReSmnRuinProc = Regexes.Parse(':' + me + ' gains the effect of Further Ruin from ' + me + ' for (\\y{Float}) Seconds\.');
kReSmnRuinProcEnd = Regexes.Parse(':' + me + ' loses the effect of Further Ruin from ' + me + '\.');
kReFoodBuff = Regexes.Parse(':' + me + ' gains the effect of Well Fed from ' + me + ' for (\\y{Float}) Seconds\.')
}

var kCasterJobs = ["RDM", "BLM", "WHM", "SCH", "SMN", "ACN", "AST", "CNJ", "THM"];
Expand Down Expand Up @@ -236,69 +236,69 @@ var kBigBuffTracker = null;
function setupBuffTracker(me) {
kBigBuffTracker = {
potion: {
gainRegex: new RegExp(':' + me + ' gains the effect of Medicated from ' + me + ' for ([0-9.]+) Seconds'),
loseRegex: new RegExp(':' + me + ' loses the effect of Medicated from '),
gainRegex: Regexes.Parse(':' + me + ' gains the effect of Medicated from ' + me + ' for (\\y{Float}) Seconds'),
loseRegex: Regexes.Parse(':' + me + ' loses the effect of Medicated from '),
durationPosition: 1,
icon: kIconBuffPotion,
borderColor: '#AA41B2',
sortKey: 0,
},
embolden: {
gainRegex: new RegExp(':' + me + ' gains the effect of Embolden from \\y{Name} for ([0-9.]+) Seconds'),
loseRegex: new RegExp(':' + me + ' loses the effect of Embolden from '),
gainRegex: Regexes.Parse(':' + me + ' gains the effect of Embolden from \\y{Name} for (\\y{Float}) Seconds'),
loseRegex: Regexes.Parse(':' + me + ' loses the effect of Embolden from '),
durationPosition: 1,
icon: kIconBuffEmbolden,
borderColor: '#57FC4A',
sortKey: 1,
},
litany: {
gainRegex: new RegExp(':' + me + ' gains the effect of Battle Litany from \\y{Name} for ([0-9.]+) Seconds'),
loseRegex: new RegExp(':' + me + ' loses the effect of Embolden from '),
gainRegex: Regexes.Parse(':' + me + ' gains the effect of Battle Litany from \\y{Name} for (\\y{Float}) Seconds'),
loseRegex: Regexes.Parse(':' + me + ' loses the effect of Embolden from '),
durationPosition: 1,
icon: kIconBuffLitany,
borderColor: '#099',
sortKey: 2,
},
balance: {
gainRegex: new RegExp(':' + me + ' gains the effect of The Balance from \\y{Name} for ([0-9.]+) Seconds'),
loseRegex: new RegExp(':' + me + ' loses the effect of The Balance from '),
gainRegex: Regexes.Parse(':' + me + ' gains the effect of The Balance from \\y{Name} for (\\y{Float}) Seconds'),
loseRegex: Regexes.Parse(':' + me + ' loses the effect of The Balance from '),
durationPosition: 1,
icon: kIconBuffBalance,
borderColor: '#C5C943',
sortKey: 3,
},
chain: {
gainRegex: /:\y{Name}:\y{AbilityCode}:Chain Strategem:/,
gainRegex: Regexes.Parse(/:\y{Name}:\y{AbilityCode}:Chain Strategem:/),
durationSeconds: 15,
icon: kIconBuffChainStrategem,
borderColor: '#4674E5',
sortKey: 5,
},
trick: {
gainRegex: /:\y{Name}:\y{AbilityCode}:Trick Attack:/,
gainRegex: Regexes.Parse(/:\y{Name}:\y{AbilityCode}:Trick Attack:/),
durationSeconds: 10,
icon: kIconBuffTrickAttack,
borderColor: '#FC4AE6',
sortKey: 6,
},
hyper: {
gainRegex: /:\y{Name}:\y{AbilityCode}:Hypercharge:/,
gainRegex: Regexes.Parse(/:\y{Name}:\y{AbilityCode}:Hypercharge:/),
durationSeconds: 20,
icon: kIconBuffHypercharge,
borderColor: '#099',
sortKey: 7,
},
sight: {
gainRegex: /:\y{Name}:\y{AbilityCode}:Dragon Sight:/,
gainRegex: Regexes.Parse(/:\y{Name}:\y{AbilityCode}:Dragon Sight:/),
durationSeconds: 20,
icon: kIconBuffDragonSight,
borderColor: '#FA8737',
sortKey: 4,
},

requiem: {
gainRegex: /:(\y{Name}) gains the effect of Foe Requiem from \1 for ([0-9.]+) Seconds/,
loseRegex: /:(\y{Name}) loses the effect of Foe Requiem from \1/,
gainRegex: Regexes.Parse(/:(\y{Name}) gains the effect of Foe Requiem from \1 for (\y{Float}) Seconds/),
loseRegex: Regexes.Parse(/:(\y{Name}) loses the effect of Foe Requiem from \1/),
durationPosition: 2,
icon: kIconBuffFoes,
borderColor: '#F272F2',
Expand Down Expand Up @@ -1245,9 +1245,9 @@ class Bars {
for (var i = 0; i < e.detail.logs.length; i++) {
var log = e.detail.logs[i];

var r = log.match(/:Battle commencing in ([0-9]+) seconds!/);
var r = log.match(/:Battle commencing in (\y{Float}) seconds!/);
if (r != null) {
var seconds = parseInt(r[1]);
var seconds = Regexes.ParseLocaleFloat(r[1]);
this.SetPullCountdown(seconds);
continue;
}
Expand All @@ -1258,26 +1258,26 @@ class Bars {

r = log.match(kReFoodBuff);
if (r != null) {
var seconds = parseFloat(r[1]);
var seconds = Regexes.ParseLocaleFloat(r[1]);
var now = Date.now(); // This is in ms.
this.foodBuffExpiresTimeMs = now + (seconds * 1000);
this.UpdateFoodBuff();
}

for (var name in kBigBuffTracker) {
var settings = kBigBuffTracker[name];
var r = log.match(Regexes.Parse(settings.gainRegex));
var r = log.match(settings.gainRegex);
if (r != null) {
var seconds = -1;
if ('durationSeconds' in settings) {
seconds = settings.durationSeconds;
} else if ('durationPosition' in settings) {
seconds = r[settings.durationPosition];
seconds = Regexes.ParseLocaleFloat(r[settings.durationPosition]);
}
this.OnBigBuff(name, seconds, settings);
}
if (settings.loseRegex) {
r = log.match(Regexes.Parse(settings.loseRegex));
r = log.match(settings.loseRegex);
if (r != null)
this.OnLoseBigBuff(name, settings);
}
Expand All @@ -1289,7 +1289,7 @@ class Bars {
if (this.job == 'SMN') {
var r = log.match(kReSmnRuinProc);
if (r != null) {
var seconds = parseFloat(r[1]);
var seconds = Regexes.ParseLocaleFloat(r[1]);
this.OnSummonerRuinProc(seconds);
continue;
}
Expand All @@ -1302,19 +1302,19 @@ class Bars {
if (this.job == 'RDM') {
var r = log.match(kReRdmBlackManaProc);
if (r != null) {
var seconds = parseFloat(r[1]);
var seconds = Regexes.ParseLocaleFloat(r[1]);
this.OnRedMageProcBlack(seconds);
continue;
}
r = log.match(kReRdmWhiteManaProc);
if (r != null) {
var seconds = parseFloat(r[1]);
var seconds = Regexes.ParseLocaleFloat(r[1]);
this.OnRedMageProcWhite(seconds);
continue;
}
r = log.match(kReRdmImpactProc);
if (r != null) {
var seconds = parseFloat(r[1]);
var seconds = Regexes.ParseLocaleFloat(r[1]);
this.OnRedMageProcImpact(seconds);
continue;
}
Expand Down Expand Up @@ -1343,7 +1343,7 @@ class Bars {

Test() {
var logs = [];
logs.push(':' + this.me + ' gains the effect of Medicated from ' + this.me + ' for 30 Seconds\.');
logs.push(':' + this.me + ' gains the effect of Medicated from ' + this.me + ' for 30,2 Seconds\.');
logs.push(':' + this.me + ' gains the effect of Embolden from for 20 Seconds\.');
logs.push(':' + this.me + ' gains the effect of Battle Litany from for 25 Seconds\.');
logs.push(':' + this.me + ' gains the effect of The Balance from for 12 Seconds\.');
Expand Down
7 changes: 4 additions & 3 deletions ui/raidboss/data/triggers/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
// Fields that can be a function will receive the following arguments:
// data: An object that triggers may store state on. It is reset when combat ends.
// It comes with the following fields pre-set:
// me: The player's character name.
// job: The player's job.
// role: The role of the player's job (tank/healer/dps-melee/dps-ranged/dps-caster/crafting/gathering).
// me: The player's character name.
// job: The player's job.
// role: The role of the player's job (tank/healer/dps-melee/dps-ranged/dps-caster/crafting/gathering).
// ParseLocaleFloat: A function that can parse \y{Float} matches from the regex.
// matches: The regex match result of the trigger's regex to the log line it matched.
// matches[0] will be the entire match, and matches[1] will be the first group
// in the regex, etc. This can be used to pull data out of the log line.
Expand Down
12 changes: 6 additions & 6 deletions ui/raidboss/data/triggers/o4s.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,15 @@
},
{ // Acceleration Bomb
id: 'O4S2 Acceleration Bomb',
regex: /:(\y{Name}) gains the effect of (?:Unknown_568|Acceleration Bomb) from .*? for ([0-9.]+) Seconds/,
regex: /:(\y{Name}) gains the effect of (?:Unknown_568|Acceleration Bomb) from .*? for (\y{Float}) Seconds/,
alarmText: 'Stop',
delaySeconds: function(data, matches) { return parseFloat(matches[2]) - 4; }, // 4 second warning.
delaySeconds: function(data, matches) { return data.ParseLocaleFloat(matches[2]) - 4; }, // 4 second warning.
condition: function(data, matches) { return matches[1] == data.me; },
tts: 'stop',
},
{ // Beyond Death (Delta)
id: 'O4S2 Beyond Death',
regex: /:(\y{Name}) gains the effect of (?:Unknown_566|Beyond Death) from .*? for ([0-9.]+) Seconds/,
regex: /:(\y{Name}) gains the effect of (?:Unknown_566|Beyond Death) from .*? for (\y{Float}) Seconds/,
delaySeconds: 7,
alarmText: 'Beyond Death: Die',
sound: '../../resources/sounds/Overwatch/Reaper_-_Die_die_die.ogg',
Expand All @@ -203,7 +203,7 @@
},
{ // Beyond Death (Omega)
id: 'O4S2 Beyond Death',
regex: /:(\y{Name}) gains the effect of (?:Unknown_566|Beyond Death) from .*? for ([0-9.]+) Seconds/,
regex: /:(\y{Name}) gains the effect of (?:Unknown_566|Beyond Death) from .*? for (\y{Float}) Seconds/,
delaySeconds: 8, // 20 seconds if you want the third flood cast, 8 for the first.
alarmText: 'Beyond Death: Die',
sound: '../../resources/sounds/Overwatch/Reaper_-_Die_die_die.ogg',
Expand Down Expand Up @@ -232,8 +232,8 @@
},
{ // Final phase Addle warning when Reprisal is ending.
id: 'O4S2 Reprisal',
regex: /gains the effect of Reprisal from .*? for ([0-9.]+) Seconds/,
durationSeconds: function(data, matches) { return parseFloat(matches[1]); },
regex: /gains the effect of Reprisal from .*? for (\y{Float}) Seconds/,
durationSeconds: function(data, matches) { return data.ParseLocaleFloat(matches[1]); },
infoText: 'Reprisal active',
condition: function(data) { return data.finalphase && !data.reprisal; },
run: function(data) { data.reprisal = true; },
Expand Down
4 changes: 2 additions & 2 deletions ui/raidboss/data/triggers/susano-ex.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@
},
{ // Churning (dice)
id: 'SusEx Churning',
regex: /:(\y{Name}) gains the effect of Churning from .*? for ([0-9.]+) Seconds/,
delaySeconds: function(data, matches) { return parseFloat(matches[2]) - 3; },
regex: /:(\y{Name}) gains the effect of Churning from .*? for (\y{Float}) Seconds/,
delaySeconds: function(data, matches) { return data.ParseLocaleFloat(matches[2]) - 3; },
alertText: 'Stop',
condition: function(data, matches) { return matches[1] == data.me; },
tts: 'stop',
Expand Down
16 changes: 8 additions & 8 deletions ui/raidboss/data/triggers/unending_coil_ultimate.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,22 +160,22 @@
tts: 'thunder',
},
{ id: 'UCU Nael Doom',
regex: /:(\y{Name}) gains the effect of Doom from .*? for ([0-9.]+) Seconds/,
regex: /:(\y{Name}) gains the effect of Doom from .*? for (\y{Float}) Seconds/,
condition: function(data, matches) { return data.me == matches[1]; },
alarmText: function(data, matches) {
if (parseFloat(matches[2]) < 9)
if (data.ParseLocaleFloat(matches[2]) < 9)
return 'Doom #1 on YOU';
if (parseFloat(matches[2]) < 14)
if (data.ParseLocaleFloat(matches[2]) < 14)
return 'Doom #2 on YOU';
return 'Doom #3 on YOU';

// TODO: call out all doom people
// TODO: reminder to clear at the right time
},
tts: function(data, matches) {
if (parseFloat(matches[2]) < 9)
if (data.ParseLocaleFloat(matches[2]) < 9)
return '1';
if (parseFloat(matches[2]) < 14)
if (data.ParseLocaleFloat(matches[2]) < 14)
return '2';
return '3';
},
Expand Down Expand Up @@ -276,14 +276,14 @@
run: function(data) { data.fireball4 = true; },
},
{ id: 'UCU Nael Dragon Placement',
regex: /:(Iceclaw:26C6|Thunderwing:26C7|Fang of Light:26CA|Tail of Darkness:26C9|Firehorn:26C5):.*:([-0-9.E]+):([-0-9.E]+):[-0-9.E]+:$/,
regex: /:(Iceclaw:26C6|Thunderwing:26C7|Fang of Light:26CA|Tail of Darkness:26C9|Firehorn:26C5):.*:(\y{Float}):(\y{Float}):\y{Float}:$/,
condition: function(data, matches) { return !data.seenDragon || !(matches[1] in data.seenDragon); },
run: function(data, matches) {
data.seenDragon = data.seenDragon || [];
data.seenDragon[matches[1]] = true;

var x = parseFloat(matches[2]);
var y = parseFloat(matches[3]);
var x = data.ParseLocaleFloat(matches[2]);
var y = data.ParseLocaleFloat(matches[3]);
// Positions are the 8 cardinals + numerical slop on a radius=24 circle.
// N = (0, -24), E = (24, 0), S = (0, 24), W = (-24, 0)
// Map N = 0, NE = 1, ..., NW = 7
Expand Down
7 changes: 6 additions & 1 deletion ui/raidboss/popup-text.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ class PopupText {
}

Reset() {
this.data = { me: this.me, job: this.job, role: this.role };
this.data = {
me: this.me,
job: this.job,
role: this.role,
ParseLocaleFloat: function(s) { return Regexes.ParseLocaleFloat(s); },
};
for (var i = 0; i < this.timers.length; ++i)
window.clearTimeout(this.timers[i]);
this.timers = [];
Expand Down

0 comments on commit 5269d55

Please sign in to comment.