Autoupdate, Syndicates, void fisures, sotries

sotries needs more data(currently all worldstate data is taken from wfcd worldstate data package)
This commit is contained in:
dutlist 2024-06-22 23:56:30 +02:00 committed by AMelonInsideLemon
parent aa1f78c6df
commit abaddeb544
9 changed files with 4473 additions and 72 deletions

View File

@ -15,9 +15,11 @@ import { statsRouter } from "@/src/routes/stats";
import { webuiRouter } from "@/src/routes/webui";
import { connectDatabase } from "@/src/services/mongoService";
import { registerLogFileCreationListener } from "@/src/utils/logger";
import { worldStateRunner } from "@/src/services/worldStateService";
void registerLogFileCreationListener();
void connectDatabase();
void worldStateRunner();
const app = express();

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,17 @@
import { RequestHandler } from "express";
import worldState from "@/static/fixed_responses/worldState.json";
import buildConfig from "@/static/data/buildConfig.json";
import { IWorldState } from "@/src/types/worldStateTypes";
import { config } from "@/src/services/configService";
import { getWorldState } from "@/src/services/worldStateService";
const worldStateController: RequestHandler = async (_req, res) => {
let ws: IWorldState = {};
let ws: { [k: string]: any } = {};
if (config.useStaticWorldState) {
ws = worldState;
ws.BuildLabel = buildConfig.buildLabel;
ws.Time = Math.round(Date.now() / 1000);
} else {
ws = (await getWorldState()).toJSON();
ws = await getWorldState();
ws.BuildLabel = buildConfig.buildLabel;
ws.Time = Math.round(Date.now() / 1000);
}

View File

@ -74,4 +74,13 @@ export const isObject = (objectCandidate: unknown): objectCandidate is Record<st
);
};
export const getRandomNumber = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1) + min);
};
export const getRandomKey = (keys: string[]) => {
const randomIndex = Math.floor(Math.random() * keys.length);
return keys[randomIndex];
};
export { isString, isNumber, parseString, parseNumber, parseDateNumber, parseBoolean, parseEmail };

View File

@ -0,0 +1,30 @@
import { ExportRegions } from "warframe-public-export-plus";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { logger } from "../utils/logger";
export const getRandomNodes = (n: number) => {
const nodes = Object.entries(ExportRegions).map(([key]) => {
return {
nodeKey: key
};
}); // may be filter that?
const output: string[] = [];
for (let i = 0; i < n; i++) {
logger.debug(i);
const randomIndex = Math.floor(Math.random() * nodes.length);
output[i] = nodes[randomIndex].nodeKey;
}
return output;
};
export const getCurrentRotation = () => {
const intervalMilliseconds = 2.5 * unixTimesInMs.hour;
const rotations = ["A", "B", "C"];
const now = new Date();
const currentTimeMs = now.getTime();
const intervalIndex = Math.floor(currentTimeMs / intervalMilliseconds) % 3;
return rotations[intervalIndex];
};

View File

@ -57,10 +57,10 @@ const EventSchema = new Schema<IEvent>({
Messages: [messageSchema],
Prop: String,
Links: [linkSchema],
Date: Date,
Date: Number,
Icon: String,
EventStartDate: Date,
EventEndDate: Date,
EventStartDate: Number,
EventEndDate: Number,
ImageUrl: String,
Priority: Boolean,
MobileOnly: Boolean,
@ -71,6 +71,9 @@ const EventSchema = new Schema<IEvent>({
EventSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Date = { $date: { $numberLong: returnedObject.Date.toString() } };
returnedObject.EventStartDate = { $date: { $numberLong: returnedObject.EventStartDate.toString() } };
returnedObject.EventEndDate = { $date: { $numberLong: returnedObject.EventEndDate.toString() } };
}
});
@ -121,8 +124,8 @@ const MissionSchema = new Schema<IMission>(
);
const AlertSchema = new Schema<IAlert>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
MissionInfo: MissionSchema,
ForceUnlock: Boolean,
Tag: String
@ -131,27 +134,26 @@ const AlertSchema = new Schema<IAlert>({
AlertSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
const SortieMissionSchema = new Schema<ISortieMission>({
missionType: String,
modifierType: String,
node: String,
tileset: String
});
SortieMissionSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
}
});
const SortieMissionSchema = new Schema<ISortieMission>(
{
missionType: String,
modifierType: String,
node: String,
tileset: String
},
{ _id: false }
);
const LiteSortieSchema = new Schema<ILiteSortie>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
Reward: String,
Seed: String,
Seed: Number,
Boss: String,
Missions: [SortieMissionSchema]
});
@ -159,15 +161,18 @@ const LiteSortieSchema = new Schema<ILiteSortie>({
LiteSortieSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
const SortieSchema = new Schema<ISortie>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
Reward: String,
Seed: String,
Seed: Number,
Boss: String,
ExtraDrops: [String],
Variants: [SortieMissionSchema],
Twitter: Boolean
});
@ -175,6 +180,8 @@ const SortieSchema = new Schema<ISortie>({
SortieSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
@ -195,10 +202,10 @@ const JobSchema = new Schema<IJob>(
);
const SyndicateMissionSchema = new Schema<ISyndicateMission>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
Tag: String,
Seed: String,
Seed: Number,
Nodes: [String],
Jobs: [JobSchema]
});
@ -206,14 +213,16 @@ const SyndicateMissionSchema = new Schema<ISyndicateMission>({
SyndicateMissionSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
const ActiveMissionSchema = new Schema<IActiveMission>({
Activation: Date,
Expiry: Date,
Region: String,
Seed: String,
Activation: Number,
Expiry: Number,
Region: Number,
Seed: Number,
Node: String,
MissionType: String,
Modifier: String,
@ -223,12 +232,14 @@ const ActiveMissionSchema = new Schema<IActiveMission>({
ActiveMissionSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
const GlobalUpgradeSchema = new Schema<IGlobalUpgrade>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
UpgradeType: String,
OperationType: String,
Value: String
@ -237,14 +248,16 @@ const GlobalUpgradeSchema = new Schema<IGlobalUpgrade>({
GlobalUpgradeSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
const FlashSaleSchema = new Schema<IFlashSale>(
{
TypeName: String,
StartDate: Date,
EndDate: Date,
StartDate: Number,
EndDate: Number,
ShowInMarket: Boolean,
HideFromMarket: Boolean,
SupporterPack: Boolean,
@ -257,6 +270,13 @@ const FlashSaleSchema = new Schema<IFlashSale>(
{ _id: false }
);
FlashSaleSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject.StartDate = { $date: { $numberLong: returnedObject.StartDate.toString() } };
returnedObject.EndDate = { $date: { $numberLong: returnedObject.EndDate.toString() } };
}
});
const ShopCategorySchema = new Schema<ICategory>(
{
CategoryName: String,
@ -291,7 +311,7 @@ const InvasionMissionInfoSchema = new Schema<IInvasionMissionInfo>(
);
const InvasionSchema = new Schema<IInvasion>({
Activation: Date,
Activation: Number,
Faction: String,
DefenderFaction: String,
Node: String,
@ -309,12 +329,13 @@ const InvasionSchema = new Schema<IInvasion>({
InvasionSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
}
});
const NodeOverrideSchema = new Schema<INodeOverride>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
Node: String,
Faction: String,
CustomNpcEncounters: [String],
@ -324,6 +345,8 @@ const NodeOverrideSchema = new Schema<INodeOverride>({
NodeOverrideSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
@ -338,16 +361,23 @@ const VoidTraderItemSchema = new Schema<IVoidTraderItem>(
const VoidTraderScheduleInfoSchema = new Schema<IVoidTraderScheduleInfo>(
{
Expiry: Date,
PreviewHiddenUntil: Date,
Expiry: Number,
PreviewHiddenUntil: Number,
FeaturedItem: String
},
{ _id: false }
);
VoidTraderScheduleInfoSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
returnedObject.PreviewHiddenUntil = { $date: { $numberLong: returnedObject.PreviewHiddenUntil.toString() } };
}
});
const VoidTraderSchema = new Schema<IVoidTrader>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
Character: String,
Node: String,
Completed: Boolean,
@ -359,12 +389,14 @@ const VoidTraderSchema = new Schema<IVoidTrader>({
VoidTraderSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
const VoidStormSchema = new Schema<IVoidStorm>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
Node: String,
ActiveMissionTier: String
});
@ -372,6 +404,8 @@ const VoidStormSchema = new Schema<IVoidStorm>({
VoidStormSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
@ -384,8 +418,8 @@ const PrimeAccessAvailabilitySchema = new Schema<IPrimeAccessAvailability>(
const DailyDealSchema = new Schema<IDailyDeal>(
{
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
StoreItem: String,
Discount: Number,
OriginalPrice: Number,
@ -396,6 +430,13 @@ const DailyDealSchema = new Schema<IDailyDeal>(
{ _id: false }
);
DailyDealSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});
const LibraryInfoSchema = new Schema<ILibraryInfo>(
{
LastCompletedTargetType: String
@ -413,8 +454,8 @@ const PVPChallengeInstanceParam = new Schema<IPVPChallengeInstanceParam>(
const PVPChallengeInstanceSchema = new Schema<IPVPChallengeInstance>({
challengeTypeRefID: String,
startDate: Date,
endDate: Date,
startDate: Number,
endDate: Number,
params: [PVPChallengeInstanceParam],
isGenerated: Boolean,
PVPMode: String,
@ -425,6 +466,8 @@ const PVPChallengeInstanceSchema = new Schema<IPVPChallengeInstance>({
PVPChallengeInstanceSchema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.startDate = { $date: { $numberLong: returnedObject.startDate.toString() } };
returnedObject.endDate = { $date: { $numberLong: returnedObject.endDate.toString() } };
}
});
@ -451,8 +494,8 @@ FeaturedGuildShema.set("toJSON", {
});
const ActiveChallengeSchema = new Schema<IActiveChallenge>({
Activation: Date,
Expiry: Date,
Activation: Number,
Expiry: Number,
Daily: Boolean,
Challenge: String
});
@ -460,6 +503,8 @@ const ActiveChallengeSchema = new Schema<IActiveChallenge>({
FeaturedGuildShema.set("toJSON", {
transform(_document, returnedObject) {
returnedObject._id = { $oid: returnedObject._id.toString() };
returnedObject.Activation = { $date: { $numberLong: returnedObject.Activation.toString() } };
returnedObject.Expiry = { $date: { $numberLong: returnedObject.Expiry.toString() } };
}
});

View File

@ -1,8 +1,43 @@
import { WorldState } from "@/src/models/worldStateModel";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import {
IActiveMission,
ILiteSortie,
ISortie,
ISyndicateMission,
IVoidStorm,
IWorldState
} from "@/src/types/worldStateTypes";
import { getRandomNumber, getRandomKey } from "@/src/helpers/general";
import { getRandomNodes, getCurrentRotation } from "@/src/helpers/worldstateHelpers";
import { ExportRailjack, ExportRegions } from "warframe-public-export-plus";
import { logger } from "@/src/utils/logger";
import {
factionSyndicates,
neutralJobsSyndicates,
neutralSyndicates,
restSyndicates,
CertusNormalJobs,
CertusNarmerJobs,
ZarimanNormalJobs,
voidFisuresMissionTypes,
validFisureMissionIndex,
omniaNodes,
liteSortiesBoss,
endStates,
modifierTypes,
SortiesMissionTypes,
voidTiers,
FortunaNarmerJobs,
FortunaNormalJobs
} from "@/src/constants/worldStateConstants";
export const createWorldState = async () => {
const worldState = new WorldState();
await worldState.save();
await updateSyndicateMissions();
await updateVoidFisures();
await updateSorties();
return worldState;
};
@ -11,6 +46,574 @@ export const getWorldState = async () => {
if (!ws) {
ws = await createWorldState();
}
return ws;
return ws.toJSON();
};
export const worldStateRunner = async () => {
await getWorldState();
setInterval(async () => {
logger.info("Update worldState");
await updateSyndicateMissions();
await updateVoidFisures();
await updateSorties();
}, unixTimesInMs.minute);
};
const updateSyndicateMissions = async (): Promise<IWorldState> => {
const currentDate = Date.now();
const oneDayIntervalStart =
Math.floor(currentDate / unixTimesInMs.day) * unixTimesInMs.day + 16 * unixTimesInMs.hour;
const oneDayIntervalEnd = oneDayIntervalStart + unixTimesInMs.day;
const neutralJobsIntervalStart = Math.floor(currentDate / (2.5 * unixTimesInMs.hour)) * (2.5 * unixTimesInMs.hour);
const neutralJobsIntervalEnd = neutralJobsIntervalStart + 2.5 * unixTimesInMs.hour;
const neutralSeed = getRandomNumber(1, 99999);
try {
const ws = await WorldState.findOne();
if (!ws) throw new Error("Missing worldState");
const syndicateArray = ws.SyndicateMissions || [];
const existingTags = syndicateArray.map(syndicate => syndicate.Tag);
const createNewSyndicateEntry = (tag: string): ISyndicateMission => {
switch (true) {
case factionSyndicates.includes(tag):
return {
Tag: tag,
Seed: getRandomNumber(1, 99999),
Nodes: getRandomNodes(7),
Activation: oneDayIntervalStart,
Expiry: oneDayIntervalEnd
};
case neutralJobsSyndicates.includes(tag):
return {
Tag: tag,
Seed: neutralSeed,
Nodes: [],
Activation: neutralJobsIntervalStart,
Expiry: neutralJobsIntervalEnd,
Jobs: getJobs(tag)
};
case neutralSyndicates.includes(tag):
return {
Tag: tag,
Seed: neutralSeed,
Nodes: [],
Activation: neutralJobsIntervalStart,
Expiry: neutralJobsIntervalEnd
};
case restSyndicates.includes(tag):
return {
Tag: tag,
Seed: getRandomNumber(1, 99999),
Nodes: [],
Activation: oneDayIntervalStart,
Expiry: oneDayIntervalEnd
};
default:
throw new Error(`Unhandled syndicate tag: ${tag}`);
}
};
[...factionSyndicates, ...neutralJobsSyndicates, ...neutralSyndicates, ...restSyndicates].forEach(tag => {
if (!existingTags.includes(tag)) {
syndicateArray.push(createNewSyndicateEntry(tag));
} else {
const syndicateIndex = existingTags.indexOf(tag);
const shouldUpdate = currentDate >= syndicateArray[syndicateIndex].Expiry;
if (shouldUpdate) {
syndicateArray[syndicateIndex] = {
...syndicateArray[syndicateIndex],
Tag: tag,
Seed:
neutralJobsSyndicates.includes(tag) || neutralSyndicates.includes(tag)
? neutralSeed
: getRandomNumber(1, 99999),
Nodes:
neutralJobsSyndicates.includes(tag) || neutralSyndicates.includes(tag)
? []
: getRandomNodes(7),
Activation:
neutralJobsSyndicates.includes(tag) || neutralSyndicates.includes(tag)
? neutralJobsIntervalStart
: oneDayIntervalStart,
Expiry:
neutralJobsSyndicates.includes(tag) || neutralSyndicates.includes(tag)
? neutralJobsIntervalEnd
: oneDayIntervalEnd,
Jobs: neutralJobsSyndicates.includes(tag) ? getJobs(tag) : undefined
};
}
}
});
ws.SyndicateMissions = syndicateArray;
await ws.save();
return ws;
} catch (error) {
throw new Error(`Error while updating Syndicates ${error}`);
}
};
const getJobs = (tag: string) => {
const rotration = getCurrentRotation();
switch (tag) {
case "CetusSyndicate":
const Certusjobs = [
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${rotration}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
maxEnemyLevel: 15,
xpAmounts: [410, 410, 410]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${rotration}Rewards`,
masteryReq: 1,
minEnemyLevel: 10,
maxEnemyLevel: 30,
xpAmounts: [750, 750, 750]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${rotration}Rewards`,
masteryReq: 2,
minEnemyLevel: 20,
maxEnemyLevel: 40,
xpAmounts: [580, 580, 580, 850]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${rotration}Rewards`,
masteryReq: 3,
minEnemyLevel: 30,
maxEnemyLevel: 50,
xpAmounts: [580, 580, 580, 580, 1130]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${rotration}Rewards`,
masteryReq: 5,
minEnemyLevel: 40,
maxEnemyLevel: 60,
xpAmounts: [710, 710, 710, 710, 1390]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${rotration}Rewards`,
masteryReq: 10,
minEnemyLevel: 100,
maxEnemyLevel: 100,
xpAmounts: [840, 840, 840, 840, 1660]
},
{
jobType: CertusNarmerJobs[Math.floor(Math.random() * CertusNarmerJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTable${rotration}Rewards`,
masteryReq: 0,
minEnemyLevel: 50,
maxEnemyLevel: 70,
xpAmounts: [820, 820, 820, 820, 1610]
}
];
return Certusjobs;
case "SolarisSyndicate":
const FortunaJobs = [
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${rotration}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
maxEnemyLevel: 15,
xpAmounts: [410, 410, 410]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${rotration}Rewards`,
masteryReq: 1,
minEnemyLevel: 10,
maxEnemyLevel: 30,
xpAmounts: [750, 750, 750]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${rotration}Rewards`,
masteryReq: 2,
minEnemyLevel: 20,
maxEnemyLevel: 40,
xpAmounts: [580, 580, 580, 850]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${rotration}Rewards`,
masteryReq: 3,
minEnemyLevel: 30,
maxEnemyLevel: 50,
xpAmounts: [580, 580, 580, 580, 1130]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${rotration}Rewards`,
masteryReq: 5,
minEnemyLevel: 40,
maxEnemyLevel: 60,
xpAmounts: [710, 710, 710, 710, 1390]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${rotration}Rewards`,
masteryReq: 10,
minEnemyLevel: 100,
maxEnemyLevel: 100,
xpAmounts: [840, 840, 840, 840, 1660]
},
{
jobType: FortunaNarmerJobs[Math.floor(Math.random() * FortunaNarmerJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusNarmerTable${rotration}Rewards`,
masteryReq: 0,
minEnemyLevel: 50,
maxEnemyLevel: 70,
xpAmounts: [820, 820, 820, 820, 1610]
}
];
return FortunaJobs;
case "EntratiSyndicate":
const ZarimanJobs = [
{
jobType: ZarimanNormalJobs[Math.floor(Math.random() * ZarimanNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${getRandomKey(["A", "B", "C"])}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
maxEnemyLevel: 15,
xpAmounts: [5, 5, 5]
},
{
jobType: ZarimanNormalJobs[Math.floor(Math.random() * ZarimanNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${getRandomKey(["A", "B", "C"])}Rewards`,
masteryReq: 1,
minEnemyLevel: 15,
maxEnemyLevel: 25,
xpAmounts: [9, 9, 9]
},
{
jobType: ZarimanNormalJobs[Math.floor(Math.random() * ZarimanNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTable${getRandomKey(["A", "B", "C"])}Rewards`,
masteryReq: 5,
minEnemyLevel: 25,
maxEnemyLevel: 30,
endless: true,
xpAmounts: [14, 14, 14]
},
{
jobType: ZarimanNormalJobs[Math.floor(Math.random() * ZarimanNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${getRandomKey(["A", "B", "C"])}Rewards`,
masteryReq: 2,
minEnemyLevel: 30,
maxEnemyLevel: 40,
xpAmounts: [19, 19, 19, 29]
},
{
jobType: ZarimanNormalJobs[Math.floor(Math.random() * ZarimanNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETable${getRandomKey(["A", "B", "C"])}Rewards`,
masteryReq: 3,
minEnemyLevel: 40,
maxEnemyLevel: 60,
xpAmounts: [21, 21, 21, 21, 41]
},
{
jobType: ZarimanNormalJobs[Math.floor(Math.random() * ZarimanNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETable${getRandomKey(["A", "B", "C"])}Rewards`,
masteryReq: 10,
minEnemyLevel: 100,
maxEnemyLevel: 100,
xpAmounts: [25, 25, 25, 25, 50]
},
{
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierATable${getCurrentRotation()}Rewards`,
masteryReq: 5,
minEnemyLevel: 30,
maxEnemyLevel: 40,
xpAmounts: [2, 2, 2, 4],
locationTag: "ChamberB",
isVault: true
},
{
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierBTable${getCurrentRotation()}Rewards`,
masteryReq: 5,
minEnemyLevel: 40,
maxEnemyLevel: 50,
xpAmounts: [4, 4, 4, 5],
locationTag: "ChamberA",
isVault: true
},
{
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierCTable${getCurrentRotation()}Rewards`,
masteryReq: 5,
minEnemyLevel: 50,
maxEnemyLevel: 60,
xpAmounts: [5, 5, 5, 7],
locationTag: "ChamberC",
isVault: true
}
];
return ZarimanJobs;
default:
throw new Error(`Error while updating Syndicates: Unknown Jobs syndicate ${tag}`);
}
};
const updateVoidFisures = async () => {
const curDate = Date.now();
try {
const ws = await WorldState.findOne();
if (!ws) throw new Error("Missing worldState");
const voidFisures = ws.ActiveMissions;
const voidStorms = ws.VoidStorms;
const voidFisuresByTier: { [key: string]: IActiveMission[] } = {
VoidT1: [],
VoidT2: [],
VoidT3: [],
VoidT4: [],
VoidT5: [],
VoidT6: []
};
const voidStormsByTier: { [key: string]: IVoidStorm[] } = {
VoidT1: [],
VoidT2: [],
VoidT3: [],
VoidT4: [],
VoidT5: [],
VoidT6: []
};
if (voidFisures) {
voidFisures.forEach(mission => {
const tier = mission.Modifier;
if (tier) {
if (!voidFisuresByTier[tier]) {
voidFisuresByTier[tier] = [];
}
voidFisuresByTier[tier].push(mission);
}
});
}
if (voidStorms) {
voidStorms.forEach(mission => {
const tier = mission.ActiveMissionTier;
if (tier) {
if (!voidStormsByTier[tier]) {
voidStormsByTier[tier] = [];
}
voidStormsByTier[tier].push(mission);
}
});
}
voidTiers.forEach(voidTier => {
if (voidFisuresByTier[voidTier].length < 3) {
const nodeData = getRandomFisureNode(false, voidTier == "VoidT6");
logger.debug(voidTier);
if (!nodeData.missionIndex) nodeData.missionIndex = 1;
const node = {
Region: nodeData.systemIndex,
Seed: getRandomNumber(1, 99999),
Activation: curDate,
Expiry: curDate + Math.floor(Math.random() * unixTimesInMs.hour),
Node: nodeData.nodeKey,
MissionType: voidFisuresMissionTypes[nodeData.missionIndex],
Modifier: voidTier,
Hard: Math.random() < 0.1
} as IActiveMission;
voidFisures?.push(node);
}
if (voidStormsByTier[voidTier].length < 2) {
const nodeData = getRandomFisureNode(true, voidTier == "VoidT6");
logger.debug(voidTier);
const node = {
Activation: curDate,
Expiry: curDate + Math.floor(Math.random() * unixTimesInMs.hour),
Node: nodeData.nodeKey,
ActiveMissionTier: voidTier
} as IVoidStorm;
voidStorms?.push(node);
}
});
await ws.save();
return ws;
} catch (error) {
throw new Error(`Error while updating VoidFisures: ${error}`);
}
};
const getRandomFisureNode = (isRailJack: boolean, isOmnia: boolean) => {
const validNodes = Object.entries(ExportRegions)
.map(([key, node]) => {
return {
...node,
nodeKey: key
};
})
.filter(node => {
return validFisureMissionIndex.includes(node.missionIndex) && !node.missionName.includes("Archwing");
});
if (isRailJack) {
const railJackNodes = Object.keys(ExportRailjack.nodes);
const randomKey = railJackNodes[Math.floor(Math.random() * railJackNodes.length)];
return {
nodeKey: randomKey
};
} else if (isOmnia) {
const validOmniaNodes = validNodes.filter(node => {
return omniaNodes.includes(node.nodeKey);
});
const randomNode = validOmniaNodes[Math.floor(Math.random() * validOmniaNodes.length)];
return {
nodeKey: randomNode.nodeKey,
systemIndex: randomNode.systemIndex,
missionIndex: randomNode.missionIndex
};
} else {
const randomNode = validNodes[Math.floor(Math.random() * validNodes.length)];
return {
nodeKey: randomNode.nodeKey,
systemIndex: randomNode.systemIndex,
missionIndex: randomNode.missionIndex
};
}
};
const updateSorties = async () => {
const currentDate = Date.now();
const oneDayIntervalStart =
Math.floor(currentDate / unixTimesInMs.day) * unixTimesInMs.day + 16 * unixTimesInMs.hour;
const oneDayIntervalEnd = oneDayIntervalStart + unixTimesInMs.day;
const oneWeekIntervalStart =
Math.floor(currentDate / (unixTimesInMs.day * 7)) * unixTimesInMs.day * 7 + 16 * unixTimesInMs.hour;
const oneWeekIntervalEnd = oneDayIntervalStart + unixTimesInMs.day * 7;
const nodes = Object.entries(ExportRegions).map(([key, node]) => {
return {
nodeSystemIndex: node.systemIndex,
nodeKey: key
};
});
try {
const ws = await WorldState.findOne();
if (!ws) throw new Error("Missing worldState");
const liteSorties: ILiteSortie[] = ws?.LiteSorties;
const sorties: ISortie[] = ws?.Sorties;
[...liteSorties, ...sorties].forEach((sortie, index, array) => {
if (currentDate >= sortie.Expiry) array.splice(index, 1);
});
if (liteSorties.length < 1) {
const sortie: ILiteSortie = {
Activation: oneWeekIntervalStart,
Expiry: oneWeekIntervalEnd,
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
Seed: getRandomNumber(1, 99999),
Boss: liteSortiesBoss[Math.floor(Math.random() * liteSortiesBoss.length)],
Missions: [
{
missionType: SortiesMissionTypes[Math.floor(Math.random() * SortiesMissionTypes.length)],
node: nodes[Math.floor(Math.random() * nodes.length)].nodeKey
},
{
missionType: SortiesMissionTypes[Math.floor(Math.random() * SortiesMissionTypes.length)],
node: nodes[Math.floor(Math.random() * nodes.length)].nodeKey
},
{
missionType: SortiesMissionTypes[Math.floor(Math.random() * SortiesMissionTypes.length)],
node: nodes[Math.floor(Math.random() * nodes.length)].nodeKey
}
]
};
liteSorties.push(sortie);
}
if (sorties.length < 1) {
const randomBoss = endStates[Math.floor(Math.random() * endStates.length)];
const randomRegionIndex = [
Math.floor(Math.random() * randomBoss.regions.length),
Math.floor(Math.random() * randomBoss.regions.length),
Math.floor(Math.random() * randomBoss.regions.length)
];
const randomRegionIndexFake = randomRegionIndex;
randomRegionIndexFake.forEach((element, index, array) => {
if (element == 13) {
array[index] = element + 2;
} else if (element == 14) {
array[index] = element + 1;
}
});
const filteredNodes = [
nodes.filter(node => {
return randomRegionIndexFake[0] === node.nodeSystemIndex;
}),
nodes.filter(node => {
return randomRegionIndexFake[1] === node.nodeSystemIndex;
}),
nodes.filter(node => {
return randomRegionIndexFake[2] === node.nodeSystemIndex;
})
];
const sortie: ISortie = {
Activation: oneDayIntervalStart,
Expiry: oneDayIntervalEnd,
ExtraDrops: [],
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: getRandomNumber(1, 99999),
Boss: randomBoss.bossName,
Variants: [
{
missionType:
randomBoss.regions[randomRegionIndex[0]].missions[
Math.floor(Math.random() * randomBoss.regions[randomRegionIndex[0]].missions.length)
],
modifierType: modifierTypes[Math.floor(Math.random() * modifierTypes.length)],
node: filteredNodes[0][Math.floor(Math.random() * filteredNodes[0].length)].nodeKey,
tileset: "CorpusShipTileset"
},
{
missionType:
randomBoss.regions[randomRegionIndex[1]].missions[
Math.floor(Math.random() * randomBoss.regions[randomRegionIndex[1]].missions.length)
],
modifierType: modifierTypes[Math.floor(Math.random() * modifierTypes.length)],
node: filteredNodes[1][Math.floor(Math.random() * filteredNodes[1].length)].nodeKey,
tileset: "OrokinMoonTilesetCorpus"
},
{
missionType:
randomBoss.regions[randomRegionIndex[2]].missions[
Math.floor(Math.random() * randomBoss.regions[randomRegionIndex[2]].missions.length)
],
modifierType: modifierTypes[Math.floor(Math.random() * modifierTypes.length)],
node: filteredNodes[2][Math.floor(Math.random() * filteredNodes[2].length)].nodeKey,
tileset: "CorpusShipTileset"
}
],
Twitter: true
};
sorties.push(sortie);
}
await ws.save();
return ws;
} catch (error) {
throw new Error(`Error while updating Sorties ${error}`);
}
};

View File

@ -1,4 +1,4 @@
import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IOid } from "@/src/types/commonTypes";
export interface IMessage {
LanguageCode?: string;
@ -11,8 +11,8 @@ export interface ILink {
}
export interface IBaseWorldStateObject {
Activation: IMongoDate;
Expiry: IMongoDate;
Activation: number;
Expiry: number;
_id?: IOid;
}
@ -61,12 +61,11 @@ export interface IEvent {
Icon?: string;
Community?: boolean;
Priority?: boolean;
EventStartDate?: IMongoDate;
EventEndDate?: IMongoDate;
EventStartDate?: number;
EventEndDate?: number;
MobileOnly?: boolean;
HideEndDateModifier?: boolean;
Date?: IMongoDate;
_id?: IOid;
Date?: number;
}
export interface IGoal extends IBaseWorldStateObject {
@ -103,7 +102,7 @@ export interface ISortieMission {
}
export interface ISortie extends Omit<ILiteSortie, "Missions"> {
// ExtraDrops: []; Unknown
ExtraDrops: string[]; //Unknown
Variants: ISortieMission[];
Twitter: boolean;
}
@ -150,8 +149,8 @@ export interface IGlobalUpgrade extends IBaseWorldStateObject {
}
export interface IFlashSale {
StartDate: IMongoDate;
EndDate: IMongoDate;
StartDate: number;
EndDate: number;
TypeName: string;
ShowInMarket: boolean;
HideFromMarket: boolean;
@ -200,8 +199,8 @@ export interface IInvasionMissionInfo {
}
export interface INodeOverride {
Activation?: IMongoDate;
Expiry?: IMongoDate;
Activation?: number;
Expiry?: number;
Node: string;
Faction?: string;
CustomNpcEncounters?: string[];
@ -224,7 +223,7 @@ export interface IVoidTraderItem {
}
export interface IVoidTraderScheduleInfo extends Omit<IBaseWorldStateObject, "Activation" | "_id"> {
PreviewHiddenUntil?: IMongoDate;
PreviewHiddenUntil?: number;
FeaturedItem?: string;
}
@ -252,8 +251,8 @@ export interface ILibraryInfo {
export interface IPVPChallengeInstance {
challengeTypeRefID: string;
startDate: IMongoDate;
endDate: IMongoDate;
startDate: number;
endDate: number;
params: IPVPChallengeInstanceParam[];
isGenerated: boolean;
PVPMode: string;
@ -302,8 +301,8 @@ export interface IWorldState {
Events?: IEvent[];
Goals?: IGoal[];
Alerts?: IAlert[];
Sorties?: ISortie[];
LiteSorties?: ILiteSortie[];
Sorties: ISortie[];
LiteSorties: ILiteSortie[];
SyndicateMissions?: ISyndicateMission[];
ActiveMissions?: IActiveMission[];
GlobalUpgrades?: IGlobalUpgrade[];

View File

@ -273,7 +273,62 @@
</div>
</div>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllMissions" />
<label class="form-check-label" for="unlockAllMissions">Unlock All Missions</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllQuests" />
<label class="form-check-label" for="unlockAllQuests">Unlock All Quests</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="completeAllQuests" />
<label class="form-check-label" for="completeAllQuests">Complete All Quests</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteResources" />
<label class="form-check-label" for="infiniteResources">
Infinite Credits and Platinum
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllShipFeatures" />
<label class="form-check-label" for="unlockAllShipFeatures">Unlock All Ship Features</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllShipDecorations" />
<label class="form-check-label" for="unlockAllShipDecorations">
Unlock All Ship Decorations
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllFlavourItems" />
<label class="form-check-label" for="unlockAllFlavourItems">
Unlock All Flavor Items (Glyphs & co.)
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllSkins" />
<label class="form-check-label" for="unlockAllSkins">Unlock All Skins</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="universalPolarityEverywhere" />
<label class="form-check-label" for="universalPolarityEverywhere">
Universal Polarity Everywhere
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useStaticWorldState" />
<label class="form-check-label" for="useStaticWorldState">Use Static WorldSatte</label>
</div>
<div class="form-group mt-2 mb-2">
<label class="form-label" for="spoofMasteryRank">
Spoofed Mastery Rank (-1 to disable)
</label>
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" />
</div>
<button class="btn btn-primary mt-3" type="submit">Save Settings</button>
</form>
</div>
</div>
</div>