mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +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
|
|
@ -1623,7 +1623,7 @@ class AvatarActor(
|
|||
case AwardBep(bep, modifier) =>
|
||||
awardProgressionOrExperience(
|
||||
setBepAction(modifier),
|
||||
avatar.bep + bep,
|
||||
bep,
|
||||
Config.app.game.promotion.battleExperiencePointsModifier
|
||||
)
|
||||
Behaviors.same
|
||||
|
|
@ -1631,7 +1631,7 @@ class AvatarActor(
|
|||
case AwardFacilityCaptureBep(bep) =>
|
||||
awardProgressionOrExperience(
|
||||
setBepAction(ExperienceType.Normal),
|
||||
avatar.bep + bep,
|
||||
bep,
|
||||
Config.app.game.promotion.captureExperiencePointsModifier
|
||||
)
|
||||
Behaviors.same
|
||||
|
|
@ -1684,7 +1684,7 @@ class AvatarActor(
|
|||
if (experienceDebt == 0L) {
|
||||
setCep(avatar.cep + cep)
|
||||
} else if (cep > 0) {
|
||||
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage(0))
|
||||
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage())
|
||||
}
|
||||
Behaviors.same
|
||||
|
||||
|
|
@ -1859,63 +1859,63 @@ class AvatarActor(
|
|||
|
||||
def performAvatarLogin(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
|
||||
performAvatarLogin0(avatarId, accountId, replyTo)
|
||||
// import ctx._
|
||||
// val result = for {
|
||||
// //log this login
|
||||
// _ <- ctx.run(
|
||||
// query[persistence.Avatar]
|
||||
// .filter(_.id == lift(avatarId))
|
||||
// .update(_.lastLogin -> lift(LocalDateTime.now()))
|
||||
// )
|
||||
// //log this choice of faction (no empire switching)
|
||||
// _ <- ctx.run(
|
||||
// query[persistence.Account]
|
||||
// .filter(_.id == lift(accountId))
|
||||
// .update(
|
||||
// _.lastFactionId -> lift(avatar.faction.id),
|
||||
// _.avatarLoggedIn -> lift(avatarId)
|
||||
// )
|
||||
// )
|
||||
// //retrieve avatar data
|
||||
// loadouts <- initializeAllLoadouts()
|
||||
// implants <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(avatarId)))
|
||||
// certs <- ctx.run(query[persistence.Certification].filter(_.avatarId == lift(avatarId)))
|
||||
// locker <- loadLocker(avatarId)
|
||||
// friends <- loadFriendList(avatarId)
|
||||
// ignored <- loadIgnoredList(avatarId)
|
||||
// shortcuts <- loadShortcuts(avatarId)
|
||||
// saved <- AvatarActor.loadSavedAvatarData(avatarId)
|
||||
// debt <- AvatarActor.loadExperienceDebt(avatarId)
|
||||
// card <- AvatarActor.loadCampaignKdaData(avatarId)
|
||||
// } yield (loadouts, implants, certs, locker, friends, ignored, shortcuts, saved, debt, card)
|
||||
// result.onComplete {
|
||||
// case Success((_loadouts, implants, certs, lockerInv, friendsList, ignoredList, shortcutList, saved, debt, card)) =>
|
||||
// avatarCopy(
|
||||
// avatar.copy(
|
||||
// loadouts = avatar.loadouts.copy(suit = _loadouts),
|
||||
// certifications =
|
||||
// certs.map(cert => Certification.withValue(cert.id)).toSet ++ Config.app.game.baseCertifications,
|
||||
// implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
|
||||
// shortcuts = shortcutList,
|
||||
// locker = lockerInv,
|
||||
// people = MemberLists(
|
||||
// friend = friendsList,
|
||||
// ignored = ignoredList
|
||||
// ),
|
||||
// cooldowns = Cooldowns(
|
||||
// purchase = AvatarActor.buildCooldownsFromClob(saved.purchaseCooldowns, Avatar.purchaseCooldowns, log),
|
||||
// use = AvatarActor.buildCooldownsFromClob(saved.useCooldowns, Avatar.useCooldowns, log)
|
||||
// ),
|
||||
// scorecard = card
|
||||
// )
|
||||
// )
|
||||
// // if we need to start stamina regeneration
|
||||
// tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) }
|
||||
// experienceDebt = debt
|
||||
// replyTo ! AvatarLoginResponse(avatar)
|
||||
// case Failure(e) =>
|
||||
// log.error(e)("db failure")
|
||||
// }
|
||||
/*import ctx._
|
||||
val result = for {
|
||||
//log this login
|
||||
_ <- ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(_.id == lift(avatarId))
|
||||
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
||||
)
|
||||
//log this choice of faction (no empire switching)
|
||||
_ <- ctx.run(
|
||||
query[persistence.Account]
|
||||
.filter(_.id == lift(accountId))
|
||||
.update(
|
||||
_.lastFactionId -> lift(avatar.faction.id),
|
||||
_.avatarLoggedIn -> lift(avatarId)
|
||||
)
|
||||
)
|
||||
//retrieve avatar data
|
||||
loadouts <- initializeAllLoadouts()
|
||||
implants <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(avatarId)))
|
||||
certs <- ctx.run(query[persistence.Certification].filter(_.avatarId == lift(avatarId)))
|
||||
locker <- loadLocker(avatarId)
|
||||
friends <- loadFriendList(avatarId)
|
||||
ignored <- loadIgnoredList(avatarId)
|
||||
shortcuts <- loadShortcuts(avatarId)
|
||||
saved <- AvatarActor.loadSavedAvatarData(avatarId)
|
||||
debt <- AvatarActor.loadExperienceDebt(avatarId)
|
||||
card <- AvatarActor.loadCampaignKdaData(avatarId)
|
||||
} yield (loadouts, implants, certs, locker, friends, ignored, shortcuts, saved, debt, card)
|
||||
result.onComplete {
|
||||
case Success((_loadouts, implants, certs, lockerInv, friendsList, ignoredList, shortcutList, saved, debt, card)) =>
|
||||
avatarCopy(
|
||||
avatar.copy(
|
||||
loadouts = avatar.loadouts.copy(suit = _loadouts),
|
||||
certifications =
|
||||
certs.map(cert => Certification.withValue(cert.id)).toSet ++ Config.app.game.baseCertifications,
|
||||
implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
|
||||
shortcuts = shortcutList,
|
||||
locker = lockerInv,
|
||||
people = MemberLists(
|
||||
friend = friendsList,
|
||||
ignored = ignoredList
|
||||
),
|
||||
cooldowns = Cooldowns(
|
||||
purchase = AvatarActor.buildCooldownsFromClob(saved.purchaseCooldowns, Avatar.purchaseCooldowns, log),
|
||||
use = AvatarActor.buildCooldownsFromClob(saved.useCooldowns, Avatar.useCooldowns, log)
|
||||
),
|
||||
scorecard = card
|
||||
)
|
||||
)
|
||||
// if we need to start stamina regeneration
|
||||
tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) }
|
||||
experienceDebt = debt
|
||||
replyTo ! AvatarLoginResponse(avatar)
|
||||
case Failure(e) =>
|
||||
log.error(e)("db failure")
|
||||
}*/
|
||||
}
|
||||
|
||||
def performAvatarLogin0(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
|
||||
|
|
@ -3084,23 +3084,25 @@ class AvatarActor(
|
|||
experience: Long,
|
||||
modifier: Float
|
||||
): Unit = {
|
||||
if (experienceDebt == 0L) {
|
||||
awardAction(experience)
|
||||
} else if (modifier > 0f) {
|
||||
val modifiedBep = (experience.toFloat * modifier).toLong
|
||||
val gain = modifiedBep - experienceDebt
|
||||
if (gain > 0L) {
|
||||
experienceDebt = 0L
|
||||
if (experience > 0) {
|
||||
if (experienceDebt == 0L) {
|
||||
awardAction(experience)
|
||||
} else {
|
||||
experienceDebt = experienceDebt - modifiedBep
|
||||
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage())
|
||||
} else if (modifier > 0f) {
|
||||
val modifiedBep = (experience.toFloat * modifier).toLong
|
||||
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 = {
|
||||
setBep(value, modifier)
|
||||
setBep(avatar.bep + value, modifier)
|
||||
}
|
||||
|
||||
private def setSupportAction(value: Long): Unit = {
|
||||
|
|
|
|||
|
|
@ -398,6 +398,7 @@ class SessionAvatarHandlers(
|
|||
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
||||
|
||||
case AvatarResponse.AwardBep(charId, bep, expType) =>
|
||||
//if the target player, always award (some) BEP
|
||||
if (charId == player.CharId) {
|
||||
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
||||
}
|
||||
|
|
@ -409,67 +410,7 @@ class SessionAvatarHandlers(
|
|||
}
|
||||
|
||||
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
|
||||
//must be in a squad to earn experience
|
||||
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)
|
||||
}
|
||||
facilityCaptureRewards(buildingId, zoneNumber, cep)
|
||||
|
||||
case AvatarResponse.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 {
|
||||
|
|
|
|||
|
|
@ -203,10 +203,10 @@ object FacilityHackParticipation {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (dataSum != 0) {
|
||||
math.max(0.15f, math.min(2f, dataSum / dataCount.toFloat))
|
||||
if (dataCount != 0) {
|
||||
math.max(0.2f, math.min(2f, dataSum / dataCount.toFloat))
|
||||
} else {
|
||||
1f
|
||||
0.5f
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,14 +220,15 @@ object FacilityHackParticipation {
|
|||
private[participation] def populationProgressModifier(
|
||||
populationNumbers: Seq[Int],
|
||||
gradingRule: Int=>Float,
|
||||
layers: Int
|
||||
layers: Int,
|
||||
ordering: Ordering[Int] = Ordering[Int]
|
||||
): Float = {
|
||||
val gradedPopulation = populationNumbers
|
||||
.map { gradingRule }
|
||||
.groupBy(x => x)
|
||||
.values
|
||||
.toSeq
|
||||
.sortBy(_.size)
|
||||
.sortBy(_.size)(ordering)
|
||||
.take(layers)
|
||||
.flatten
|
||||
gradedPopulation.sum / gradedPopulation.size.toFloat
|
||||
|
|
@ -236,7 +237,8 @@ object FacilityHackParticipation {
|
|||
private[participation] def populationBalanceModifier(
|
||||
victorPopulationNumbers: Seq[Int],
|
||||
opposingPopulationNumbers: Seq[Int],
|
||||
healthyPercentage: Float
|
||||
healthyPercentage: Float,
|
||||
maxRatio: Float = 1f
|
||||
): Float = {
|
||||
val rate = for {
|
||||
victorPop <- victorPopulationNumbers
|
||||
|
|
@ -252,7 +254,7 @@ object FacilityHackParticipation {
|
|||
}
|
||||
if true
|
||||
} yield out
|
||||
rate.sum / rate.size.toFloat
|
||||
math.max(0f, math.min(rate.sum / rate.size.toFloat, maxRatio))
|
||||
}
|
||||
|
||||
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.zones.{HotSpotInfo, ZoneHotSpotProjector}
|
||||
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 akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
|
|
@ -45,7 +45,7 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
import net.psforever.objects.zones.ZoneHotSpotProjector
|
||||
|
||||
import scala.concurrent.Promise
|
||||
import scala.util.Success
|
||||
// import scala.util.Success
|
||||
val requestLayers: Promise[ZoneHotSpotProjector.ExposedHeat] = Promise[ZoneHotSpotProjector.ExposedHeat]()
|
||||
// val request = updateHotSpotInfoOnly()
|
||||
// requestLayers.completeWith(request)
|
||||
|
|
@ -67,204 +67,237 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
completionTime: Long,
|
||||
isResecured: Boolean
|
||||
): Unit = {
|
||||
val curr = System.currentTimeMillis()
|
||||
val hackStart = curr - completionTime
|
||||
val socketOpt = building.GetFlagSocket
|
||||
val (victorFaction, opposingFaction, hasFlag, flagCarrier) = if (!isResecured) {
|
||||
val carrier = socketOpt.flatMap(_.previousFlag).flatMap(_.Carrier)
|
||||
(attackingFaction, defenderFaction, socketOpt.nonEmpty, carrier)
|
||||
} else {
|
||||
(defenderFaction, attackingFaction, socketOpt.nonEmpty, None)
|
||||
}
|
||||
val (contributionVictor, contributionOpposing, _) = {
|
||||
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))
|
||||
//has the facility ran out of nanites during the hack
|
||||
if (building.NtuLevel > 0) {
|
||||
val curr = System.currentTimeMillis()
|
||||
val hackStart = curr - completionTime
|
||||
val socketOpt = building.GetFlagSocket
|
||||
val (victorFaction, opposingFaction, hasFlag, flagCarrier) = if (!isResecured) {
|
||||
val carrier = socketOpt.flatMap(_.previousFlag).flatMap(_.Carrier)
|
||||
(attackingFaction, defenderFaction, socketOpt.nonEmpty, carrier)
|
||||
} else {
|
||||
(defenderFaction, attackingFaction, socketOpt.nonEmpty, None)
|
||||
}
|
||||
val contributionOpposingSize = contributionOpposing.size
|
||||
val killsByPlayersNotInTower = eliminateClosestTowerFromParticipating(
|
||||
building,
|
||||
FacilityHackParticipation.allocateKillsByPlayers(
|
||||
building.Position,
|
||||
building.Definition.SOIRadius.toFloat,
|
||||
hackStart,
|
||||
completionTime,
|
||||
opposingFaction,
|
||||
contributionOpposing
|
||||
)
|
||||
)
|
||||
//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.
|
||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||
killsByPlayersNotInTower,
|
||||
contributionOpposingSize
|
||||
)
|
||||
//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 (contributionVictor, contributionOpposing, _) = {
|
||||
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 heatMapModifier = FacilityHackParticipation.heatMapComparison(
|
||||
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, victorFaction).values,
|
||||
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, opposingFaction).values
|
||||
val contributionOpposingSize = contributionOpposing.size
|
||||
val killsByPlayersNotInTower = eliminateClosestTowerFromParticipating(
|
||||
building,
|
||||
FacilityHackParticipation.allocateKillsByPlayers(
|
||||
building.Position,
|
||||
building.Definition.SOIRadius.toFloat,
|
||||
hackStart,
|
||||
completionTime,
|
||||
opposingFaction,
|
||||
contributionOpposing
|
||||
)
|
||||
)
|
||||
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 uit
|
||||
// will incur an abbreviated duration
|
||||
val overallTimeMultiplier: Float = {
|
||||
if (
|
||||
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
|
||||
0.25f
|
||||
} 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
|
||||
//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.
|
||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||
killsByPlayersNotInTower,
|
||||
contributionOpposingSize
|
||||
)
|
||||
val events = building.Zone.AvatarEvents
|
||||
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(
|
||||
opposingPopulationByLayer,
|
||||
{ pop =>
|
||||
if (pop > 75) 0.5f
|
||||
else if (pop > 59) 0.6f
|
||||
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 {
|
||||
0f
|
||||
})
|
||||
} else {
|
||||
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||
if (isResecured) {
|
||||
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||
} else {
|
||||
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 {
|
||||
if (isResecured) {
|
||||
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||
} else {
|
||||
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.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
|
||||
//no need to calculate a fancy score
|
||||
val hackerId = hacker.CharId
|
||||
val hackerScore = List((hackerId, 0L, "cep"))
|
||||
ToDatabase.reportFacilityCaptureInBulk(
|
||||
if (isResecured) {
|
||||
hackerScore
|
||||
} else {
|
||||
200L + durationPoints
|
||||
}
|
||||
math.min(
|
||||
betterDurationPoints,
|
||||
(finalCep * Config.app.game.experience.cep.lluCarrierModifier).toLong
|
||||
)
|
||||
}
|
||||
ToDatabase.reportFacilityCapture(
|
||||
charId,
|
||||
building.Zone.Number,
|
||||
building.GUID.guid,
|
||||
finalModifiedCep,
|
||||
expType="llu"
|
||||
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
|
||||
)
|
||||
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.sourcing.PlayerSource
|
||||
import net.psforever.objects.zones.exp.ToDatabase
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
|
@ -38,21 +39,15 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
|||
}
|
||||
val contributionVictorSize = contributionVictor.size
|
||||
if (contributionVictorSize > 0) {
|
||||
//setup for ...
|
||||
//early setup ...
|
||||
import scala.concurrent.duration._
|
||||
val curr = System.currentTimeMillis()
|
||||
val soiPlayers = building.PlayersInSOI.filter { _.Faction == victorFaction }
|
||||
val contributionOpposingSize = contributionOpposing.size
|
||||
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 soiPlayers = building.PlayersInSOI
|
||||
val events = building.Zone.AvatarEvents
|
||||
val buildingId = building.GUID.guid
|
||||
val zoneNumber = building.Zone.Number
|
||||
val hackerId = hacker.CharId
|
||||
//1) experience from killing opposingFaction
|
||||
//Because the hack duration of towers is instantaneous, the prior period of five minutes is artificially selected.
|
||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||
|
|
@ -66,93 +61,115 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
|||
),
|
||||
contributionOpposingSize
|
||||
)
|
||||
//2) peak population modifier
|
||||
//Towers should not be regarded as major battles.
|
||||
//As the population rises, the rewards decrease (dramatically).
|
||||
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
||||
victorPopulationByLayer,
|
||||
{ pop =>
|
||||
if (pop > 80) 0f
|
||||
else if (pop > 39) (80 - pop).toFloat * 0.01f
|
||||
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
|
||||
},
|
||||
2
|
||||
)
|
||||
//3) competition multiplier
|
||||
val competitionMultiplier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||
victorPopulationByLayer,
|
||||
opposingPopulationByLayer,
|
||||
healthyPercentage = 1.25f
|
||||
)
|
||||
//4a. individual contribution factors - by time
|
||||
//Once again, an arbitrary five minute period.
|
||||
val contributionPerPlayerByTime = playerContribution.collect {
|
||||
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.45f)
|
||||
case (a, (_, d, _)) if d >= 300000 =>
|
||||
(a, 0.25f)
|
||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.25f * (0.5f + (d.toFloat / 300000f)))
|
||||
case (a, (_, _, _)) =>
|
||||
(a, 0.15f)
|
||||
}
|
||||
//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 }
|
||||
//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
|
||||
//Towers should not be regarded as major battles.
|
||||
//As the population rises, the rewards decrease (dramatically).
|
||||
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
||||
victorPopulationByLayer,
|
||||
{ pop =>
|
||||
if (pop > 40) 0.075f
|
||||
else if (pop > 8) (40 - pop).toFloat * 0.1f
|
||||
else 1f
|
||||
},
|
||||
2
|
||||
)
|
||||
//3) competition multiplier
|
||||
val competitionMultiplier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||
victorPopulationByLayer,
|
||||
opposingPopulationByLayer,
|
||||
healthyPercentage = 1.25f
|
||||
)
|
||||
//4a. individual contribution factors - by time
|
||||
//Once again, an arbitrary five minute period.
|
||||
val contributionPerPlayerByTime = playerContribution.collect {
|
||||
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.75f)
|
||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.15f + (d.toFloat / 600000f))
|
||||
case (a, (_, _, _)) =>
|
||||
(a, 0.15f)
|
||||
}
|
||||
//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
|
||||
.map { p =>
|
||||
val distance = Vector3.Distance(p.Position, location)
|
||||
minDistance = math.min(minDistance, distance)
|
||||
(p.CharId, distance)
|
||||
.filterNot(_.CharId == hackerId)
|
||||
.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.FacilityCaptureRewards(buildingId, zoneNumber, outputValue)
|
||||
)
|
||||
}
|
||||
.map { case (id, distance) =>
|
||||
(id, math.max(0.15f, 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
|
||||
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))
|
||||
} 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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…
Reference in a new issue