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:
Fate-JH 2023-11-21 02:38:44 -05:00
parent cc2089b513
commit 42d1422fc7
6 changed files with 503 additions and 428 deletions

View file

@ -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,6 +3084,7 @@ class AvatarActor(
experience: Long, experience: Long,
modifier: Float modifier: Float
): Unit = { ): Unit = {
if (experience > 0) {
if (experienceDebt == 0L) { if (experienceDebt == 0L) {
awardAction(experience) awardAction(experience)
} else if (modifier > 0f) { } else if (modifier > 0f) {
@ -3098,9 +3099,10 @@ class AvatarActor(
} }
} }
} }
}
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 = {

View file

@ -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 {

View file

@ -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(

View file

@ -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,6 +67,8 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
completionTime: Long, completionTime: Long,
isResecured: Boolean isResecured: Boolean
): Unit = { ): Unit = {
//has the facility ran out of nanites during the hack
if (building.NtuLevel > 0) {
val curr = System.currentTimeMillis() val curr = System.currentTimeMillis()
val hackStart = curr - completionTime val hackStart = curr - completionTime
val socketOpt = building.GetFlagSocket val socketOpt = building.GetFlagSocket
@ -85,7 +87,9 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
if (contributionVictorSize > 0) { if (contributionVictorSize > 0) {
//setup for ... //setup for ...
val populationIndices = playerPopulationOverTime.indices val populationIndices = playerPopulationOverTime.indices
val allFactions = PlanetSideEmpire.values.filterNot { _ == PlanetSideEmpire.NEUTRAL }.toSeq val allFactions = PlanetSideEmpire.values.filterNot {
_ == PlanetSideEmpire.NEUTRAL
}.toSeq
val (victorPopulationByLayer, opposingPopulationByLayer) = { val (victorPopulationByLayer, opposingPopulationByLayer) = {
val individualPopulationByLayer = allFactions.map { f => val individualPopulationByLayer = allFactions.map { f =>
(f, populationIndices.indices.map { i => playerPopulationOverTime(i)(f) }) (f, populationIndices.indices.map { i => playerPopulationOverTime(i)(f) })
@ -110,16 +114,24 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
killsByPlayersNotInTower, killsByPlayersNotInTower,
contributionOpposingSize contributionOpposingSize
) )
//2) peak population modifier val events = building.Zone.AvatarEvents
//Large facility battles should be well-rewarded. val buildingId = building.GUID.guid
val zoneNumber = building.Zone.Number
val playersInSoi = building.PlayersInSOI.filter {
_.Faction == victorFaction
}
if (baseExperienceFromFacilityCapture > 0) {
//2) population modifier
//The value of the first should grow as population grows.
//This is an intentionally imperfect counterbalance to that growth.
val populationModifier = FacilityHackParticipation.populationProgressModifier( val populationModifier = FacilityHackParticipation.populationProgressModifier(
opposingPopulationByLayer, opposingPopulationByLayer,
{ pop => { pop =>
if (pop > 75) 0.9f if (pop > 75) 0.5f
else if (pop > 59) 0.6f else if (pop > 59) 0.6f
else if (pop > 29) 0.55f else if (pop > 29) 0.7f
else if (pop > 25) 0.5f else if (pop > 19) 0.75f
else 0.45f else 0.8f
}, },
4 4
) )
@ -128,7 +140,8 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
val populationBalanceModifier: Float = FacilityHackParticipation.populationBalanceModifier( val populationBalanceModifier: Float = FacilityHackParticipation.populationBalanceModifier(
victorPopulationByLayer, victorPopulationByLayer,
opposingPopulationByLayer, opposingPopulationByLayer,
healthyPercentage = 1.5f healthyPercentage = 1.5f,
maxRatio = 2.0f
) )
//compensate for heat //compensate for heat
val regionHeatMapProgression = { val regionHeatMapProgression = {
@ -166,18 +179,12 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
//4) hack time modifier //4) hack time modifier
//Captured major facilities without a lattice link unit and resecured major facilities with a lattice link unit //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 // 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 uit //Captured major facilities with a lattice link unit and resecure major facilities without a lattice link unit
// will incur an abbreviated duration // will incur an abbreviated duration
val overallTimeMultiplier: Float = { val overallTimeMultiplier: Float = {
if ( if (hasFlag) {
building.Faction == PlanetSideEmpire.NEUTRAL ||
building.NtuLevel == 0 ||
building.Generator.map { _.Condition }.contains(PlanetSideGeneratorState.Destroyed)
) { //the facility ran out of nanites or power during the hack or became neutral
0f
} else if (hasFlag) {
if (completionTime >= hackTime) { //hack timed out without llu delivery if (completionTime >= hackTime) { //hack timed out without llu delivery
0.25f 0.5f
} else if (isResecured) { } else if (isResecured) {
0.5f + (if (hackTime <= completionTime * 0.3f) { 0.5f + (if (hackTime <= completionTime * 0.3f) {
completionTime.toFloat / hackTime.toFloat completionTime.toFloat / hackTime.toFloat
@ -200,13 +207,11 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
//5. individual contribution factors - by time //5. individual contribution factors - by time
val contributionPerPlayerByTime = playerContribution.collect { val contributionPerPlayerByTime = playerContribution.collect {
case (a, (_, d, t)) if d >= 600000 && math.abs(completionTime - t) < 5000 => case (a, (_, d, t)) if d >= 600000 && math.abs(completionTime - t) < 5000 =>
(a, 0.45f) (a, 0.65f)
case (a, (_, d, _)) if d >= 600000 =>
(a, 0.25f)
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 => case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
(a, 0.25f * (0.5f + (d.toFloat / 600000f))) (a, 0.25f + (d.toFloat / 1800000f))
case (a, (_, _, _)) => case (a, (_, _, _)) =>
(a, 0.15f) (a, 0.25f)
} }
//6. competition bonus //6. competition bonus
//This value will probably suck, and that's fine. //This value will probably suck, and that's fine.
@ -229,19 +234,27 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
//8. reward participants //8. reward participants
//Classically, only players in the SOI are rewarded, and the llu runner too //Classically, only players in the SOI are rewarded, and the llu runner too
val hackerId = hacker.CharId val hackerId = hacker.CharId
val events = building.Zone.AvatarEvents //terminal hacker (always cep)
val playersInSoi = building.PlayersInSOI
if (playersInSoi.exists(_.CharId == hackerId) && flagCarrier.map(_.CharId).getOrElse(0L) != hackerId) { 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)) events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hackerId, finalCep))
} }
//bystanders (cep if squad leader, bep otherwise)
playersInSoi playersInSoi
.filter { player => player.Faction == victorFaction && player.CharId != hackerId } .filterNot { _.CharId == hackerId }
.foreach { player => .foreach { player =>
val charId = player.CharId val charId = player.CharId
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(charId, 1f) val contributionMultiplier = contributionPerPlayerByTime.getOrElse(charId, 1f)
val outputValue = (finalCep * contributionMultiplier).toLong val outputValue = (finalCep * contributionMultiplier).toLong
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(0, outputValue)) events ! AvatarServiceMessage(player.Name, AvatarAction.FacilityCaptureRewards(buildingId, zoneNumber, outputValue))
} }
//flag carrier (won't be in soi, but earns cep from capture)
flagCarrier.collect { flagCarrier.collect {
case player if !isResecured => case player if !isResecured =>
val charId: Long = player.CharId val charId: Long = player.CharId
@ -259,13 +272,33 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
} }
ToDatabase.reportFacilityCapture( ToDatabase.reportFacilityCapture(
charId, charId,
building.Zone.Number, zoneNumber,
building.GUID.guid, buildingId,
finalModifiedCep, finalModifiedCep,
expType="llu" expType = "llu"
) )
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(charId, finalModifiedCep)) events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(charId, finalModifiedCep))
} }
} else {
//no need to calculate a fancy score
val hackerId = hacker.CharId
val hackerScore = List((hackerId, 0L, "cep"))
ToDatabase.reportFacilityCaptureInBulk(
if (isResecured) {
hackerScore
} else {
val flagCarrierScore = flagCarrier.map (p => List((p.CharId, 0L, "llu"))).getOrElse(Nil)
if (playersInSoi.exists(_.CharId == hackerId) && !flagCarrierScore.exists { case (charId, _,_) => charId == hackerId }) {
hackerScore ++ flagCarrierScore
} else {
flagCarrierScore
}
} ++ playersInSoi.filterNot { p => p.CharId == hackerId }.map(p => (p.CharId, 0L, "bep")),
zoneNumber,
buildingId
)
}
}
} }
} }

View file

@ -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,18 +61,28 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
), ),
contributionOpposingSize contributionOpposingSize
) )
//based on this math, the optimal number of enemy for experience gain is 20
//max value of: 1000 * pop * max(0, (40 - pop)) * 0.1
if (baseExperienceFromFacilityCapture > 0) {
//more setup ...
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))
}
//2) peak population modifier //2) peak population modifier
//Towers should not be regarded as major battles. //Towers should not be regarded as major battles.
//As the population rises, the rewards decrease (dramatically). //As the population rises, the rewards decrease (dramatically).
val populationModifier = FacilityHackParticipation.populationProgressModifier( val populationModifier = FacilityHackParticipation.populationProgressModifier(
victorPopulationByLayer, victorPopulationByLayer,
{ pop => { pop =>
if (pop > 80) 0f if (pop > 40) 0.075f
else if (pop > 39) (80 - pop).toFloat * 0.01f else if (pop > 8) (40 - pop).toFloat * 0.1f
else if (pop > 25) 0.5f
else if (pop > 19) 0.55f
else if (pop > 9) 0.6f
else if (pop > 5) 0.75f
else 1f else 1f
}, },
2 2
@ -92,11 +97,9 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
//Once again, an arbitrary five minute period. //Once again, an arbitrary five minute period.
val contributionPerPlayerByTime = playerContribution.collect { val contributionPerPlayerByTime = playerContribution.collect {
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 => case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
(a, 0.45f) (a, 0.75f)
case (a, (_, d, _)) if d >= 300000 =>
(a, 0.25f)
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 => case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
(a, 0.25f * (0.5f + (d.toFloat / 300000f))) (a, 0.15f + (d.toFloat / 600000f))
case (a, (_, _, _)) => case (a, (_, _, _)) =>
(a, 0.15f) (a, 0.15f)
} }
@ -115,7 +118,7 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
(p.CharId, distance) (p.CharId, distance)
} }
.map { case (id, distance) => .map { case (id, distance) =>
(id, math.max(0.15f, minDistance / distance)) (id, math.max(0.25f, minDistance / distance))
} }
}.toMap[Long, Float] }.toMap[Long, Float]
//5) token competition bonus //5) token competition bonus
@ -137,11 +140,18 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
).toLong ).toLong
//7. reward participants //7. reward participants
//Classically, only players in the SOI are rewarded //Classically, only players in the SOI are rewarded
val events = building.Zone.AvatarEvents //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
.filter { player => .filterNot(_.CharId == hackerId)
player.Faction == victorFaction && player.CharId != hacker.CharId
}
.foreach { player => .foreach { player =>
val charId = player.CharId val charId = player.CharId
val contributionTimeMultiplier = contributionPerPlayerByTime.getOrElse(charId, 0.5f) val contributionTimeMultiplier = contributionPerPlayerByTime.getOrElse(charId, 0.5f)
@ -149,10 +159,17 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
val outputValue = (finalCep * contributionTimeMultiplier * contributionDistanceMultiplier).toLong val outputValue = (finalCep * contributionTimeMultiplier * contributionDistanceMultiplier).toLong
events ! AvatarServiceMessage( events ! AvatarServiceMessage(
player.Name, player.Name,
AvatarAction.AwardCep(0, outputValue) AvatarAction.FacilityCaptureRewards(buildingId, zoneNumber, outputValue)
)
}
} else {
//no need to calculate a fancy score
ToDatabase.reportFacilityCaptureInBulk(
(hackerId, 0L, "cep") +: soiPlayers.filterNot(_.CharId == hackerId).map(p => (p.CharId, 0L, "bep")),
zoneNumber,
buildingId
) )
} }
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hacker.CharId, finalCep))
} }
playerContribution.clear() playerContribution.clear()

View file

@ -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)))
}
} }