Skip to content

Commit

Permalink
fix: zarimanCycle logic (#400)
Browse files Browse the repository at this point in the history
Co-authored-by: Tobiah <[email protected]>
  • Loading branch information
EricSihaoLin and TobiTenno authored Jun 18, 2022
1 parent 6fbaa30 commit b7c6055
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 55 deletions.
5 changes: 4 additions & 1 deletion lib/WorldState.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,14 @@ module.exports = class WorldState {
*/
this.cambionCycle = new CambionCycle(this.cetusCycle, deps);

const zarimanSynd = safeArray(data.SyndicateMissions).filter((syndicate) => syndicate.Tag === 'ZarimanSyndicate');
const zarimanBountyEnd = timeDate.parseDate(zarimanSynd.length > 0 ? zarimanSynd[0].Expiry : { $date: 0 });

/**
* The current Zariman cycle based off current time
* @type {ZarimanCycle}
*/
this.zarimanCycle = new ZarimanCycle(Date.now(), deps);
this.zarimanCycle = new ZarimanCycle(zarimanBountyEnd, deps);

/**
* Weekly challenges
Expand Down
43 changes: 24 additions & 19 deletions lib/ZarimanCycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ const WorldstateObject = require('./WorldstateObject');
// This is a confirmed starting time for Corpus (in millis)
// All faction operation should use this as a calculation point
// Unless there's a better logic
const corpusTimeMillis = 1654725600000;
const fullCycle = 28800000;
const stateMaximum = 14400000;
const corpusTimeMillis = 1655182800000;
const fullCycle = 18000000;
const stateMaximum = 9000000;

/**
* Represents the current Zariman Corpus/Grineer Cycle
* @extends {WorldstateObject}
*/
module.exports = class ZarimanCycle extends WorldstateObject {
/**
* @param {Date} currentTime The current time to calculate Zariman cycle for
* @param {Date} bountiesEndDate The current zariman cycle expiry
* @param {Object} deps The dependencies object
* @param {MarkdownSettings} deps.mdConfig The markdown settings
* @param {TimeDateFunctions} deps.timeDate The time and date functions
*/
constructor(currentTime, { mdConfig, timeDate }) {
constructor(bountiesEndDate, { mdConfig, timeDate }) {
super({ _id: { $oid: 'zarimanCycle0' } }, { timeDate });

/**
Expand All @@ -36,7 +36,7 @@ module.exports = class ZarimanCycle extends WorldstateObject {
* @type {Date}
* @private
*/
this.currentTime = currentTime;
this.bountiesEndDate = bountiesEndDate;
Object.defineProperty(this, 'currentTime', { enumerable: false, configurable: false });

/**
Expand Down Expand Up @@ -68,10 +68,10 @@ module.exports = class ZarimanCycle extends WorldstateObject {
this.activation = new Date(ec.start);

/**
* Whether or not this it's daytime
* Whether or not this it's corpus or grineer
* @type {boolean}
*/
this.isCorpus = ec.corpusTime;
this.isCorpus = ec.isCorpus;

/**
* Current cycle state. One of `corpus`, `grineer`
Expand All @@ -95,24 +95,29 @@ module.exports = class ZarimanCycle extends WorldstateObject {
* @returns {boolean}
*/
getExpired() {
return this.timeDate.fromNow(this.expiry) < 0;
return this.expiry ? this.timeDate.fromNow(this.expiry) < 0 : true;
}

getCurrentZarimanCycle() {
// determine if it is corpus cycle or grineer cycle
const timeInCycle = (this.currentTime - corpusTimeMillis) % fullCycle;
// if timeInCycle is less than 4 hours, it is corpus, otherwise it is grineer
const corpusTime = timeInCycle <= stateMaximum;

// cycles are offset by 2 hours from bounties
const millisLeft = fullCycle - timeInCycle;
const now = Date.now();
// determine if it is corpus cycle or grineer cycle based on bounty end time
// we subtract 5000 millis (5 seconds) to ensure the corpus/grineer calculation is correct
const bountiesClone = this.bountiesEndDate.getTime() - 5000;
const millisLeft = this.timeDate.fromNow(new Date(bountiesClone));
// the following line is a modulus operation
// this ensures that our calculation is correct if bountiesClone is before corpusTimeMillis
// if you really care, read https://torstencurdt.com/tech/posts/modulo-of-negative-numbers/
const cycleTimeElapsed = (((bountiesClone - corpusTimeMillis) % fullCycle) + fullCycle) % fullCycle;
const cycleTimeLeft = fullCycle - cycleTimeElapsed;
// if timeInCycle is more than 2.5 hours, it is corpus, otherwise it is grineer
const isCorpus = cycleTimeLeft > stateMaximum;

const minutesCoef = 1000 * 60;
const expiry = new Date(Math.round((this.currentTime + millisLeft) / minutesCoef) * minutesCoef);
const state = corpusTime ? 'corpus' : 'grineer';
const expiry = new Date(Math.round((now + millisLeft) / minutesCoef) * minutesCoef);
const state = isCorpus ? 'corpus' : 'grineer';

return {
corpusTime,
isCorpus,
timeLeft: this.timeDate.timeDeltaToString(millisLeft),
expiry,
expiresIn: millisLeft,
Expand Down
55 changes: 26 additions & 29 deletions test/integration/integration.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
const chai = require('chai');
const sinonChai = require('sinon-chai');
const rewire = require('rewire');
const fs = require('fs');
const fs = require('fs').promises;

const fetch = require('node-fetch');
const path = require('path');

const logger = {
error: () => {},
Expand All @@ -21,34 +22,30 @@ const WorldState = rewire('../../lib/WorldState.js');

describe('WorldState (integration)', () => {
describe('#constructor()', async () => {
['pc', 'ps4', 'xb1', 'swi'].forEach((platform) => {
it('should parse live worldstate data', function (done) {
this.timeout = 10000; // allow 10 seconds to parse the worldstate
const url = `https://content.${platform === 'pc' ? '' : `${platform}.`}warframe.com/dynamic/worldState.php`;
fetch(url)
.then((d) => d.text())
.then((ws) => {
let wsl;
(() => {
wsl = new WorldState(ws, { logger, locale: 'en' });
}).should.not.throw();

wsl.news.forEach((article) => {
if (article.message.toLowerCase().includes('stream')) {
article.should.include({ stream: true });
}
});

/* Easy debugging! */
setTimeout(() => {
fs.writeFileSync(
`./data.${platform}.json`,
JSON.stringify(wsl.syndicateMissions.find((m) => m.syndicateKey === 'Ostrons'))
);
done();
}, 1000);
await Promise.all(
['pc', 'ps4', 'xb1', 'swi'].map(async function (platform) {
it(`should parse live ${platform} worldstate data`, async function () {
this.timeout = 10000; // allow 10 seconds to parse the worldstate
const url = `https://content.${platform === 'pc' ? '' : `${platform}.`}warframe.com/dynamic/worldState.php`;
const ws = await fetch(url).then((d) => d.text());

let wsl;
(() => {
wsl = new WorldState(ws, { logger, locale: 'en' });
}).should.not.throw();

wsl.news.forEach((article) => {
if (article.message.toLowerCase().includes('stream')) {
article.should.include({ stream: true });
}
});
});
});
/* Easy debugging! */
return fs.writeFile(
path.resolve(`./data.${platform}.json`),
JSON.stringify(wsl.syndicateMissions.find((m) => m.syndicateKey === 'Ostrons'))
);
});
})
);
});
});
11 changes: 5 additions & 6 deletions test/unit/zariman.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ const MarkdownSettings = require('../../lib/supporting/MarkdownSettings');

const should = chai.should();
const mdConfig = new MarkdownSettings();
// this is a confirmed corpus cycle time
// the zariman cycle logic depends on the current time
const confirmedCorpus = 1654725600001;
// these are confirmed corpus and grineer cycle time
// the zariman cycle logic depends on zariman bounty expiry time
const confirmedCorpus = 1655059552000;
const confirmedGrineer = 1655181672000;

describe('ZarimanCycle', function () {
describe('#constructor()', function () {
Expand Down Expand Up @@ -43,9 +44,7 @@ describe('ZarimanCycle', function () {
});

it('should show grineer cycle string', () => {
// 14400000 is 4 hours in millis, by adding 4 hours, it should show
// a grineer cycle
const cycle = new ZarimanCycle(new Date(confirmedCorpus + 14400000), {
const cycle = new ZarimanCycle(new Date(confirmedGrineer), {
timeDate,
mdConfig,
});
Expand Down

0 comments on commit b7c6055

Please sign in to comment.