System values estimated.
This commit is contained in:
parent
5c69024c02
commit
75f180526d
13 changed files with 572 additions and 101 deletions
113
index.html
113
index.html
|
@ -7,78 +7,69 @@
|
|||
<script src="src/assets/ldom.dev.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div :key="reload">
|
||||
<!-- CURRENT LOCATION -->
|
||||
<div class="container-fluid">
|
||||
<div class="row separator align-items-center">
|
||||
<div class="col"><hr class="separator"></div>
|
||||
<div class="col-auto">Current Location</div>
|
||||
<div class="col"><hr class="separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- CURRENT LOCATION -->
|
||||
<div class="container-fluid">
|
||||
<div class="row separator align-items-center">
|
||||
<div class="col"><hr class="separator"></div>
|
||||
<div class="col-auto">Current Location</div>
|
||||
<div class="col"><hr class="separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- table header -->
|
||||
<div class="container-fluid">
|
||||
<div class="row ms-1 me-1 separator">
|
||||
<div class="col-3 text-center">Name</div>
|
||||
<div class="col">Type</div>
|
||||
<div class="col-auto text-center">Distance</div>
|
||||
<div class="col-1 text-center">Details</div>
|
||||
<div class="col-2 text-center">Mapped Value</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- table header -->
|
||||
<div class="container-fluid">
|
||||
<div class="row ms-1 me-1 separator">
|
||||
<div class="col-3 text-center">Name</div>
|
||||
<div class="col">Type</div>
|
||||
<div class="col-auto text-center">Distance</div>
|
||||
<div class="col-1 text-center">Details</div>
|
||||
<div class="col-2 text-center">Mapped Value</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- system name -->
|
||||
<div class="container-fluid">
|
||||
<div class="row ms-1 me-1">
|
||||
<div id="currentSystem" class="col system highlighted text-center">
|
||||
<i id="currentSystemIcon" class="flaticon-solar-system hidden"></i>
|
||||
<span id="currentSystemName">Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- system name -->
|
||||
<div class="container-fluid" id="currentSystem">
|
||||
|
||||
<!-- high-value scans -->
|
||||
<div class="container-fluid" id="highValueScans">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- high-value scans -->
|
||||
<div class="container-fluid" id="highValueScans">
|
||||
|
||||
<!-- low-value scans -->
|
||||
<div class="container-fluid" id="lowValueScans">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- low-value scans -->
|
||||
<div class="container-fluid" id="lowValueScans">
|
||||
|
||||
<!-- NAV ROUTE -->
|
||||
<div class="container-fluid">
|
||||
<div class="row separator align-items-center">
|
||||
<div class="col"><hr class="separator"></div>
|
||||
<div class="col-auto">Nav Route</div>
|
||||
<div class="col"><hr class="separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- table header -->
|
||||
<div class="container-fluid">
|
||||
<div class="row ms-1 me-1 separator">
|
||||
<div class="col-1 text-right">LY</div>
|
||||
<div class="col-3 text-center">Name</div>
|
||||
<div class="col">Type</div>
|
||||
<div class="col-2 text-center">Mapped Value</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- NAV ROUTE -->
|
||||
<div class="container-fluid">
|
||||
<div class="row separator align-items-center">
|
||||
<div class="col"><hr class="separator"></div>
|
||||
<div class="col-auto">Nav Route</div>
|
||||
<div class="col"><hr class="separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- route -->
|
||||
<div id="navRoute" class="container-fluid">
|
||||
<!-- table header -->
|
||||
<div class="container-fluid">
|
||||
<div class="row ms-1 me-1 separator">
|
||||
<div class="col-1 text-right">LY</div>
|
||||
<div class="col-3 text-center">Name</div>
|
||||
<div class="col">Type</div>
|
||||
<div class="col-2 text-center">Mapped Value</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- route -->
|
||||
<div id="navRoute" class="container-fluid">
|
||||
|
||||
<!-- bottom border -->
|
||||
<div class="container-fluid">
|
||||
<div class="row separator align-items-center">
|
||||
<div class="col"><hr class="separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- bottom border -->
|
||||
<div class="container-fluid">
|
||||
<div class="row separator align-items-center">
|
||||
<div class="col"><hr class="separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
19
package-lock.json
generated
19
package-lock.json
generated
|
@ -11,7 +11,6 @@
|
|||
"dependencies": {
|
||||
"bootstrap": "^5.3.0-alpha3",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron-fetch": "^1.9.1",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"glob": "^10.2.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
@ -2292,17 +2291,6 @@
|
|||
"node": ">= 12.20.55"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-fetch": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.9.1.tgz",
|
||||
"integrity": "sha512-M9qw6oUILGVrcENMSRRefE1MbHPIz0h79EKIeJWK9v563aT9Qkh8aEHPO1H5vi970wPirNY+jO9OpFoLiMsMGA==",
|
||||
"dependencies": {
|
||||
"encoding": "^0.1.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-common": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz",
|
||||
|
@ -2823,6 +2811,8 @@
|
|||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
|
@ -2831,6 +2821,8 @@
|
|||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
|
@ -5834,7 +5826,8 @@
|
|||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.62.1",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"dependencies": {
|
||||
"bootstrap": "^5.3.0-alpha3",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron-fetch": "^1.9.1",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"glob": "^10.2.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
|
16
src/@types/edsmResponses.d.ts
vendored
Normal file
16
src/@types/edsmResponses.d.ts
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
interface valuableBody {
|
||||
bodyId: number,
|
||||
bodyName: string,
|
||||
distance: number,
|
||||
valueMax: number,
|
||||
}
|
||||
|
||||
export interface systemEstimatedValue {
|
||||
id: number,
|
||||
id64: number,
|
||||
name: string,
|
||||
url: string,
|
||||
estimatedValue: number,
|
||||
estimatedValueMapped: number,
|
||||
valuableBodies: valuableBody[]
|
||||
}
|
18
src/interfaces/EDSMInterface.js
Normal file
18
src/interfaces/EDSMInterface.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
class EDSM {
|
||||
constructor() {
|
||||
this.data = {}
|
||||
this.dataError = false
|
||||
}
|
||||
|
||||
request(url) {
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('HTTP error' + response.status)
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then(json => this.data = json)
|
||||
.catch(() => this.dataError = true)
|
||||
}
|
||||
}
|
359
src/interfaces/JournalInterface.ts
Normal file
359
src/interfaces/JournalInterface.ts
Normal file
|
@ -0,0 +1,359 @@
|
|||
import type { Tail as TailType } from 'tail'
|
||||
import type { autoScan, completeFsdJump, detailedScan, journalEntry, navRoute, planetScan } from '../@types/journalLines'
|
||||
|
||||
const chokidar = require('chokidar')
|
||||
const EventEmitter = require('node:events')
|
||||
const fs = require('node:fs')
|
||||
const { globSync } = require('glob')
|
||||
import * as _ from 'lodash-es'
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
const { readFile } = require('node:fs/promises')
|
||||
const reverseLineReader = require('reverse-line-reader')
|
||||
const Tail = require('tail').Tail
|
||||
|
||||
import { Body } from '../models/Body'
|
||||
import { System } from '../models/System'
|
||||
|
||||
// Set log() to console.log() so whenever I get around to setting up a log file, I don't have to
|
||||
// search and replace all the console.log()'s.
|
||||
const log = console.log.bind(console)
|
||||
|
||||
export class JournalInterface extends EventEmitter {
|
||||
journalDir: null|string
|
||||
journalPattern: string
|
||||
currentJournal: string|undefined
|
||||
location: System
|
||||
navRoute: System[]
|
||||
|
||||
|
||||
constructor(isPackaged: boolean) {
|
||||
super()
|
||||
|
||||
this.journalDir = null
|
||||
if (!isPackaged) { // Account for WSL during development
|
||||
this.journalDir = "/mnt/c/Users/marle/Saved\ Games/Frontier\ Developments/Elite\ Dangerous/"
|
||||
} else if (os.platform() === 'win32') { // Windows
|
||||
this.journalDir = os.homedir() + '\\Saved Games\\Frontier Developments\\Elite Dangerous'
|
||||
} else if (os.platform() === 'linux') { // Linux
|
||||
this.journalDir = os.homedir() + '/.local/share/Steam/steamapps/compatdata/359320/pfx/drive_c/users/steamuser/Saved Games/Frontier Developments/Elite Dangerous/'
|
||||
} else {
|
||||
log(`ERROR: Journal files not found. OS: ${os.platform()}.`)
|
||||
}
|
||||
|
||||
this.journalPattern = this.journalDir + "Journal.*.log"
|
||||
|
||||
this.currentJournal = this.getLatestJournal()
|
||||
log(`New journal file found, now watching ${path.basename(this.currentJournal)}.`)
|
||||
|
||||
this.navRoute = []
|
||||
|
||||
// LineReader seems to be async, so start async processes here.
|
||||
this.location = new System('Unknown')
|
||||
|
||||
log('JournalInterface initialized. Attempting to find current location.')
|
||||
this.getCurrentLocation()
|
||||
// -> getScannedBodies()
|
||||
// --> getNavRoute()
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- getLatestJournal ---- */
|
||||
|
||||
// https://stackoverflow.com/questions/15696218/get-the-most-recent-file-in-a-directory-node-js
|
||||
getLatestJournal(): string|undefined {
|
||||
const journals = globSync(this.journalPattern)
|
||||
|
||||
return _.maxBy(journals, file => fs.statSync(file).mtime)
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ getCurrentLocation ---- */
|
||||
|
||||
// Get current location on setup, so if app is restarted, user can pick up where they left off
|
||||
// Rather than waiting til they jump to the next system to use the program again.
|
||||
getCurrentLocation(): void {
|
||||
reverseLineReader.eachLine(this.currentJournal, (raw: string, last: boolean) => {
|
||||
if (raw) { // skip blank line at end of file
|
||||
const line = JSON.parse(raw)
|
||||
|
||||
if (line.event === 'FSDJump') {
|
||||
this.location = new System(line)
|
||||
log(`Current location set to ${this.location.name}.`)
|
||||
this.emit('ENTERED_NEW_SYSTEM')
|
||||
return false
|
||||
} else if (last) {
|
||||
log('Unable to find last hyperspace jump. Searching for last known location.')
|
||||
return false
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
reverseLineReader.eachLine(this.currentJournal, (raw: string, last: boolean) => {
|
||||
// TODO: figure out if we can avoid entering eachLine() altogether? realyyy wish
|
||||
// it returned a promise :(
|
||||
if (this.location.name !== 'Unknown') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
const line = JSON.parse(raw)
|
||||
|
||||
if (line.event === 'Location') {
|
||||
this.location = new System(line)
|
||||
log(`Current location set to ${this.location.name}.`)
|
||||
this.emit('ENTERED_NEW_SYSTEM')
|
||||
return false
|
||||
} else if (last) {
|
||||
log('WARNING: Unable to find last known location.')
|
||||
return false
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
if (this.location.name !== 'Unknown') {
|
||||
log('Attempting to find scanned bodies in current system.')
|
||||
this.getScannedBodies()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- getScannedBodies ---- */
|
||||
|
||||
// Look for all scanned bodies before last FSDJump, for same reasons as getCurrentLocation().
|
||||
getScannedBodies(): void {
|
||||
let detailedScanLine: detailedScan|null = null
|
||||
|
||||
reverseLineReader.eachLine(this.currentJournal, (raw: string, last: boolean) => {
|
||||
|
||||
if (raw) { // Skip blank line at end of file.
|
||||
const line: journalEntry = JSON.parse(raw)
|
||||
|
||||
// Check if previous line was ScanType = Detailed, and handle that.
|
||||
if (detailedScanLine !== null) {
|
||||
if (line.event === 'SAAScanComplete') {
|
||||
// This was a DSS, so add to list with DSS flag set to true.
|
||||
this.location.bodies.push(new Body(detailedScanLine, true))
|
||||
} else {
|
||||
// Else, check that the body hasn't already been added (by a DSS scan line).
|
||||
let dupChecker = {'BodyName': detailedScanLine.BodyName, 'BodyID': detailedScanLine.BodyID}
|
||||
let r = _.find(this.location.bodies, dupChecker)
|
||||
|
||||
if (r === undefined) {
|
||||
// Body was not already logged, so add to list.
|
||||
this.location.bodies.push(new Body(detailedScanLine))
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, clear the variable.
|
||||
detailedScanLine = null
|
||||
}
|
||||
|
||||
// Now move on to evaluating the current line.
|
||||
if (line.event === 'Scan' && 'ScanType' in line) {
|
||||
// If ScanType = Detailed and body is not a star, save the line so we can check
|
||||
// the one immediately above for event = SAAScanComplete, which indicates this
|
||||
// was a DSS.
|
||||
if (line.ScanType === 'Detailed' && !('StarType' in line)) {
|
||||
detailedScanLine = (line as detailedScan)
|
||||
|
||||
} else if ('StarType' in line) { // Save stars to bodies list.
|
||||
this.location.bodies.push(new Body((line as autoScan|detailedScan)))
|
||||
|
||||
} else if (line.ScanType === 'AutoScan') { // Save auto/discovery scan bodies.
|
||||
// Check if planet, and then do the duplicate check (otherwise it's an
|
||||
// astroid, as we've already accounted for stars).
|
||||
if ('PlanetClass' in line) {
|
||||
let dupChecker = {'BodyName': (line as planetScan<'AutoScan'>).BodyName, 'BodyID': (line as planetScan<'AutoScan'>).BodyID}
|
||||
let r = _.find(this.location.bodies, dupChecker)
|
||||
|
||||
if (r === undefined) {
|
||||
this.location.bodies.push(new Body((line as autoScan)))
|
||||
}
|
||||
|
||||
} else { // Asteroids.
|
||||
this.location.bodies.push(new Body((line as autoScan)))
|
||||
}
|
||||
}
|
||||
} else if (line.event === 'FSDJump') {
|
||||
// Stop evaluating once we reach the beginning of current system entries.
|
||||
return false
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
log('Scanned bodies found.')
|
||||
log('Reading current nav route.')
|
||||
this.getNavRoute(true)
|
||||
})
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- watchDirectory ---- */
|
||||
|
||||
// Set up journal directory watcher to catch new journal files as the game seems to sometimes
|
||||
// make more than one journal per day.
|
||||
// Also for instances where UTC day switches over mid-play session.
|
||||
watchDirectory(): void {
|
||||
const watcher = chokidar.watch(this.journalPattern, {usePolling: true, persistent: true})
|
||||
|
||||
watcher.on('add', () => this.currentJournal = this.getLatestJournal())
|
||||
|
||||
log('Watching journal folder for changes...')
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------- parseScanLine ---- */
|
||||
|
||||
// Parse and handle scan lines.
|
||||
parseScanLine(line: autoScan|detailedScan, DSS: boolean = false) {
|
||||
const dupChecker = {'BodyName': line.BodyName, 'BodyID': line.BodyID}
|
||||
let body: Body|null = null
|
||||
|
||||
// If it's a DSS scan, then we should have already added the body to the list. But we'll
|
||||
// check to make sure.
|
||||
if (DSS) {
|
||||
// Using findIndex() rather than find() so we can edit the body if found.
|
||||
// @ts-ignore since it doesn't understand dupChecker is a valid predicate.
|
||||
let bodyIndex: number = _.findIndex(this.location.bodies, dupChecker)
|
||||
|
||||
if (bodyIndex > -1) { // Body was found in list, so simply toggle the DSS flag.
|
||||
body = (this.location.bodies[bodyIndex] as Body)
|
||||
body.DSSDone = true
|
||||
} else { // Body was missed on initial journal scan, so add it to the list.
|
||||
body = new Body(line, true)
|
||||
this.location.bodies.push(body)
|
||||
}
|
||||
|
||||
} else { // Otherwise it's an FSS or auto scan, and needs to be added to the list.
|
||||
// Probably overkill, but do a duplicate check just in case.
|
||||
let r = _.find(this.location.bodies, dupChecker)
|
||||
|
||||
if (r === undefined) {
|
||||
body = new Body(line)
|
||||
this.location.bodies.push(body)
|
||||
}
|
||||
}
|
||||
|
||||
log(`Scan detected. Body: ${line.BodyName}.`)
|
||||
this.emit('BODY_SCANNED', body, DSS)
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- getNavRoute ---- */
|
||||
|
||||
async getNavRoute(init: boolean = false) {
|
||||
this.navRoute = [] // clear previous route, to catch overwritten routes
|
||||
let routeFile: string|null = null
|
||||
|
||||
try {
|
||||
routeFile = await readFile(this.journalDir + 'NavRoute.json', { encoding: 'utf8' })
|
||||
} catch (err) {
|
||||
log(`Error reading nav route file: ${err.message}.`)
|
||||
}
|
||||
|
||||
if (routeFile) {
|
||||
const route: navRoute = JSON.parse(routeFile)
|
||||
|
||||
// system -> skip
|
||||
// CURRENT -> push = true; skip
|
||||
// system -> push
|
||||
|
||||
let push: boolean = false
|
||||
route.Route.forEach((system) => {
|
||||
if (!push && system.SystemAddress === this.location.SystemAddress) {
|
||||
push = true
|
||||
}
|
||||
|
||||
if (push && system.SystemAddress !== this.location.SystemAddress) {
|
||||
this.navRoute.push(new System(system))
|
||||
}
|
||||
})
|
||||
|
||||
if (this.navRoute.length > 0) {
|
||||
log('Nav route set.')
|
||||
} else {
|
||||
log('No nav route found.')
|
||||
}
|
||||
|
||||
if (init) {
|
||||
this.emit('INIT_COMPLETE')
|
||||
}
|
||||
|
||||
this.emit('SET_NAV_ROUTE')
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------- handleFsdJump ---- */
|
||||
|
||||
handleFsdJump(line: completeFsdJump): void {
|
||||
this.location = new System((line as completeFsdJump))
|
||||
log(`FSD Jump detected, current location updated to ${this.location.name}.`)
|
||||
|
||||
if (this.navRoute.length > 0) {
|
||||
_.remove(this.navRoute, (system) => {
|
||||
return system.SystemAddress === this.location.SystemAddress
|
||||
})
|
||||
}
|
||||
|
||||
this.emit('ENTERED_NEW_SYSTEM')
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------- parseLine ---- */
|
||||
|
||||
// Parse and handle journal lines.
|
||||
parseLine(raw: string) {
|
||||
const line: journalEntry = JSON.parse(raw)
|
||||
let DSSFlag: boolean = false
|
||||
|
||||
switch (line.event) {
|
||||
// Hyperspace jump started (3.. 2.. 1..)
|
||||
case 'StartJump': {
|
||||
if ('JumpType' in line && line.JumpType === 'Hyperspace') {
|
||||
this.emit('ENTERING_WITCH_SPACE')
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// CMDR jumped to new system, so update current location.
|
||||
case 'FSDJump': {
|
||||
this.handleFsdJump((line as completeFsdJump))
|
||||
break
|
||||
}
|
||||
|
||||
// CMDR completed DSS scan, so set flag for when next line processes and we want to
|
||||
// figure out what kind of scan occurred.
|
||||
case 'SAAScanComplete': {
|
||||
DSSFlag = true
|
||||
break
|
||||
}
|
||||
|
||||
// A scan occurred, so let's hand that info off to the appropriate function and then
|
||||
// reset the DSS flag.
|
||||
case 'Scan': {
|
||||
this.parseScanLine((line as autoScan|detailedScan), DSSFlag)
|
||||
DSSFlag = false
|
||||
break
|
||||
}
|
||||
|
||||
// CMDR set a new nav route.
|
||||
case 'NavRoute': {
|
||||
this.getNavRoute()
|
||||
break
|
||||
}
|
||||
|
||||
// CMDR cleared the nav route.
|
||||
case 'NavRouteClear': {
|
||||
this.navRoute = []
|
||||
log('Nav route cleared.')
|
||||
this.emit('SET_NAV_ROUTE')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ watchJournal ---- */
|
||||
|
||||
// Watch the journal for changes.
|
||||
watchJournal(): void {
|
||||
const tail: TailType = new Tail(this.currentJournal, {useWatchFile: true})
|
||||
|
||||
log(`Watching ${path.basename(this.currentJournal)}...`)
|
||||
|
||||
tail.on('line', data => data ? this.parseLine(data) : undefined)
|
||||
tail.on('error', err => log(`Tail error in JournalInterface.watchJournal(): ${err}`))
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import type { valuableBody } from "../@types/edsmResponses"
|
||||
import type { asteroidScan, autoScan, detailedScan, planetScan, starScan } from "../@types/journalLines"
|
||||
|
||||
export interface Body extends starScan<'AutoScan'|'DetailedScan'>, asteroidScan<'AutoScan'|'DetailedScan'>, planetScan<'AutoScan'|'DetailedScan'> {}
|
||||
export class Body {
|
||||
DSSDone: boolean
|
||||
|
||||
constructor(journalLine: autoScan|detailedScan|null = null, DSS: boolean = false) {
|
||||
constructor(journalLine: autoScan|detailedScan|valuableBody|null = null, DSS: boolean = false) {
|
||||
this.DSSDone = DSS
|
||||
|
||||
if (journalLine !== null) {
|
||||
|
@ -79,10 +80,4 @@ export class Body {
|
|||
|
||||
return typeIcon
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------- distance ---- */
|
||||
|
||||
distance(): string {
|
||||
return Intl.NumberFormat().format(Math.round(this.DistanceFromArrivalLS))
|
||||
}
|
||||
}
|
|
@ -1,10 +1,28 @@
|
|||
import fetch from 'electron-fetch'
|
||||
import type { systemEstimatedValue } from '../@types/edsmResponses'
|
||||
|
||||
const EventEmitter = require('node:events')
|
||||
import { Log } from './Log'
|
||||
import { System } from './System'
|
||||
|
||||
class EDSM {
|
||||
private constructor() {}
|
||||
export class EDSM extends EventEmitter {
|
||||
static #instance: EDSM
|
||||
|
||||
static async request(url: string, options: {[x: string]: string}): Promise<object|undefined> {
|
||||
private constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
static connect(): EDSM {
|
||||
if (!EDSM.#instance) {
|
||||
EDSM.#instance = new EDSM()
|
||||
}
|
||||
|
||||
return EDSM.#instance
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------- #request ---- */
|
||||
|
||||
// Submit a request to EDSM and return the response as an object
|
||||
static async #request(url: string, options: {[x: string]: string}): Promise<object|undefined> {
|
||||
let data: object|undefined = undefined
|
||||
|
||||
try {
|
||||
|
@ -21,4 +39,12 @@ class EDSM {
|
|||
|
||||
return data
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- getSystemValue ---- */
|
||||
|
||||
static async getSystemValue(system: System): Promise<systemEstimatedValue|undefined> {
|
||||
const url = 'https://www.edsm.net/api-system-v1/estimated-value'
|
||||
const response = await EDSM.#request(url, {systemName: system.name})
|
||||
return (response as systemEstimatedValue)
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ export class Journal extends EventEmitter {
|
|||
super()
|
||||
|
||||
this.#path = journalPath
|
||||
this.location = new System('Unknown')
|
||||
this.location = new System()
|
||||
this.navRoute = []
|
||||
|
||||
// Start ReverseLineReader chain here.
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
import type { completeFsdJump, location, navRouteSystem } from "../@types/journalLines"
|
||||
import { Body } from "./Body"
|
||||
import { EDSM } from "./EDSM"
|
||||
|
||||
export class System {
|
||||
name: string
|
||||
SystemAddress?: number
|
||||
StarClass?: string
|
||||
charted: boolean
|
||||
bodies: Body[]
|
||||
|
||||
constructor(line: navRouteSystem|completeFsdJump|location|string) {
|
||||
// ESDM data
|
||||
estimatedValue?: number
|
||||
estimatedValueMapped?: number
|
||||
valuableBodies?: Body[]
|
||||
|
||||
constructor(line?: navRouteSystem|completeFsdJump|location) {
|
||||
// In future, this is where we preform EDSM lookup
|
||||
|
||||
if (typeof line === 'string') {
|
||||
this.name = line
|
||||
if (!line) {
|
||||
this.name = 'Unknown'
|
||||
} else {
|
||||
this.name = line.StarSystem
|
||||
this.SystemAddress = line.SystemAddress
|
||||
|
@ -19,8 +26,43 @@ export class System {
|
|||
if ('StarClass' in line) {
|
||||
this.StarClass = line.StarClass
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set this to true initially, since it likely is and the system is technically inserted
|
||||
// into the UI before it's appraised.
|
||||
this.charted = true
|
||||
this.bodies = []
|
||||
|
||||
if (this.name !== 'Unknown') {
|
||||
this.#getValue()
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------- #getValue ---- */
|
||||
|
||||
async #getValue() {
|
||||
// display estimatedValueMapped
|
||||
const data = await EDSM.getSystemValue(this)
|
||||
|
||||
if (data) {
|
||||
this.estimatedValue = data.estimatedValue
|
||||
this.estimatedValueMapped = data.estimatedValueMapped
|
||||
|
||||
// If EDSM doesn't have an estimate, then it's likely undiscovered.
|
||||
this.charted = this.estimatedValue > 0
|
||||
|
||||
// Save valuable bodies in system, if any.
|
||||
if (data.valuableBodies.length > 0) {
|
||||
this.valuableBodies = []
|
||||
|
||||
data.valuableBodies.forEach((body) => {
|
||||
this.valuableBodies?.push(new Body(body))
|
||||
})
|
||||
}
|
||||
|
||||
// Let the UI know it needs to update.
|
||||
EDSM.connect().emit('SYSTEM_APPRAISED', this)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,12 @@
|
|||
export class UI {
|
||||
constructor() {}
|
||||
|
||||
/* ----------------------------------------------------------------------- #formatNumber ---- */
|
||||
|
||||
static #formatNumber(number) {
|
||||
return Intl.NumberFormat().format(Math.round(number))
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- enterWitchSpace ---- */
|
||||
|
||||
static enterWitchSpace() {
|
||||
|
@ -18,16 +24,20 @@ export class UI {
|
|||
static setCurrentSystem(system) {
|
||||
$('#highValueScans').children().remove()
|
||||
$('#lowValueScans').children().remove()
|
||||
$('#currentSystem').children().remove()
|
||||
|
||||
let row
|
||||
|
||||
if (system.name === 'Unknown') {
|
||||
$('#currentSystem').removeClass('charted').addClass('highlighted text-center')
|
||||
$('#currentSystemIcon').addClass('hidden')
|
||||
row = $('<div>').addClass('row ms-1 me-1')
|
||||
const child = $('<div>').addClass('col system highlighted text-center')
|
||||
child.text(system.name)
|
||||
row.appendChild(child)
|
||||
} else {
|
||||
$('#currentSystem').addClass('charted').removeClass('highlighted text-center')
|
||||
$('#currentSystemIcon').removeClass('hidden')
|
||||
row = UI.createSystemRow(system)
|
||||
}
|
||||
|
||||
$('#currentSystemName').text(system.name)
|
||||
$('#currentSystem').appendChild(row)
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- buildRings ---- */
|
||||
|
@ -103,7 +113,7 @@ export class UI {
|
|||
|
||||
// distance
|
||||
const distance = $('<div>').addClass(`col-auto ps-2 ms-0 system ${chartedStyle}`)
|
||||
distance.text(body.distance())
|
||||
distance.text(UI.#formatNumber(body.DistanceFromArrivalLS))
|
||||
row.appendChild(distance)
|
||||
|
||||
// info
|
||||
|
@ -135,8 +145,8 @@ export class UI {
|
|||
static createSystemRow(system) {
|
||||
const row = $('<div>').addClass('row ms-1 me-1')
|
||||
row.attr('id', system.SystemAddress)
|
||||
// TODO APPRAISAL DATA
|
||||
const chartedStyle = 'charted'
|
||||
// This is probably still the default 'true' value, but check in case the fetch() was quick.
|
||||
const chartedStyle = system.charted ? 'charted' : 'uncharted'
|
||||
|
||||
// name
|
||||
const name = $('<div>').addClass(`col system ${chartedStyle}`)
|
||||
|
@ -145,10 +155,19 @@ export class UI {
|
|||
row.appendChild(name)
|
||||
|
||||
// mapped value
|
||||
// TODO APPRAISAL DATA
|
||||
const value = $('<div>').addClass(`col-2 text-end system ${chartedStyle}`)
|
||||
// Check if EDSM has responded yet, otherwise value will be filled in later.
|
||||
const value = $('<div>').addClass(`col-2 text-end system ${chartedStyle} value`)
|
||||
if ('estimatedValueMapped' in system) {
|
||||
value.text(UI.#formatNumber(system.estimatedValueMapped))
|
||||
}
|
||||
row.appendChild(value)
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------- setValue ---- */
|
||||
|
||||
static setValue(row, value) {
|
||||
row.children().filter('.value').text(UI.#formatNumber(value))
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import { Safari } from './models/Safari'
|
|||
import { UI } from './models/UI'
|
||||
import { Body } from './models/Body'
|
||||
import { sep } from 'path'
|
||||
import { EDSM } from './models/EDSM'
|
||||
|
||||
// Grab app.isPackaged from main process
|
||||
let isPackaged = false
|
||||
|
@ -48,6 +49,7 @@ window.process.argv.forEach((item) => {
|
|||
|
||||
const safari = Safari.start(isPackaged)
|
||||
const journal = safari.journal
|
||||
const edsm = EDSM.connect()
|
||||
|
||||
if (!journal) {
|
||||
// handle error
|
||||
|
@ -77,7 +79,7 @@ journal.on('ENTERING_WITCH_SPACE', () => UI.enterWitchSpace())
|
|||
journal.on('ENTERED_NEW_SYSTEM', () => {
|
||||
UI.setCurrentSystem(journal.location)
|
||||
|
||||
$(`#${CSS.escape(journal.location.SystemAddress)}`).remove()
|
||||
$('#navRoute').children().filter(`#${CSS.escape(journal.location.SystemAddress)}`).remove()
|
||||
|
||||
// verify that the internal navRoute matches the UI navRoute, and rebuild it if not
|
||||
if ($('#navRoute').children().length !== journal.navRoute.length) {
|
||||
|
@ -126,4 +128,14 @@ journal.on('SET_NAV_ROUTE', () => {
|
|||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/* ------------------------------------------------------------------------ system value set ---- */
|
||||
|
||||
edsm.on('SYSTEM_APPRAISED', (system) => {
|
||||
const systemRow = $(`#${CSS.escape(system.SystemAddress)}`)
|
||||
|
||||
if (systemRow.length > 0) {
|
||||
UI.setValue(systemRow, system.estimatedValueMapped)
|
||||
}
|
||||
})
|
|
@ -2,6 +2,7 @@
|
|||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"useUnknownInCatchVariables": false,
|
||||
"lib": ["DOM"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
|
|
Loading…
Reference in a new issue