Skip to content

Commit

Permalink
feat(map): add tooltips for statics, effect, and online characters an…
Browse files Browse the repository at this point in the history
…d refactor css for multiple views
  • Loading branch information
updraft0 committed Jun 18, 2024
1 parent a2acb29 commit 0cb2ce7
Show file tree
Hide file tree
Showing 19 changed files with 637 additions and 236 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,39 @@ package org.updraft0.controltower.constant
enum SpaceType derives CanEqual:
case Known, Wormhole, Pochven, Abyssal, Internal

enum WormholeClass(val value: Int, val spaceType: SpaceType) derives CanEqual:
enum WormholeClass(val tag: String, val value: Int, val spaceType: SpaceType) derives CanEqual:
// "normal" w-space
case C1 extends WormholeClass(1, SpaceType.Wormhole)
case C2 extends WormholeClass(2, SpaceType.Wormhole)
case C3 extends WormholeClass(3, SpaceType.Wormhole)
case C4 extends WormholeClass(4, SpaceType.Wormhole)
case C5 extends WormholeClass(5, SpaceType.Wormhole)
case C6 extends WormholeClass(6, SpaceType.Wormhole)
case C1 extends WormholeClass("C1", 1, SpaceType.Wormhole)
case C2 extends WormholeClass("C2", 2, SpaceType.Wormhole)
case C3 extends WormholeClass("C3", 3, SpaceType.Wormhole)
case C4 extends WormholeClass("C4", 4, SpaceType.Wormhole)
case C5 extends WormholeClass("C5", 5, SpaceType.Wormhole)
case C6 extends WormholeClass("C6", 6, SpaceType.Wormhole)
// k-space
case H extends WormholeClass(7, SpaceType.Known)
case L extends WormholeClass(8, SpaceType.Known)
case NS extends WormholeClass(9, SpaceType.Known)
case H extends WormholeClass("H", 7, SpaceType.Known)
case L extends WormholeClass("L", 8, SpaceType.Known)
case NS extends WormholeClass("NS", 9, SpaceType.Known)
// internal
case Internal10 extends WormholeClass(10, SpaceType.Internal)
case Internal11 extends WormholeClass(11, SpaceType.Internal)
case Internal10 extends WormholeClass("?", 10, SpaceType.Internal)
case Internal11 extends WormholeClass("?", 11, SpaceType.Internal)
// thera
case Thera extends WormholeClass(12, SpaceType.Wormhole)
case Thera extends WormholeClass("T", 12, SpaceType.Wormhole)
// frig shattered
case ShatteredFrig extends WormholeClass(13, SpaceType.Wormhole)
case ShatteredFrig extends WormholeClass("SF", 13, SpaceType.Wormhole)
// drifter
case SentinelDrifter extends WormholeClass(14, SpaceType.Wormhole)
case BarbicanDrifter extends WormholeClass(15, SpaceType.Wormhole)
case VidetteDrifter extends WormholeClass(16, SpaceType.Wormhole)
case ConfluxDrifter extends WormholeClass(17, SpaceType.Wormhole)
case RedoubtDrifter extends WormholeClass(18, SpaceType.Wormhole)
case SentinelDrifter extends WormholeClass("D", 14, SpaceType.Wormhole)
case BarbicanDrifter extends WormholeClass("D", 15, SpaceType.Wormhole)
case VidetteDrifter extends WormholeClass("D", 16, SpaceType.Wormhole)
case ConfluxDrifter extends WormholeClass("D", 17, SpaceType.Wormhole)
case RedoubtDrifter extends WormholeClass("D", 18, SpaceType.Wormhole)
// abyssal space
case Void extends WormholeClass(19, SpaceType.Abyssal)
case Abyssal20 extends WormholeClass(20, SpaceType.Abyssal)
case Abyssal21 extends WormholeClass(21, SpaceType.Abyssal)
case Abyssal22 extends WormholeClass(22, SpaceType.Abyssal)
case Abyssal23 extends WormholeClass(23, SpaceType.Abyssal)
case Void extends WormholeClass("V", 19, SpaceType.Abyssal)
case Abyssal20 extends WormholeClass("A", 20, SpaceType.Abyssal)
case Abyssal21 extends WormholeClass("A", 21, SpaceType.Abyssal)
case Abyssal22 extends WormholeClass("A", 22, SpaceType.Abyssal)
case Abyssal23 extends WormholeClass("A", 23, SpaceType.Abyssal)
// pochven
case Pochven extends WormholeClass(25, SpaceType.Pochven)
case Pochven extends WormholeClass("P", 25, SpaceType.Pochven)

def isDrifter: Boolean = value > 13 && value < 19

Expand Down
20 changes: 15 additions & 5 deletions db/src/main/scala/org/updraft0/controltower/db/query/auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,21 @@ object auth:
private inline def insert[T](inline entity: Quoted[EntityQuery[T]], inline value: T): Insert[T] =
quote(entity.insertValue(value))

def getAllAuthTokens(): DbOperation[List[(UserCharacter, CharacterAuthToken)]] =
ctx.run(schema.userCharacter.join(characterAuthToken).on((uc, cat) => uc.characterId == cat.characterId))

def getUserForCharacter(characterId: CharacterId): DbOperation[Option[UserCharacter]] =
ctx.run(schema.userCharacter.filter(_.characterId == lift(characterId))).map(_.headOption)
def getAllAuthTokens(): DbOperation[List[(AuthCharacter, UserCharacter, CharacterAuthToken)]] =
ctx.run(quote {
for
c <- character
uc <- userCharacter.filter(_.characterId == c.id)
ac <- characterAuthToken.filter(_.characterId == uc.characterId)
yield (c, uc, ac)
})

def getUserForCharacter(characterId: CharacterId): DbOperation[Option[(AuthCharacter, UserCharacter)]] =
ctx
.run(
quote(character.filter(_.id == lift(characterId)).join(userCharacter).on((ac, uc) => ac.id == uc.characterId))
)
.map(_.headOption)

def upsertCharacter(char: AuthCharacter): DbOperation[Long] =
ctx.run(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ enum WormholeK162Type derives CanEqual:
enum IntelStance derives CanEqual:
case Unknown, Friendly, Hostile

enum IntelGroup derives CanEqual:
case Unknown, HQ, Farm, Staging

enum MapDisplayType derives CanEqual:
case Manual

Expand All @@ -132,7 +135,9 @@ extension (sd: SystemDisplayData)

case class CharacterLocation(
characterId: CharacterId,
characterName: String,
shipTypeId: Long,
shipName: String,
structureId: Option[Long],
stationId: Option[Int],
updatedAt: Instant
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import java.util.UUID
case class CharacterAuth(
userId: UserId,
characterId: CharacterId,
characterName: String,
token: JwtString,
refreshToken: Base64,
expiry: Instant
Expand Down Expand Up @@ -63,8 +64,10 @@ object Users:
def loadAll: ZIO[Env, Throwable, Chunk[CharacterAuth]] =
for
allTokens <- auth.getAllAuthTokens()
authTokens <- ZIO.foreach(allTokens)((uc, ecat) =>
decryptAuthToken(ecat).map((t, r) => CharacterAuth(uc.userId, uc.characterId, JwtString(t), r, ecat.expiresAt))
authTokens <- ZIO.foreach(allTokens)((c, uc, ecat) =>
decryptAuthToken(ecat).map((t, r) =>
CharacterAuth(uc.userId, uc.characterId, c.name, JwtString(t), r, ecat.expiresAt)
)
)
yield Chunk.fromIterable(authTokens)

Expand All @@ -90,7 +93,14 @@ object Users:
_ <- newUserSession(userId, sessionId).flatMap(auth.insertUserSession)
token <- encryptJwtResponse(tokenMeta, jwt)
_ <- auth.upsertAuthToken(token)
yield CharacterAuth(userId, tokenMeta.characterId, jwt.accessToken, Base64.raw(jwt.refreshToken), tokenMeta.expiry)
yield CharacterAuth(
userId,
tokenMeta.characterId,
char.name,
jwt.accessToken,
Base64.raw(jwt.refreshToken),
tokenMeta.expiry
)

private def newSession(
jwt: JwtAuthResponse,
Expand All @@ -105,7 +115,14 @@ object Users:
_ <- auth.upsertCharacter(char)
token <- encryptJwtResponse(tokenMeta, jwt)
_ <- auth.upsertAuthToken(token)
yield CharacterAuth(user.id, tokenMeta.characterId, jwt.accessToken, Base64.raw(jwt.refreshToken), tokenMeta.expiry)
yield CharacterAuth(
user.id,
tokenMeta.characterId,
char.name,
jwt.accessToken,
Base64.raw(jwt.refreshToken),
tokenMeta.expiry
)

private def addCharacterToUser(
jwt: JwtAuthResponse,
Expand All @@ -122,7 +139,14 @@ object Users:
_ <- auth.upsertCharacter(char)
token <- encryptJwtResponse(tokenMeta, jwt)
_ <- auth.upsertAuthToken(token)
yield CharacterAuth(user.id, tokenMeta.characterId, jwt.accessToken, Base64.raw(jwt.refreshToken), tokenMeta.expiry)
yield CharacterAuth(
user.id,
tokenMeta.characterId,
char.name,
jwt.accessToken,
Base64.raw(jwt.refreshToken),
tokenMeta.expiry
)

private def updateRefreshToken(
jwt: JwtAuthResponse,
Expand All @@ -138,17 +162,26 @@ object Users:
_ <- auth.upsertCharacter(char)
token <- encryptJwtResponse(tokenMeta, jwt)
_ <- auth.upsertAuthToken(token)
yield CharacterAuth(userId, tokenMeta.characterId, jwt.accessToken, Base64.raw(jwt.refreshToken), tokenMeta.expiry)
yield CharacterAuth(
userId,
tokenMeta.characterId,
char.name,
jwt.accessToken,
Base64.raw(jwt.refreshToken),
tokenMeta.expiry
)

private def refreshTokenMinimal(jwt: JwtAuthResponse, tokenMeta: EsiTokenMeta) =
for
token <- encryptJwtResponse(tokenMeta, jwt)
_ <- auth.upsertAuthToken(token)
userOpt <- auth.getUserForCharacter(token.characterId)
_ <- ZIO.fail(new RuntimeException("Character has no user")).unless(userOpt.isDefined)
(c, user) = userOpt.get
yield CharacterAuth(
userOpt.get.userId,
user.userId,
tokenMeta.characterId,
c.name,
jwt.accessToken,
Base64.raw(jwt.refreshToken),
tokenMeta.expiry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import zio.*
import java.time.Instant
import java.util.UUID

type MapEnv = javax.sql.DataSource & LocationTracker & MapPermissionTracker
type SystemId = Long // TODO opaque type
type MapEnv = javax.sql.DataSource & LocationTracker & MapPermissionTracker
type SystemId = Long // TODO opaque type
type ShipTypeId = Int

private[map] case class MapSolarSystem(
systemId: SystemId,
Expand All @@ -36,7 +37,10 @@ private[map] case class MapState(
connections: Map[ConnectionId, MapWormholeConnectionWithSigs],
connectionRanks: Map[ConnectionId, MapWormholeConnectionRank],
locations: Map[CharacterId, MapLocationState],
locationsOnline: Map[CharacterId, SystemId], // manual cache of location systems - used to prevent too many updates
locationsOnline: Map[
CharacterId,
(SystemId, ShipTypeId)
], // manual cache of location systems - used to prevent too many updates
ref: MapRef
):
// TODO this should be loaded from DB
Expand Down Expand Up @@ -871,7 +875,9 @@ object MapEntity extends ReactiveEntity[MapEnv, MapId, MapState, Identified[MapR
state.copy(locationsOnline =
state.locations
.filter((_, mls) => mls.locationInfo.isDefined && mls.online)
.transform((_, mls) => mls.locationInfo.get.system.value)
.transform: (_, mls) =>
val locationInfo = mls.locationInfo.get
locationInfo.system.value -> locationInfo.shipTypeId
)

private def isPotentialWormholeJump(state: MapState, fromSystemId: SystemId, toSystemId: SystemId) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,9 @@ private def toProtoCharacterLocation(
): protocol.CharacterLocation =
protocol.CharacterLocation(
characterId = characterId,
characterName = inSystem.characterName,
shipTypeId = inSystem.shipTypeId,
shipName = inSystem.shipName,
structureId = inSystem.structureId,
stationId = inSystem.stationId,
updatedAt = inSystem.updatedAt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ object CharacterAuthTracker:
_ <- refreshPending(esi, state, hub).repeat(Schedule.fixed(PollInterval)).forkScoped
// start the snapshot timer
_ <- sendSnapshot(state, hub)
.repeat(Schedule.once.andThen(Schedule.fixed(SnapshotInterval)))
.repeat(Schedule.duration(10.seconds).andThen(Schedule.fixed(SnapshotInterval)))
.forkScoped
yield new CharacterAuthTracker:
override def newLogin(auth: CharacterAuth): UIO[Unit] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ enum LocationTrackingRequest:

enum CharacterLocationState derives CanEqual:
case InSystem(
characterName: String,
system: SystemId,
prevSystem: Option[SystemId],
shipTypeId: Int,
shipName: String,
stationId: Option[Int],
structureId: Option[Long],
updatedAt: Instant
Expand Down Expand Up @@ -164,6 +166,7 @@ object LocationTracker:
refreshLocation(esi, now, cs) @@ Log.CharacterId(cs.charId) @@ Log.BackgroundOperation("locationTracker")
)
next <- state.updateAndGet(ts => ts.copy(charState = res.foldLeft(ts.charState)((m, s) => updateOnRefresh(m, s))))
_ <- traceLogNext(next)
locUpdate = LocationUpdate(next.charState.transform((_, s) => s.state))
_ <- response.offer(locUpdate).when(next.charState.nonEmpty)
yield ()
Expand Down Expand Up @@ -198,7 +201,7 @@ object LocationTracker:
ZIO.logTrace(s"Timed out during ESI call: ${t.error}").as(st) // ignore gateway timeouts
case e =>
ZIO
.logError(s"ESI error while refreshing character status, ignoring: ${e}")
.logWarning(s"ESI error while refreshing character status, ignoring: ${e}")
.as(
st.copy(state = CharacterLocationState.ApiError, prevState = Some(prevState), updatedAt = now)
)
Expand Down Expand Up @@ -239,12 +242,37 @@ object LocationTracker:
if (!online.online) CharacterLocationState.Offline
else
CharacterLocationState.InSystem(
location.solarSystemId,
prevSystemId,
ship.shipTypeId,
location.stationId,
location.structureId,
now
characterName = auth.characterName,
system = location.solarSystemId,
prevSystem = prevSystemId,
shipTypeId = ship.shipTypeId,
shipName = unicodeUnescape(ship.shipName),
stationId = location.stationId,
structureId = location.structureId,
updatedAt = now
)

CharacterState(charId, newState, Some(auth), now, Some(prevState))

private def traceLogNext(state: TrackerState) =
def byStateName(_c: CharacterId, state: CharacterState) =
state.state.productPrefix
def stateGroups() =
state.charState
.groupMap(byStateName)(_._1)
.map((s, ids) => s"$s[${ids.mkString(",")}]")
.mkString(";")

ZIO.logTrace(s"Location states: ${stateGroups()}")

private val UnicodeRegex = """(\\u[a-f0-9]{4})""".r

private def unicodeUnescape(s: String) =
UnicodeRegex.replaceSomeIn(
s.stripPrefix("u'").stripSuffix("'"),
{ m =>
val literal = m.group(0).stripPrefix("\\u")
val codePoint = Integer.parseInt(literal, 16)
Option.when(Character.isValidCodePoint(codePoint))(Character.toString(codePoint))
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ object ServerStatusTracker:
_ <- doRefresh(ref)
// fork in background
_ <- (doRefresh(ref)
.repeat(Schedule.once.andThen(Schedule.fixed(PollInterval)))
.repeat(Schedule.fixed(PollInterval))
.ignoreLogged @@ Log.BackgroundOperation("statusTracker")).forkScoped
yield new ServerStatusTracker:
override def status: UIO[Either[EsiError, ServerStatusResponse]] = ref.get

private def doRefresh(ref: Ref[Either[EsiError, ServerStatusResponse]]) =
ZIO
.serviceWithZIO[EsiClient](_.getServerStatus(()).either)
.tap {
case Left(esiError) => ZIO.logWarning(s"Server status check returned failure: ${esiError}")
case Right(status) => ZIO.logTrace(s"Server status: ${status}")
}
.flatMap(ref.set(_))
Loading

0 comments on commit 0cb2ce7

Please sign in to comment.