diff --git a/.env.defaults b/.env.defaults index 0eb3b82..e9367c4 100644 --- a/.env.defaults +++ b/.env.defaults @@ -2,6 +2,16 @@ STT_BOT_USERNAME= STT_BOT_PASSWORD= JWT_SECRET= DB_CONNECTION_STRING=postgres://... +MONGO_CONN_STRING=mongodb://admin:secret@localhost +MONGO_DB_NAME=datacore +MONGO_PROFILE_COLLECTION=playerProfiles +MONGO_TRACKED_VOYAGES_COLLECTION=trackedVoyages +MONGO_TRACKED_ASSIGNMENTS_COLLECTION=trackedAssignments +MONGO_TELEMETRY_VOYAGE_COLLECTION=telemetry +MONGO_FBB_SOLVES_COLLECTION=solves +MONGO_FBB_TRIALS_COLLECTION=trials +MONGO_FBB_COLLECTION=bossBattles +MONGO_DISCORD_USERS_COLLECTION=discordUsers CORS_ORIGIN=https://datacore.app/ PROFILE_DATA_PATH=static LOG_PATH= \ No newline at end of file diff --git a/app/controllers/api.controller.ts b/app/controllers/api.controller.ts index 0b07cb4..02a275f 100644 --- a/app/controllers/api.controller.ts +++ b/app/controllers/api.controller.ts @@ -1,5 +1,9 @@ import { Router, Request, Response } from 'express'; -import { DataCoreAPI, LogData } from '../logic'; +import { ApiResult, DataCoreAPI, LogData } from '../logic'; +import { PlayerData } from '../datacore/player'; +import { ITrackedAssignment, ITrackedVoyage } from '../datacore/voyage'; +import { IFBB_BossBattle_Document } from '../mongoModels/playerCollab'; +import { CrewTrial, Solve } from '../datacore/boss'; // Assign router to the express.Router() instance const router: Router = Router(); @@ -174,6 +178,294 @@ router.post('/telemetry', async (req: Request, res: Response, next) => { }); +/** MongoDB-connected routes */ + +router.get('/queryAlive', async (req: Request, res: Response, next) => { + if (!req.query || !req.query.what) { + res.status(400).send('Whaat?'); + return; + } + + try { + let apiResult = { + Status: 200, + Body: { + service: req.query.what, + result: "UP" + } + } + + if (req.query.what === 'mongodb') { + if (!DataCoreAPI.mongoAvailable) { + apiResult.Status = 503; + apiResult.Body.result = "DOWN"; + } + } + + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + + +router.post('/postProfile', async (req: Request, res: Response, next) => { + if (!req.body) { + res.status(400).send('Whaat?'); + return; + } + try { + let playerData = req.body as PlayerData; + let apiResult = await DataCoreAPI.mongoPostPlayerData(playerData.player.dbid, playerData, getLogDataFromReq(req)); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.get('/getProfile', async (req: Request, res: Response, next) => { + if (!req.query || (!req.query.dbid && !req.query.dbidhash)) { + res.status(400).send('Whaat?'); + return; + } + + try { + let apiResult: ApiResult | undefined = undefined; + + if (req.query.dbid) { + apiResult = await DataCoreAPI.mongoGetPlayerData(Number.parseInt(req.query.dbid.toString())); + } + else if (req.query.dbidhash) { + apiResult = await DataCoreAPI.mongoGetPlayerData(undefined, req.query.dbidhash.toString()); + } + if (apiResult) { + res.status(apiResult.Status).send(apiResult.Body); + } + else { + res.status(500).send(); + } + } catch (e) { + next(e); + } +}); + +router.get('/getProfiles', async (req: Request, res: Response, next) => { + if (!req.query || (!req.query.fleet && !req.query.squadron )) { + res.status(400).send('Whaat?'); + return; + } + + try { + let fleet = req.query?.fleet ? Number.parseInt(req.query.fleet.toString()) : undefined; + let squadron = req.query?.squadron ? Number.parseInt(req.query.squadron.toString()) : undefined; + let apiResult = await DataCoreAPI.mongoGetManyPlayers(fleet, squadron); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.post('/postVoyage', async (req: Request, res: Response, next) => { + if (!req.body) { + res.status(400).send('Whaat?'); + return; + } + try { + let dbid = req.body.dbid; + let voyage = req.body.voyage as ITrackedVoyage; + let apiResult = await DataCoreAPI.mongoPostTrackedVoyage(dbid, voyage, getLogDataFromReq(req)); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.get('/getVoyages', async (req: Request, res: Response, next) => { + if (!req.query || (!req.query.dbid && !req.query.trackerId )) { + res.status(400).send('Whaat?'); + return; + } + + try { + let dbid = req.query?.dbid ? Number.parseInt(req.query.dbid.toString()) : undefined; + let trackerId = req.query?.trackerId ? Number.parseInt(req.query.trackerId.toString()) : undefined; + let apiResult = await DataCoreAPI.mongoGetTrackedVoyages(dbid, trackerId); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.post('/postAssignment', async (req: Request, res: Response, next) => { + if (!req.body) { + res.status(400).send('Whaat?'); + return; + } + + try { + let dbid = req.body.dbid; + let crew = req.body.crew; + let assignment = req.body.assignment as ITrackedAssignment; + let apiResult = await DataCoreAPI.mongoPostTrackedAssignment(dbid, crew, assignment, getLogDataFromReq(req)); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.post('/postAssignments', async (req: Request, res: Response, next) => { + if (!req.body) { + res.status(400).send('Whaat?'); + return; + } + + try { + let dbid = req.body.dbid; + let assign = req.body.assignments as { [key: string]: ITrackedAssignment[] }; + let crew = Object.keys(assign); + let assignmap = Object.values(assign); + let assignments = [] as ITrackedAssignment[]; + let finalcrew = [] as string[]; + let x = 0; + + for (let a1 of assignmap) { + let symbol = crew[x]; + for (let a2 of a1) { + assignments.push(a2); + finalcrew.push(symbol); + } + x++; + } + + let apiResult = await DataCoreAPI.mongoPostTrackedAssignmentsMany(dbid, finalcrew, assignments, getLogDataFromReq(req)); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.get('/getAssignments', async (req: Request, res: Response, next) => { + if (!req.query || (!req.query.dbid && !req.query.trackerId )) { + res.status(400).send('Whaat?'); + return; + } + + try { + let dbid = req.query?.dbid ? Number.parseInt(req.query.dbid.toString()) : undefined; + let trackerId = req.query?.trackerId ? Number.parseInt(req.query.trackerId.toString()) : undefined; + let apiResult = await DataCoreAPI.mongoGetTrackedVoyages(dbid, trackerId); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.get('/getTrackedData', async (req: Request, res: Response, next) => { + if (!req.query || (!req.query.dbid && !req.query.trackerId )) { + res.status(400).send('Whaat?'); + return; + } + + try { + let dbid = req.query?.dbid ? Number.parseInt(req.query.dbid.toString()) : undefined; + let trackerId = req.query?.trackerId ? Number.parseInt(req.query.trackerId.toString()) : undefined; + let apiResult = await DataCoreAPI.mongoGetTrackedData(dbid, trackerId); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.post('/postBossBattle', async (req: Request, res: Response, next) => { + if (!req.body) { + res.status(400).send('Whaat?'); + return; + } + + try { + if ("id" in req.body) { + req.body.bossBattleId = req.body.id; + delete req.body.id; + } + let battle = req.body as IFBB_BossBattle_Document; + let apiResult = await DataCoreAPI.mongoPostBossBattle(battle); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + + +router.post('/postBossBattleSolves', async (req: Request, res: Response, next) => { + if (!req.body) { + res.status(400).send('Whaat?'); + return; + } + + try { + let bossBattleId = req.body.bossBattleId as number; + let chainIndex = req.body.chainIndex as number; + let solves = req.body.solves as Solve[]; + + if (!bossBattleId || !chainIndex || !solves) { + res.status(400).send("Bad data"); + } + + let apiResult = await DataCoreAPI.mongoPostSolves(bossBattleId, chainIndex, solves); + + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + +router.post('/postBossBattleTrials', async (req: Request, res: Response, next) => { + if (!req.body) { + res.status(400).send('Whaat?'); + return; + } + + try { + let bossBattleId = req.body.bossBattleId as number; + let chainIndex = req.body.chainIndex as number; + let trials = req.body.trials as CrewTrial[]; + + if (!bossBattleId || !chainIndex || !trials) { + res.status(400).send("Bad data"); + } + + let apiResult = await DataCoreAPI.mongoPostTrials(bossBattleId, chainIndex, trials); + + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); + + +router.get('/getBossBattle', async (req: Request, res: Response, next) => { + if (!req.query || (!req.query.room && !req.query.id )) { + res.status(400).send('Whaat?'); + return; + } + + try { + let room = undefined as string | undefined; + let id = undefined as number | undefined; + + if (req.query.room) { + room = req.query.room as string; + } + if (req.query.id) { + id = Number.parseInt(req.query.id as string); + } + + let apiResult = await DataCoreAPI.mongoGetCollaboration(id, room); + res.status(apiResult.Status).send(apiResult.Body); + } catch (e) { + next(e); + } +}); // Export the express.Router() instance to be used by server.ts export const ApiController: Router = router; diff --git a/app/datacore/archetype.ts b/app/datacore/archetype.ts new file mode 100644 index 0000000..550444a --- /dev/null +++ b/app/datacore/archetype.ts @@ -0,0 +1,62 @@ +import { Icon } from "./game-elements" + +export interface ArchetypeRoot20 { + archetypes: Archetype20[] +} + +export interface ArchetypeRoot17 { + archetypes: Archetype17[] +} + +export interface ArchetypeBase { + id: number; + type: string | number; + symbol: string; + name: string; + icon: Icon; + flavor: string; + rarity: number; + recipe?: ArchetypeRecipe; + item_sources: ItemSource[]; + bonuses?: ArchetypeBonus; + short_name?: string; +} + +export interface Archetype20 extends ArchetypeBase { + type: string; + item_type: number; + } + + export interface Archetype17 extends ArchetypeBase { + type: number; + } + + export interface ArchetypeRecipe { + demands: ArchetypeDemand[] + validity_hash: string + } + + export interface ArchetypeDemand { + archetype_id: number + count: number + } + + export interface ItemSource { + challenge_id?: number + challenge_skill?: string + challenge_difficulty?: number + type: number + id: number + name: string + energy_quotient: number + chance_grade: number + place?: string + mission?: number + dispute?: number + mastery?: number + } + + export interface ArchetypeBonus { + [key: string]: number | undefined; + } + \ No newline at end of file diff --git a/app/datacore/boss.ts b/app/datacore/boss.ts new file mode 100644 index 0000000..841e631 --- /dev/null +++ b/app/datacore/boss.ts @@ -0,0 +1,375 @@ +import { PlayerCrew, Reward } from './player'; +import { Icon } from './game-elements'; +import { ShipAction } from "./ship"; + +export interface BossBattlesRoot { + env: BossConfig + statuses: Status[] + fleet_boss_battles_energy: Energy + groups: BossGroup[] +} + +export interface BossConfig { + enabled: boolean + battle_start_restricted_by_rank: boolean +} + +export interface Status { + desc_id: number + symbol: string + group: string + blocker_boss?: string + duration: number + place: string + boss_ship: BossShip + difficulty_id: number + attack_energy_cost: AttackEnergyCost + card_icon: Icon + damage_rewards: DamageReward[] + destruction_rewards: DestructionReward[] + id?: number + ends_in?: number + hp?: number + combo?: Combo + creator_character?: CreatorCharacter + blocked_by_another_boss?: boolean +} + +export interface BossShip { + icon: Icon + archetype_id: number + symbol: string + ship_name: string + rarity: number + shields: number + hull: number + evasion: number + attack: number + accuracy: number + crit_chance: number + crit_bonus: number + attacks_per_second: number + shield_regen: number + actions: ShipAction[] +} + +export interface AttackEnergyCost { + currency: number + amount: number +} + +export interface BossReward extends Reward { + type: number + id: number + symbol: string + item_type?: number + name: string + full_name: string + flavor: string + quantity: number + rarity: number +} + +export interface DamageReward { + threshold: number + rewards: BossReward[] +} + +export interface DestructionReward { + threshold: number + rewards: BossReward[] +} + +export interface Combo { + nodes: ComboNode[] + traits: string[] + restart_number: number + restart_limit: number + damage: number + active_effects: BossEffect[] + next_effect: BossEffect + previous_node_counts: number[] + reroll_count: number + reroll_limit: number + reroll_price: RerollPrice +} + +export interface ComboNode { + open_traits: string[] + hidden_traits: string[] + unlocked_character?: UnlockedCharacter + unlocked_crew_archetype_id?: number +} + +export interface UnlockedCharacter { + name: string + crew_avatar_icon: Icon + is_current: boolean +} + +export interface BossEffect { + icon: Icon + icon_color: string + description: string + value: number + multiplier: number + min_value: number + max_value: number + string_format: string +} + +export interface RerollPrice { + currency: number + amount: number +} + +export interface CreatorCharacter { + name: string + icon: Icon +} + +export interface Energy { + id: number + quantity: number + regeneration: Regeneration + regenerated_at: number +} + +export interface Regeneration { + increment?: number + interval_seconds?: number + regeneration_cap?: number + seconds?: number; +} + +export interface BossGroup { + symbol: string + name: string +} + +/** Boss Battle Engine Models Start Here */ + +export interface BossBattle { + id: number; // equivalent to ephemeral/fbbRoot/statuses/id + fleetId: number; + bossGroup: string; + difficultyId: number; + chainIndex: number; + chain: Chain; + description: string; // Boss, Difficulty +}; + +export interface Chain { + id: string; + traits: string[]; + nodes: ComboNode[]; +} + +export interface UserPreferences { + view: string; + pollInterval: number; +} + +export interface SpotterPreferences { + onehand: string; + alpha: string; + nonoptimal: string; + noncoverage: string; +} + +export interface SoloPreferences { + usable: string; + shipAbility: string; +} + +export type ShowHideValue = 'show' | 'hide'; + +export interface ExportPreferences { + header: string; + solve: string; + node_format: string; + node_traits: ShowHideValue; + bullet: string; + delimiter: string; + coverage_format: string; + crew_traits: ShowHideValue; + duplicates: string; + flag_onehand: string; + flag_alpha: string; + flag_unique: string; + flag_nonoptimal: string; +} + +export interface FilterNotes { + oneHandException: boolean; + alphaException: boolean; + uniqueCrew: boolean; + nonPortal: boolean; + nonOptimal: boolean; +} + +export interface ComboCount { + index: number; + combo: string[]; + crew: string[]; + portals: number; +} + +export interface IgnoredCombo { + index: number; + combo: string[]; +} + +export interface SolverTrait { + id: number; + trait: string; + name: string; + poolCount: number; + instance: number; + source: string; + consumed: boolean; +} + +export interface SolverNode { + index: number; + givenTraitIds: number[]; + solve: string[]; + traitsKnown: string[]; + hiddenLeft: number; + open: boolean; + spotSolve: boolean; + alphaTest: string; + oneHandTest: boolean; + possible?: any; /* { id: number, trait: string } */ + solveOptions?: SolveOption[]; +} + +export interface Solve { + node: number; + traits: string[]; +} + +export interface Solver { + id: string; + nodes: SolverNode[]; + traits: SolverTrait[]; + crew: BossCrew[]; +} + +export interface SolveOption { + key: number; + value?: string[]; + rarity: number; +} + +export interface TraitOption { + key: string | number; + value?: string; + text: string; +} + +export interface Spotter { + id: string; + solves: Solve[]; + attemptedCrew: string[]; + pendingCrew: string[]; + ignoredTraits: string[]; +} + +export interface NodeMatch { + index: number; + combos: string[][]; + traits: string[]; +} + +export interface PossibleCombo { + combo: string[]; + crew: string[]; +} + +export interface TraitRarities { + [key: string]: number; +} + +export interface NodeMatches { + [key: string]: NodeMatch; +} + +export interface Rule { + compliant: number; + exceptions: RuleException[]; +} + +export interface RuleException { + index: number; + combo: string[]; +} + +export interface BossCrew extends PlayerCrew { + highest_owned_rarity: number; + only_frozen: boolean; + nodes: number[]; + nodes_rarity: number; + node_matches: NodeMatches; + onehand_rule: Rule; + alpha_rule: Rule; +} + +export interface ViableCombo { + traits: string[]; + nodes: number[]; +} + +export interface NodeRarity { + combos: PossibleCombo[]; + traits: TraitRarities; +} + +export interface NodeRarities { + [key: string]: NodeRarity; +} + +export interface Optimizer { + crew: BossCrew[]; + optimalCombos: ViableCombo[]; + rarities: NodeRarities; + groups: FilteredGroups; + prefs: { + spotter: SpotterPreferences; + solo: SoloPreferences; + }; +} + +export interface FilteredGroup { + traits: string[]; + score: number; + crewList: BossCrew[]; + notes: FilterNotes; +} + +export interface FilteredGroups { + [key: string]: FilteredGroup[]; +} + +export interface RarityStyle { + background: string; + color: string; +} + +export interface Collaboration { + bossBattleId: number; // Same as bossBattle.id + fleetId: number; + bossGroup: string; + difficultyId: number; + chainIndex: number; + chain: Chain; + description: string; + roomCode: string; + solves: Solve[]; + trials: CrewTrial[]; +}; + +export interface CrewTrial { + crewSymbol: string; + trialType: string; +}; diff --git a/app/datacore/bridge.ts b/app/datacore/bridge.ts new file mode 100644 index 0000000..db62c10 --- /dev/null +++ b/app/datacore/bridge.ts @@ -0,0 +1,78 @@ +import { BaseSkills } from "./crew" +import { Icon } from "./game-elements" +import { Ship, ShipAction } from "./ship" + +export interface CaptainsBridgeRoot { + id: number + level: number + max_level: number + buffs: BridgeBuff[] + claimed_rewards_positions: number[] + rewards_per_level: RewardsPerLevel + } + + export interface BridgeBuff { + symbol: string + id: number + name: string + icon: Icon + index: number + level: number + max_level: number + levels: Level[] + } + + export interface Level { + buffs: BridgeBuff[] + cost: Cost[] + } + + export interface Cost { + archetype_id: number + count: number + } + + export interface RewardsPerLevel { + [key: string]: LevelReward; + } + + export interface LevelReward { + rewards: Reward[] + } + + export interface Reward { + type: number + id: number + symbol: string + name: string + full_name: string + flavor: string + icon: Icon + quantity: number + rarity: number + item_type?: number + ship?: ShipReward + portrait?: Icon + full_body?: Icon + skills?: BaseSkills + traits?: string[] + action?: ShipAction + } + + export interface ShipReward extends Ship { + type: number + id: number + symbol: string + name: string + full_name: string + flavor: string + icon: Icon + shields: number + hull: number + attack: number + evasion: number + accuracy: number + quantity: number + rarity: number + } + diff --git a/app/datacore/collectionfilter.ts b/app/datacore/collectionfilter.ts new file mode 100644 index 0000000..82d8039 --- /dev/null +++ b/app/datacore/collectionfilter.ts @@ -0,0 +1,101 @@ +import { PlayerCollection, PlayerCrew, PlayerData } from "./player"; + +export interface MapFilterOptions { + collectionsFilter?: number[]; + rewardFilter?: string[]; +} + +export interface CollectionMap { + collection: PlayerCollection; + crew: PlayerCrew[]; + neededStars?: number[]; + completes: boolean; +} + +export interface ColComboMap { + names: string[]; + count: number; + crew: string[]; +} + +export interface ComboCostMap { + collection: string; + combo: ColComboMap; + cost: number; crew: PlayerCrew[] +} + +export interface CollectionGroup { + name: string; + maps: CollectionMap[]; + uniqueCrew: PlayerCrew[]; + commonCrew: PlayerCrew[]; + collection: PlayerCollection; + nonfullfilling?: number; + nonfullfillingRatio?: number; + neededStars?: number[]; + uniqueCost?: number; + combos?: ColComboMap[]; + comboCost?: number[]; +} + + +export interface CollectionFilterProps { + short: boolean; + mapFilter: MapFilterOptions; + searchFilter: string; + rarityFilter: number[]; + fuseFilter: string; + ownedFilter: string; + costMode: 'normal' | 'sale'; + matchMode: CollectionMatchMode; +}; + + +export interface CollectionFilterContextProps extends CollectionFilterProps { + short: boolean; + setShort: (value: boolean) => void; + + mapFilter: MapFilterOptions; + setMapFilter: (options: MapFilterOptions) => void; + + searchFilter: string; + setSearchFilter: (value?: string) => void; + + rarityFilter: number[]; + setRarityFilter: (value: number[]) => void; + + fuseFilter: string; + setFuseFilter: (value?: string) => void; + + ownedFilter: string; + setOwnedFilter: (value?: string) => void; + + costMode: 'normal' | 'sale'; + setCostMode: (value: 'normal' | 'sale') => void; + + byCost: boolean; + setByCost: (value: boolean) => void; + + matchMode: CollectionMatchMode; + setMatchMode: (value: CollectionMatchMode) => void; + + checkCommonFilter: (filter: CollectionFilterProps, crew: PlayerCrew, exclude?: string[]) => boolean; + checkRewardFilter: (collection: PlayerCollection, filters: string[]) => boolean; +}; + +export type CollectionMatchMode = 'normal' | 'exact-only' | 'extended' | 'inexact-only'; + +export interface CollectionWorkerConfig { + playerData: PlayerData; + filterProps: CollectionFilterProps; + playerCollections: PlayerCollection[]; + collectionCrew: PlayerCrew[]; + matchMode: CollectionMatchMode; + byCost: boolean; +} + +export interface CollectionWorkerResult { + groups: CollectionGroup[]; + maps: CollectionMap[]; + costMap: ComboCostMap[]; +} \ No newline at end of file diff --git a/app/datacore/continuum.ts b/app/datacore/continuum.ts new file mode 100644 index 0000000..2c0f359 --- /dev/null +++ b/app/datacore/continuum.ts @@ -0,0 +1,59 @@ +import { Icon } from "./game-elements" +import { PotentialReward } from "./player" + +export interface ContinuumRoot { + action: string + continuum_mission: ContinuumMission +} + +export interface ContinuumMission { + qbit_cost_by_slot: number[] + active: boolean + mission: MissionInfo + quest_ids: number[] + rewards: Rewards + end_time: number + chain_rewards: ChainRewards + quest_rewards: QuestRewards + character_xp: number + qbits_rewards: number +} + +export interface MissionInfo { + id: number + title: string + description: string + portrait: Icon +} + +export interface Rewards { + standard: MasteryLoot + elite: MasteryLoot + epic: MasteryLoot +} + +export interface MasteryLoot { + all_loot_entries: AllLootEntry[] +} + +export interface AllLootEntry { + type: number + icon: Icon + rarity: number + potential_rewards: PotentialReward[] + quantity: number +} + + +export interface ChainRewards { + standard: MasteryLoot[] + elite: MasteryLoot[] + epic: MasteryLoot[] +} + + +export interface QuestRewards { + standard: MasteryLoot[] + elite: MasteryLoot[] + epic: MasteryLoot[] +} diff --git a/app/datacore/crew.ts b/app/datacore/crew.ts new file mode 100644 index 0000000..8b451a8 --- /dev/null +++ b/app/datacore/crew.ts @@ -0,0 +1,215 @@ +import { ShipBonus, ShipAction as ShipAction } from "./ship"; +import { Icon } from "./game-elements" + +export interface CrossFuseTarget { + symbol: string; + name?: string; +} + +export interface MarkdownInfo { + author: string; + modified: Date; +} + +/** + * The is the crew roster model from crew.json + * + * This is the model for the master list of all crew in STT. + * PlayerCrew derives from this and CompactCrew. + */ +export interface CrewMember { + symbol: string + name: string + short_name: string + flavor: string + archetype_id: number + max_rarity: number + equipment_slots: EquipmentSlot[] + voice_over?: string + traits: string[] + traits_hidden: string[] + base_skills: BaseSkills + ship_battle: ShipBonus + action: ShipAction; + cross_fuse_targets: CrossFuseTarget | []; + skill_data: SkillData[] + intermediate_skill_data: IntermediateSkillData[] + is_craftable: boolean + imageUrlPortrait: string + imageUrlFullBody: string + series?: string + traits_named: string[] + collections: string[] + nicknames: Nickname[] + cab_ov: string + cab_ov_rank: number + cab_ov_grade: string + totalChronCost: number + factionOnlyTotal: number + craftCost: number + ranks: Ranks + bigbook_tier: number + events: number + in_portal: boolean + date_added: Date + obtained: string + markdownContent: string + markdownInfo: MarkdownInfo; + unique_polestar_combos?: string[][] + constellation?: CrewConstellation + kwipment?: any[] + q_bits?: number + + /** Used internally, not part of incoming data */ + pickerId?: number; + pairs?: Skill[][]; +} + +export interface EquipmentSlot { + level: number + symbol: string + imageUrl?: string; +} + +export enum BaseSkillFields { + SecuritySkill = 'security_skill', + CommandSkill = 'command_skill', + DiplomacySkill = 'diplomacy_skill', + MedicineSkill = 'medicine_skill', + ScienceSkill = 'science_skill', + EngineeringSkill = 'engineering_skill', +} + +export interface BaseSkills { + security_skill?: Skill + command_skill?: Skill + diplomacy_skill?: Skill + medicine_skill?: Skill + science_skill?: Skill + engineering_skill?: Skill +} + + +export interface Skill { + core: number + range_min: number + range_max: number + skill?: string; +} + +export interface ComputedSkill { + core: number; + min: number; + max: number; + skill?: string; +} + +export interface SkillsSummary { + key: string; + skills: string[]; + total: number; + owned: number; + ownedPct: number; + average: number; + best: { + score: number; + name: string; + }, + tenAverage: number; + maxPct: number; +}; + +export interface ComputedBuff { + core: number + min: number + max: number + skill?: string; +} + +export interface SkillData { + rarity: number + base_skills: BaseSkills +} + +export interface IntermediateSkillData extends SkillData { + level: number + action: ShipAction + ship_battle: ShipBonus +} + +export interface Nickname { + actualName: string + cleverThing: string + creator?: string +} + +export interface Ranks { + voyRank: number + gauntletRank: number + chronCostRank: number + B_SEC?: number + A_SEC?: number + V_CMD_SEC?: number + G_CMD_SEC?: number + V_SCI_SEC?: number + G_SCI_SEC?: number + V_SEC_ENG?: number + G_SEC_ENG?: number + V_SEC_DIP?: number + G_SEC_DIP?: number + V_SEC_MED?: number + G_SEC_MED?: number + B_CMD?: number + A_CMD?: number + V_CMD_SCI?: number + G_CMD_SCI?: number + V_CMD_ENG?: number + G_CMD_ENG?: number + V_CMD_DIP?: number + G_CMD_DIP?: number + V_CMD_MED?: number + G_CMD_MED?: number + B_DIP?: number + A_DIP?: number + voyTriplet?: VoyTriplet + V_SCI_DIP?: number + G_SCI_DIP?: number + V_ENG_DIP?: number + G_ENG_DIP?: number + V_DIP_MED?: number + G_DIP_MED?: number + B_MED?: number + A_MED?: number + V_SCI_MED?: number + G_SCI_MED?: number + V_ENG_MED?: number + G_ENG_MED?: number + B_SCI?: number + A_SCI?: number + V_SCI_ENG?: number + G_SCI_ENG?: number + B_ENG?: number + A_ENG?: number +} + +export interface VoyTriplet { + name: string + rank: number +} + +export interface CrewConstellation { + id: number + symbol: string + name: string + short_name: string + flavor: string + icon: Icon + keystones: number[] + type: string + crew_archetype_id: number +} + +export interface RewardsGridNeed { + symbol: string; + quantity: number; +} diff --git a/app/datacore/equipment.ts b/app/datacore/equipment.ts new file mode 100644 index 0000000..5554796 --- /dev/null +++ b/app/datacore/equipment.ts @@ -0,0 +1,68 @@ +import { Icon } from "./game-elements" +import { PlayerCrew, PlayerEquipmentItem } from "./player" + +export interface EquipmentCommon extends PlayerEquipmentItem { + symbol: string + type: number + name: string + flavor: string + rarity: number + short_name?: string + imageUrl: string + bonuses?: EquipmentBonuses + quantity?: number; + needed?: number; + factionOnly?: boolean; + demandCrew?: string[]; +} + +export interface EquipmentItem extends EquipmentCommon { + symbol: string + type: number + name: string + flavor: string + rarity: number + short_name?: string + imageUrl: string + bonuses?: EquipmentBonuses + quantity?: number; + needed?: number; + factionOnly?: boolean; + + item_sources: EquipmentItemSource[] + recipe?: EquipmentRecipe + + empty?: boolean; + isReward?: boolean; + +} + +export interface EquipmentItemSource { + type: number + name: string + energy_quotient: number + chance_grade: number + dispute?: number + mastery?: number + mission_symbol?: string + cost?: number + avg_cost?: number + cadet_mission?: string; + cadet_symbol?: string; +} + +export interface EquipmentRecipe { + incomplete: boolean + craftCost: number + list: EquipmentIngredient[] +} + +export interface EquipmentIngredient { + count: number + factionOnly: boolean + symbol: string +} + +export interface EquipmentBonuses { + [key: string]: number; +} diff --git a/app/datacore/events.ts b/app/datacore/events.ts new file mode 100644 index 0000000..e9b9ea3 --- /dev/null +++ b/app/datacore/events.ts @@ -0,0 +1,34 @@ +import { Icon } from "./game-elements" + +export interface EventLeaderboard { + instance_id: number + leaderboard: Leaderboard[] +} + +export interface Leaderboard { + dbid: number + display_name: string + pid: number + avatar?: Icon + level: number + uid: number + rank: number + score: number + fleetid?: number + fleetname: any +} + +// Stripped, modified version of GameData for Event Planner, Shuttle Helper, and Voyage tools +export interface IEventData { + symbol: string; + name: string; + description: string; + bonus_text: string; + content_types: string[]; /* shuttles, gather, etc. */ + seconds_to_start: number; + seconds_to_end: number; + image: string; + bonus: string[]; /* ALL bonus crew by symbol */ + featured: string[]; /* ONLY featured crew by symbol */ + bonusGuessed?: boolean; +}; diff --git a/app/datacore/game-elements.ts b/app/datacore/game-elements.ts new file mode 100644 index 0000000..a7012ad --- /dev/null +++ b/app/datacore/game-elements.ts @@ -0,0 +1,199 @@ +import { CrewMember } from "./crew"; +import { MilestoneBuff, Reward } from "./player"; + +export type Variant = { + name: string; + trait_variants: CrewMember[]; +}; + +export type PolestarCombo = { + count: number; + alts: { symbol: string; name: string }[]; + polestars: string[]; +}; + +export function categorizeKeystones(data: KeystoneBase[]): [Constellation[], Polestar[]] { + let cons = [] as Constellation[]; + let pols = [] as Polestar[]; + + data.forEach((k) => { + if (k.type === "keystone") { + pols.push(k as Polestar); + } + else { + cons.push(k as Constellation); + } + }); + + return [cons, pols]; +} + +export interface KeystoneBase { + id: number; + symbol: string; + type: "keystone_crate" | "crew_keystone_crate" | "keystone"; + name: string; + short_name: string; + flavor: string; + icon: Icon; + rarity?: number; + crew_archetype_id?: number; + quantity?: number; +} + +export interface Constellation extends KeystoneBase { + type: "keystone_crate" | "crew_keystone_crate"; + keystones: number[]; + quantity: number; +} + +export interface Polestar extends KeystoneBase { + type: "keystone"; + quantity: number; + loaned: number; + crew_count: number; + filter?: Filter; + useful?: number + useful_alone?: boolean; + scan_odds?: number; + crate_count?: number; + owned_crate_count?: number; + owned_best_odds?: number; + owned_total_odds?: number; +} + +export interface Icon { + file: string; +} + +export interface Negatable { + negated: boolean; +} + +export interface FilterCondition extends Negatable { + keyword: string; + value?: any; +} + +export interface TextSegment extends Negatable { + text: string; +} + +export interface Filter { + type: "trait" | "rarity" | "skill"; + trait?: string; + rarity?: number; + skill?: string; + textSegments?: TextSegment[]; + conditionArray?: FilterCondition[]; +} + +export interface ConstellationMap { + name: string; + flavor: string; + keystones: Polestar[]; + raritystone: Polestar[]; + skillstones: Polestar[]; +}; + +export interface Collection { + id: number; + name: string; + crew?: string[]; + description?: string; + image?: string; + milestones?: Milestone[]; +} +export interface Milestone { + goal: number + buffs: MilestoneBuff[] + rewards: Reward[] +} + +export const rarityLabels = [ + "Common", + "Uncommon", + "Rare", + "Super Rare", + "Legendary", +]; + +export interface RarityOptions { + key: string; + value?: string | null | undefined; + text: string; + content?: string; +} + + +export interface RetrievalOptions { + initialized: boolean; + list: RetrievalOption[]; +} + +export interface AvatarIcon { + avatar: boolean; + src: string; +} + +export interface RetrievalOption { + key: string | 0; + value: string | 0; + text: string; + image?: AvatarIcon; // image: { avatar: true, src: `${process.env.GATSBY_ASSETS_URL}${c.imageUrlPortrait}` }}]; +} + +export interface FuseGroup { + [key: string]: number[][]; +} + +export interface NumericOptions { + key: number; + value: number; + text: string; +} + +export interface LockedProspect { + symbol: string; + name: string; + rarity: number; + level?: number; + prospect?: boolean; + imageUrlPortrait?: string; + max_rarity?: number; +} + +export interface InitialOptions { + search?: string; + filter?: string; + column?: string; + direction?: 'ascending' | 'descending'; + rows?: number; + page?: number; +} + +export interface ISymbol { + symbol: string; +} + +export interface SymbolName extends ISymbol { + symbol: string; + name: string; +} + +export interface MarkdownRemark { + frontmatter: { + name?: string; + rarity?: number; + series?: string; + memory_alpha?: string; + bigbook_tier?: number; + events?: number; + in_portal?: boolean; + date?: Date; + obtained?: string; + mega?: boolean; + published?: boolean; + } +} + diff --git a/app/datacore/gauntlets.ts b/app/datacore/gauntlets.ts new file mode 100644 index 0000000..9891546 --- /dev/null +++ b/app/datacore/gauntlets.ts @@ -0,0 +1,145 @@ +import { CrewMember } from "./crew" +import { Icon } from "./game-elements" +import { GauntletPairScore, PlayerCrew } from "./player" + +export interface Gauntlet { + gauntlet_id?: number + state: string + jackpot_crew?: string + seconds_to_join?: number + contest_data?: ContestData + date: string + unavailable_msg?: string + unavailable_desc_msg?: string, + matchedCrew?: (PlayerCrew | CrewMember)[]; + prettyTraits: string[] | undefined; + origRanks?: { [key: string]: number }; + template?: boolean; + maximal?: number; + minimal?: number; + pairMin?: GauntletPairScore[]; + pairMax?: GauntletPairScore[]; + + bracket_id?: string + rank?: number + score?: number + seconds_to_next_crew_refresh?: number + seconds_to_next_opponent_refresh?: number + seconds_to_end?: number + consecutive_wins?: number + refresh_cost?: GauntletRefreshCost + revive_cost?: GauntletReviveCost + revive_and_save_cost?: GauntletReviveAndSaveCost + opponents?: Opponent[] +} + +export interface ContestData { + featured_skill: string + primary_skill: string + traits: string[] + crit_chance_per_trait: number + + secondary_skill?: string + selected_crew?: SelectedCrew[] + ranked_rewards?: RankedReward[] + contest_rewards?: ContestReward[] + } + +export interface GauntletRoot { + action: string + character: GauntletCharacter + } + + export interface GauntletCharacter { + id: number + gauntlets: Gauntlet[] + } + + export interface GauntletRefreshCost { + currency: number + amount: number + } + + export interface GauntletReviveCost { + currency: number + amount: number + } + + export interface GauntletReviveAndSaveCost { + currency: number + amount: number + } + + + + export interface SelectedCrew { + crew_id: number + archetype_symbol: string + rarity: number + level: number + skills: GauntletSkill[] + max_rarity: number + debuff: number + disabled: boolean + selected: boolean + crit_chance: number + } + + export interface GauntletSkill { + skill: string + max: number + min: number + } + + export interface RankedReward { + first: number + last: number + rewards: GauntletReward[] + quantity: number + loot_box_rarity: number + } + + export interface GauntletReward { + type: number + icon: Icon + quantity: number + } + + export interface ContestReward { + streak_required: number + loot_box_rarity: number + quantity: number + win_interval?: number + } + + export interface Opponent { + player_id: number + rank: number + value: number + level: number + icon: Icon2 + name: string + crew_contest_data: CrewContestData + } + + export interface Icon2 { + file: string + } + + export interface CrewContestData { + crew: GauntletContestCrew[] + } + + export interface GauntletContestCrew { + crew_id: number + archetype_symbol: string + rarity: number + level: number + skills: GauntletSkill[] + max_rarity: number + debuff: number + disabled: boolean + selected: boolean + crit_chance: number + } + \ No newline at end of file diff --git a/app/datacore/missions.ts b/app/datacore/missions.ts new file mode 100644 index 0000000..a1d52d7 --- /dev/null +++ b/app/datacore/missions.ts @@ -0,0 +1,216 @@ +import { Ship } from "./ship" +import { PotentialReward } from "./player" +import { Icon } from "./game-elements" + +export interface Mission { + id: number + symbol: string + description?: string + episode: number + episode_title?: string + episode_portrait?: Icon + marker: number[] + marker_icon?: Icon + exclude_from_timeline?: boolean + total_stars: number + character_xp_reward?: number + loot_rewards?: any[] + quests: Quest[] + type?: number + cadet?: boolean + name?: string + faction_id?: number +} + +export interface Quest { + id: number + quest_type: string + status?: number + current_quest_path?: string + symbol: string + name?: string + description?: string + action?: string + place?: string + notifier_icon?: Icon + intro?: MissionIntro + mastery_levels?: MasteryLevel[] + warpLogs: WarpLog[] + traits_used?: string[] + crew?: any[] + stages?: Stage[] + starting_challenge_id?: number + challenges?: MissionChallenge[] + locked?: boolean + star_background?: boolean + material_bundle?: string + timeline_icon?: Icon + mission_id?: number + crew_requirement?: CrewRequirement + cadet?: boolean + cadet_crew_select_info?: string + screens?: MissionScreen[] + compiled_paths?: string[] + unlock_text?: string +} + +export interface MissionIntro { + text: string + portrait: Icon + speaker_name: string + response: string + voice_over_bundle?: string +} + +export interface MasteryLevel { + id: number + energy_cost: number + rewards: MissionReward[] + locked: boolean + progress: Progress + opponent?: Ship + jackpots?: Jackpot[] +} + +export interface MissionReward { + type: number + icon: Icon + rarity?: number + potential_rewards?: PotentialReward[] + quantity: number + symbol?: string + name?: string + quantity_as_percentage_increase?: number + id?: number + full_name?: string + flavor?: string + item_type?: number + bonuses?: { [key: number]: number}; +} + +export interface Bonuses { + [key: number]: number; +} + +export interface Progress { + goal_progress: number + goals: number +} + + +export interface Jackpot { + id: number + reward: MissionReward[] + claimed: boolean +} + + +export interface WarpLog { + quest_id: number + quest_name: string + mastery_level: number + warp_count: number + averages: MissionAverageLogEntry[] + rewards: MissionReward[] +} + +export interface MissionAverageLogEntry { + symbol: string + average: number + average_cost: number +} + +export interface Stage { + grid_x: number + text: string +} + +export interface MissionChallenge { + id: number + name: string + grid_x: number + grid_y: number + skill: string + image: Icon + difficulty: number + children: number[] + locks: MissionLock[] + trait_bonuses: MissionTraitBonus[] + difficulty_by_mastery: number[] + critical?: MissionCritical +} + +export interface MissionLock { + trait?: string + success_on_node_id?: number +} + +export interface MissionTraitBonus { + trait: string + bonuses: number[] +} + +export interface MissionCritical { + claimed: boolean + reward: MissionReward[] + threshold: number + standard_loot: MissionLoot[] +} + + +export interface MissionLoot { + type: number + icon: Icon + rarity: number + potential_rewards: PotentialReward[] + quantity: number +} + +export interface CrewRequirement { + min_stars: number + max_stars: number + traits: string[] + description: string +} + +export interface MissionScreen { + speaker_name: string + speaker_image: SpeakerImage + text: string + responses: MissionResponse[] + index: number + prerequisites?: MissionPrerequisites + voice_over_bundle?: string +} + +export interface SpeakerImage { + file: string +} + +export interface MissionResponse { + text: string + button: number + rewards: MissionResponseRewards + index: number + loot_rewards: MissionResponseLootReward[] + prerequisites: any + paraphrase?: string +} + +export interface MissionResponseRewards { + mission_tags: string[] +} + +export interface MissionResponseLootReward { + type: number + symbol?: string + name: string + icon: Icon + quantity: number + quantity_as_percentage_increase?: number + id?: number +} + +export interface MissionPrerequisites { + mission_tags: string[][] +} diff --git a/app/datacore/player.ts b/app/datacore/player.ts new file mode 100644 index 0000000..37ce2d6 --- /dev/null +++ b/app/datacore/player.ts @@ -0,0 +1,1650 @@ + + +import { Ship } from "./ship"; +import { BossBattlesRoot } from "./boss"; +import { CaptainsBridgeRoot } from "./bridge"; +import { BaseSkills, ComputedBuff, CrewMember, CrossFuseTarget, EquipmentSlot, IntermediateSkillData, Skill } from "./crew" +import { ShipAction, ShipBonus } from "./ship"; +import { EquipmentCommon, EquipmentItem } from "./equipment"; +import { Collection, Icon } from "./game-elements" +import { ShuttleAdventure } from "./shuttle"; +import { Archetype17, ArchetypeRoot17, ArchetypeRoot20 } from "./archetype"; + +export interface AtlasIcon extends Icon { + atlas_info: string +} + +export type CiteEngine = 'original' | 'beta_tachyon_pulse'; + +export interface CiteMode { + rarities?: number[], + portal?: boolean, + nameFilter?: string, + customSorter?: (left: PlayerCrew, right: PlayerCrew) => number; + priSkills?: string[]; + secSkills?: string[]; + seatSkills?: string[]; + engine?: CiteEngine; +} + + +export interface PlayerData { + player: Player; + fleet_boss_battles_root?: BossBattlesRoot; + captains_bridge_root?: CaptainsBridgeRoot; + calc?: { lastImported?: string, lastModified?: Date; numImmortals?: number; }; + archetype_cache?: ArchetypeRoot20; + item_archetype_cache?: ArchetypeRoot17; + [key: string]: any; + version?: 17 | 20; + stripped?: boolean; + citeMode?: CiteMode; + calculatedDemands?: EquipmentItem[]; +} + +export interface Player { + id: number + dbid: number + lang: string + timezone: string + locale: string + display_name: string + money: number + premium_purchasable: number + premium_earnable: number + honor: number + shuttle_rental_tokens: number + vip_points: number + vip_level: number + currency_exchanges?: CurrencyExchange[] + replicator_uses_today: number + replicator_limit: number + replicator_ration_types: ReplicatorRationType[] + character: Character + fleet: Fleet + squad: Squad + mailbox?: Mailbox + fleet_invite?: FleetInvite + entitlements?: Entitlements + chats?: Chats + environment?: Environment + motd?: Motd + npe_complete?: boolean + community_links?: CommunityLink[] + legal_update: boolean + legal_popup_variant: number + ads_consent_required: boolean + consent: boolean + ccpa_opted_out: boolean + u_13: boolean + + } + + + + export interface CurrencyExchange { + id: number + amount: number + output: number + input: number + schedule: number[] + exchanges_today: number + bonus?: number + limit?: number + dynamic_amount?: DynamicAmount + disallow_sale_above_cap?: boolean + } + + export interface DynamicAmount { + enabled: boolean + max: number + } + + export interface ReplicatorRationType { + id: number + symbol: string + type: number + name: string + icon: Icon + flavor: string + rarity: number + item_sources: any[] + } + + export interface Character { + id: number + display_name: string + using_default_name?: boolean + level: number + max_level?: number + xp: number + xp_for_current_level: number + xp_for_next_level: number + location?: Location + destination?: Location + navmap?: Navmap + accepted_missions: AcceptedMission[] + active_conflict: any + shuttle_bays: number + next_shuttle_bay_cost: any + can_purchase_shuttle_bay?: boolean + crew_avatar: CrewAvatar + stored_immortals: StoredImmortal[] + c_stored_immortals?: number[] + replay_energy_max: number + replay_energy_rate?: number + seconds_from_replay_energy_basis?: number + replay_energy_overflow: number + boost_windows?: BoostWindow[] + seconds_from_last_boost_claim?: number + video_ad_chroniton_boost_reward?: Reward + cadet_tickets?: Tickets + pvp_tickets?: Tickets + event_tickets?: Tickets + cadet_schedule?: CadetSchedule + pvp_divisions?: PvpDivision[] + pvp_timer?: PvpTimer + fbb_difficulties: FbbDifficulty[] + crew: PlayerCrew[]; + unOwnedCrew?: PlayerCrew[]; + items: PlayerEquipmentItem[] + crew_borrows?: any[] + crew_shares?: any[] + crew_limit: number + crew_limit_increase_per_purchase?: number + next_crew_limit_increase_cost?: NextCrewLimitIncreaseCost + can_purchase_crew_limit_increase?: boolean + item_limit?: number + alert_item_limit: number + ships: Ship[] + current_ship_id: number + shuttle_adventures?: ShuttleAdventure[] + factions: Faction[] + disputes?: any[] + tng_the_game_level?: number + open_packs?: any[] + daily_activities: DailyActivity[] + next_daily_activity_reset?: number + next_starbase_donation_reset?: number + fleet_activities?: FleetActivity[] + next_fleet_activity_reset?: number + freestanding_quests?: any[] + daily_rewards_state?: DailyRewardsState + events?: GameEvent[] + dispute_histories: DisputeHistory[] + stimpack?: Stimpack + tutorials?: Tutorial[] + location_channel_prefix?: string + honor_reward_by_rarity?: number[] + voyage_descriptions?: VoyageDescription[] + voyage?: Voyage[] + voyage_summaries?: VoyageSummaries + cryo_collections: CryoCollection[] + crew_collection_buffs: AdvancementBuff[] + collection_buffs_cap_hash: CollectionBuffsCapHash + starbase_buffs: AdvancementBuff[] + starbase_buffs_cap_hash: StarbaseBuffsCapHash + captains_bridge_buffs: AdvancementBuff[] + captains_bridge_buffs_cap_hash: CaptainsBridgeBuffsCapHash + all_buffs_cap_hash: AllBuffsCapHash + all_buffs: AllBuff[] + total_marketplace_claimables: number + seasons: Season[] + } + + export interface ClientAsset { + system: string + place: string + } + + export interface Location extends ClientAsset { + setup: string + x: number + y: number + } + + export interface Navmap { + places: Place[] + systems: System[] + } + + export interface Place { + id: number + symbol: string + system: string + client_asset: ClientAsset + display_name?: string + visited?: boolean + } + + export interface System { + id: number + symbol: string + x: number + y: number + default_place: string + display_name?: string + star?: number + decorator?: number + faction?: string + scale?: number + active?: boolean + } + + export interface AcceptedMission extends DisputeHistory { + id: number + symbol: string + description?: string + episode?: number + episode_title?: string + episode_portrait?: Icon + marker?: number[] + marker_icon?: Icon + exclude_from_timeline?: boolean + stars_earned: number + total_stars: number + accepted: boolean + state: number + main_story?: boolean + cadet?: any; + } + + export interface CrewAvatar { + id: number + symbol: string + name: string + traits: any[] + traits_hidden: string[] + short_name: string + max_rarity: number + icon: Icon + portrait: Icon + full_body: Icon + default_avatar: boolean + hide_from_cryo: boolean + skills: any[] + } + + export interface StoredImmortal { + id: number + quantity: number + } + + export interface BoostWindow { + window: number[] + reward: Reward + } + + export interface Tickets { + current: number + max: number + spend_in: number + reset_in: number + } + + export interface CadetSchedule { + day: number + schedule: Schedule[] + missions: CadetMission[] + current: number + ends_in: number + next: number + next_starts_in: number + } + + export interface Schedule { + day: number + mission: number + } + + export interface CadetMission { + id: number + title: string + speaker: string + description: string + portrait: Icon + image: Icon + image_small: Icon + requirement: string + } + + export interface PvpDivision { + id: number + tier: number + name: string + description: string + min_ship_rarity: number + max_ship_rarity: number + max_crew_rarity: number + setup: PvpRefSetup + } + + export interface Setup { + ship_id: number; + slots: number[]; + } + + export interface PvpRefSetup extends Setup { + slot_info?: { [key: string]: PlayerCrew }; + } + + export interface PvpTimer { + supports_rewarding: boolean + pvp_allowed: boolean + changes_in: number + } + + export interface FbbDifficulty { + id: number + tier: number + name: string + description: string + color_code: string + min_ship_rarity: number + max_ship_rarity: number + max_crew_rarity: number + setup?: Setup + } + export enum CompletionState { + + /** + * Display as immortal, no way to reference. + * (Same as -2/DisplayAsImmortal but with different wording) + */ + DisplayAsImmortalOpponent=-10, + + /** + * Display as immortal, no way to reference. + * (Same as -2/DisplayAsImmortal but with different wording) + */ + DisplayAsImmortalStatic=-5, + + /** + * Display as immortal, owned crew. + */ + DisplayAsImmortalOwned=-4, + + /** + * Display as immortal, unowned crew. + * Also, generally for unowned crew. + */ + DisplayAsImmortalUnowned=-3, + + /** + * Display as immortal. Owned state not known/not needed. + */ + DisplayAsImmortal=-2, + + /** + * Crew is immortalized (owned) + */ + Immortalized=-1, + + /** + * Crew is frozen (1 or greater is the count) + */ + Frozen=1, + + /** + * Crew is owned, not completed. + */ + NotComplete=0 + } + + /** + * This object is the smallest representation of a crew member, + * and contains only minimal information. + * + * PlayerCrew derives from this and CrewMember + */ + export interface CompactCrew { + symbol: string; + name?: string; + archetype_id?: number; + level: number; + max_level?: number; + rarity: number; + equipment: number[][] | number[]; + base_skills?: BaseSkills; + skills?: BaseSkills; + favorite?: boolean; + ship_battle?: ShipBonus; + active_status?: number; + } + + /** + * This is the model for crew that has come from the player's roster + * and either been merged with the main crew.json source (CrewMember), or whittled + * down into CompactCrew. + * + * This interface inherits from both CrewMember and CompactCrew + */ + export interface PlayerCrew extends CrewMember, CompactCrew, IntermediateSkillData { + id: number + symbol: string + name: string + short_name: string + flavor: string + archetype_id: number + xp: number + xp_for_current_level: number + xp_for_next_level: number + bonus: number + max_xp: number + favorite: boolean + level: number + + /** + * This means the crew is in the recycle-bin and are eligible for re-enlistment in exchange for honor + */ + in_buy_back_state: boolean + max_level: number + rarity: number + max_rarity: number + equipment_rank: number + max_equipment_rank: number + equipment_slots: EquipmentSlot[] + + /** + * Input equipment slots are nested arrays, + * they are mapped to 1-dimensional arrays during processing if the crew is frozen + */ + equipment: number[][] | number[] + + kwipment: number[][] + kwipment_expiration: number[] + q_bits: number + + icon: Icon + portrait: Icon + full_body: Icon + voice_over?: string + expires_in: any + active_status: number + active_id?: number + active_index: number + passive_status: number + passive_id?: number + passive_index: number + traits: string[] + traits_hidden: string[] + /** This typically lists the current in-game skills with buffs applied */ + skills: BaseSkills + /** This typically lists the immortalized skills (without buffs) */ + base_skills: BaseSkills + + /** Ship battle ability. Is a superclass of Ship */ + ship_battle: ShipBonus + + /** Ship action */ + action: ShipAction + default_avatar: boolean + /** If this crew can be fused with other crew */ + cross_fuse_targets: CrossFuseTarget; + cap_achiever: CapAchiever + + /** Highest rarity from out of all copies of crew owned by the player */ + highest_owned_rarity?: number; + + /** Highest level from out of all copies of crew owned by the player */ + highest_owned_level?: number; + + /** + * Immortalized count or CompletionState. + * + * If this value is greater than zero, that's the number of + * frozen copies. + * + * If this number is less than zero, this character is immortalized or shown immortalized. + * + * If this number is zero, this character is not immortalized. + * + * To determine a specific value other than a positive number, consult CompletionState + */ + immortal: CompletionState | number; + + /** + * Return the ID numbers of all the collections the crew is a member of + */ + collectionIds?: number[]; + + /** Used internally. Not part of source data. */ + unmaxedIds?: number[]; + + /** Collection rewards for immortalizing this crew. Used internally. Not part of source data. */ + immortalRewards?: ImmortalReward[]; + + /** Crew is an inserted prospect on the crew table. Used internally. Not part of source data. */ + prospect?: boolean; + + /** + * Indicates whether the crew is owned by the player or not. + * Used internally. Not part of source data. + */ + have?: boolean; + + /** Used internally. Not part of source data. */ + traits_matched?: string[]; + /** Used internally. Not part of source data. */ + only_frozen?: boolean; + + /** Reserved for Combo Matches */ + nodes?: number[]; + /** Reserved for Combo Matches */ + node_matches?: NodeMatches; + /** Reserved for Combo Matches */ + nodes_rarity?: number; + + /** Used internally. Not part of source data. */ + variants?: string[]; + + + /** Citation Optimizer */ + + /** Used internally. Not part of source data. */ + addedEV?: number; + /** Used internally. Not part of source data. */ + totalEVContribution?: number; + /** Used internally. Not part of source data. */ + totalEVRemaining?: number; + /** Used internally. Not part of source data. */ + evPerCitation?: number; + /** Used internally. Not part of source data. */ + voyagesImproved?: string[]; + /** Used internally. Not part of source data. */ + amTraits?: string[]; + /** Used internally. Not part of source data. */ + voyScores?: { [key: string]: number }; + /** Used internally. Not part of source data. */ + collectionsIncreased?: string[]; + + /** Used internally. Not part of source data. */ + ssId?: string; + + /** Used internally by gauntlets. Not part of source data. */ + score?: number; + + /** Used internally by gauntlets. Not part of source data. */ + scoreTrip?: number; + + /** Used internally by gauntlets. Not part of source data. */ + pairScores?: GauntletPairScore[]; + + /** Used internally by gauntlets. Not part of source data. */ + isOpponent?: boolean; + + /** Used internally by gauntlets. Not part of source data. */ + isDebuffed?: boolean; + + /** Used internally by gauntlets. Not part of source data. */ + isDisabled?: boolean; + + /** Used internally. Not part of source data. */ + utility?: PlayerUtility + + // used for exports + /** Used for exports and internally. Not part of source data. */ + command_skill?: ComputedBuff; + /** Used for exports and internally. Not part of source data. */ + diplomacy_skill?: ComputedBuff; + /** Used for exports and internally. Not part of source data. */ + security_skill?: ComputedBuff; + /** Used for exports and internally. Not part of source data. */ + science_skill?: ComputedBuff; + /** Used for exports and internally. Not part of source data. */ + medicine_skill?: ComputedBuff; + /** Used for exports and internally. Not part of source data. */ + engineering_skill?: ComputedBuff; + } + export interface GauntletPairScore { + score: number; + pair: Skill[]; + } + export interface PlayerUtilityRanks { + [key: string]: number[]; + } + + export interface PlayerUtility { + ranks: PlayerUtilityRanks; + thresholds: string[]; + counts: { + shuttle: number; + gauntlet: number; + voyage: number; + } + } + + export interface NodeMatch { + index: number, + traits: string[]; + combos: string[][]; + nodes?: number[]; + } + + export interface NodeMatches { + [key: string]: NodeMatch; + } + + export interface CapAchiever { + name: string + date: number + } + + export interface PlayerEquipmentItem extends BuffBase { + id?: number + type?: number + symbol: string + name?: string + flavor?: string + archetype_id: number + quantity?: number + icon?: Icon + rarity: number + expires_in?: number + short_name?: string + bonuses?: Bonuses + time_modifier?: number + cr_modifier?: number + reward_modifier?: number + crafting_bonuses?: Bonuses + imageUrl?: string; + } + + export interface Bonuses { + [key: number]: number; + } + + export interface NextCrewLimitIncreaseCost { + currency: number + amount: number + } + + export interface Faction { + id: number + name: string + reputation: number + discovered?: number + completed_shuttle_adventures: number + icon?: Icon + representative_icon?: Icon + representative_full_body?: Icon + reputation_icon?: Icon + reputation_item_icon?: Icon + home_system?: string + shop_layout?: string + shuttle_token_id?: number + shuttle_token_preview_item?: ShuttleTokenPreviewItem + event_winner_rewards?: any[] + } + + export interface ShuttleTokenPreviewItem extends PlayerEquipmentItem { + type: number + id: number + symbol: string + item_type: number + name: string + full_name: string + flavor: string + icon: Icon + quantity: number + rarity: number + } + + export interface DailyActivity { + id?: number + name: string + description: string + icon?: AtlasIcon + area?: string + weight?: number + category?: any + lifetime?: number + rewards?: Reward[] + goal?: number + min_level?: number + rarity?: number + progress?: number + status?: string + } + + + export interface FleetActivity { + id: number + name: string + description: string + icon: AtlasIcon + area: string + sort_priority: number + category: string + total_points: number + current_points: number + milestones: Milestone[] + claims_available_count: number + } + + export interface Milestone { + goal: number | "n/a" + rewards?: Reward[] + claimed?: boolean + claimable?: boolean + buffs?: MilestoneBuff[]; + } + + + export interface DailyRewardsState { + seconds_until_next_reward: number + today_reward_day_index: number + season_points_per_day: number + ism_subcoin_per_day: number + reward_days: RewardDay[] + } + + export interface RewardDay { + id: number + symbol: string + rewards: Reward[] + double_at_vip?: number + } + + export interface GameEvent { + id: number + symbol: string + name: string + description: string + rules: string + bonus_text: string + rewards_teaser: string + shop_layout: string + featured_crew: FeaturedCrew[] + threshold_rewards: ThresholdReward[] + ranked_brackets: RankedBracket[] + squadron_ranked_brackets: SquadronRankedBracket[] + content: Content + instance_id: number + status: number + seconds_to_start: number + content_types: string[] + seconds_to_end: number + phases: Phase[] + opened?: boolean + opened_phase?: number + victory_points?: number + bonus_victory_points?: number + claimed_threshold_reward_points?: number + unclaimed_threshold_rewards?: any[] + last_threshold_points?: number + next_threshold_points?: number + next_threshold_rewards?: any[] + bonus?: string[]; + } + + + export interface FeaturedCrew extends CrewMember { + type: number + id: number + symbol: string + name: string + full_name: string + flavor: string + icon: Icon + portrait: Icon + rarity: number + full_body: Icon + skills: BaseSkills + traits: string[] + action: ShipAction + quantity: number + } + + export interface ThresholdReward { + points: number + rewards: Reward[] + } + + export interface RankedBracket { + first: number + last: number + rewards: Reward[] + quantity: number + } + + export interface SquadronRankedBracket { + first: number + last: number + rewards: Reward[] + quantity: number + } + + export interface Content { + content_type: string + crew_bonuses?: CrewBonuses + gather_pools?: GatherPool[] + craft_bonus?: number + refresh_cost?: RefreshCost + supports_boosts?: boolean + shuttles?: Shuttle[] + bonus_crew?: string[] + bonus_traits?: string[] + } + + export interface CrewBonuses { + [key: string]: number; + } + + export interface GatherPool { + id: number + adventures: Adventure[] + goal_index: number + rewards: PoolReward[] + golden_octopus_rewards: GoldenOctopusReward[] + } + + export interface Adventure { + id: number + name: string + description: string + demands: Demand[] + golden_octopus: boolean + } + + export interface Demand { + archetype_id: number + count: number + } + + export interface PoolReward { + type: number + symbol: string + name: string + icon: Icon + flavor: string + quantity: number + faction_id: number + } + + export interface GoldenOctopusReward { + type: number + symbol: string + name: string + icon: Icon + flavor: string + quantity: number + faction_id: number + } + + export interface RefreshCost { + currency: number + amount: number + } + + export interface Shuttle { + token: number + allow_borrow: boolean + crew_bonuses: CrewBonuses + shuttle_mission_rewards: ShuttleMissionReward[] + } + + export interface ShuttleMissionReward { + type: number + icon: Icon + rarity?: number + potential_rewards?: PotentialReward[] + quantity: number + symbol?: string + name?: string + flavor?: string + faction_id?: number + id?: number + } + + export interface PotentialReward { + type: number + icon: Icon + rarity: number + potential_rewards?: PotentialRewardDetails[] + quantity: number + id?: number + symbol?: string + item_type?: number + name?: string + full_name?: string + flavor?: string + bonuses?: Bonuses + ship?: Ship + } + + export interface PotentialRewardDetails { + type: number + id: number + symbol: string + name: string + full_name: string + flavor: string + icon: AtlasIcon + quantity: number + rarity: number + portrait?: Icon + full_body?: Icon + skills?: BaseSkills + traits?: string[] + action?: ShipAction + item_type?: number + bonuses?: Bonuses + } + + export interface Phase { + splash_image: Icon + goals: Goal[] + id: number + seconds_to_end: number + } + + export interface Goal { + id: number + faction_id: number + flavor: string + rewards: GoalReward[] + winner_rewards?: WinnerRewards + victory_points: number + claimed_reward_points?: number + } + + export interface GoalReward { + points: number + rewards: GoalRewardDetails[] + } + + export interface GoalRewardDetails { + type: number + id: number + symbol: string + name: string + full_name: string + flavor: string + icon: Icon + portrait?: Icon + rarity: number + full_body?: Icon + skills?: Skill + traits?: string[] + action?: ShipAction + quantity: number + } + + export interface WinnerRewards { + bonuses: Bonuses + time_modifier: number + cr_modifier: number + reward_modifier: number + rewards: Reward[] + } + + export interface DisputeHistory { + id: number + symbol: string + name?: string + episode?: number + marker?: number[] + completed: boolean + mission_ids?: number[] + stars_earned: number + total_stars: number + exclude_from_timeline?: boolean + faction_id?: number; + } + + export interface Stimpack { + energy_discount: number + nonpremium_currency_multiplier: number + crew_xp_multiplier: number + ends_in: number + } + + export interface Tutorial { + id: number + symbol: string + state: string + } + + export interface VoyageDescription { + id: number + symbol: string + name: string + description: string + icon: string + skills: VoyageSkills + ship_trait: string + crew_slots: CrewSlot[] + potential_rewards: PotentialRewardDetails[] + } + + export interface VoyageSkills { + primary_skill: string + secondary_skill: string + } + + export interface CrewSlot { + symbol: string + name: string + skill: string + trait: string + } + + export interface Voyage { + id: number + name: string + description: string + icon: string + skills: VoyageSkills + ship_trait: string + state: string + ship_name: any + max_hp: number + hp: number + log_index: number + pending_rewards: PendingRewards + granted_rewards: any + seed: number + created_at: string + recalled_at: string + completed_at: any + voyage_duration: number + skill_aggregates: BaseSkills + seconds_between_dilemmas: number + seconds_since_last_dilemma: number + first_leave: boolean + time_to_next_event: number + ship_id: number + crew_slots: VoyageCrewSlot[] + } + + export interface PendingRewards { + loot: Loot[] + } + + export interface Loot { + type: number + id: number + symbol: string + item_type?: number + name: string + full_name: string + flavor: string + icon: AtlasIcon + quantity: number + rarity: number + portrait?: Icon + full_body?: Icon + skills?: BaseSkills + traits?: string[] + action?: ShipAction + } + + export interface VoyageCrewSlot { + symbol: string + name: string + skill: string + trait: string + crew: PlayerCrew + } + + export interface VoyageSummaries { + summaries: Summary[] + flavor_amount: number + } + + export interface Summary { + name: string + min: number + max: number + } + + export interface CryoCollection extends Collection { + id: number + type_id?: number + name: string + image?: string + description?: string + progress: number | "n/a" + traits?: string[] + extra_crew?: number[] + claimable_milestone_index?: number + milestone: Milestone + } + + export interface PlayerCollection extends CryoCollection { + crew?: string[]; + simpleDescription?: string; + progressPct?: number; + neededPct?: number; + needed?: number; + neededCost?: number; + totalRewards?: number; + owned: number; + } + +export interface BuffBase { + symbol?: string + name?: string + icon?: Icon | AtlasIcon; + flavor?: string + quantity?: number; + rarity?: number; + } + + export interface ImmortalReward extends BuffBase { + quantity: number; + icon?: AtlasIcon; + } + + export interface Reward extends BuffBase { + type: number + id: number + full_name: string + quantity: number + rarity: number + portrait?: Icon + full_body?: Icon + skills?: BaseSkills + traits?: string[] + action?: ShipAction + ship?: Ship + icon?: AtlasIcon; + item_type?: number + bonuses?: Bonuses + faction_id?: number + } + + export interface MilestoneBuff extends BuffBase { + id: number + type: number + rarity: number + item_sources: any[] + } + + export interface AdvancementBuff extends BuffBase { + short_name?: string + operator: string + value: number + stat: string + source?: string + } + + export interface CollectionBuffsCapHash { + "science_skill_core,percent_increase": number + "engineering_skill_core,percent_increase": number + "medicine_skill_range_min,percent_increase": number + "medicine_skill_range_max,percent_increase": number + "science_skill_range_min,percent_increase": number + "science_skill_range_max,percent_increase": number + "engineering_skill_range_min,percent_increase": number + "engineering_skill_range_max,percent_increase": number + "diplomacy_skill_core,percent_increase": number + "command_skill_core,percent_increase": number + "diplomacy_skill_range_min,percent_increase": number + "diplomacy_skill_range_max,percent_increase": number + "command_skill_range_min,percent_increase": number + "command_skill_range_max,percent_increase": number + "security_skill_core,percent_increase": number + "security_skill_range_min,percent_increase": number + "security_skill_range_max,percent_increase": number + "medicine_skill_core,percent_increase": number + "replicator_fuel_cost,percent_decrease": number + "chroniton_max,increment": number + "crew_experience_training,percent_increase": number + "replicator_uses,increment": number + } + + export interface StarbaseBuffsCapHash { + "replicator_uses,increment": number + "replicator_cost,percent_decrease": number + "chroniton_max,increment": number + "command_skill_core,percent_increase": number + "command_skill_range_min,percent_increase": number + "command_skill_range_max,percent_increase": number + "diplomacy_skill_core,percent_increase": number + "diplomacy_skill_range_min,percent_increase": number + "diplomacy_skill_range_max,percent_increase": number + "security_skill_core,percent_increase": number + "security_skill_range_min,percent_increase": number + "security_skill_range_max,percent_increase": number + "science_skill_core,percent_increase": number + "science_skill_range_min,percent_increase": number + "science_skill_range_max,percent_increase": number + "medicine_skill_core,percent_increase": number + "medicine_skill_range_min,percent_increase": number + "medicine_skill_range_max,percent_increase": number + "engineering_skill_core,percent_increase": number + "engineering_skill_range_min,percent_increase": number + "engineering_skill_range_max,percent_increase": number + } + + + export interface CaptainsBridgeBuffsCapHash { + "ship_attack,percent_increase": number + "ship_shields,percent_increase": number + "fbb_player_ship_attack,percent_increase": number + "ship_accuracy,percent_increase": number + "ship_hull,percent_increase": number + "ship_evasion,percent_increase": number + "fbb_boss_ship_attack,percent_decrease": number + "ship_antimatter,percent_increase": number + } + + export interface AllBuffsCapHash { + "science_skill_core,percent_increase": number + "engineering_skill_core,percent_increase": number + "medicine_skill_range_min,percent_increase": number + "medicine_skill_range_max,percent_increase": number + "science_skill_range_min,percent_increase": number + "science_skill_range_max,percent_increase": number + "engineering_skill_range_min,percent_increase": number + "engineering_skill_range_max,percent_increase": number + "diplomacy_skill_core,percent_increase": number + "command_skill_core,percent_increase": number + "diplomacy_skill_range_min,percent_increase": number + "diplomacy_skill_range_max,percent_increase": number + "command_skill_range_min,percent_increase": number + "command_skill_range_max,percent_increase": number + "security_skill_core,percent_increase": number + "security_skill_range_min,percent_increase": number + "security_skill_range_max,percent_increase": number + "medicine_skill_core,percent_increase": number + "replicator_fuel_cost,percent_decrease": number + "chroniton_max,increment": number + "crew_experience_training,percent_increase": number + "replicator_uses,increment": number + "replicator_cost,percent_decrease": number + "ship_attack,percent_increase": number + "ship_shields,percent_increase": number + "fbb_player_ship_attack,percent_increase": number + "ship_accuracy,percent_increase": number + "ship_hull,percent_increase": number + "ship_evasion,percent_increase": number + "fbb_boss_ship_attack,percent_decrease": number + "ship_antimatter,percent_increase": number + } + + export interface AllBuff { + name: string + short_name: string + flavor: string + icon: Icon + operator: string + value: number + stat: string + source: string + symbol: string + } + + export interface Season { + id: number + symbol: string + title: string + description: string + exclusive_crew: ExclusiveCrew[] + tiers: Tier[] + points_per_tier: number + tier_dilithium_cost: number + start_at: number + end_at: number + premium_tier_offer_store_symbol: string + premium_tier_entitlement_symbol: string + premium_tier_entitlement_specialization: string + supremium_tier_offer_store_symbol: string + supremium_tier_entitlement_symbol: string + supremium_tier_entitlement_specialization: string + supremium_tier_combo_offer_store_symbol: string + opened: boolean + points: number + redeemed_points: number + redeemed_premium: number + redeemed_supremium: number + acknowledged: boolean + concluded: boolean + } + + export interface ExclusiveCrew { + name: string + max_rarity: number + full_body: AtlasIcon + archetype_id: number + } + + export interface Tier { + points: number + rewards: Reward[] + premium_rewards: Reward[] + supremium_rewards: Reward[] + } + + + export interface Fleet { + id: number + rlevel: number + sinsignia: string + nicon_index: number + nleader_player_dbid: number + nstarbase_level: number + nleader_login: number + slabel: string + cursize: number + maxsize: number + created: number + enrollment: string + nmin_level: number + rank: string + epoch_time: number + } + + export interface Squad { + id: number + rank: string + } + + export interface Mailbox { + status: string + sendable: number + sent: number + accepted: number + stores: Stores + received: number + } + + export interface Stores { + [key: string]: number; + } + + export interface FleetInvite { + status: string + sendable: number + sent: number + accepted: number + stores: Stores + received: number + } + + export interface Entitlements { + granted: Granted[] + claimed: Claimed[] + } + + export interface Granted { + uuid: string + gamerTag: number + symbol: string + state: string + updated: number + history: History[] + specialized?: string + } + + export interface History { + what: string + when: string + to?: string + from?: string + reason?: string + } + + export interface Claimed { + uuid: string + gamerTag: number + symbol: string + state: string + updated: number + history: ClaimedHistory[] + specialized?: string + cwin?: Cwin + cwinSecsTillOpen?: number + cwinSecsTillClose?: number + ttl?: number + } + + export interface ClaimedHistory { + what: string + when: string + to?: string + from?: string + gift_quantity?: string + who?: string + quantity?: string + image?: string + ecount?: string + reward_image?: string + obtain?: string + } + + export interface Cwin { + open: number + close: number + } + + export interface Chats {} + + export interface Environment { + tutorials: string[] + level_requirement_123s: number + restrictions: any + background_idle_period: number + fleet_request_purge_threshold: number + fleet_request_purge_expiration_days: number + event_refresh_min_seconds: number + event_refresh_max_seconds: number + allow_webgl_looping_audio: boolean + display_server_environment: boolean + video_ad_campaign_limit: VideoAdCampaignLimit + fleet_activities_restriction_enabled: boolean + shuttle_rental_dil_cost: number + location_updates_enabled: boolean + location_chat_enabled: boolean + enable_server_toasts: boolean + minimum_toast_delay_in_seconds: number + starbase_refresh: number + detect_conflict_mastery_errors: boolean + dilithium_purchase_popup_enabled: boolean + dilithium_purchase_popup_threshold: number + honor_purchase_popup_enabled: boolean + honor_purchase_popup_threshold: number + help_center_button_enabled: boolean + anti_macro: AntiMacro + use_updated_speed_up_cost: boolean + rental_shuttles_enabled: boolean + ship_battle_assist_character_level: number + ship_battle_speedup_multipliers: number[] + hud_popup_queue: HudPopupQueue + limited_time_offers_v2: LimitedTimeOffersV2 + load_with_equipment_rank_caching: boolean + currency_gained_analytic_enabled: boolean + fix_chroniton_ad_boost: boolean + season_123_tier_threshold: number + season_123_no_premium_tier_threshold: number + webgl_debug_cohort: boolean + ratings_whitelist: string[] + ironsource_ios_app_id: string + ironsource_android_app_id: string + ironsource_underage_ios_app_id: string + ironsource_underage_android_app_id: string + offerwall_enabled: boolean + create_player_forte_wallet_on_login: boolean + replicate_forte_wallet_on_login: boolean + replicate_forte_wallet_on_update: boolean + crew_crafting: CrewCrafting + dusting_enabled: boolean + ism_for_polestar_dusting: number + ism_for_constellation_dusting: number + collect_entitlement_claim_result_data: boolean + publish_entitlement_claim_results: boolean + handle_entitlement_claim_result_publications: boolean + privacy_policy_version: number + terms_service_version: number + event_hub_historical_event_limit: number + nerf_refresh_all: boolean + track_battles_at_start: boolean + track_battles_at_end: boolean + retargeting: Retargeting + ccpa_opt_out_url: string + age_gate: boolean + consent_age: number + log_errors_to_analytics: boolean + offer_location_on_hud: string + marketplace_enabled: boolean + maximum_quantity_per_order: number + maximum_orders_per_type_per_player: number + order_lifetime_value: number + order_lifetime_unit: string + maximum_price_per_order: number + use_market_transaction_notifications: boolean + market_receipt_count: number + quick_order_unfilled_is_error: boolean + marketplace_txn_history_caching: MarketplaceTxnHistoryCaching + firebase_analytics_enabled: boolean + daily_missions_repair_enabled: boolean + enable_photo_mode_ui: boolean + include_faction_shops_as_item_sources: boolean + enable_voyage_analytics_tracking: boolean + enable_voyage_analytics_client_tracking: boolean + display_dabo_spin_flash: boolean + quantum_card_enabled: boolean + report_mail_list_benchmarks: boolean + xsolla_guard: boolean + pause_xsolla_giveaway: boolean + fleet_boss_battles_enabled: boolean + fleet_boss_battles: FleetBossBattles + continuum_mission_enabled: boolean + continuum_containers?: ContinuumContainer[]; + use_v2_activities_panel: boolean + grant_current_season_entitlement: boolean + should_reject_disabled_activities: boolean + should_repair_progress: boolean + ism_daily_rewards_reward_start_date: string + fleet_activity_complete_all_daily_activities_start_date: string + scanning_v2: ScanningV2 + allow_forte_inventory_access: boolean + xsolla_giveaway: XsollaGiveaway[] + } + + export interface VideoAdCampaignLimit { + master_limit: Chance + stt_rewarded_scan: Chance + stt_rewarded_warp: Chance + stt_cadet_warp: Chance + stt_rewarded_shuttle: Chance + stt_rewarded_credits: Chance + stt_rewarded_dabo: Chance + stt_rewarded_chroniton_boost: Chance + stt_rewarded_double_rewards: Chance + } + + export interface Chance { + chance: number + period_minutes: number + } + + export interface AntiMacro { + min_minutes_to_popup: number + variable_minutes_to_popup: number + } + + export interface HudPopupQueue { + max_sequential_popups: number + popup_cooldown_seconds: number + } + + export interface LimitedTimeOffersV2 extends HudPopupQueue { + enabled: boolean + force_popup_at_login: boolean + } + + export interface CrewCrafting { + enabled: boolean + crew_source_stores: string[] + } + + export interface Retargeting { + enabled: boolean + lapsed_days: number + spec_name: string + } + + export interface MarketplaceTxnHistoryCaching { + enabled: boolean + duration_mins: number + } + + export interface FleetBossBattles { + battle_start_disabled: BattleStartDisabled + battle_start_restricted_by_rank: boolean + } + + export interface BattleStartDisabled { + active: boolean + use_notification: boolean + message: Message + } + + export interface Message { + [key: string]: string; + } + + export interface ScanningV2 { + enabled: boolean + } + + export interface XsollaGiveaway { + sku: string + quantity: number + } + + export interface Motd { + title: string + text: string + priority: number + image: Icon + url: string + additional_motds: any[] + } + + export interface CommunityLink { + symbol: string + image: LinkImage + title: string + date: string + url: string + } + + export interface LinkImage { + file: string + url: string + version: string + } + + export interface CrewRoster { + key: number; + rarity: number; + name: string; + total: number; + owned: number; + ownedPct: number; + portalPct?: number; + progress: number; + progressPct: number; + immortal: number; + unfrozen: number; + frozen: number; + dupes: number; + + } + export interface ContinuumContainer { + fill_cap: number; + fill_rate: FillRate; + cooldown_time: number; + cooldown_skip_cost_per_hour: number; + unlock_cost: number; + unlock_currency: string; +} + +export interface FillRate { + quantity: number; + time_unit: string; +} diff --git a/app/datacore/ship.ts b/app/datacore/ship.ts new file mode 100644 index 0000000..02953ee --- /dev/null +++ b/app/datacore/ship.ts @@ -0,0 +1,117 @@ +import { Icon } from "./game-elements"; +import { CompletionState } from "./player"; + + +export interface Schematics { + id: number; + icon: Icon; + cost: number; + ship: Ship; + rarity: number; +} + +/** Ship bonuses. Ship derives from this, and PlayerCrew/CrewMember directly reference this */ +export interface ShipBonus { + accuracy?: number; + evasion?: number; + crit_chance?: number; + crit_bonus?: number; +} + +/** + * Ship + */ +export interface Ship extends ShipBonus { + archetype_id?: number; + symbol: string; + name?: string; + rarity: number; + icon?: Icon; + flavor?: string; + model?: string; + max_level?: number; + actions?: ShipAction[]; + shields: number; + hull: number; + attack: number; + evasion: number; + accuracy: number; + crit_chance: number; + crit_bonus: number; + attacks_per_second: number; + shield_regen: number; + traits?: string[]; + traits_hidden?: string[]; + antimatter: number; + id: number; + level: number; + schematic_gain_cost_next_level?: number; + schematic_id?: number; + schematic_icon?: Icon; + battle_stations?: BattleStation[]; + traits_named?: string[]; + owned?: boolean; + tier?: number; + index?: { left: number, right: number }; + immortal?: CompletionState | number; + score?: number; +} + + +export interface BattleStation { + skill: string; +} + + + +export interface ShipAction { + bonus_amount: number; + name: string; + symbol: string; + cooldown: number; + initial_cooldown: number; + duration: number; + + /** Used internally. Not part of source data. */ + cycle_time?: number; + + bonus_type: number; + crew: number; + crew_archetype_id: number; + icon: Icon; + special: boolean; + penalty?: Penalty; + limit?: number; + status?: number; + ability?: Ability; + charge_phases?: ChargePhase[]; + + ability_text?: string; + ability_trigger?: string; + charge_text?: string; + + /** Not part of data, used internally */ + source?: string; +} + +export interface Penalty { + type: number; + amount: number; +} + +export interface Ability extends Penalty { + condition: number; +} + +export interface ChargePhase { + charge_time: number; + ability_amount?: number; + cooldown?: number; + bonus_amount?: number; + duration?: number; +} + +export interface BattleStations { + symbol: string; + battle_stations: BattleStation[] +} \ No newline at end of file diff --git a/app/datacore/shuttle.ts b/app/datacore/shuttle.ts new file mode 100644 index 0000000..34a6ff2 --- /dev/null +++ b/app/datacore/shuttle.ts @@ -0,0 +1,71 @@ +import { Icon } from "./game-elements" +import { Bonuses, PotentialReward, Reward } from "./player" + +export interface ShuttleAdventure { + id: number + symbol: string + name: string + faction_id: number + token_archetype_id: number + challenge_rating: number + shuttles: Shuttle[] + completes_in_seconds: number + x: number + y: number + } + + export interface Shuttle { + id: number + name: string + description: string + state: number + expires_in: number + faction_id: number + slots: Slot[] + rewards: Reward[] + is_rental: boolean + } + + export interface Slot { + level: any + required_trait: any + skills: string[] + trait_bonuses: TraitBonuses + crew_symbol?: string; + } + + export interface TraitBonuses { + [key: string]: any; + } + + export interface Faction { + id: number + name: string + reputation: number + discovered: number + completed_shuttle_adventures: number + icon: Icon + representative_icon: Icon + representative_full_body: Icon + reputation_icon: Icon + reputation_item_icon: Icon + home_system: string + shop_layout: string + shuttle_token_id: number + shuttle_token_preview_item: ShuttleTokenPreviewItem + event_winner_rewards: any[] + } + + export interface ShuttleTokenPreviewItem { + type: number + id: number + symbol: string + item_type: number + name: string + full_name: string + flavor: string + icon: Icon + quantity: number + rarity: number + } + \ No newline at end of file diff --git a/app/datacore/traits.ts b/app/datacore/traits.ts new file mode 100644 index 0000000..4fba830 --- /dev/null +++ b/app/datacore/traits.ts @@ -0,0 +1,329 @@ +export interface AllTraits { + trait_names: TraitNames + ship_trait_names: ShipTraitNames + crew_archetypes: CrewArchetype[] + ship_archetypes: ShipArchetype[] + } + + export interface TraitNames { + [key: string]: string + artist: string + athlete: string + betelgeusian: string + brutal: string + caregiver: string + casual: string + chef: string + civilian: string + clone: string + communicator: string + costumed: string + counselor: string + crafty: string + criminal: string + cultural_figure: string + desperate: string + displaced: string + duelist: string + emerald_chain: string + empath: string + engineered: string + explorer: string + gambler: string + gardener: string + hero: string + hunter: string + innovator: string + inspiring: string + interrogator: string + investigator: string + jury_rigger: string + marksman: string + maverick: string + merchant: string + mirror_universe: string + musician: string + pilot: string + primal: string + prisoner: string + prodigy: string + resourceful: string + romantic: string + royalty: string + saboteur: string + scoundrel: string + shapeshifter: string + smuggler: string + spiritual: string + survivalist: string + tactician: string + telekinetic: string + telepath: string + temporal_agent: string + thief: string + tribbled: string + undercover_operative: string + villain: string + augment: string + dominion: string + federation: string + imperial_guard: string + kca: string + maco: string + maquis: string + obsidian_order: string + qowat_milat: string + section31: string + starfleet: string + tal_shiar: string + tardigrade: string + terran_empire: string + terran_rebellion: string + zhat_vash: string + ambassador: string + astrophysicist: string + bartender: string + botanist: string + constable: string + courier: string + cyberneticist: string + dahar_master: string + diplomat: string + doctor: string + exoarchaeology: string + exobiology: string + exomycologist: string + geneticist: string + geologist: string + high_command: string + kai: string + linguist: string + neurologist: string + nurse: string + politician: string + quantum_mechanics: string + theoretical_engineer: string + timekeeper: string + vedek: string + veteran: string + warp_theorist: string + writer: string + xenoanthropology: string + aenar: string + allasomorph: string + andorian: string + android: string + angosian: string + antedian: string + archon: string + argelian: string + aurelian: string + automated_unit: string + bajoran: string + baku: string + barzan: string + baul: string + benzite: string + beta_annari: string + betazoid: string + borg: string + breen: string + brunali: string + bynar: string + caitian: string + cardassian: string + chameloid: string + changeling: string + control: string + deltan: string + denobulan: string + doopler: string + dosi: string + douwd: string + dramen: string + edosian: string + efrosian: string + elaurian: string + elaysian: string + excalbian: string + ferengi: string + gorn: string + hirogen: string + hologram: string + human: string + historian: string + ikaaran: string + jahsepp: string + jemhadar: string + jnaii: string + kaelon: string + kalandan: string + kantare: string + kataan: string + kazon: string + kelpien: string + klingon: string + kiley: string + kobali: string + kukulkan: string + kraylor: string + krenim: string + kriosian: string + ktarian: string + kwejian: string + kzinti: string + kyrian: string + loqueeque: string + lotian: string + lurian: string + m113_creature: string + malon: string + mintakan: string + mugato: string + mylean: string + nacene: string + nakuhl: string + nasat: string + nausicaan: string + neural: string + ocampa: string + orion: string + pakled: string + pandronian: string + photonic: string + presage: string + probe: string + prophet: string + q: string + rakhari: string + ramatis_3_native: string + reman: string + risian: string + romulan: string + rongovian: string + sarpeidon: string + saurian: string + sentient_computer: string + serilian: string + serpent: string + sikarian: string + silicon_lifeform: string + sona: string + species_8472: string + suliban: string + syrrannite: string + talaxian: string + talosian: string + tamarian: string + tandaran: string + tanugan: string + taresian: string + tellarite: string + terrellian: string + tholian: string + tkon: string + tosk: string + tribble: string + trill: string + vedala: string + vidiian: string + vorta: string + voth: string + vulcan: string + wadi: string + xahean: string + xindi: string + yaderan: string + yridian: string + zahl: string + zibelian: string + advocate: string + astronomer: string + boomer: string + brat: string + chancellor: string + cetacean_biologist: string + cursed: string + empress: string + engineer: string + evolved: string + fenris_ranger: string + khanuttu: string + lord: string + torchbearer: string + mimetic_symbiote: string + multi_dimensional: string + president: string + rich: string + scientist: string + senator: string + shepherd: string + special_envoy: string + spy: string + tailor: string + warrior: string + zhiantara: string + beanlike: string + firefighter: string + cool: string + handsome: string + } + + export interface ShipTraitNames { + [key: string]: string + hirogen: string + borg: string + federation: string + ferengi: string + cardassian: string + klingon: string + romulan: string + terran: string + maquis: string + dominion: string + andorian: string + orion_syndicate: string + vulcan: string + breen: string + tholian: string + malon: string + gorn: string + xindi: string + reman: string + sikarian: string + warship: string + cloaking_device: string + battle_cruiser: string + freighter: string + explorer: string + fighter: string + scout: string + transwarp: string + ruthless: string + war_veteran: string + emp: string + historic: string + pioneer: string + spore_drive: string + hologram: string + raider: string + } + + export interface CrewArchetype { + symbol: string + name: string + short_name: string + } + + export interface ShipArchetype { + symbol: string + name: string + flavor: string + actions: Action[] + } + + export interface Action { + symbol: string + name: string + } + \ No newline at end of file diff --git a/app/datacore/voyage.ts b/app/datacore/voyage.ts new file mode 100644 index 0000000..ca97140 --- /dev/null +++ b/app/datacore/voyage.ts @@ -0,0 +1,89 @@ +import { BaseSkills } from './crew'; +import { CrewSlot, PlayerCrew, VoyageCrewSlot, VoyageSkills } from './player'; + +// Voyage calculator require crew.skills +export interface IVoyageCrew extends PlayerCrew { + skills: BaseSkills; +}; + +// The slimmest possible version of voyageConfig, as expected as input by calculator +// Use Voyage for a config from player data when voyage started +// Use VoyageDescription for a config from player data when voyage not yet started +export interface IVoyageInputConfig { + skills: VoyageSkills; + ship_trait: string; + crew_slots: CrewSlot[]; +}; + +// Extends IVoyageInputConfig to include calculation result +export interface IVoyageCalcConfig extends IVoyageInputConfig { + state: string; + max_hp: number; + skill_aggregates: BaseSkills; + crew_slots: VoyageCrewSlot[]; +}; + +export interface IVoyageHistory { + voyages: ITrackedVoyage[]; + crew: ITrackedAssignmentsByCrew; +}; + +export interface ITrackedVoyage { + tracker_id: number; // Used to match tracked voyage with tracked crew + voyage_id: number; // Used to match tracked voyage with in-game voyage + skills: VoyageSkills; + ship_trait: string; + ship: string; + max_hp: number; + skill_aggregates: BaseSkills; + estimate: ITrackedFlatEstimate; + created_at: number; // Date.now() | voyage.created_at + checkpoint: ITrackedCheckpoint; + revivals: number; +}; + +export interface ITrackedFlatEstimate { + median: number; + minimum: number; + moonshot: number; + dilemma: { + hour: number; + chance: number; + }; +}; + +export interface ITrackedCheckpoint { + state: string; + runtime: number; + hp: number; + estimate: ITrackedFlatEstimate; + checked_at: number; // Date.now() +}; + +export interface ITrackedAssignmentsByCrew { + [key: string]: ITrackedAssignment[]; // key is crew.symbol +}; + + +export interface ITrackedAssignment { + tracker_id: number; + slot: number; // Slot index where crew is seated + trait: string; // Matched trait or empty string if no match +}; + +export interface ITrackedAssignmentsBySkill { + [key: string]: { + ids: number[], + usage: number + }; +}; + +export interface ITrackedCrewMember extends PlayerCrew { + assignments: ITrackedAssignment[]; + average_estimate: number; + skill_assignments: ITrackedAssignmentsBySkill; + last_assignment: { + tracker_id: number, + created_at: number + }; +}; diff --git a/app/datacore/worker.ts b/app/datacore/worker.ts new file mode 100644 index 0000000..48e5675 --- /dev/null +++ b/app/datacore/worker.ts @@ -0,0 +1,161 @@ +import { BossBattlesRoot } from "./boss"; +import { BaseSkills, Skill } from "./crew"; +import { PlayerCollection, PlayerCrew, PlayerData } from "./player"; +import { Ship } from "./ship"; + +import { EquipmentCommon, EquipmentItem } from "./equipment"; + + +/* TODO: move IBuffStat, calculateBuffConfig to crewutils.ts (currently not used by voyage calculator) */ +export interface IBuffStat { + multiplier: number; + percent_increase: number; +} + +export interface BuffStatTable { + [key: string]: IBuffStat; +} + +export interface GameWorkerOptionsList { + key: number; + value: number; + text: string; +} +export interface VoyageStatsConfig { + others?: number[]; + numSims: number; + startAm: number; + currentAm: number; + elapsedSeconds: number; + variance: number; + ps?: Skill; + ss?: Skill; +} + +export interface GameWorkerOptions { + strategy?: string; + searchDepth?: number; + extendsTarget?: number; +} + +export interface CalculatorProps { + playerData: PlayerData; + allCrew: PlayerCrew[]; +} + +export interface AllData extends CalculatorProps { + allShips?: Ship[]; + playerShips?: Ship[]; + useInVoyage?: boolean; + bossData?: BossBattlesRoot; + buffConfig?: BuffStatTable; +} + +export interface VoyageConsideration { + ship: Ship; + score: number; + traited: boolean; + bestIndex: number; + archetype_id: number; +} + +export interface Calculation { + id: string; + requestId: string; + name: string; + calcState: number; + result?: CalcResult; + trackState?: number; + confidenceState?: number; +} + +export interface CalcResult { + estimate: Estimate; + entries: CalcResultEntry[]; + aggregates: Aggregates; + startAM: number; +} + +export interface Estimate { + refills: Refill[]; + dilhr20: number; + refillshr20: number; + final: boolean; + deterministic?: boolean; + antimatter?: number; +} + +export interface Refill { + all: number[]; + result: number; + safeResult: number; + saferResult: number; + moonshotResult: number; + lastDil: number; + dilChance: number; + refillCostResult: number; +} + +export interface CalcResultEntry { + slotId: number; + choice: PlayerCrew; + hasTrait: boolean | number; +} + +export interface Aggregates { + command_skill: AggregateSkill; + science_skill: AggregateSkill; + security_skill: AggregateSkill; + engineering_skill: AggregateSkill; + diplomacy_skill: AggregateSkill; + medicine_skill: AggregateSkill; +} + +export interface AggregateSkill extends Skill { + skill: string; +} + +export interface CalcConfig { + estimate: number; + minimum: number; + moonshot: number; + antimatter: number; + dilemma: { + hour: number; + chance: number; + }; + refills?: Refill[]; + confidence?: number; +} + +export interface JohnJayBest { + key: string; + crew: JJBestCrewEntry[]; + traits: number[]; + skills: BaseSkills; + estimate: Estimate; +} + +export interface JJBestCrewEntry { + id: number; + name: string; + score: number; +} + +export interface ExportCrew { + id: number; + name: string; + traitBitMask: number; + max_rarity: number; + skillData: number[]; +} + +export interface EquipmentWorkerConfig { + items: EquipmentItem[]; + playerData: PlayerData; + addNeeded?: boolean; +} + +export interface EquipmentWorkerResults { + items: (EquipmentCommon | EquipmentItem)[]; +} diff --git a/app/logic/api.ts b/app/logic/api.ts index 207a315..5a9b866 100644 --- a/app/logic/api.ts +++ b/app/logic/api.ts @@ -4,10 +4,18 @@ import fetch, { Response } from 'node-fetch'; import { sign, verify } from 'jsonwebtoken'; import { Logger, LogData } from './logger'; -import { uploadProfile, loadProfileCache, loginUser, getDBIDbyDiscord } from './profiletools'; +import { loadProfileCache, loginUser, getDBIDbyDiscord, uploadProfile } from './profiletools'; import { loadCommentsDB, saveCommentDB } from './commenttools'; import { recordTelemetryDB, getTelemetryDB } from './telemetry'; import { getSTTToken } from './stttools'; +import { getAssignmentsByDbid, getAssignmentsByTrackerId, getCollaborationById, getProfile, getProfileByHash, getProfiles, getVoyagesByDbid, getVoyagesByTrackerId, postOrPutAssignment, postOrPutAssignmentsMany, postOrPutBossBattle, postOrPutProfile, postOrPutSolves, postOrPutTrials, postOrPutVoyage } from './mongotools'; +import { PlayerProfile } from '../mongoModels/playerProfile'; +import { PlayerData } from '../datacore/player'; +import { ITrackedAssignment, ITrackedVoyage } from '../datacore/voyage'; +import { TrackedCrew, TrackedVoyage } from '../mongoModels/voyageHistory'; +import { connectToMongo } from '../mongo'; +import { IFBB_BossBattle_Document, IFBB_Solve_Document, IFBB_Trial_Document } from '../mongoModels/playerCollab'; +import { CrewTrial, Solve } from '../datacore/boss'; require('dotenv').config(); @@ -23,6 +31,8 @@ export class ApiClass { private _player_data: any; private _stt_token: any; + public mongoAvailable: boolean = false; + async initializeCache() { this._player_data = await loadProfileCache(); this._stt_token = 'd6458837-34ba-4883-8588-4530f1a9cc53'; @@ -85,11 +95,30 @@ export class ApiClass { try { await uploadProfile(dbid, player_data, new Date()); - } catch (err: any) { - return { - Status: 500, - Body: err.toString() - }; + if (this.mongoAvailable) { + try { + console.log("Posting to Mongo, also ..."); + await this.mongoPostPlayerData(Number.parseInt(dbid), player_data, logData); + console.log("Success!"); + } + catch { + console.log("Failed!"); + } + } + + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } } this._player_data[dbid] = new Date().toUTCString(); @@ -244,7 +273,7 @@ export class ApiClass { let payload = verify(token, JWT_SECRET); if (!payload) { return { - Status: 404, + Status: 200, // 204 Body: 'Aah, something went wrong!' }; } @@ -271,7 +300,7 @@ export class ApiClass { } } else { return { - Status: 404, + Status: 200, // 204 Body: JSON.stringify({ error: 'No DBID found for Discord user' }), } } @@ -303,6 +332,515 @@ export class ApiClass { Body: result } } + + /** MongoDB Methods */ + + async tryInitMongo() { + try { + return await connectToMongo(); + } + catch { + return false; + } + } + + async mongoPostPlayerData(dbid: number, player_data: PlayerData, logData: LogData): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + Logger.info('Post player data', { dbid, logData }); + + const timeStamp = new Date(); + let res: string | number = 0; + + try { + res = await postOrPutProfile(dbid, player_data, timeStamp); + + if (typeof res === 'number' && res >= 300) { + return { + Status: res, + Body: { + 'dbid': dbid, + 'error': 'Unable to insert profile record.', + 'timeStamp': timeStamp.toISOString() + } + }; + } + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + this._player_data[dbid] = new Date().toUTCString(); + fs.writeFileSync(`${process.env.PROFILE_DATA_PATH}/${dbid}`, JSON.stringify(player_data)); + + if (typeof res === 'string') { + return { + Status: 201, + Body: { + 'dbid': dbid, + 'dbidHash': res, + timeStamp: timeStamp.toISOString() + } + }; + + } + else { + return { + Status: res, + Body: { + 'dbid': dbid, + timeStamp: timeStamp.toISOString() + } + }; + + } + } + + async mongoGetPlayerData(dbid?: number, hash?: string): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + + Logger.info('Get player data', { dbid }); + let player: PlayerProfile | null = null; + + try { + if (dbid) { + player = await getProfile(dbid); + } + else if (hash) { + player = await getProfileByHash(hash); + } + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + if (player?.playerData) { + return { + Status: 200, + Body: player + }; + } + else { + return { + Status: 404, // 204 + Body: null + }; + } + + } + + async mongoGetManyPlayers(fleet?: number, squadron?: number): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + + Logger.info('Get many players', { fleet, squadron }); + let players: PlayerProfile[] | null = null; + + try { + players = await getProfiles(fleet, squadron); + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + if (players?.length) { + return { + Status: 200, + Body: players + }; + } + else { + return { + Status: 200, // 204 + Body: [] + }; + } + + } + + async mongoPostTrackedVoyage(dbid: number, voyage: ITrackedVoyage, logData: LogData): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + + Logger.info('Tracked Voyage data', { dbid, voyage, logData }); + + const timeStamp = new Date(); + + try { + let res = await postOrPutVoyage(dbid, voyage, timeStamp); + if (res >= 300) { + return { + Status: res, + Body: { + 'dbid': dbid, + 'error': 'Unable to insert record.', + 'timeStamp': timeStamp.toISOString() + } + }; + } + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + return { + Status: 201, + Body: { + 'dbid': dbid, + 'trackerId': voyage.tracker_id, + timeStamp: timeStamp.toISOString() + } + }; + } + + async mongoGetTrackedVoyages(dbid?: number, trackerId?: number): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + + Logger.info('Get voyage data', { dbid, trackerId }); + let voyages: TrackedVoyage[] | null = null; + + if (!dbid && !trackerId) return { + Status: 400, + Body: { result: "bad input" } + } + + try { + voyages = dbid ? await getVoyagesByDbid(dbid) : (trackerId ? await getVoyagesByTrackerId(trackerId) : null); + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + if (voyages) { + return { + Status: 200, + Body: voyages + }; + } + else { + return { + Status: 200, // 204 + Body: [] + }; + } + + } + + + + async mongoPostTrackedAssignment(dbid: number, crew: string, assignment: ITrackedAssignment, logData: LogData): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + + Logger.info('Tracked Voyage data', { dbid, voyage: assignment, logData }); + + const timeStamp = new Date(); + + try { + let res = await postOrPutAssignment(dbid, crew, assignment, timeStamp); + if (res >= 300) { + return { + Status: res, + Body: { + 'dbid': dbid, + 'error': 'Unable to insert record.', + 'timeStamp': timeStamp.toISOString() + } + }; + } + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + return { + Status: 201, + Body: { + 'dbid': dbid, + 'trackerId': assignment.tracker_id, + timeStamp: timeStamp.toISOString() + } + }; + } + + + async mongoPostTrackedAssignmentsMany(dbid: number, crew: string[], assignments: ITrackedAssignment[], logData: LogData): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + + Logger.info('Tracked Voyage data', { dbid, voyage: assignments, logData }); + + const timeStamp = new Date(); + + try { + let res = await postOrPutAssignmentsMany(dbid, crew, assignments, timeStamp); + if (res >= 300) { + return { + Status: res, + Body: { + 'dbid': dbid, + 'error': 'Unable to insert record.', + 'timeStamp': timeStamp.toISOString() + } + }; + } + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + return { + Status: 201, + Body: { + 'dbid': dbid, + 'trackerIds': assignments.map(a => a.tracker_id), + timeStamp: timeStamp.toISOString() + } + }; + } + + + async mongoGetTrackedAssignments(dbid?: number, trackerId?: number): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + + Logger.info('Get voyage data', { dbid, trackerId }); + let assignments: TrackedCrew[] | null = null; + + if (!dbid && !trackerId) return { + Status: 400, + Body: { result: "bad input" } + } + + try { + assignments = dbid ? await getAssignmentsByDbid(dbid) : (trackerId ? await getAssignmentsByTrackerId(trackerId) : null); + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + if (assignments) { + return { + Status: 200, + Body: assignments + }; + } + else { + return { + Status: 200, // 204 + Body: [] + }; + } + + } + + + async mongoGetTrackedData(dbid?: number, trackerId?: number): Promise { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + + Logger.info('Get tracked data', { dbid, trackerId }); + let voyages: TrackedVoyage[] | null = null; + let assignments: TrackedCrew[] | null = null; + + if (!dbid && !trackerId) return { + Status: 400, + Body: { result: "bad input" } + } + + try { + voyages = dbid ? await getVoyagesByDbid(dbid) : (trackerId ? await getVoyagesByTrackerId(trackerId) : null); + assignments = dbid ? await getAssignmentsByDbid(dbid) : (trackerId ? await getAssignmentsByTrackerId(trackerId) : null); + } catch (err) { + if (typeof err === 'string') { + return { + Status: 500, + Body: err + }; + } + else if (err instanceof Error) { + return { + Status: 500, + Body: err.toString() + }; + } + } + + if (voyages || assignments) { + return { + Status: 200, + Body: { + voyages, + assignments + } + }; + } + else { + return { + Status: 200, // 204 + Body: { voyages: [], assignments: [] } + }; + } + + } + + async mongoPostBossBattle(battle: IFBB_BossBattle_Document) { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + Logger.info('Post boss battle', { battle }); + + try { + let res = await postOrPutBossBattle(battle); + return { + Status: res, + Body: { result: "ok" } + } + } + catch { + return { + Status: 500, + Body: { result: "fail" } + } + } + } + + async mongoGetCollaboration(bossBattleId?: number, roomCode?: string) { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + Logger.info('Get boss battle', { bossBattleId }); + + try { + let battle = await getCollaborationById(bossBattleId, roomCode); + if (!battle) { + return { + Status: 200, // 204 + Body: [] + } + } + return { + Status: 200, + Body: battle + } + } + catch { + return { + Status: 500, + Body: { result: "fail" } + } + } + + } + + async mongoPostSolves(bossBattleId: number, chainIndex: number, solves: Solve[]) { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + Logger.info('Post trials', { solves }); + + try { + let res = await postOrPutSolves(bossBattleId, chainIndex, solves); + return { + Status: res, + Body: { result: "ok" } + } + } + catch { + return { + Status: 500, + Body: { result: "fail" } + } + } + } + + async mongoPostTrials(bossBattleId: number, chainIndex: number, trials: CrewTrial[]) { + if (!this.mongoAvailable) return { Status: 500, Body: 'Database is down' }; + Logger.info('Post trials', { trials }); + + try { + let res = await postOrPutTrials(bossBattleId, chainIndex, trials); + return { + Status: res, + Body: { result: "ok" } + } + } + catch { + return { + Status: 500, + Body: { result: "fail" } + } + } + } + + } export let DataCoreAPI = new ApiClass(); diff --git a/app/logic/mongotools.ts b/app/logic/mongotools.ts new file mode 100644 index 0000000..947e1c9 --- /dev/null +++ b/app/logic/mongotools.ts @@ -0,0 +1,349 @@ + +import { WithId } from "mongodb"; +import { collections } from "../mongo"; +import { PlayerProfile } from "../mongoModels/playerProfile"; +import { PlayerData } from "../datacore/player"; +import { ITrackedAssignment, ITrackedVoyage, IVoyageHistory } from "../datacore/voyage"; +import { ITelemetryVoyage, TelemetryVoyage, TrackedCrew, TrackedVoyage } from "../mongoModels/voyageHistory"; +import { BossBattleDocument, IFBB_BossBattle_Document, SolveDocument, TrialDocument } from "../mongoModels/playerCollab"; +import * as seedrandom from 'seedrandom'; +import { Collaboration, CrewTrial, Solve } from "../datacore/boss"; +import { createProfileObject } from "./profiletools"; +import { createHash } from 'node:crypto' + +export async function getProfile(dbid: number) { + let res: PlayerProfile | null = null; + + if (collections.profiles) { + res = (await collections.profiles.findOne>({ dbid: dbid })) as PlayerProfile; + } + + return res; +} + +export async function getProfileByHash(dbidHash: string) { + let res: PlayerProfile | null = null; + + if (collections.profiles) { + res = (await collections.profiles.findOne>({ dbidHash })) as PlayerProfile; + } + + return res; +} + + +export async function getProfiles(fleet?: number, squadron?: number) { + let res: PlayerProfile[] | null = null; + + if (collections.profiles) { + if (fleet) { + res = (await collections.profiles.find>({ fleet }).toArray()) as PlayerProfile[]; + } + else if (squadron) { + res = (await collections.profiles.find>({ squadron }).toArray()) as PlayerProfile[]; + } + } + + return res; +} + +export async function postOrPutProfile(dbid: number, player_data: PlayerData, timeStamp: Date = new Date()) { + if (collections.profiles) { + let res = (await collections.profiles.findOne>({ dbid: dbid })) as PlayerProfile; + + let fleet = player_data.player.fleet?.id ?? 0; + let squadron = player_data.player.squad?.id ?? 0; + let profile = createProfileObject(dbid.toString(), player_data, timeStamp); + let dbidHash = createHash('sha3-256').update(dbid.toString()).digest('hex'); + + if (!res) { + res = new PlayerProfile(dbid, dbidHash, player_data, timeStamp, profile.captainName, profile.buffConfig, profile.shortCrewList, fleet, squadron); + let insres = await collections.profiles?.insertOne(res); + return !!(insres?.insertedId) ? dbidHash : 400; + } else { + res.playerData = player_data; + res.dbidHash = dbidHash; + res.timeStamp = timeStamp; + res.captainName = profile.captainName; + res.buffConfig = profile.buffConfig; + res.shortCrewList = profile.shortCrewList; + res.fleet = fleet; + res.squadron = squadron; + res.timeStamp = new Date(); + let updres = await collections.profiles.updateOne( + { dbid }, + { $set: res } + ); + + return !!(updres?.modifiedCount) ? dbidHash : 400; + } + } + + return 500; +} + +export async function getVoyagesByDbid(dbid: number) { + let res: TrackedVoyage[] | null = null; + + if (collections.trackedVoyages) { + res = await collections.trackedVoyages.find>({ dbid: dbid }).toArray(); + } + + return res; +} + +export async function getVoyagesByTrackerId(trackerId: number) { + let res: TrackedVoyage[] | null = null; + + if (collections.trackedVoyages) { + res = await collections.trackedVoyages.find>({ trackerId: trackerId }).toArray(); + } + + return res; +} + +export async function postOrPutVoyage( + dbid: number, + voyage: ITrackedVoyage, + timeStamp: Date = new Date()) { + if (collections.trackedVoyages) { + let insres = await collections.trackedVoyages?.insertOne({ + dbid, + trackerId: voyage.tracker_id, + voyage, + timeStamp + } as TrackedVoyage); + + return !!(insres?.insertedId) ? 201 : 400; + } + + return 500; +} + +export async function getAssignmentsByDbid(dbid: number) { + let res: TrackedCrew[] | null = null; + + if (collections.trackedAssignments) { + res = await collections.trackedAssignments.find>({ dbid: dbid }).toArray(); + } + + return res; +} + +export async function getAssignmentsByTrackerId(trackerId: number) { + let res: TrackedCrew[] | null = null; + + if (collections.trackedAssignments) { + res = await collections.trackedAssignments.find>({ trackerId: trackerId }).toArray(); + } + + return res; +} + +export async function postOrPutAssignment( + dbid: number, + crew: string, + assignment: ITrackedAssignment, + timeStamp: Date = new Date()) { + if (collections.trackedAssignments) { + let insres = await collections.trackedAssignments.insertOne({ + dbid, + crew, + trackerId: assignment.tracker_id, + assignment, + timeStamp + } as TrackedCrew); + + return !!(insres?.insertedId) ? 201 : 400; + } + + return 500; +} + +export async function postOrPutAssignmentsMany( + dbid: number, + crew: string[], + assignments: ITrackedAssignment[], + timeStamp: Date = new Date()) { + let result = true; + if (collections.trackedAssignments) { + const newdata = [] as TrackedCrew[]; + let x = 0; + for (let crewMember of crew) { + let assignment = assignments[x++]; + + newdata.push({ + dbid, + crew: crewMember, + trackerId: assignment.tracker_id, + assignment, + timeStamp + } as TrackedCrew); + + } + let insres = await collections.trackedAssignments.insertMany(newdata); + result &&= !!insres && Object.keys(insres.insertedIds).length === newdata.length; + return result ? 201 : 400; + } + + return 500; +} + +export async function getTelemetry(a: Date | string, b?: Date) { + let res: TelemetryVoyage[] | null = null; + b ??= new Date(); + if (collections.telemetry) { + if (typeof a !== 'string') { + let result = collections.telemetry.find>({ + voyageDate: { + $gt: a, + $lt: b + } + }); + + res = await result?.toArray(); + } + else { + let result = collections.telemetry.find>({ + crewSymbol: a + }); + + res = await result?.toArray(); + } + } + + return res; +} + +export async function postTelemetry( + voyage: ITelemetryVoyage, + timeStamp: Date = new Date()) { + if (collections.telemetry) { + let insres = await collections.telemetry?.insertOne({ + ... voyage, + voyageDate: timeStamp + } as TelemetryVoyage); + + return !!(insres?.insertedId) ? 201 : 400; + } + + return 500; +} + +export async function postOrPutBossBattle( + battle: IFBB_BossBattle_Document) { + let result = true; + + if (collections.bossBattles) { + let roomCode = (seedrandom.default(battle.bossBattleId.toString())() * 1000000).toString(); + let insres = await collections.bossBattles?.updateOne( + { bossBattleId: battle.bossBattleId }, + { + $set: { + ... battle, + roomCode + } as BossBattleDocument + }, + { + upsert: true + } + ); + + result &&= !!insres && (!!insres.upsertedId || !!insres.matchedCount || !!insres.modifiedCount); + return result ? 201 : 400; + } + + return 500; +} + +export async function postOrPutSolves( + bossBattleId: number, + chainIndex: number, + solves: Solve[]) { + + let result = true; + if (collections.solves) { + const newdata = [] as SolveDocument[]; + + for (let solve of solves) { + newdata.push({ + bossBattleId, + chainIndex, + solve, + timeStamp: new Date(), + }) + } + + let insres = await collections.solves.insertMany(newdata); + result &&= !!insres && Object.keys(insres.insertedIds).length === newdata.length; + return result ? 201 : 400; + } + + return 500; +} + + +export async function postOrPutTrials( + bossBattleId: number, + chainIndex: number, + trials: CrewTrial[]) { + + let result = true; + if (collections.trials) { + const newdata = [] as TrialDocument[]; + + for (let trial of trials) { + newdata.push({ + bossBattleId, + chainIndex, + trial, + timeStamp: new Date(), + }) + } + + let insres = await collections.trials.insertMany(newdata); + result &&= !!insres && Object.keys(insres.insertedIds).length === newdata.length; + return result ? 201 : 400; + } + + return 500; +} + +export async function getCollaborationById( + bossBattleId?: number, + roomCode?: string) { + + if (collections.bossBattles && collections.solves && collections.trials && (!!bossBattleId || !!roomCode)) { + let bossBattleDoc: BossBattleDocument | null = null; + + if (bossBattleId) { + bossBattleDoc = await collections.bossBattles.findOne({ bossBattleId: bossBattleId }) as BossBattleDocument; + } + else if (roomCode) { + bossBattleDoc = await collections.bossBattles.findOne>({ roomCode }) as BossBattleDocument; + } + + if (bossBattleDoc) { + let solveFind = collections.solves.find>({ bossBattleId, chainIndex: bossBattleDoc.chainIndex }); + let trialFind = collections.trials.find>({ bossBattleId, chainIndex: bossBattleDoc.chainIndex }); + + let solves = await solveFind?.toArray() ?? []; + let trials = await trialFind?.toArray() ?? []; + + return [{ + bossBattleId, + fleetId: bossBattleDoc.fleetId, + bossGroup: bossBattleDoc.bossGroup, + difficultyId: bossBattleDoc.difficultyId, + chainIndex: bossBattleDoc.chainIndex, + chain: bossBattleDoc.chain, + description: bossBattleDoc.description, + roomCode: bossBattleDoc.roomCode, + solves: solves.map(solveDoc => solveDoc.solve) as Solve[], + trials: trials.map(trialDoc => trialDoc.trial) as CrewTrial[] + }] as Collaboration[]; + } + } + + return null; +} \ No newline at end of file diff --git a/app/logic/profiletools.ts b/app/logic/profiletools.ts index 7c92d66..0bfc756 100644 --- a/app/logic/profiletools.ts +++ b/app/logic/profiletools.ts @@ -37,8 +37,7 @@ function calculateBuffConfig(playerData: any): { [index: string]: IBuffStat } { return buffConfig; } -export async function uploadProfile(dbid: string, player_data: any, lastUpdate: Date = new Date()) { - // Validate player_data +export function createProfileObject(dbid: string, player_data: any, lastUpdate: Date) { if (!player_data || !player_data.player || !player_data.player.character || player_data.player.dbid.toString() !== dbid) { throw new Error('Invalid player_data!'); } @@ -51,12 +50,20 @@ export async function uploadProfile(dbid: string, player_data: any, lastUpdate: stored_immortals: player_data.player.character.stored_immortals }; + return { dbid, buffConfig: calculateBuffConfig(player_data.player), shortCrewList, captainName, lastUpdate }; +} + +export async function uploadProfile(dbid: string, player_data: any, lastUpdate: Date = new Date()) { + // Validate player_data + + let profile = createProfileObject(dbid, player_data, lastUpdate); + let res = await Profile.findAll({ where: { dbid } }); if (res.length === 0) { - return await Profile.create({ dbid, buffConfig: calculateBuffConfig(player_data.player), shortCrewList, captainName, lastUpdate }); + return await Profile.create({ ... profile }); } else { await res[0].update( - { dbid, buffConfig: calculateBuffConfig(player_data.player), shortCrewList, captainName, lastUpdate }, + { ...profile }, { where: { dbid } } ); diff --git a/app/models/Tracked.ts b/app/models/Tracked.ts new file mode 100644 index 0000000..f5ba903 --- /dev/null +++ b/app/models/Tracked.ts @@ -0,0 +1,42 @@ +import { ObjectId } from "mongodb"; +import { Column, CreatedAt, DataType, Model, Table } from "sequelize-typescript"; +import { ITrackedVoyage, ITrackedAssignment } from "../datacore/voyage"; +import { + ITelemetryVoyage, + ITrackedVoyageRecord, + ITrackedCrewRecord, +} from "../mongoModels/voyageHistory"; + + +@Table +export class TrackedVoyage extends Model implements ITrackedVoyageRecord { + @Column + dbid!: number; + + @Column + trackerId!: number; + + @Column(DataType.JSON) + voyage!: ITrackedVoyage; + + @CreatedAt + timeStamp!: Date; +} + +@Table +export class TrackedCrew extends Model implements ITrackedCrewRecord { + @Column + dbid!: number; + + @Column + crew!: string; + + @Column + trackerId!: number; + + @Column(DataType.JSON) + assignment!: ITrackedAssignment; + + @CreatedAt + timeStamp!: Date; +} diff --git a/app/mongo.ts b/app/mongo.ts new file mode 100644 index 0000000..a82f535 --- /dev/null +++ b/app/mongo.ts @@ -0,0 +1,90 @@ +import * as mongoDB from "mongodb"; + +export const collections: { + profiles?: mongoDB.Collection; + trackedVoyages?: mongoDB.Collection; + trackedAssignments?: mongoDB.Collection; + telemetry?: mongoDB.Collection; + solves?: mongoDB.Collection; + trials?: mongoDB.Collection; + bossBattles?: mongoDB.Collection; + users?: mongoDB.Collection; +} = {} +// test gittower +require('dotenv').config(); + +export async function connectToMongo() { + + try { + const client: mongoDB.MongoClient = new mongoDB.MongoClient(process.env.MONGO_CONN_STRING as string); + await client.connect(); + + const db: mongoDB.Db = client.db(process.env.MONGO_DB_NAME); + + const profilesCollection: mongoDB.Collection = db.collection(process.env.MONGO_PROFILE_COLLECTION as string); + const trackedVoyagesCollection: mongoDB.Collection = db.collection(process.env.MONGO_TRACKED_VOYAGES_COLLECTION as string); + const trackedAssignmentsCollection: mongoDB.Collection = db.collection(process.env.MONGO_TRACKED_ASSIGNMENTS_COLLECTION as string); + const telemetry: mongoDB.Collection = db.collection(process.env.MONGO_TELEMETRY_VOYAGE_COLLECTION as string); + + const solves: mongoDB.Collection = db.collection(process.env.MONGO_FBB_SOLVES_COLLECTION as string); + const trials: mongoDB.Collection = db.collection(process.env.MONGO_FBB_TRIALS_COLLECTION as string); + const fbb: mongoDB.Collection = db.collection(process.env.MONGO_FBB_COLLECTION as string); + const users: mongoDB.Collection = db.collection(process.env.MONGO_DISCORD_USERS_COLLECTION as string); + + collections.users = users; + collections.users.createIndex("discordUserName"); + collections.users.createIndex("discordUserId"); + collections.users.createIndex("discordUserDiscriminator"); + + collections.profiles = profilesCollection; + collections.profiles.createIndex("dbid"); + collections.profiles.createIndex("dbidHash"); + collections.profiles.createIndex("fleet"); + collections.profiles.createIndex("squadron"); + + collections.telemetry = telemetry; + collections.telemetry.createIndex("crewSymbol"); + collections.telemetry.createIndex("voyageDate"); + + collections.trackedVoyages = trackedVoyagesCollection; + + collections.trackedVoyages.createIndex("dbid"); + collections.trackedVoyages.createIndex("trackerId"); + + collections.trackedAssignments = trackedAssignmentsCollection; + + collections.trackedAssignments.createIndex("dbid"); + collections.trackedAssignments.createIndex("crew"); + collections.trackedAssignments.createIndex("trackerId"); + + collections.solves = solves; + collections.solves.createIndex("bossBattleId"); + collections.solves.createIndex("chainIndex"); + + collections.trials = trials; + collections.trials.createIndex("bossBattleId"); + collections.trials.createIndex("chainIndex"); + + collections.bossBattles = fbb; + collections.bossBattles.createIndex("bossBattleId"); + collections.bossBattles.createIndex({ + bossBattleId: 1, + fleetId: 1, + difficultyId: 1 + }); + + console.log(`Successfully connected to MongoDB database: ${db.databaseName}`); + Object.values(collections).forEach((col: mongoDB.Collection) => { + console.log(` - Collection: ${col.collectionName}`); + }); + + return true; + } + catch(err) { + console.log("Connection to MongoDB did not succeed!"); + console.log(err); + } + + return false; + + } \ No newline at end of file diff --git a/app/mongoModels/mongoUser.ts b/app/mongoModels/mongoUser.ts new file mode 100644 index 0000000..1083c7c --- /dev/null +++ b/app/mongoModels/mongoUser.ts @@ -0,0 +1,19 @@ +import { ObjectId } from "mongodb"; +import { PlayerData } from "../datacore/player"; +import { UserRole } from "../models/User"; +import { PlayerProfile } from "./playerProfile"; + +export class User { + constructor( + public discordUserName: string, + public discordUserDiscriminator: string, + public discordUserId: string, + public profiles: number[], + public userRole: UserRole = UserRole.NORMAL, + public creationDate: Date = new Date(), + public avatar?: string, + public id?: ObjectId) { + } +} + + diff --git a/app/mongoModels/playerCollab.ts b/app/mongoModels/playerCollab.ts new file mode 100644 index 0000000..fe85def --- /dev/null +++ b/app/mongoModels/playerCollab.ts @@ -0,0 +1,69 @@ + +import { ObjectId } from "mongodb"; +import { PlayerData } from "../datacore/player"; +import { Chain, Solve, CrewTrial } from "../datacore/boss"; + +export interface IFBB_BossBattle_Document { + bossBattleId: number; // can also index ON fleetId AND bossId AND difficultyId + fleetId: number; + bossGroup: string; + difficultyId: number; + chainIndex: number; + chain: Chain; + description: string; + roomCode: string; + timeStamp: Date; + id?: ObjectId; +}; + +export interface IFBB_Solve_Document { + bossBattleId: number; + chainIndex: number; + solve: Solve; + timeStamp: Date; + id?: ObjectId; +}; + +export interface IFBB_Trial_Document { + bossBattleId: number; + chainIndex: number; + trial: CrewTrial; + timeStamp: Date; + id?: ObjectId; +}; + + +export class BossBattleDocument implements IFBB_BossBattle_Document { + constructor( + public bossBattleId: number, + public fleetId: number, + public bossGroup: string, + public difficultyId: number, + public chainIndex: number, + public chain: Chain, + public description: string, + public roomCode: string, + public timeStamp: Date = new Date(), + public id?: ObjectId) { + } +} + +export class SolveDocument implements IFBB_Solve_Document { + constructor( + public bossBattleId: number, + public chainIndex: number, + public solve: Solve, + public timeStamp: Date = new Date(), + public id?: ObjectId) { + } +}; + +export class TrialDocument implements IFBB_Trial_Document { + constructor( + public bossBattleId: number, + public chainIndex: number, + public trial: CrewTrial, + public timeStamp: Date = new Date(), + public id?: ObjectId | undefined) { + } +} diff --git a/app/mongoModels/playerProfile.ts b/app/mongoModels/playerProfile.ts new file mode 100644 index 0000000..1ce4eb5 --- /dev/null +++ b/app/mongoModels/playerProfile.ts @@ -0,0 +1,18 @@ +import { ObjectId } from "mongodb"; +import { PlayerData } from "../datacore/player"; + +export class PlayerProfile { + constructor(public dbid: number, + public dbidHash: string, + public playerData: PlayerData, + public timeStamp: Date, + public captainName: string, + public buffConfig: any, + public shortCrewList: any, + public fleet: number = 0, + public squadron: number = 0, + public id?: ObjectId) { + } +} + + diff --git a/app/mongoModels/voyageHistory.ts b/app/mongoModels/voyageHistory.ts new file mode 100644 index 0000000..493e4d2 --- /dev/null +++ b/app/mongoModels/voyageHistory.ts @@ -0,0 +1,62 @@ +import { ObjectId } from "mongodb"; +import { + ITrackedAssignment, + ITrackedVoyage, + IVoyageHistory, +} from "../datacore/voyage"; + +export interface ITelemetryVoyage { + crewSymbol: string; + estimatedDuration: number; + voyageDate: Date; +} + +export interface ITrackedVoyageRecord { + dbid: number; + trackerId: number; + voyage: ITrackedVoyage; + timeStamp: Date; +} + +export interface ITrackedCrewRecord { + dbid: number; + crew: string; + trackerId: number; + assignment: ITrackedAssignment; + timeStamp: Date; +} + +export interface ITrackedDataRecord { + voyages: ITrackedVoyageRecord[]; + crew: ITrackedCrewRecord[]; +} + +export class TelemetryVoyage implements ITelemetryVoyage { + constructor( + public crewSymbol: string, + public estimatedDuration: number, + public voyageDate: Date = new Date(), + public id?: ObjectId + ) {} +} + +export class TrackedVoyage implements ITrackedVoyageRecord { + constructor( + public dbid: number, + public trackerId: number, + public voyage: ITrackedVoyage, + public timeStamp: Date = new Date(), + public id?: ObjectId + ) {} +} + +export class TrackedCrew implements ITrackedCrewRecord { + constructor( + public dbid: number, + public crew: string, + public trackerId: number, + public assignment: ITrackedAssignment, + public timeStamp: Date = new Date(), + public id?: ObjectId + ) {} +} diff --git a/app/server.ts b/app/server.ts index 00a30bb..d333008 100644 --- a/app/server.ts +++ b/app/server.ts @@ -8,7 +8,12 @@ import expressWinston from 'express-winston'; import { ApiController } from './controllers'; import { Logger, DataCoreAPI } from './logic'; import { sequelize } from './sequelize'; - +import { collections, connectToMongo } from './mongo'; +import { VoyageRecord } from './models/VoyageRecord'; +import { Op, Sequelize } from 'sequelize'; +import { ITelemetryVoyage, TelemetryVoyage } from './mongoModels/voyageHistory'; +import fs from 'fs' +import { CrewMember } from './datacore/crew'; require('dotenv').config(); // Create a new express application instance @@ -60,12 +65,106 @@ app.use(cors(corsOptions)); // Mount the controllers' routes app.use('/api', nocache, expressLogger, ApiController); +const cycleInitMongo = async (force?: boolean) => { + if (DataCoreAPI.mongoAvailable && !force) return; + + try { + DataCoreAPI.mongoAvailable = await connectToMongo(); + } + catch { + DataCoreAPI.mongoAvailable = false; + } + + if (!DataCoreAPI.mongoAvailable) { + console.log("MongoDB is not available. Disabling affected routes. Will try again in 60 seconds."); + setTimeout(() => { + console.log("Re-attempting MongoDB connection...") + cycleInitMongo(); + }, 60000); + } +}; + (async () => { await sequelize.sync(); - + // Now that the DB is actually up, initialize the cache await DataCoreAPI.initializeCache(); + setTimeout(async () => { + await cycleInitMongo(); + // if (DataCoreAPI.mongoAvailable && collections.telemetry) { + // let response = collections.telemetry.aggregate([ + // {$group : {_id: "$crewSymbol", count: { $sum: 1 } } } + // ]); + // if (response) { + // let result = await response.toArray(); + // console.log(`${result.length} records queried.`); + // console.log(result); + // } + // // let response = collections.telemetry.find({ crewSymbol: 'mariner_mirror_crew' }); + // // if (response) { + // // console.log(await response.toArray()); + // // } + // } + + // if (DataCoreAPI.mongoAvailable) { + // console.log("Connection Established, Querying Old Telemetry Data..."); + // const baseFilter = { + // group: ['crewSymbol'], + // attributes: ['crewSymbol', [Sequelize.fn('COUNT', Sequelize.col('crewSymbol')), 'crewCount'], [Sequelize.fn('AVG', Sequelize.col('estimatedDuration')), 'averageDuration']], + // } as any; + + // if (collections.telemetry){ + // console.log("Wiping current MongoDB telemetry collection..."); + // await collections.telemetry.deleteMany({}); + // console.log("Done."); + // } + + // const crews = JSON.parse(fs.readFileSync("../website/static/structured/crew.json", 'utf8')) as CrewMember[]; + + // for (let crew of crews) { + + // console.log(`Reading old data from '${crew.name}' ... `); + + // let data: VoyageRecord[] | null; + + // data = await VoyageRecord.findAll({ where: { crewSymbol: crew.symbol } }); + + // if (!data?.length) { + // console.log("No data, skipping..."); + // continue; + // } + + // console.log(`Old data from '${crew.name}': ${data.length} Records...`); + + // if (collections.telemetry) { + + // let mapped = data.map(item => { return { + // crewSymbol: item.crewSymbol, + // voyageDate: item.voyageDate, + // estimatedDuration: item.estimatedDuration ?? 0 + // } as ITelemetryVoyage }); + // data.length = 0; + // data = null; + + // console.log(`Inserting records from crew '${mapped[0].crewSymbol}' into Mongo ...`); + // collections.telemetry.insertMany(mapped); + // console.log("Done. Moving on to next set..."); + // } + // else { + // data.length = 0; + // data = null; + + // console.log("Mongo is not found!"); + // break; + // } + // } + + // console.log("Populating MongoDB completed. You may quit."); + + // } + }) + // Serve the application at the given port app.listen(port, '0.0.0.0', () => { // Success callback