mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-04-29 15:55:23 +00:00
corrected issue with multiplier for experience earned while in debt; poor separation between special case players in facility capture situation; missing database calls during facility capture situations
This commit is contained in:
parent
cc2089b513
commit
42d1422fc7
6 changed files with 503 additions and 428 deletions
|
|
@ -1623,7 +1623,7 @@ class AvatarActor(
|
||||||
case AwardBep(bep, modifier) =>
|
case AwardBep(bep, modifier) =>
|
||||||
awardProgressionOrExperience(
|
awardProgressionOrExperience(
|
||||||
setBepAction(modifier),
|
setBepAction(modifier),
|
||||||
avatar.bep + bep,
|
bep,
|
||||||
Config.app.game.promotion.battleExperiencePointsModifier
|
Config.app.game.promotion.battleExperiencePointsModifier
|
||||||
)
|
)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
@ -1631,7 +1631,7 @@ class AvatarActor(
|
||||||
case AwardFacilityCaptureBep(bep) =>
|
case AwardFacilityCaptureBep(bep) =>
|
||||||
awardProgressionOrExperience(
|
awardProgressionOrExperience(
|
||||||
setBepAction(ExperienceType.Normal),
|
setBepAction(ExperienceType.Normal),
|
||||||
avatar.bep + bep,
|
bep,
|
||||||
Config.app.game.promotion.captureExperiencePointsModifier
|
Config.app.game.promotion.captureExperiencePointsModifier
|
||||||
)
|
)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
@ -1684,7 +1684,7 @@ class AvatarActor(
|
||||||
if (experienceDebt == 0L) {
|
if (experienceDebt == 0L) {
|
||||||
setCep(avatar.cep + cep)
|
setCep(avatar.cep + cep)
|
||||||
} else if (cep > 0) {
|
} else if (cep > 0) {
|
||||||
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage(0))
|
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage())
|
||||||
}
|
}
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
|
@ -1859,63 +1859,63 @@ class AvatarActor(
|
||||||
|
|
||||||
def performAvatarLogin(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
|
def performAvatarLogin(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
|
||||||
performAvatarLogin0(avatarId, accountId, replyTo)
|
performAvatarLogin0(avatarId, accountId, replyTo)
|
||||||
// import ctx._
|
/*import ctx._
|
||||||
// val result = for {
|
val result = for {
|
||||||
// //log this login
|
//log this login
|
||||||
// _ <- ctx.run(
|
_ <- ctx.run(
|
||||||
// query[persistence.Avatar]
|
query[persistence.Avatar]
|
||||||
// .filter(_.id == lift(avatarId))
|
.filter(_.id == lift(avatarId))
|
||||||
// .update(_.lastLogin -> lift(LocalDateTime.now()))
|
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
||||||
// )
|
)
|
||||||
// //log this choice of faction (no empire switching)
|
//log this choice of faction (no empire switching)
|
||||||
// _ <- ctx.run(
|
_ <- ctx.run(
|
||||||
// query[persistence.Account]
|
query[persistence.Account]
|
||||||
// .filter(_.id == lift(accountId))
|
.filter(_.id == lift(accountId))
|
||||||
// .update(
|
.update(
|
||||||
// _.lastFactionId -> lift(avatar.faction.id),
|
_.lastFactionId -> lift(avatar.faction.id),
|
||||||
// _.avatarLoggedIn -> lift(avatarId)
|
_.avatarLoggedIn -> lift(avatarId)
|
||||||
// )
|
)
|
||||||
// )
|
)
|
||||||
// //retrieve avatar data
|
//retrieve avatar data
|
||||||
// loadouts <- initializeAllLoadouts()
|
loadouts <- initializeAllLoadouts()
|
||||||
// implants <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(avatarId)))
|
implants <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(avatarId)))
|
||||||
// certs <- ctx.run(query[persistence.Certification].filter(_.avatarId == lift(avatarId)))
|
certs <- ctx.run(query[persistence.Certification].filter(_.avatarId == lift(avatarId)))
|
||||||
// locker <- loadLocker(avatarId)
|
locker <- loadLocker(avatarId)
|
||||||
// friends <- loadFriendList(avatarId)
|
friends <- loadFriendList(avatarId)
|
||||||
// ignored <- loadIgnoredList(avatarId)
|
ignored <- loadIgnoredList(avatarId)
|
||||||
// shortcuts <- loadShortcuts(avatarId)
|
shortcuts <- loadShortcuts(avatarId)
|
||||||
// saved <- AvatarActor.loadSavedAvatarData(avatarId)
|
saved <- AvatarActor.loadSavedAvatarData(avatarId)
|
||||||
// debt <- AvatarActor.loadExperienceDebt(avatarId)
|
debt <- AvatarActor.loadExperienceDebt(avatarId)
|
||||||
// card <- AvatarActor.loadCampaignKdaData(avatarId)
|
card <- AvatarActor.loadCampaignKdaData(avatarId)
|
||||||
// } yield (loadouts, implants, certs, locker, friends, ignored, shortcuts, saved, debt, card)
|
} yield (loadouts, implants, certs, locker, friends, ignored, shortcuts, saved, debt, card)
|
||||||
// result.onComplete {
|
result.onComplete {
|
||||||
// case Success((_loadouts, implants, certs, lockerInv, friendsList, ignoredList, shortcutList, saved, debt, card)) =>
|
case Success((_loadouts, implants, certs, lockerInv, friendsList, ignoredList, shortcutList, saved, debt, card)) =>
|
||||||
// avatarCopy(
|
avatarCopy(
|
||||||
// avatar.copy(
|
avatar.copy(
|
||||||
// loadouts = avatar.loadouts.copy(suit = _loadouts),
|
loadouts = avatar.loadouts.copy(suit = _loadouts),
|
||||||
// certifications =
|
certifications =
|
||||||
// certs.map(cert => Certification.withValue(cert.id)).toSet ++ Config.app.game.baseCertifications,
|
certs.map(cert => Certification.withValue(cert.id)).toSet ++ Config.app.game.baseCertifications,
|
||||||
// implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
|
implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
|
||||||
// shortcuts = shortcutList,
|
shortcuts = shortcutList,
|
||||||
// locker = lockerInv,
|
locker = lockerInv,
|
||||||
// people = MemberLists(
|
people = MemberLists(
|
||||||
// friend = friendsList,
|
friend = friendsList,
|
||||||
// ignored = ignoredList
|
ignored = ignoredList
|
||||||
// ),
|
),
|
||||||
// cooldowns = Cooldowns(
|
cooldowns = Cooldowns(
|
||||||
// purchase = AvatarActor.buildCooldownsFromClob(saved.purchaseCooldowns, Avatar.purchaseCooldowns, log),
|
purchase = AvatarActor.buildCooldownsFromClob(saved.purchaseCooldowns, Avatar.purchaseCooldowns, log),
|
||||||
// use = AvatarActor.buildCooldownsFromClob(saved.useCooldowns, Avatar.useCooldowns, log)
|
use = AvatarActor.buildCooldownsFromClob(saved.useCooldowns, Avatar.useCooldowns, log)
|
||||||
// ),
|
),
|
||||||
// scorecard = card
|
scorecard = card
|
||||||
// )
|
)
|
||||||
// )
|
)
|
||||||
// // if we need to start stamina regeneration
|
// if we need to start stamina regeneration
|
||||||
// tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) }
|
tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) }
|
||||||
// experienceDebt = debt
|
experienceDebt = debt
|
||||||
// replyTo ! AvatarLoginResponse(avatar)
|
replyTo ! AvatarLoginResponse(avatar)
|
||||||
// case Failure(e) =>
|
case Failure(e) =>
|
||||||
// log.error(e)("db failure")
|
log.error(e)("db failure")
|
||||||
// }
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
def performAvatarLogin0(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
|
def performAvatarLogin0(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
|
||||||
|
|
@ -3084,23 +3084,25 @@ class AvatarActor(
|
||||||
experience: Long,
|
experience: Long,
|
||||||
modifier: Float
|
modifier: Float
|
||||||
): Unit = {
|
): Unit = {
|
||||||
if (experienceDebt == 0L) {
|
if (experience > 0) {
|
||||||
awardAction(experience)
|
if (experienceDebt == 0L) {
|
||||||
} else if (modifier > 0f) {
|
|
||||||
val modifiedBep = (experience.toFloat * modifier).toLong
|
|
||||||
val gain = modifiedBep - experienceDebt
|
|
||||||
if (gain > 0L) {
|
|
||||||
experienceDebt = 0L
|
|
||||||
awardAction(experience)
|
awardAction(experience)
|
||||||
} else {
|
} else if (modifier > 0f) {
|
||||||
experienceDebt = experienceDebt - modifiedBep
|
val modifiedBep = (experience.toFloat * modifier).toLong
|
||||||
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage())
|
val gain = modifiedBep - experienceDebt
|
||||||
|
if (gain > 0L) {
|
||||||
|
experienceDebt = 0L
|
||||||
|
awardAction(experience)
|
||||||
|
} else {
|
||||||
|
experienceDebt = experienceDebt - modifiedBep
|
||||||
|
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def setBepAction(modifier: ExperienceType)(value: Long): Unit = {
|
private def setBepAction(modifier: ExperienceType)(value: Long): Unit = {
|
||||||
setBep(value, modifier)
|
setBep(avatar.bep + value, modifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def setSupportAction(value: Long): Unit = {
|
private def setSupportAction(value: Long): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -398,6 +398,7 @@ class SessionAvatarHandlers(
|
||||||
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
||||||
|
|
||||||
case AvatarResponse.AwardBep(charId, bep, expType) =>
|
case AvatarResponse.AwardBep(charId, bep, expType) =>
|
||||||
|
//if the target player, always award (some) BEP
|
||||||
if (charId == player.CharId) {
|
if (charId == player.CharId) {
|
||||||
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
||||||
}
|
}
|
||||||
|
|
@ -409,67 +410,7 @@ class SessionAvatarHandlers(
|
||||||
}
|
}
|
||||||
|
|
||||||
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
|
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
|
||||||
//must be in a squad to earn experience
|
facilityCaptureRewards(buildingId, zoneNumber, cep)
|
||||||
val cepConfig = Config.app.game.experience.cep
|
|
||||||
val charId = player.CharId
|
|
||||||
val squadUI = sessionData.squad.squadUI
|
|
||||||
val participation = continent
|
|
||||||
.Building(buildingId)
|
|
||||||
.map { building =>
|
|
||||||
building.Participation.PlayerContribution()
|
|
||||||
}
|
|
||||||
squadUI
|
|
||||||
.find { _._1 == charId }
|
|
||||||
.collect {
|
|
||||||
case (_, elem) if elem.index == 0 =>
|
|
||||||
//squad leader earns CEP, modified by squad effort, capped by squad size present during the capture
|
|
||||||
val squadParticipation = participation match {
|
|
||||||
case Some(map) => map.filter { case (id, _) => squadUI.contains(id) }
|
|
||||||
case _ => Map.empty[Long, Float]
|
|
||||||
}
|
|
||||||
val maxCepBySquadSize: Long = {
|
|
||||||
val maxCepList = cepConfig.maximumPerSquadSize
|
|
||||||
val squadSize: Int = squadParticipation.size
|
|
||||||
maxCepList.lift(squadSize - 1).getOrElse(squadSize * maxCepList.head).toLong
|
|
||||||
}
|
|
||||||
val groupContribution: Float = squadUI
|
|
||||||
.map { case (id, _) => (id, squadParticipation.getOrElse(id, 0f) / 10f) }
|
|
||||||
.values
|
|
||||||
.max
|
|
||||||
val modifiedExp: Long = (cep.toFloat * groupContribution).toLong
|
|
||||||
val cappedModifiedExp: Long = math.min(modifiedExp, maxCepBySquadSize)
|
|
||||||
val finalExp: Long = if (modifiedExp > cappedModifiedExp) {
|
|
||||||
val overLimitOverflow = if (cepConfig.squadSizeLimitOverflow == -1) {
|
|
||||||
cep.toFloat
|
|
||||||
} else {
|
|
||||||
cepConfig.squadSizeLimitOverflow.toFloat
|
|
||||||
}
|
|
||||||
cappedModifiedExp + (overLimitOverflow * cepConfig.squadSizeLimitOverflowMultiplier).toLong
|
|
||||||
} else {
|
|
||||||
cappedModifiedExp
|
|
||||||
}
|
|
||||||
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, finalExp, expType="cep")
|
|
||||||
avatarActor ! AvatarActor.AwardCep(finalExp)
|
|
||||||
Some(finalExp)
|
|
||||||
|
|
||||||
case _ =>
|
|
||||||
//squad member earns BEP based on CEP, modified by personal effort
|
|
||||||
val individualContribution = {
|
|
||||||
val contributionList = for {
|
|
||||||
facilityMap <- participation
|
|
||||||
if facilityMap.contains(charId)
|
|
||||||
} yield facilityMap(charId)
|
|
||||||
if (contributionList.nonEmpty) {
|
|
||||||
contributionList.max
|
|
||||||
} else {
|
|
||||||
0f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val modifiedExp = (cep * individualContribution).toLong
|
|
||||||
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, modifiedExp, expType="bep")
|
|
||||||
avatarActor ! AvatarActor.AwardFacilityCaptureBep(modifiedExp)
|
|
||||||
Some(modifiedExp)
|
|
||||||
}
|
|
||||||
|
|
||||||
case AvatarResponse.SendResponse(msg) =>
|
case AvatarResponse.SendResponse(msg) =>
|
||||||
sendResponse(msg)
|
sendResponse(msg)
|
||||||
|
|
@ -665,6 +606,71 @@ class SessionAvatarHandlers(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def facilityCaptureRewards(buildingId: Int, zoneNumber: Int, cep: Long): Unit = {
|
||||||
|
//TODO squad services deactivated, participation trophy rewards for now - 11-20-2023
|
||||||
|
//must be in a squad to earn experience
|
||||||
|
val charId = player.CharId
|
||||||
|
val squadUI = sessionData.squad.squadUI
|
||||||
|
val participation = continent
|
||||||
|
.Building(buildingId)
|
||||||
|
.map { building =>
|
||||||
|
building.Participation.PlayerContribution()
|
||||||
|
}
|
||||||
|
squadUI
|
||||||
|
.find { _._1 == charId }
|
||||||
|
.collect {
|
||||||
|
case (_, elem) if elem.index == 0 =>
|
||||||
|
val cepConfig = Config.app.game.experience.cep
|
||||||
|
//squad leader earns CEP, modified by squad effort, capped by squad size present during the capture
|
||||||
|
val squadParticipation = participation match {
|
||||||
|
case Some(map) => map.filter { case (id, _) => squadUI.contains(id) }
|
||||||
|
case _ => Map.empty[Long, Float]
|
||||||
|
}
|
||||||
|
val maxCepBySquadSize: Long = {
|
||||||
|
val maxCepList = cepConfig.maximumPerSquadSize
|
||||||
|
val squadSize: Int = squadParticipation.size
|
||||||
|
maxCepList.lift(squadSize - 1).getOrElse(squadSize * maxCepList.head).toLong
|
||||||
|
}
|
||||||
|
val groupContribution: Float = squadUI
|
||||||
|
.map { case (id, _) => (id, squadParticipation.getOrElse(id, 0f) / 10f) }
|
||||||
|
.values
|
||||||
|
.max
|
||||||
|
val modifiedExp: Long = (cep.toFloat * groupContribution).toLong
|
||||||
|
val cappedModifiedExp: Long = math.min(modifiedExp, maxCepBySquadSize)
|
||||||
|
val finalExp: Long = if (modifiedExp > cappedModifiedExp) {
|
||||||
|
val overLimitOverflow = if (cepConfig.squadSizeLimitOverflow == -1) {
|
||||||
|
cep.toFloat
|
||||||
|
} else {
|
||||||
|
cepConfig.squadSizeLimitOverflow.toFloat
|
||||||
|
}
|
||||||
|
cappedModifiedExp + (overLimitOverflow * (math.random().toFloat % cepConfig.squadSizeLimitOverflowMultiplier)).toLong
|
||||||
|
} else {
|
||||||
|
cappedModifiedExp
|
||||||
|
}
|
||||||
|
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, finalExp, expType="cep")
|
||||||
|
avatarActor ! AvatarActor.AwardCep(finalExp)
|
||||||
|
Some(finalExp)
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
//squad member earns BEP based on CEP, modified by personal effort
|
||||||
|
val individualContribution = {
|
||||||
|
val contributionList = for {
|
||||||
|
facilityMap <- participation
|
||||||
|
if facilityMap.contains(charId)
|
||||||
|
} yield facilityMap(charId)
|
||||||
|
if (contributionList.nonEmpty) {
|
||||||
|
contributionList.max
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val modifiedExp = (cep * individualContribution).toLong
|
||||||
|
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, modifiedExp, expType="bep")
|
||||||
|
avatarActor ! AvatarActor.AwardFacilityCaptureBep(modifiedExp)
|
||||||
|
Some(modifiedExp)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object SessionAvatarHandlers {
|
object SessionAvatarHandlers {
|
||||||
|
|
|
||||||
|
|
@ -203,10 +203,10 @@ object FacilityHackParticipation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dataSum != 0) {
|
if (dataCount != 0) {
|
||||||
math.max(0.15f, math.min(2f, dataSum / dataCount.toFloat))
|
math.max(0.2f, math.min(2f, dataSum / dataCount.toFloat))
|
||||||
} else {
|
} else {
|
||||||
1f
|
0.5f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,14 +220,15 @@ object FacilityHackParticipation {
|
||||||
private[participation] def populationProgressModifier(
|
private[participation] def populationProgressModifier(
|
||||||
populationNumbers: Seq[Int],
|
populationNumbers: Seq[Int],
|
||||||
gradingRule: Int=>Float,
|
gradingRule: Int=>Float,
|
||||||
layers: Int
|
layers: Int,
|
||||||
|
ordering: Ordering[Int] = Ordering[Int]
|
||||||
): Float = {
|
): Float = {
|
||||||
val gradedPopulation = populationNumbers
|
val gradedPopulation = populationNumbers
|
||||||
.map { gradingRule }
|
.map { gradingRule }
|
||||||
.groupBy(x => x)
|
.groupBy(x => x)
|
||||||
.values
|
.values
|
||||||
.toSeq
|
.toSeq
|
||||||
.sortBy(_.size)
|
.sortBy(_.size)(ordering)
|
||||||
.take(layers)
|
.take(layers)
|
||||||
.flatten
|
.flatten
|
||||||
gradedPopulation.sum / gradedPopulation.size.toFloat
|
gradedPopulation.sum / gradedPopulation.size.toFloat
|
||||||
|
|
@ -236,7 +237,8 @@ object FacilityHackParticipation {
|
||||||
private[participation] def populationBalanceModifier(
|
private[participation] def populationBalanceModifier(
|
||||||
victorPopulationNumbers: Seq[Int],
|
victorPopulationNumbers: Seq[Int],
|
||||||
opposingPopulationNumbers: Seq[Int],
|
opposingPopulationNumbers: Seq[Int],
|
||||||
healthyPercentage: Float
|
healthyPercentage: Float,
|
||||||
|
maxRatio: Float = 1f
|
||||||
): Float = {
|
): Float = {
|
||||||
val rate = for {
|
val rate = for {
|
||||||
victorPop <- victorPopulationNumbers
|
victorPop <- victorPopulationNumbers
|
||||||
|
|
@ -252,7 +254,7 @@ object FacilityHackParticipation {
|
||||||
}
|
}
|
||||||
if true
|
if true
|
||||||
} yield out
|
} yield out
|
||||||
rate.sum / rate.size.toFloat
|
math.max(0f, math.min(rate.sum / rate.size.toFloat, maxRatio))
|
||||||
}
|
}
|
||||||
|
|
||||||
private[participation] def competitionBonus(
|
private[participation] def competitionBonus(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import net.psforever.objects.serverobject.structures.{Building, StructureType}
|
||||||
import net.psforever.objects.sourcing.{PlayerSource, UniquePlayer}
|
import net.psforever.objects.sourcing.{PlayerSource, UniquePlayer}
|
||||||
import net.psforever.objects.zones.{HotSpotInfo, ZoneHotSpotProjector}
|
import net.psforever.objects.zones.{HotSpotInfo, ZoneHotSpotProjector}
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
|
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||||
import net.psforever.util.Config
|
import net.psforever.util.Config
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
|
|
@ -45,7 +45,7 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
||||||
import net.psforever.objects.zones.ZoneHotSpotProjector
|
import net.psforever.objects.zones.ZoneHotSpotProjector
|
||||||
|
|
||||||
import scala.concurrent.Promise
|
import scala.concurrent.Promise
|
||||||
import scala.util.Success
|
// import scala.util.Success
|
||||||
val requestLayers: Promise[ZoneHotSpotProjector.ExposedHeat] = Promise[ZoneHotSpotProjector.ExposedHeat]()
|
val requestLayers: Promise[ZoneHotSpotProjector.ExposedHeat] = Promise[ZoneHotSpotProjector.ExposedHeat]()
|
||||||
// val request = updateHotSpotInfoOnly()
|
// val request = updateHotSpotInfoOnly()
|
||||||
// requestLayers.completeWith(request)
|
// requestLayers.completeWith(request)
|
||||||
|
|
@ -67,204 +67,237 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
||||||
completionTime: Long,
|
completionTime: Long,
|
||||||
isResecured: Boolean
|
isResecured: Boolean
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val curr = System.currentTimeMillis()
|
//has the facility ran out of nanites during the hack
|
||||||
val hackStart = curr - completionTime
|
if (building.NtuLevel > 0) {
|
||||||
val socketOpt = building.GetFlagSocket
|
val curr = System.currentTimeMillis()
|
||||||
val (victorFaction, opposingFaction, hasFlag, flagCarrier) = if (!isResecured) {
|
val hackStart = curr - completionTime
|
||||||
val carrier = socketOpt.flatMap(_.previousFlag).flatMap(_.Carrier)
|
val socketOpt = building.GetFlagSocket
|
||||||
(attackingFaction, defenderFaction, socketOpt.nonEmpty, carrier)
|
val (victorFaction, opposingFaction, hasFlag, flagCarrier) = if (!isResecured) {
|
||||||
} else {
|
val carrier = socketOpt.flatMap(_.previousFlag).flatMap(_.Carrier)
|
||||||
(defenderFaction, attackingFaction, socketOpt.nonEmpty, None)
|
(attackingFaction, defenderFaction, socketOpt.nonEmpty, carrier)
|
||||||
}
|
} else {
|
||||||
val (contributionVictor, contributionOpposing, _) = {
|
(defenderFaction, attackingFaction, socketOpt.nonEmpty, None)
|
||||||
val (a, b1) = playerContribution.partition { case (_, (p, _, _)) => p.Faction == victorFaction }
|
|
||||||
val (b, c) = b1.partition { case (_, (p, _, _)) => p.Faction == opposingFaction }
|
|
||||||
(a.values, b.values, c.values)
|
|
||||||
}
|
|
||||||
val contributionVictorSize = contributionVictor.size
|
|
||||||
if (contributionVictorSize > 0) {
|
|
||||||
//setup for ...
|
|
||||||
val populationIndices = playerPopulationOverTime.indices
|
|
||||||
val allFactions = PlanetSideEmpire.values.filterNot { _ == PlanetSideEmpire.NEUTRAL }.toSeq
|
|
||||||
val (victorPopulationByLayer, opposingPopulationByLayer) = {
|
|
||||||
val individualPopulationByLayer = allFactions.map { f =>
|
|
||||||
(f, populationIndices.indices.map { i => playerPopulationOverTime(i)(f) })
|
|
||||||
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
|
||||||
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
|
||||||
}
|
}
|
||||||
val contributionOpposingSize = contributionOpposing.size
|
val (contributionVictor, contributionOpposing, _) = {
|
||||||
val killsByPlayersNotInTower = eliminateClosestTowerFromParticipating(
|
val (a, b1) = playerContribution.partition { case (_, (p, _, _)) => p.Faction == victorFaction }
|
||||||
building,
|
val (b, c) = b1.partition { case (_, (p, _, _)) => p.Faction == opposingFaction }
|
||||||
FacilityHackParticipation.allocateKillsByPlayers(
|
(a.values, b.values, c.values)
|
||||||
building.Position,
|
}
|
||||||
building.Definition.SOIRadius.toFloat,
|
val contributionVictorSize = contributionVictor.size
|
||||||
hackStart,
|
if (contributionVictorSize > 0) {
|
||||||
completionTime,
|
//setup for ...
|
||||||
opposingFaction,
|
val populationIndices = playerPopulationOverTime.indices
|
||||||
contributionOpposing
|
val allFactions = PlanetSideEmpire.values.filterNot {
|
||||||
)
|
_ == PlanetSideEmpire.NEUTRAL
|
||||||
)
|
}.toSeq
|
||||||
//1) experience from killing opposingFaction across duration of hack
|
val (victorPopulationByLayer, opposingPopulationByLayer) = {
|
||||||
//The kills that occurred in the facility's attached field tower's sphere of influence have been eliminated from consideration.
|
val individualPopulationByLayer = allFactions.map { f =>
|
||||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
(f, populationIndices.indices.map { i => playerPopulationOverTime(i)(f) })
|
||||||
killsByPlayersNotInTower,
|
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
||||||
contributionOpposingSize
|
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
||||||
)
|
|
||||||
//2) peak population modifier
|
|
||||||
//Large facility battles should be well-rewarded.
|
|
||||||
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
|
||||||
opposingPopulationByLayer,
|
|
||||||
{ pop =>
|
|
||||||
if (pop > 75) 0.9f
|
|
||||||
else if (pop > 59) 0.6f
|
|
||||||
else if (pop > 29) 0.55f
|
|
||||||
else if (pop > 25) 0.5f
|
|
||||||
else 0.45f
|
|
||||||
},
|
|
||||||
4
|
|
||||||
)
|
|
||||||
//3) competition multiplier
|
|
||||||
val competitionMultiplier: Float = {
|
|
||||||
val populationBalanceModifier: Float = FacilityHackParticipation.populationBalanceModifier(
|
|
||||||
victorPopulationByLayer,
|
|
||||||
opposingPopulationByLayer,
|
|
||||||
healthyPercentage = 1.5f
|
|
||||||
)
|
|
||||||
//compensate for heat
|
|
||||||
val regionHeatMapProgression = {
|
|
||||||
/*
|
|
||||||
transform the different layers of the facility heat map timeline into a progressing timeline of regional hotspot information;
|
|
||||||
where the grouping are of simultaneous hotspots,
|
|
||||||
the letter indicates a unique hotspot,
|
|
||||||
and the number an identifier between related hotspots:
|
|
||||||
((A-1, B-2, C-3), (D-1, E-2, F-3), (G-1, H-2, I-3)) ... (1->(A, D, G), 2->(B, E, H), 3->(C, F, I))
|
|
||||||
*/
|
|
||||||
val finalMap = mutable.HashMap[Vector3, Map[PlanetSideEmpire.Value, Seq[Long]]]()
|
|
||||||
.addAll(
|
|
||||||
hotSpotLayersOverTime.flatMap { entry =>
|
|
||||||
entry.map { f => (f.DisplayLocation, Map.empty[PlanetSideEmpire.Value, Seq[Long]]) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
//note: this pre-seeding of keys allows us to skip a getOrElse call in the foldLeft
|
|
||||||
hotSpotLayersOverTime.foldLeft(finalMap) { (map, list) =>
|
|
||||||
list.foreach { entry =>
|
|
||||||
val key = entry.DisplayLocation
|
|
||||||
val newValues = entry.Activity.map { case (f, e) => (f, e.Heat.toLong) }
|
|
||||||
val combinedValues = map(key).map { case (f, e) => (f, e :+ newValues(f)) }
|
|
||||||
map.put(key, combinedValues)
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}.toMap
|
|
||||||
finalMap //explicit for no good reason
|
|
||||||
}
|
}
|
||||||
val heatMapModifier = FacilityHackParticipation.heatMapComparison(
|
val contributionOpposingSize = contributionOpposing.size
|
||||||
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, victorFaction).values,
|
val killsByPlayersNotInTower = eliminateClosestTowerFromParticipating(
|
||||||
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, opposingFaction).values
|
building,
|
||||||
|
FacilityHackParticipation.allocateKillsByPlayers(
|
||||||
|
building.Position,
|
||||||
|
building.Definition.SOIRadius.toFloat,
|
||||||
|
hackStart,
|
||||||
|
completionTime,
|
||||||
|
opposingFaction,
|
||||||
|
contributionOpposing
|
||||||
|
)
|
||||||
)
|
)
|
||||||
heatMapModifier * populationBalanceModifier
|
//1) experience from killing opposingFaction across duration of hack
|
||||||
}
|
//The kills that occurred in the facility's attached field tower's sphere of influence have been eliminated from consideration.
|
||||||
//4) hack time modifier
|
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||||
//Captured major facilities without a lattice link unit and resecured major facilities with a lattice link unit
|
killsByPlayersNotInTower,
|
||||||
// incur the full hack time if the module is not transported to a friendly facility
|
contributionOpposingSize
|
||||||
//Captured major facilities with a lattice link unit and resecure major facilities without a lattice link uit
|
)
|
||||||
// will incur an abbreviated duration
|
val events = building.Zone.AvatarEvents
|
||||||
val overallTimeMultiplier: Float = {
|
val buildingId = building.GUID.guid
|
||||||
if (
|
val zoneNumber = building.Zone.Number
|
||||||
building.Faction == PlanetSideEmpire.NEUTRAL ||
|
val playersInSoi = building.PlayersInSOI.filter {
|
||||||
building.NtuLevel == 0 ||
|
_.Faction == victorFaction
|
||||||
building.Generator.map { _.Condition }.contains(PlanetSideGeneratorState.Destroyed)
|
}
|
||||||
) { //the facility ran out of nanites or power during the hack or became neutral
|
if (baseExperienceFromFacilityCapture > 0) {
|
||||||
0f
|
//2) population modifier
|
||||||
} else if (hasFlag) {
|
//The value of the first should grow as population grows.
|
||||||
if (completionTime >= hackTime) { //hack timed out without llu delivery
|
//This is an intentionally imperfect counterbalance to that growth.
|
||||||
0.25f
|
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
||||||
} else if (isResecured) {
|
opposingPopulationByLayer,
|
||||||
0.5f + (if (hackTime <= completionTime * 0.3f) {
|
{ pop =>
|
||||||
completionTime.toFloat / hackTime.toFloat
|
if (pop > 75) 0.5f
|
||||||
} else if (hackTime >= completionTime * 0.6f) {
|
else if (pop > 59) 0.6f
|
||||||
(hackTime - completionTime).toFloat / hackTime.toFloat
|
else if (pop > 29) 0.7f
|
||||||
|
else if (pop > 19) 0.75f
|
||||||
|
else 0.8f
|
||||||
|
},
|
||||||
|
4
|
||||||
|
)
|
||||||
|
//3) competition multiplier
|
||||||
|
val competitionMultiplier: Float = {
|
||||||
|
val populationBalanceModifier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||||
|
victorPopulationByLayer,
|
||||||
|
opposingPopulationByLayer,
|
||||||
|
healthyPercentage = 1.5f,
|
||||||
|
maxRatio = 2.0f
|
||||||
|
)
|
||||||
|
//compensate for heat
|
||||||
|
val regionHeatMapProgression = {
|
||||||
|
/*
|
||||||
|
transform the different layers of the facility heat map timeline into a progressing timeline of regional hotspot information;
|
||||||
|
where the grouping are of simultaneous hotspots,
|
||||||
|
the letter indicates a unique hotspot,
|
||||||
|
and the number an identifier between related hotspots:
|
||||||
|
((A-1, B-2, C-3), (D-1, E-2, F-3), (G-1, H-2, I-3)) ... (1->(A, D, G), 2->(B, E, H), 3->(C, F, I))
|
||||||
|
*/
|
||||||
|
val finalMap = mutable.HashMap[Vector3, Map[PlanetSideEmpire.Value, Seq[Long]]]()
|
||||||
|
.addAll(
|
||||||
|
hotSpotLayersOverTime.flatMap { entry =>
|
||||||
|
entry.map { f => (f.DisplayLocation, Map.empty[PlanetSideEmpire.Value, Seq[Long]]) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
//note: this pre-seeding of keys allows us to skip a getOrElse call in the foldLeft
|
||||||
|
hotSpotLayersOverTime.foldLeft(finalMap) { (map, list) =>
|
||||||
|
list.foreach { entry =>
|
||||||
|
val key = entry.DisplayLocation
|
||||||
|
val newValues = entry.Activity.map { case (f, e) => (f, e.Heat.toLong) }
|
||||||
|
val combinedValues = map(key).map { case (f, e) => (f, e :+ newValues(f)) }
|
||||||
|
map.put(key, combinedValues)
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}.toMap
|
||||||
|
finalMap //explicit for no good reason
|
||||||
|
}
|
||||||
|
val heatMapModifier = FacilityHackParticipation.heatMapComparison(
|
||||||
|
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, victorFaction).values,
|
||||||
|
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, opposingFaction).values
|
||||||
|
)
|
||||||
|
heatMapModifier * populationBalanceModifier
|
||||||
|
}
|
||||||
|
//4) hack time modifier
|
||||||
|
//Captured major facilities without a lattice link unit and resecured major facilities with a lattice link unit
|
||||||
|
// incur the full hack time if the module is not transported to a friendly facility
|
||||||
|
//Captured major facilities with a lattice link unit and resecure major facilities without a lattice link unit
|
||||||
|
// will incur an abbreviated duration
|
||||||
|
val overallTimeMultiplier: Float = {
|
||||||
|
if (hasFlag) {
|
||||||
|
if (completionTime >= hackTime) { //hack timed out without llu delivery
|
||||||
|
0.5f
|
||||||
|
} else if (isResecured) {
|
||||||
|
0.5f + (if (hackTime <= completionTime * 0.3f) {
|
||||||
|
completionTime.toFloat / hackTime.toFloat
|
||||||
|
} else if (hackTime >= completionTime * 0.6f) {
|
||||||
|
(hackTime - completionTime).toFloat / hackTime.toFloat
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
0f
|
if (isResecured) {
|
||||||
})
|
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||||
} else {
|
} else {
|
||||||
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
0.5f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//5. individual contribution factors - by time
|
||||||
|
val contributionPerPlayerByTime = playerContribution.collect {
|
||||||
|
case (a, (_, d, t)) if d >= 600000 && math.abs(completionTime - t) < 5000 =>
|
||||||
|
(a, 0.65f)
|
||||||
|
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||||
|
(a, 0.25f + (d.toFloat / 1800000f))
|
||||||
|
case (a, (_, _, _)) =>
|
||||||
|
(a, 0.25f)
|
||||||
|
}
|
||||||
|
//6. competition bonus
|
||||||
|
//This value will probably suck, and that's fine.
|
||||||
|
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
||||||
|
contributionVictorSize,
|
||||||
|
contributionOpposingSize,
|
||||||
|
steamrollPercentage = 1.25f,
|
||||||
|
steamrollBonus = 5L,
|
||||||
|
overwhelmingOddsPercentage = 0.5f,
|
||||||
|
overwhelmingOddsBonus = 15L
|
||||||
|
)
|
||||||
|
//7. calculate overall command experience points
|
||||||
|
val finalCep: Long = math.ceil(
|
||||||
|
math.max(0L, baseExperienceFromFacilityCapture) *
|
||||||
|
populationModifier *
|
||||||
|
competitionMultiplier *
|
||||||
|
overallTimeMultiplier *
|
||||||
|
Config.app.game.experience.cep.rate + competitionBonus
|
||||||
|
).toLong
|
||||||
|
//8. reward participants
|
||||||
|
//Classically, only players in the SOI are rewarded, and the llu runner too
|
||||||
|
val hackerId = hacker.CharId
|
||||||
|
//terminal hacker (always cep)
|
||||||
|
if (playersInSoi.exists(_.CharId == hackerId) && flagCarrier.map(_.CharId).getOrElse(0L) != hackerId) {
|
||||||
|
ToDatabase.reportFacilityCapture(
|
||||||
|
hackerId,
|
||||||
|
zoneNumber,
|
||||||
|
buildingId,
|
||||||
|
finalCep,
|
||||||
|
expType = "cep"
|
||||||
|
)
|
||||||
|
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hackerId, finalCep))
|
||||||
|
}
|
||||||
|
//bystanders (cep if squad leader, bep otherwise)
|
||||||
|
playersInSoi
|
||||||
|
.filterNot { _.CharId == hackerId }
|
||||||
|
.foreach { player =>
|
||||||
|
val charId = player.CharId
|
||||||
|
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(charId, 1f)
|
||||||
|
val outputValue = (finalCep * contributionMultiplier).toLong
|
||||||
|
events ! AvatarServiceMessage(player.Name, AvatarAction.FacilityCaptureRewards(buildingId, zoneNumber, outputValue))
|
||||||
|
}
|
||||||
|
//flag carrier (won't be in soi, but earns cep from capture)
|
||||||
|
flagCarrier.collect {
|
||||||
|
case player if !isResecured =>
|
||||||
|
val charId: Long = player.CharId
|
||||||
|
val finalModifiedCep: Long = {
|
||||||
|
val durationPoints: Long = (hackTime - completionTime) / 1500L
|
||||||
|
val betterDurationPoints: Long = if (durationPoints >= 200L) {
|
||||||
|
durationPoints
|
||||||
|
} else {
|
||||||
|
200L + durationPoints
|
||||||
|
}
|
||||||
|
math.min(
|
||||||
|
betterDurationPoints,
|
||||||
|
(finalCep * Config.app.game.experience.cep.lluCarrierModifier).toLong
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ToDatabase.reportFacilityCapture(
|
||||||
|
charId,
|
||||||
|
zoneNumber,
|
||||||
|
buildingId,
|
||||||
|
finalModifiedCep,
|
||||||
|
expType = "llu"
|
||||||
|
)
|
||||||
|
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(charId, finalModifiedCep))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isResecured) {
|
//no need to calculate a fancy score
|
||||||
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
val hackerId = hacker.CharId
|
||||||
} else {
|
val hackerScore = List((hackerId, 0L, "cep"))
|
||||||
0.5f
|
ToDatabase.reportFacilityCaptureInBulk(
|
||||||
}
|
if (isResecured) {
|
||||||
}
|
hackerScore
|
||||||
}
|
|
||||||
//5. individual contribution factors - by time
|
|
||||||
val contributionPerPlayerByTime = playerContribution.collect {
|
|
||||||
case (a, (_, d, t)) if d >= 600000 && math.abs(completionTime - t) < 5000 =>
|
|
||||||
(a, 0.45f)
|
|
||||||
case (a, (_, d, _)) if d >= 600000 =>
|
|
||||||
(a, 0.25f)
|
|
||||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
|
||||||
(a, 0.25f * (0.5f + (d.toFloat / 600000f)))
|
|
||||||
case (a, (_, _, _)) =>
|
|
||||||
(a, 0.15f)
|
|
||||||
}
|
|
||||||
//6. competition bonus
|
|
||||||
//This value will probably suck, and that's fine.
|
|
||||||
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
|
||||||
contributionVictorSize,
|
|
||||||
contributionOpposingSize,
|
|
||||||
steamrollPercentage = 1.25f,
|
|
||||||
steamrollBonus = 5L,
|
|
||||||
overwhelmingOddsPercentage = 0.5f,
|
|
||||||
overwhelmingOddsBonus = 15L
|
|
||||||
)
|
|
||||||
//7. calculate overall command experience points
|
|
||||||
val finalCep: Long = math.ceil(
|
|
||||||
math.max(0L, baseExperienceFromFacilityCapture) *
|
|
||||||
populationModifier *
|
|
||||||
competitionMultiplier *
|
|
||||||
overallTimeMultiplier *
|
|
||||||
Config.app.game.experience.cep.rate + competitionBonus
|
|
||||||
).toLong
|
|
||||||
//8. reward participants
|
|
||||||
//Classically, only players in the SOI are rewarded, and the llu runner too
|
|
||||||
val hackerId = hacker.CharId
|
|
||||||
val events = building.Zone.AvatarEvents
|
|
||||||
val playersInSoi = building.PlayersInSOI
|
|
||||||
if (playersInSoi.exists(_.CharId == hackerId) && flagCarrier.map(_.CharId).getOrElse(0L) != hackerId) {
|
|
||||||
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hackerId, finalCep))
|
|
||||||
}
|
|
||||||
playersInSoi
|
|
||||||
.filter { player => player.Faction == victorFaction && player.CharId != hackerId }
|
|
||||||
.foreach { player =>
|
|
||||||
val charId = player.CharId
|
|
||||||
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(charId, 1f)
|
|
||||||
val outputValue = (finalCep * contributionMultiplier).toLong
|
|
||||||
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(0, outputValue))
|
|
||||||
}
|
|
||||||
flagCarrier.collect {
|
|
||||||
case player if !isResecured =>
|
|
||||||
val charId: Long = player.CharId
|
|
||||||
val finalModifiedCep: Long = {
|
|
||||||
val durationPoints: Long = (hackTime - completionTime) / 1500L
|
|
||||||
val betterDurationPoints: Long = if (durationPoints >= 200L) {
|
|
||||||
durationPoints
|
|
||||||
} else {
|
} else {
|
||||||
200L + durationPoints
|
val flagCarrierScore = flagCarrier.map (p => List((p.CharId, 0L, "llu"))).getOrElse(Nil)
|
||||||
}
|
if (playersInSoi.exists(_.CharId == hackerId) && !flagCarrierScore.exists { case (charId, _,_) => charId == hackerId }) {
|
||||||
math.min(
|
hackerScore ++ flagCarrierScore
|
||||||
betterDurationPoints,
|
} else {
|
||||||
(finalCep * Config.app.game.experience.cep.lluCarrierModifier).toLong
|
flagCarrierScore
|
||||||
)
|
}
|
||||||
}
|
} ++ playersInSoi.filterNot { p => p.CharId == hackerId }.map(p => (p.CharId, 0L, "bep")),
|
||||||
ToDatabase.reportFacilityCapture(
|
zoneNumber,
|
||||||
charId,
|
buildingId
|
||||||
building.Zone.Number,
|
|
||||||
building.GUID.guid,
|
|
||||||
finalModifiedCep,
|
|
||||||
expType="llu"
|
|
||||||
)
|
)
|
||||||
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(charId, finalModifiedCep))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.structures.participation
|
||||||
|
|
||||||
import net.psforever.objects.serverobject.structures.Building
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
import net.psforever.objects.sourcing.PlayerSource
|
import net.psforever.objects.sourcing.PlayerSource
|
||||||
|
import net.psforever.objects.zones.exp.ToDatabase
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||||
import net.psforever.util.Config
|
import net.psforever.util.Config
|
||||||
|
|
@ -38,21 +39,15 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
||||||
}
|
}
|
||||||
val contributionVictorSize = contributionVictor.size
|
val contributionVictorSize = contributionVictor.size
|
||||||
if (contributionVictorSize > 0) {
|
if (contributionVictorSize > 0) {
|
||||||
//setup for ...
|
//early setup ...
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
val curr = System.currentTimeMillis()
|
val curr = System.currentTimeMillis()
|
||||||
|
val soiPlayers = building.PlayersInSOI.filter { _.Faction == victorFaction }
|
||||||
val contributionOpposingSize = contributionOpposing.size
|
val contributionOpposingSize = contributionOpposing.size
|
||||||
val populationIndices = playerPopulationOverTime.indices
|
val events = building.Zone.AvatarEvents
|
||||||
val allFactions = PlanetSideEmpire.values.filterNot {
|
val buildingId = building.GUID.guid
|
||||||
_ == PlanetSideEmpire.NEUTRAL
|
val zoneNumber = building.Zone.Number
|
||||||
}.toSeq
|
val hackerId = hacker.CharId
|
||||||
val (victorPopulationByLayer, opposingPopulationByLayer) = {
|
|
||||||
val individualPopulationByLayer = allFactions.map { f =>
|
|
||||||
(f, populationIndices.indices.map { i => playerPopulationOverTime(i)(f) })
|
|
||||||
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
|
||||||
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
|
||||||
}
|
|
||||||
val soiPlayers = building.PlayersInSOI
|
|
||||||
//1) experience from killing opposingFaction
|
//1) experience from killing opposingFaction
|
||||||
//Because the hack duration of towers is instantaneous, the prior period of five minutes is artificially selected.
|
//Because the hack duration of towers is instantaneous, the prior period of five minutes is artificially selected.
|
||||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||||
|
|
@ -66,93 +61,115 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
||||||
),
|
),
|
||||||
contributionOpposingSize
|
contributionOpposingSize
|
||||||
)
|
)
|
||||||
//2) peak population modifier
|
//based on this math, the optimal number of enemy for experience gain is 20
|
||||||
//Towers should not be regarded as major battles.
|
//max value of: 1000 * pop * max(0, (40 - pop)) * 0.1
|
||||||
//As the population rises, the rewards decrease (dramatically).
|
if (baseExperienceFromFacilityCapture > 0) {
|
||||||
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
//more setup ...
|
||||||
victorPopulationByLayer,
|
val populationIndices = playerPopulationOverTime.indices
|
||||||
{ pop =>
|
val allFactions = PlanetSideEmpire.values.filterNot {
|
||||||
if (pop > 80) 0f
|
_ == PlanetSideEmpire.NEUTRAL
|
||||||
else if (pop > 39) (80 - pop).toFloat * 0.01f
|
}.toSeq
|
||||||
else if (pop > 25) 0.5f
|
val (victorPopulationByLayer, opposingPopulationByLayer) = {
|
||||||
else if (pop > 19) 0.55f
|
val individualPopulationByLayer = allFactions.map { f =>
|
||||||
else if (pop > 9) 0.6f
|
(f, populationIndices.indices.map { i => playerPopulationOverTime(i)(f) })
|
||||||
else if (pop > 5) 0.75f
|
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
||||||
else 1f
|
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
||||||
},
|
}
|
||||||
2
|
//2) peak population modifier
|
||||||
)
|
//Towers should not be regarded as major battles.
|
||||||
//3) competition multiplier
|
//As the population rises, the rewards decrease (dramatically).
|
||||||
val competitionMultiplier: Float = FacilityHackParticipation.populationBalanceModifier(
|
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
||||||
victorPopulationByLayer,
|
victorPopulationByLayer,
|
||||||
opposingPopulationByLayer,
|
{ pop =>
|
||||||
healthyPercentage = 1.25f
|
if (pop > 40) 0.075f
|
||||||
)
|
else if (pop > 8) (40 - pop).toFloat * 0.1f
|
||||||
//4a. individual contribution factors - by time
|
else 1f
|
||||||
//Once again, an arbitrary five minute period.
|
},
|
||||||
val contributionPerPlayerByTime = playerContribution.collect {
|
2
|
||||||
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
|
)
|
||||||
(a, 0.45f)
|
//3) competition multiplier
|
||||||
case (a, (_, d, _)) if d >= 300000 =>
|
val competitionMultiplier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||||
(a, 0.25f)
|
victorPopulationByLayer,
|
||||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
opposingPopulationByLayer,
|
||||||
(a, 0.25f * (0.5f + (d.toFloat / 300000f)))
|
healthyPercentage = 1.25f
|
||||||
case (a, (_, _, _)) =>
|
)
|
||||||
(a, 0.15f)
|
//4a. individual contribution factors - by time
|
||||||
}
|
//Once again, an arbitrary five minute period.
|
||||||
//4b. individual contribution factors - by distance to goal (secondary_capture)
|
val contributionPerPlayerByTime = playerContribution.collect {
|
||||||
//Because the hack duration of towers is instantaneous, distance from terminal is a more important factor
|
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
|
||||||
val contributionPerPlayerByDistanceFromGoal = {
|
(a, 0.75f)
|
||||||
var minDistance: Float = Float.PositiveInfinity
|
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||||
val location = building
|
(a, 0.15f + (d.toFloat / 600000f))
|
||||||
.CaptureTerminal
|
case (a, (_, _, _)) =>
|
||||||
.map { terminal => terminal.Position }
|
(a, 0.15f)
|
||||||
.getOrElse { hacker.Position }
|
}
|
||||||
|
//4b. individual contribution factors - by distance to goal (secondary_capture)
|
||||||
|
//Because the hack duration of towers is instantaneous, distance from terminal is a more important factor
|
||||||
|
val contributionPerPlayerByDistanceFromGoal = {
|
||||||
|
var minDistance: Float = Float.PositiveInfinity
|
||||||
|
val location = building
|
||||||
|
.CaptureTerminal
|
||||||
|
.map { terminal => terminal.Position }
|
||||||
|
.getOrElse { hacker.Position }
|
||||||
|
soiPlayers
|
||||||
|
.map { p =>
|
||||||
|
val distance = Vector3.Distance(p.Position, location)
|
||||||
|
minDistance = math.min(minDistance, distance)
|
||||||
|
(p.CharId, distance)
|
||||||
|
}
|
||||||
|
.map { case (id, distance) =>
|
||||||
|
(id, math.max(0.25f, minDistance / distance))
|
||||||
|
}
|
||||||
|
}.toMap[Long, Float]
|
||||||
|
//5) token competition bonus
|
||||||
|
//This value will probably suck, and that's fine.
|
||||||
|
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
||||||
|
contributionVictorSize,
|
||||||
|
contributionOpposingSize,
|
||||||
|
steamrollPercentage = 1.25f,
|
||||||
|
steamrollBonus = 2L,
|
||||||
|
overwhelmingOddsPercentage = 0.5f,
|
||||||
|
overwhelmingOddsBonus = 30L
|
||||||
|
)
|
||||||
|
//6. calculate overall command experience points
|
||||||
|
val finalCep: Long = math.ceil(
|
||||||
|
baseExperienceFromFacilityCapture *
|
||||||
|
populationModifier *
|
||||||
|
competitionMultiplier *
|
||||||
|
Config.app.game.experience.cep.rate + competitionBonus
|
||||||
|
).toLong
|
||||||
|
//7. reward participants
|
||||||
|
//Classically, only players in the SOI are rewarded
|
||||||
|
//terminal hacker (always cep)
|
||||||
|
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hacker.CharId, finalCep))
|
||||||
|
ToDatabase.reportFacilityCapture(
|
||||||
|
hackerId,
|
||||||
|
zoneNumber,
|
||||||
|
buildingId,
|
||||||
|
finalCep,
|
||||||
|
expType = "cep"
|
||||||
|
)
|
||||||
|
//bystanders (cep if squad leader, bep otherwise)
|
||||||
soiPlayers
|
soiPlayers
|
||||||
.map { p =>
|
.filterNot(_.CharId == hackerId)
|
||||||
val distance = Vector3.Distance(p.Position, location)
|
.foreach { player =>
|
||||||
minDistance = math.min(minDistance, distance)
|
val charId = player.CharId
|
||||||
(p.CharId, distance)
|
val contributionTimeMultiplier = contributionPerPlayerByTime.getOrElse(charId, 0.5f)
|
||||||
|
val contributionDistanceMultiplier = contributionPerPlayerByDistanceFromGoal.getOrElse(charId, 0.5f)
|
||||||
|
val outputValue = (finalCep * contributionTimeMultiplier * contributionDistanceMultiplier).toLong
|
||||||
|
events ! AvatarServiceMessage(
|
||||||
|
player.Name,
|
||||||
|
AvatarAction.FacilityCaptureRewards(buildingId, zoneNumber, outputValue)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.map { case (id, distance) =>
|
} else {
|
||||||
(id, math.max(0.15f, minDistance / distance))
|
//no need to calculate a fancy score
|
||||||
}
|
ToDatabase.reportFacilityCaptureInBulk(
|
||||||
}.toMap[Long, Float]
|
(hackerId, 0L, "cep") +: soiPlayers.filterNot(_.CharId == hackerId).map(p => (p.CharId, 0L, "bep")),
|
||||||
//5) token competition bonus
|
zoneNumber,
|
||||||
//This value will probably suck, and that's fine.
|
buildingId
|
||||||
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
)
|
||||||
contributionVictorSize,
|
}
|
||||||
contributionOpposingSize,
|
|
||||||
steamrollPercentage = 1.25f,
|
|
||||||
steamrollBonus = 2L,
|
|
||||||
overwhelmingOddsPercentage = 0.5f,
|
|
||||||
overwhelmingOddsBonus = 30L
|
|
||||||
)
|
|
||||||
//6. calculate overall command experience points
|
|
||||||
val finalCep: Long = math.ceil(
|
|
||||||
baseExperienceFromFacilityCapture *
|
|
||||||
populationModifier *
|
|
||||||
competitionMultiplier *
|
|
||||||
Config.app.game.experience.cep.rate + competitionBonus
|
|
||||||
).toLong
|
|
||||||
//7. reward participants
|
|
||||||
//Classically, only players in the SOI are rewarded
|
|
||||||
val events = building.Zone.AvatarEvents
|
|
||||||
soiPlayers
|
|
||||||
.filter { player =>
|
|
||||||
player.Faction == victorFaction && player.CharId != hacker.CharId
|
|
||||||
}
|
|
||||||
.foreach { player =>
|
|
||||||
val charId = player.CharId
|
|
||||||
val contributionTimeMultiplier = contributionPerPlayerByTime.getOrElse(charId, 0.5f)
|
|
||||||
val contributionDistanceMultiplier = contributionPerPlayerByDistanceFromGoal.getOrElse(charId, 0.5f)
|
|
||||||
val outputValue = (finalCep * contributionTimeMultiplier * contributionDistanceMultiplier).toLong
|
|
||||||
events ! AvatarServiceMessage(
|
|
||||||
player.Name,
|
|
||||||
AvatarAction.AwardCep(0, outputValue)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hacker.CharId, finalCep))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
playerContribution.clear()
|
playerContribution.clear()
|
||||||
|
|
|
||||||
|
|
@ -214,4 +214,19 @@ object ToDatabase {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert multiple entries into the database's `buildingCapture` table as a single transaction.
|
||||||
|
*/
|
||||||
|
def reportFacilityCaptureInBulk(
|
||||||
|
avatarIdAndExp: List[(Long, Long, String)],
|
||||||
|
zoneId: Int,
|
||||||
|
buildingId: Int
|
||||||
|
): Unit = {
|
||||||
|
ctx.run(quote { liftQuery(
|
||||||
|
avatarIdAndExp.map { case (avatarId, exp, expType) =>
|
||||||
|
persistence.Buildingcapture(-1, avatarId, zoneId, buildingId, exp, expType)
|
||||||
|
}
|
||||||
|
)}.foreach(e => query[persistence.Buildingcapture].insertValue(e)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue