PSF-LoginServer/src/main/scala/net/psforever/util/Config.scala
Fate-JH 2b6edc25fb
No Work (#1116)
* make the timer for outbound packet bundling a fallback option rather than a primary control for outbound timing

* better emergency timer determination

* eh

* master merge and light changes
2023-07-26 23:07:24 -04:00

239 lines
5.7 KiB
Scala

package net.psforever.util
import java.nio.file.Paths
import com.typesafe.config.{Config => TypesafeConfig}
import enumeratum.{Enum, EnumEntry}
import enumeratum.values.{IntEnum, IntEnumEntry}
import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank}
import net.psforever.packet.game.ServerType
import net.psforever.types.ChatMessageType
import pureconfig.ConfigConvert.viaNonEmptyStringOpt
import pureconfig.{ConfigConvert, ConfigSource}
import scala.concurrent.duration._
import scala.reflect.ClassTag
import pureconfig.generic.auto._ // intellij: this is not unused
object Config {
private val logger = org.log4s.getLogger
// prog.home is defined when we are running from SBT pack
val directory: String = System.getProperty("prog.home") match {
case null =>
Paths.get("config").toAbsolutePath.toString
case home =>
Paths.get(home, "config").toAbsolutePath.toString
}
implicit def enumeratumIntConfigConvert[A <: IntEnumEntry](implicit
e: IntEnum[A],
ct: ClassTag[A]
): ConfigConvert[A] =
viaNonEmptyStringOpt[A](
v =>
e.values.toList.collectFirst {
case e: ServerType if e.name == v => e.asInstanceOf[A]
case e: BattleRank if e.value.toString == v => e.asInstanceOf[A]
case e: CommandRank if e.value.toString == v => e.asInstanceOf[A]
case e: Certification if e.name == v => e.asInstanceOf[A]
},
_.value.toString
)
implicit def enumeratumConfigConvert[A <: EnumEntry](implicit
e: Enum[A],
ct: ClassTag[A]
): ConfigConvert[A] =
viaNonEmptyStringOpt[A](
v =>
e.values.toList.collectFirst {
case e if e.toString.toLowerCase == v.toLowerCase => e
},
_.toString
)
private val source = {
val configFile = Paths.get(directory, "psforever.conf").toFile
if (configFile.exists)
ConfigSource.file(configFile).withFallback(ConfigSource.default)
else
ConfigSource.default
}
// Raw config object - prefer app when possible
lazy val config: TypesafeConfig = source.config() match {
case Right(config) => config
case Left(failures) =>
logger.error("Loading config failed")
failures.toList.foreach { failure =>
logger.error(failure.toString)
}
sys.exit(1)
}
// Typed config object
lazy val app: AppConfig = source.load[AppConfig] match {
case Right(config) => config
case Left(failures) =>
logger.error("Loading config failed")
failures.toList.foreach { failure =>
logger.error(failure.toString)
}
sys.exit(1)
}
}
case class AppConfig(
bind: String,
public: String,
login: LoginConfig,
world: WorldConfig,
admin: AdminConfig,
database: DatabaseConfig,
game: GameConfig,
antiCheat: AntiCheatConfig,
network: NetworkConfig,
development: DevelopmentConfig,
kamon: KamonConfig,
sentry: SentryConfig
)
case class LoginConfig(
port: Int,
createMissingAccounts: Boolean
)
case class WorldConfig(
port: Int,
serverName: String,
serverType: ServerType
)
case class AdminConfig(
port: Int,
bind: String
)
case class DatabaseConfig(
host: String,
port: Int,
username: String,
password: String,
database: String,
sslmode: String
) {
def toJdbc = s"jdbc:postgresql://$host:$port/$database"
}
case class AntiCheatConfig(
hitPositionDiscrepancyThreshold: Int
)
case class NetworkConfig(
session: SessionConfig,
middleware: MiddlewareConfig
)
case class MiddlewareConfig(
packetBundlingDelay: FiniteDuration,
packetBundlingDelayMultiplier: Float,
inReorderTimeout: FiniteDuration,
inSubslotMissingDelay: FiniteDuration,
inSubslotMissingAttempts: Int
)
case class SessionConfig(
inboundGraceTime: FiniteDuration,
outboundGraceTime: FiniteDuration
)
case class GameConfig(
instantAction: InstantActionConfig,
amenityAutorepairRate: Float,
amenityAutorepairDrainRate: Float,
bepRate: Double,
cepRate: Double,
newAvatar: NewAvatar,
hart: HartConfig,
sharedMaxCooldown: Boolean,
sharedBfrCooldown: Boolean,
baseCertifications: Seq[Certification],
warpGates: WarpGateConfig,
cavernRotation: CavernRotationConfig,
savedMsg: SavedMessageEvents,
playerDraw: PlayerStateDrawSettings,
doorsCanBeOpenedByMedAppFromThisDistance: Float
)
case class InstantActionConfig(
spawnOnAms: Boolean,
thirdParty: Boolean
)
case class NewAvatar(
br: BattleRank,
cr: CommandRank
)
case class HartConfig(
inFlightDuration: Long,
boardingDuration: Long
)
case class DevelopmentConfig(
unprivilegedGmCommands: Seq[ChatMessageType],
netSim: NetSimConfig
)
case class NetSimConfig(
enable: Boolean,
loss: Double,
delay: Duration,
reorderChance: Double,
reorderTime: Duration
)
case class KamonConfig(
enable: Boolean
)
case class SentryConfig(
enable: Boolean,
dsn: String
)
case class WarpGateConfig(
defaultToSanctuaryDestination: Boolean,
broadcastBetweenConflictedFactions: Boolean
)
case class CavernRotationConfig(
hoursBetweenRotation: Float,
simultaneousUnlockedZones: Int,
enhancedRotationOrder: Seq[Int],
forceRotationImmediately: Boolean
)
case class SavedMessageEvents(
short: SavedMessageTimings,
renewal: SavedMessageTimings,
interruptedByAction: SavedMessageTimings
)
case class SavedMessageTimings(
fixed: Long,
variable: Long
)
case class PlayerStateDrawSettings(
populationThreshold: Int,
populationStep: Int,
rangeMin: Int,
rangeMax: Int,
rangeStep: Int,
ranges: Seq[Int],
delayMax: Long,
delays: Seq[Long]
) {
assert(ranges.nonEmpty)
assert(ranges.size == delays.size)
}