warframe-worldstate-parser 2.23.0 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/lib/Alert.js +8 -8
  2. package/lib/CambionCycle.js +1 -5
  3. package/lib/CetusCycle.js +3 -3
  4. package/lib/ChallengeInstance.js +6 -6
  5. package/lib/ConclaveChallenge.js +2 -5
  6. package/lib/ConstructionProgress.js +4 -2
  7. package/lib/DailyDeal.js +1 -3
  8. package/lib/DarkSector.js +11 -8
  9. package/lib/EarthCycle.js +4 -4
  10. package/lib/Fissure.js +3 -3
  11. package/lib/FlashSale.js +2 -7
  12. package/lib/GlobalUpgrade.js +5 -5
  13. package/lib/Invasion.js +11 -10
  14. package/lib/Kuva.js +16 -10
  15. package/lib/Mission.js +30 -20
  16. package/lib/News.js +13 -15
  17. package/lib/Nightwave.js +7 -4
  18. package/lib/NightwaveChallenge.js +2 -4
  19. package/lib/PersistentEnemy.js +1 -3
  20. package/lib/Reward.js +64 -60
  21. package/lib/SentientOutpost.js +6 -8
  22. package/lib/Simaris.js +5 -3
  23. package/lib/Sortie.js +12 -8
  24. package/lib/SortieVariant.js +13 -13
  25. package/lib/SyndicateJob.js +49 -17
  26. package/lib/SyndicateMission.js +6 -6
  27. package/lib/VallisCycle.js +2 -2
  28. package/lib/VoidTrader.js +9 -13
  29. package/lib/WeeklyChallenge.js +4 -3
  30. package/lib/WorldEvent.js +24 -30
  31. package/lib/WorldState.js +32 -24
  32. package/lib/WorldstateObject.js +3 -3
  33. package/lib/ZarimanCycle.js +136 -0
  34. package/lib/timeDate.js +2 -2
  35. package/lib/translation.js +22 -12
  36. package/package.json +3 -118
  37. package/types/lib/Alert.d.ts +1 -1
  38. package/types/lib/CambionCycle.d.ts +1 -0
  39. package/types/lib/ConclaveChallenge.d.ts +1 -1
  40. package/types/lib/DailyDeal.d.ts +1 -1
  41. package/types/lib/DarkSector.d.ts +1 -1
  42. package/types/lib/Fissure.d.ts +3 -3
  43. package/types/lib/FlashSale.d.ts +1 -1
  44. package/types/lib/GlobalUpgrade.d.ts +1 -1
  45. package/types/lib/Invasion.d.ts +4 -4
  46. package/types/lib/Kuva.d.ts +1 -6
  47. package/types/lib/Mission.d.ts +11 -4
  48. package/types/lib/News.d.ts +4 -4
  49. package/types/lib/Nightwave.d.ts +1 -1
  50. package/types/lib/NightwaveChallenge.d.ts +1 -1
  51. package/types/lib/PersistentEnemy.d.ts +1 -1
  52. package/types/lib/SentientOutpost.d.ts +1 -1
  53. package/types/lib/Sortie.d.ts +1 -1
  54. package/types/lib/SortieVariant.d.ts +10 -10
  55. package/types/lib/SyndicateJob.d.ts +13 -9
  56. package/types/lib/SyndicateMission.d.ts +1 -1
  57. package/types/lib/VoidTrader.d.ts +1 -1
  58. package/types/lib/WorldEvent.d.ts +3 -3
  59. package/types/lib/WorldState.d.ts +5 -0
  60. package/types/lib/ZarimanCycle.d.ts +55 -0
@@ -5,7 +5,7 @@ const fetch = require('node-fetch');
5
5
  const WorldstateObject = require('./WorldstateObject');
6
6
 
7
7
  const apiBase = process.env.API_BASE_URL || 'https://api.warframestat.us';
8
- const bountyRewardRegex = /Tier([ABCDE])Table([ABC])Rewards/i;
8
+ const bountyRewardRegex = /(?:Tier([ABCDE])|Narmer)Table([ABC])Rewards/i;
9
9
  const ghoulRewardRegex = /GhoulBountyTable([AB])Rewards/i;
10
10
 
11
11
  /**
@@ -63,7 +63,9 @@ const getBountyRewards = async (i18n, isVault, raw) => {
63
63
  ({ location, locationWRot } = determineLocation(i18n, isVault, raw));
64
64
  }
65
65
  const url = `${apiBase}/drops/search/${encodeURIComponent(location)}?grouped_by=location`;
66
- const reply = await fetch(url).then((res) => res.json()).catch(() => {}); // swallow errors
66
+ const reply = await fetch(url)
67
+ .then((res) => res.json())
68
+ .catch(() => {}); // swallow errors
67
69
  const pool = (reply || {})[locationWRot];
68
70
  if (!pool) {
69
71
  return ['Pattern Mismatch. Results inaccurate.'];
@@ -75,41 +77,50 @@ const getBountyRewards = async (i18n, isVault, raw) => {
75
77
  return [];
76
78
  };
77
79
 
80
+ const FIFTY_MINUTES = 3000000;
81
+
78
82
  /**
79
83
  * Represents a syndicate daily mission
80
84
  * @extends {WorldstateObject}
81
85
  */
82
86
  class SyndicateJob extends WorldstateObject {
83
87
  /**
84
- * @param {Object} data The syndicate mission data
85
- * @param {Date} expiry The syndicate job expiration
86
- * @param {Object} deps The dependencies object
87
- * @param {Translator} deps.translator The string translator
88
- * @param {string} deps.locale Locale to use for translations
88
+ * @param {Object} data The syndicate mission data
89
+ * @param {Date} expiry The syndicate job expiration
90
+ * @param {Object} deps The dependencies object
91
+ * @param {Object} timeDate Time/Date functions
92
+ * @param {Translator} translator The string translator
93
+ * @param {string} locale Locale to use for translations
89
94
  */
90
95
  constructor(data, expiry, { translator, timeDate, locale }) {
91
- super({ _id: { $oid: data.JobCurrentVersion ? data.JobCurrentVersion.$oid : `${(data.jobType || '').split('/').slice(-1)[0]}${expiry.getTime()}` } }, { timeDate });
96
+ super(
97
+ {
98
+ _id: {
99
+ $oid: data.JobCurrentVersion
100
+ ? data.JobCurrentVersion.$oid
101
+ : `${(data.jobType || '').split('/').slice(-1)[0]}${expiry.getTime()}`,
102
+ },
103
+ },
104
+ { timeDate }
105
+ );
92
106
 
93
107
  /**
94
108
  * Array of strings describing rewards
95
109
  * @type {Array.<String>}
96
110
  */
97
111
  this.rewardPool = [];
98
- getBountyRewards(data.rewards, data.isVault, data)
99
- .then((rewards) => {
100
- this.rewardPool = rewards;
101
- });
112
+ getBountyRewards(data.rewards, data.isVault, data).then((rewards) => {
113
+ this.rewardPool = rewards;
114
+ });
102
115
 
103
- const chamber = ((data.locationTag || '')
104
- .match(/[A-Z]+(?![a-z])|[A-Z]?[a-z]+|\d+/g) || [])
105
- .join(' ');
116
+ const chamber = ((data.locationTag || '').match(/[A-Z]+(?![a-z])|[A-Z]?[a-z]+|\d+/g) || []).join(' ');
106
117
 
107
118
  /**
108
119
  * The type of job this is
109
120
  * @type {String}
110
121
  */
111
- this.type = translator.languageString(data.jobType, locale)
112
- || `${data.isVault ? 'Isolation Vault' : ''} ${chamber}`;
122
+ this.type =
123
+ translator.languageString(data.jobType, locale) || `${data.isVault ? 'Isolation Vault' : ''} ${chamber}`;
113
124
 
114
125
  /**
115
126
  * Array of enemy levels
@@ -141,6 +152,27 @@ class SyndicateJob extends WorldstateObject {
141
152
  * @type {string|null}
142
153
  */
143
154
  this.locationTag = data.locationTag;
155
+
156
+ /**
157
+ * End time for the syndicate mission.
158
+ * Should be inherited from the Syndicate, but some are timebound.
159
+ * @type {Date}
160
+ */
161
+ this.expiry = expiry;
162
+
163
+ /**
164
+ * What time phase this bounty is bound to
165
+ * @type {string}
166
+ */
167
+ this.timeBound = undefined;
168
+ if (data.jobType && data.jobType.toLowerCase().includes('narmer')) {
169
+ if (data.jobType.toLowerCase().includes('eidolon')) {
170
+ this.timeBound = 'day';
171
+ this.expiry = new Date(this.expiry.getTime() - FIFTY_MINUTES);
172
+ } else {
173
+ this.timeBoound = 'night';
174
+ }
175
+ }
144
176
  }
145
177
  }
146
178
 
@@ -16,13 +16,14 @@ class SyndicateMission extends WorldstateObject {
16
16
  * @param {TimeDateFunctions} deps.timeDate The time and date functions
17
17
  * @param {string} deps.locale Locale to use for translations
18
18
  */
19
- constructor(data, {
20
- mdConfig, translator, timeDate, locale,
21
- }) {
19
+ constructor(data, { mdConfig, translator, timeDate, locale }) {
22
20
  super(data, { timeDate });
23
21
 
24
22
  const deps = {
25
- mdConfig, translator, timeDate, locale,
23
+ mdConfig,
24
+ translator,
25
+ timeDate,
26
+ locale,
26
27
  };
27
28
 
28
29
  /**
@@ -113,8 +114,7 @@ class SyndicateMission extends WorldstateObject {
113
114
  toString() {
114
115
  if (this.nodes.length > 0) {
115
116
  const missions = this.nodes.map((n) => ` \u2022${n.toString()}`).join(this.mdConfig.lineEnd);
116
- return `[${this.getETAString()}] ${this.syndicate} currently has missions on: `
117
- + `${this.mdConfig.lineEnd}${missions}`;
117
+ return `[${this.getETAString()}] ${this.syndicate} currently has missions on:${this.mdConfig.lineEnd}${missions}`;
118
118
  }
119
119
 
120
120
  return `No missions available for ${this.syndicate}`;
@@ -21,7 +21,7 @@ function getCurrentCycle() {
21
21
  toNextMinor = toNextFull - coldTime;
22
22
  }
23
23
  const milliAtNext = Date.now() + toNextMinor;
24
- const milliAtPrev = (Date.now() + toNextFull) - (state === 'warm' ? loopTime : coldTime);
24
+ const milliAtPrev = Date.now() + toNextFull - (state === 'warm' ? loopTime : coldTime);
25
25
  const timeAtPrevious = new Date(milliAtPrev);
26
26
  timeAtPrevious.setSeconds(0);
27
27
 
@@ -104,7 +104,7 @@ class VallisCycle extends WorldstateObject {
104
104
 
105
105
  this.id = `vallisCycle${ec.timeAtPrevious.getTime()}`;
106
106
 
107
- this.shortString = `${this.timeLeft.replace(/\s\d*s/ig, '')} to ${this.isWarm ? 'Cold' : 'Warm'}`;
107
+ this.shortString = `${this.timeLeft.replace(/\s\d*s/gi, '')} to ${this.isWarm ? 'Cold' : 'Warm'}`;
108
108
  }
109
109
 
110
110
  /**
package/lib/VoidTrader.js CHANGED
@@ -17,9 +17,7 @@ class VoidTrader extends WorldstateObject {
17
17
  * @param {TimeDateFunctions} deps.timeDate The time and date functions
18
18
  * @param {string} deps.locale Locale to use for translations
19
19
  */
20
- constructor(data, {
21
- mdConfig, translator, timeDate, locale,
22
- }) {
20
+ constructor(data, { mdConfig, translator, timeDate, locale }) {
23
21
  super(data, { timeDate });
24
22
 
25
23
  /**
@@ -54,7 +52,7 @@ class VoidTrader extends WorldstateObject {
54
52
  * The void trader's name
55
53
  * @type {string}
56
54
  */
57
- this.character = data.Character ? data.Character.replace('Baro\'Ki Teel', 'Baro Ki\'Teer') : '';
55
+ this.character = data.Character ? data.Character.replace("Baro'Ki Teel", "Baro Ki'Teer") : '';
58
56
 
59
57
  /**
60
58
  * The node at which the Void Trader appears
@@ -66,9 +64,7 @@ class VoidTrader extends WorldstateObject {
66
64
  * The trader's inventory
67
65
  * @type {VoidTraderItem[]}
68
66
  */
69
- this.inventory = data.Manifest
70
- ? data.Manifest.map((i) => new VoidTraderItem(i, { translator, locale }))
71
- : [];
67
+ this.inventory = data.Manifest ? data.Manifest.map((i) => new VoidTraderItem(i, { translator, locale })) : [];
72
68
 
73
69
  /**
74
70
  * Pseudo Identifier for identifying changes in inventory
@@ -99,8 +95,7 @@ class VoidTrader extends WorldstateObject {
99
95
  this.initialStart = timeDate.parseDate(data.InitialStartDate);
100
96
  this.completed = data.Completed;
101
97
  this.schedule = data.ScheduleInfo
102
- ? data.ScheduleInfo
103
- .map((i) => new VoidTraderSchedule(i, { timeDate, translator, locale }))
98
+ ? data.ScheduleInfo.map((i) => new VoidTraderSchedule(i, { timeDate, translator, locale }))
104
99
  : [];
105
100
  }
106
101
 
@@ -109,8 +104,7 @@ class VoidTrader extends WorldstateObject {
109
104
  * @returns {boolean}
110
105
  */
111
106
  isActive() {
112
- return (this.timeDate.fromNow(this.activation) < 0)
113
- && (this.timeDate.fromNow(this.expiry) > 0);
107
+ return this.timeDate.fromNow(this.activation) < 0 && this.timeDate.fromNow(this.expiry) > 0;
114
108
  }
115
109
 
116
110
  /**
@@ -137,8 +131,10 @@ class VoidTrader extends WorldstateObject {
137
131
  if (!this.isActive()) {
138
132
  const timeDelta = this.timeDate.fromNow(this.activation);
139
133
  const nextArrivalTime = this.timeDate.timeDeltaToString(timeDelta);
140
- return `${this.mdConfig.codeMulti}${this.character} is not here yet, he will arrive in `
141
- + `${nextArrivalTime} at ${this.location}${this.mdConfig.blockEnd}`;
134
+ return (
135
+ `${this.mdConfig.codeMulti}${this.character} is not here yet, he will arrive in ` +
136
+ `${nextArrivalTime} at ${this.location}${this.mdConfig.blockEnd}`
137
+ );
142
138
  }
143
139
 
144
140
  const inventoryString = this.inventory
@@ -18,8 +18,7 @@ class WeeklyChallenge extends WorldstateObject {
18
18
  constructor(data, { timeDate, translator }) {
19
19
  super(data, { timeDate });
20
20
 
21
- this.challenges = data.Challenges
22
- .map((challengeData) => new ChallengeInstance(challengeData, { translator }));
21
+ this.challenges = data.Challenges.map((challengeData) => new ChallengeInstance(challengeData, { translator }));
23
22
  }
24
23
 
25
24
  /**
@@ -27,7 +26,9 @@ class WeeklyChallenge extends WorldstateObject {
27
26
  * @returns {string}
28
27
  */
29
28
  toString() {
30
- return `Starts: ${this.getStartString()}\nEnds: ${this.getEndString()}\nChallenges:\n${this.challenges.map((challenge) => `\t${challenge.toString()}`).join('\n')}`;
29
+ return `Starts: ${this.getStartString()}\nEnds: ${this.getEndString()}\nChallenges:\n${this.challenges
30
+ .map((challenge) => `\t${challenge.toString()}`)
31
+ .join('\n')}`;
31
32
  }
32
33
  }
33
34
 
package/lib/WorldEvent.js CHANGED
@@ -36,13 +36,15 @@ class WorldEvent extends WorldstateObject {
36
36
  * @param {Reward} deps.Reward The Reward parser
37
37
  * @param {string} deps.locale Locale to use for translations
38
38
  */
39
- constructor(data, {
40
- mdConfig, translator, timeDate, Reward, locale,
41
- }) {
39
+ constructor(data, { mdConfig, translator, timeDate, Reward, locale }) {
42
40
  super(data, { timeDate });
43
41
 
44
42
  const opts = {
45
- translator, mdConfig, Reward, timeDate, locale,
43
+ translator,
44
+ mdConfig,
45
+ Reward,
46
+ timeDate,
47
+ locale,
46
48
  };
47
49
 
48
50
  /**
@@ -118,9 +120,7 @@ class WorldEvent extends WorldstateObject {
118
120
  * The other nodes where the event takes place
119
121
  * @type {string[]}
120
122
  */
121
- this.concurrentNodes = data.ConcurrentNodes
122
- ? data.ConcurrentNodes.map((n) => translator.node(n), locale)
123
- : [];
123
+ this.concurrentNodes = data.ConcurrentNodes ? data.ConcurrentNodes.map((n) => translator.node(n), locale) : [];
124
124
 
125
125
  /**
126
126
  * The victim node
@@ -132,16 +132,14 @@ class WorldEvent extends WorldstateObject {
132
132
  * The score description
133
133
  * @type {?string}
134
134
  */
135
- this.scoreLocTag = data.Fomorian
136
- ? 'Fomorian Assault Score'
137
- : translator.languageString(data.ScoreLocTag, locale);
135
+ this.scoreLocTag = data.Fomorian ? 'Fomorian Assault Score' : translator.languageString(data.ScoreLocTag, locale);
138
136
 
139
137
  /**
140
138
  * The event's rewards
141
139
  * @type {Reward[]}
142
140
  */
143
141
  this.rewards = Object.keys(data)
144
- .filter((k) => (k.includes('Reward') || k.includes('reward')))
142
+ .filter((k) => k.includes('Reward') || k.includes('reward'))
145
143
  .map((k) => new Reward(data[k], opts));
146
144
 
147
145
  /**
@@ -154,16 +152,14 @@ class WorldEvent extends WorldstateObject {
154
152
  * Health remaining for the target
155
153
  * @type {Number}
156
154
  */
157
- this.health = typeof data.HealthPct !== 'undefined'
158
- ? Number.parseFloat(((data.HealthPct || 0.00) * 100).toFixed(2))
159
- : undefined;
155
+ this.health =
156
+ typeof data.HealthPct !== 'undefined' ? Number.parseFloat(((data.HealthPct || 0.0) * 100).toFixed(2)) : undefined;
160
157
 
161
158
  if (data.JobAffiliationTag) {
162
159
  this.affiliatedWith = translator.syndicate(data.JobAffiliationTag, locale);
163
160
  if (data.Jobs) {
164
161
  this.jobs = (data.Jobs || []).map((j) => new SyndicateJob(j, this.expiry, opts));
165
- this.previousJobs = (data.PreviousJobs || [])
166
- .map((j) => new SyndicateJob(j, this.expiry, opts));
162
+ this.previousJobs = (data.PreviousJobs || []).map((j) => new SyndicateJob(j, this.expiry, opts));
167
163
  }
168
164
  }
169
165
 
@@ -180,7 +176,7 @@ class WorldEvent extends WorldstateObject {
180
176
  this.interimSteps = [];
181
177
 
182
178
  (data.InterimRewards || []).forEach((reward, index) => {
183
- const msg = ((data.InterimRewardMessages || [])[index] || {});
179
+ const msg = (data.InterimRewardMessages || [])[index] || {};
184
180
  this.interimSteps[index] = {
185
181
  goal: Number.parseInt(data.InterimGoals[index], 10),
186
182
  reward: reward ? new Reward(reward, opts) : undefined,
@@ -191,13 +187,14 @@ class WorldEvent extends WorldstateObject {
191
187
  senderIcon: msg.senderIcon,
192
188
  attachments: msg.attachments,
193
189
  },
190
+ // eslint-disable-next-line no-underscore-dangle
194
191
  winnerCount: (data._interimWinnerCounts || [])[index],
195
192
  };
196
193
  });
197
194
 
198
195
  /**
199
196
  * Progress Steps, if any are present
200
- * @type {ProgessStep[]}
197
+ * @type {ProgressStep[]}
201
198
  */
202
199
  this.progressSteps = [];
203
200
 
@@ -213,8 +210,7 @@ class WorldEvent extends WorldstateObject {
213
210
  * Total of all MultiProgress
214
211
  * @type {Number}
215
212
  */
216
- this.progressTotal = Number.parseFloat(data.MultiProgress
217
- .reduce((accumulator, val) => accumulator + val));
213
+ this.progressTotal = Number.parseFloat(data.MultiProgress.reduce((accumulator, val) => accumulator + val));
218
214
  }
219
215
 
220
216
  /**
@@ -237,15 +233,13 @@ class WorldEvent extends WorldstateObject {
237
233
  * Affectors for this mission
238
234
  * @type {string[]}
239
235
  */
240
- this.regionDrops = (data.RegionDrops || [])
241
- .map((drop) => translator.languageString(drop, locale));
236
+ this.regionDrops = (data.RegionDrops || []).map((drop) => translator.languageString(drop, locale));
242
237
 
243
238
  /**
244
239
  * Archwing Drops in effect while this event is active
245
240
  * @type {string[]}
246
241
  */
247
- this.archwingDrops = (data.ArchwingDrops || [])
248
- .map((drop) => translator.languageString(drop, locale));
242
+ this.archwingDrops = (data.ArchwingDrops || []).map((drop) => translator.languageString(drop, locale));
249
243
 
250
244
  this.asString = this.toString();
251
245
 
@@ -285,9 +279,7 @@ class WorldEvent extends WorldstateObject {
285
279
  * @returns {string}
286
280
  */
287
281
  toString() {
288
- let lines = [
289
- `${this.description} : ${this.faction}`,
290
- ];
282
+ let lines = [`${this.description} : ${this.faction}`];
291
283
 
292
284
  if (this.scoreLocTag && this.maximumScore) {
293
285
  lines.push(`${this.scoreLocTag} : ${this.maximumScore}`);
@@ -307,9 +299,11 @@ class WorldEvent extends WorldstateObject {
307
299
  lines.push(`${this.health}% Remaining`);
308
300
  }
309
301
 
310
- if (this.affiliatedWith) {
311
- lines.push(`${this.affiliatedWith} will reward you for performing `
312
- + `${this.jobs.map((job) => job.type).join(', ')} job${this.jobs.length > 1 ? 's' : ''}`);
302
+ if (this.affiliatedWith && this.jobs) {
303
+ lines.push(
304
+ `${this.affiliatedWith} will reward you for performing ` +
305
+ `${this.jobs.map((job) => job.type).join(', ')} job${this.jobs.length > 1 ? 's' : ''}`
306
+ );
313
307
  }
314
308
 
315
309
  return lines.join(this.mdConfig.lineEnd);
package/lib/WorldState.js CHANGED
@@ -27,6 +27,7 @@ const EarthCycle = require('./EarthCycle');
27
27
  const CetusCycle = require('./CetusCycle');
28
28
  const ConstructionProgress = require('./ConstructionProgress');
29
29
  const VallisCycle = require('./VallisCycle');
30
+ const ZarimanCycle = require('./ZarimanCycle');
30
31
  const WeeklyChallenge = require('./WeeklyChallenge');
31
32
  const Nightwave = require('./Nightwave');
32
33
  const Kuva = require('./Kuva');
@@ -41,8 +42,8 @@ const ExternalMission = require('./supporting/ExternalMission');
41
42
  const MarkdownSettings = require('./supporting/MarkdownSettings');
42
43
  /* eslint-enable no-unused-vars */
43
44
 
44
- const safeArray = (arr) => (arr || []);
45
- const safeObj = (obj) => (obj || {});
45
+ const safeArray = (arr) => arr || [];
46
+ const safeObj = (obj) => obj || {};
46
47
 
47
48
  /**
48
49
  * Default Dependency object
@@ -81,7 +82,7 @@ function parseArray(ParserClass, dataArray, deps, uniqueField) {
81
82
  const arr = (dataArray || []).map((d) => new ParserClass(d, deps));
82
83
  if (uniqueField) {
83
84
  const utemp = {};
84
- arr.sort((a, b) => a.id.localeCompare((b.id)));
85
+ arr.sort((a, b) => a.id.localeCompare(b.id));
85
86
  arr.forEach((obj) => {
86
87
  utemp[obj[uniqueField]] = obj;
87
88
  });
@@ -126,9 +127,13 @@ module.exports = class WorldState {
126
127
  * The in-game news
127
128
  * @type {Array.<News>}
128
129
  */
129
- this.news = parseArray(deps.News, (data.Events
130
- ? data.Events.filter((e) => typeof e.Messages.find((msg) => msg.LanguageCode === deps.locale) !== 'undefined')
131
- : []), deps);
130
+ this.news = parseArray(
131
+ deps.News,
132
+ data.Events
133
+ ? data.Events.filter((e) => typeof e.Messages.find((msg) => msg.LanguageCode === deps.locale) !== 'undefined')
134
+ : [],
135
+ deps
136
+ );
132
137
 
133
138
  /**
134
139
  * The current events
@@ -158,8 +163,9 @@ module.exports = class WorldState {
158
163
  * The current fissures: 'ActiveMissions' & 'VoidStorms'
159
164
  * @type {Array.<News>}
160
165
  */
161
- this.fissures = parseArray(deps.Fissure, data.ActiveMissions, deps)
162
- .concat(parseArray(deps.Fissure, data.VoidStorms, deps));
166
+ this.fissures = parseArray(deps.Fissure, data.ActiveMissions, deps).concat(
167
+ parseArray(deps.Fissure, data.VoidStorms, deps)
168
+ );
163
169
 
164
170
  /**
165
171
  * The current global upgrades
@@ -221,10 +227,8 @@ module.exports = class WorldState {
221
227
  */
222
228
  this.earthCycle = new EarthCycle(deps);
223
229
 
224
- const cetusSynd = safeArray(data.SyndicateMissions)
225
- .filter((syndicate) => syndicate.Tag === 'CetusSyndicate');
226
- const cetusBountyEnd = timeDate
227
- .parseDate(cetusSynd.length > 0 ? cetusSynd[0].Expiry : { $date: 0 });
230
+ const cetusSynd = safeArray(data.SyndicateMissions).filter((syndicate) => syndicate.Tag === 'CetusSyndicate');
231
+ const cetusBountyEnd = timeDate.parseDate(cetusSynd.length > 0 ? cetusSynd[0].Expiry : { $date: 0 });
228
232
 
229
233
  /**
230
234
  * The current Cetus cycle
@@ -238,28 +242,32 @@ module.exports = class WorldState {
238
242
  */
239
243
  this.cambionCycle = new CambionCycle(this.cetusCycle, deps);
240
244
 
245
+ /**
246
+ * The current Zariman cycle based off current time
247
+ * @type {ZarimanCycle}
248
+ */
249
+ this.zarimanCycle = new ZarimanCycle(Date.now(), deps);
250
+
241
251
  /**
242
252
  * Weekly challenges
243
253
  * @type {Array.<WeeklyChallenge>}
244
254
  */
245
- this.weeklyChallenges = data.WeeklyChallenges
246
- ? new deps.WeeklyChallenge(data.WeeklyChallenges, deps)
247
- : [];
255
+ this.weeklyChallenges = data.WeeklyChallenges ? new deps.WeeklyChallenge(data.WeeklyChallenges, deps) : [];
248
256
 
249
- const projectPCTwithOid = data.ProjectPct ? {
250
- ProjectPct: data.ProjectPct,
251
- _id: {
252
- $oid: `${Date.now()}${data.ProjectPct[0]}`,
253
- },
254
- } : undefined;
257
+ const projectPCTwithOid = data.ProjectPct
258
+ ? {
259
+ ProjectPct: data.ProjectPct,
260
+ _id: {
261
+ $oid: `${Date.now()}${data.ProjectPct[0]}`,
262
+ },
263
+ }
264
+ : undefined;
255
265
 
256
266
  /**
257
267
  * The Current construction progress for Fomorians/Razorback/etc.
258
268
  * @type {ConstructionProgress}
259
269
  */
260
- this.constructionProgress = projectPCTwithOid
261
- ? new ConstructionProgress(projectPCTwithOid, deps)
262
- : {};
270
+ this.constructionProgress = projectPCTwithOid ? new ConstructionProgress(projectPCTwithOid, deps) : {};
263
271
 
264
272
  /**
265
273
  * The current Orb Vallis cycle state
@@ -12,7 +12,8 @@ class WorldstateObject {
12
12
  * The object's id field
13
13
  * @type {string}
14
14
  */
15
- this.id = data._id ? (data._id.$oid || data._id.$id) : undefined;
15
+ // eslint-disable-next-line no-underscore-dangle
16
+ this.id = data._id ? data._id.$oid || data._id.$id : undefined;
16
17
 
17
18
  /**
18
19
  * The time and date functions
@@ -67,8 +68,7 @@ class WorldstateObject {
67
68
  * @returns {boolean}
68
69
  */
69
70
  isActive() {
70
- return (this.timeDate.fromNow(this.activation) < 0)
71
- && (this.timeDate.fromNow(this.expiry) > 0);
71
+ return this.timeDate.fromNow(this.activation) < 0 && this.timeDate.fromNow(this.expiry) > 0;
72
72
  }
73
73
 
74
74
  /**
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ const WorldstateObject = require('./WorldstateObject');
4
+
5
+ // This is a confirmed starting time for Corpus (in millis)
6
+ // All faction operation should use this as a calculation point
7
+ // Unless there's a better logic
8
+ const corpusTimeMillis = 1654725600000;
9
+ const fullCycle = 28800000;
10
+ const stateMaximum = 14400000;
11
+
12
+ /**
13
+ * Represents the current Zariman Corpus/Grineer Cycle
14
+ * @extends {WorldstateObject}
15
+ */
16
+ module.exports = class ZarimanCycle extends WorldstateObject {
17
+ /**
18
+ * @param {Date} currentTime The current time to calculate Zariman cycle for
19
+ * @param {Object} deps The dependencies object
20
+ * @param {MarkdownSettings} deps.mdConfig The markdown settings
21
+ * @param {TimeDateFunctions} deps.timeDate The time and date functions
22
+ */
23
+ constructor(currentTime, { mdConfig, timeDate }) {
24
+ super({ _id: { $oid: 'zarimanCycle0' } }, { timeDate });
25
+
26
+ /**
27
+ * The markdown settings
28
+ * @type {MarkdownSettings}
29
+ * @private
30
+ */
31
+ this.mdConfig = mdConfig;
32
+ Object.defineProperty(this, 'mdConfig', { enumerable: false, configurable: false });
33
+
34
+ /**
35
+ * The end of the Zariman bounties timer, the faction changes exactly half way through
36
+ * @type {Date}
37
+ * @private
38
+ */
39
+ this.currentTime = currentTime;
40
+ Object.defineProperty(this, 'currentTime', { enumerable: false, configurable: false });
41
+
42
+ /**
43
+ * The time and date functions
44
+ * @type {TimeDateFunctions}
45
+ * @private
46
+ */
47
+ this.timeDate = timeDate;
48
+ Object.defineProperty(this, 'timeDate', { enumerable: false, configurable: false });
49
+
50
+ /**
51
+ * The current zariman cycle, for calculating the other fields
52
+ * @type {Object}
53
+ * @private
54
+ */
55
+ const ec = this.getCurrentZarimanCycle();
56
+ Object.defineProperty(this, 'ec', { enumerable: false, configurable: false });
57
+
58
+ /**
59
+ * The date and time at which the event ends
60
+ * @type {Date}
61
+ */
62
+ this.expiry = ec.expiry;
63
+
64
+ /**
65
+ * The date and time at which the event started
66
+ * @type {Date}
67
+ */
68
+ this.activation = new Date(ec.start);
69
+
70
+ /**
71
+ * Whether or not this it's daytime
72
+ * @type {boolean}
73
+ */
74
+ this.isCorpus = ec.corpusTime;
75
+
76
+ /**
77
+ * Current cycle state. One of `corpus`, `grineer`
78
+ * @type {string}
79
+ */
80
+ this.state = ec.state;
81
+
82
+ /**
83
+ * Time remaining string
84
+ * @type {string}
85
+ */
86
+ this.timeLeft = ec.timeLeft;
87
+
88
+ this.id = `zarimanCycle${this.expiry.getTime()}`;
89
+
90
+ this.shortString = `${this.timeLeft.replace(/\s\d*s/gi, '')} to ${this.isCorpus ? 'grineer' : 'corpus'}`;
91
+ }
92
+
93
+ /**
94
+ * Get whether or not the event has expired
95
+ * @returns {boolean}
96
+ */
97
+ getExpired() {
98
+ return this.timeDate.fromNow(this.expiry) < 0;
99
+ }
100
+
101
+ getCurrentZarimanCycle() {
102
+ // determine if it is corpus cycle or grineer cycle
103
+ const timeInCycle = (this.currentTime - corpusTimeMillis) % fullCycle;
104
+ // if timeInCycle is less than 4 hours, it is corpus, otherwise it is grineer
105
+ const corpusTime = timeInCycle <= stateMaximum;
106
+
107
+ // cycles are offset by 2 hours from bounties
108
+ const millisLeft = fullCycle - timeInCycle;
109
+
110
+ const minutesCoef = 1000 * 60;
111
+ const expiry = new Date(Math.round((this.currentTime + millisLeft) / minutesCoef) * minutesCoef);
112
+ const state = corpusTime ? 'corpus' : 'grineer';
113
+
114
+ return {
115
+ corpusTime,
116
+ timeLeft: this.timeDate.timeDeltaToString(millisLeft),
117
+ expiry,
118
+ expiresIn: millisLeft,
119
+ state,
120
+ start: expiry.getTime() - stateMaximum,
121
+ };
122
+ }
123
+
124
+ /**
125
+ * The event's string representation
126
+ * @returns {string}
127
+ */
128
+ toString() {
129
+ const lines = [
130
+ `Operator, Zariman Ten Zero is currently occupied by ${this.state}`,
131
+ `Time remaining until ${this.isCorpus ? 'grineer' : 'corpus'} takeover: ${this.timeLeft}`,
132
+ ];
133
+
134
+ return lines.join(this.mdConfig.lineEnd);
135
+ }
136
+ };
package/lib/timeDate.js CHANGED
@@ -74,8 +74,8 @@ function toNow(d, now = Date.now) {
74
74
  * @returns {Date}
75
75
  */
76
76
  function parseDate(d) {
77
- const safeD = (d || epochZero);
78
- const dt = (safeD.$date || epochZero.$date);
77
+ const safeD = d || epochZero;
78
+ const dt = safeD.$date || epochZero.$date;
79
79
  return new Date(safeD.$date ? Number(dt.$numberLong) : 1000 * d.sec);
80
80
  }
81
81